Header And Logo

PostgreSQL
| The world's most advanced open source database.

xpath.c

Go to the documentation of this file.
00001 /*
00002  * contrib/xml2/xpath.c
00003  *
00004  * Parser interface for DOM-based parser (libxml) rather than
00005  * stream-based SAX-type parser
00006  */
00007 #include "postgres.h"
00008 
00009 #include "access/htup_details.h"
00010 #include "executor/spi.h"
00011 #include "fmgr.h"
00012 #include "funcapi.h"
00013 #include "lib/stringinfo.h"
00014 #include "miscadmin.h"
00015 #include "utils/builtins.h"
00016 #include "utils/xml.h"
00017 
00018 /* libxml includes */
00019 
00020 #include <libxml/xpath.h>
00021 #include <libxml/tree.h>
00022 #include <libxml/xmlmemory.h>
00023 #include <libxml/xmlerror.h>
00024 #include <libxml/parserInternals.h>
00025 
00026 
00027 PG_MODULE_MAGIC;
00028 
00029 /* externally accessible functions */
00030 
00031 Datum       xml_is_well_formed(PG_FUNCTION_ARGS);
00032 Datum       xml_encode_special_chars(PG_FUNCTION_ARGS);
00033 Datum       xpath_nodeset(PG_FUNCTION_ARGS);
00034 Datum       xpath_string(PG_FUNCTION_ARGS);
00035 Datum       xpath_number(PG_FUNCTION_ARGS);
00036 Datum       xpath_bool(PG_FUNCTION_ARGS);
00037 Datum       xpath_list(PG_FUNCTION_ARGS);
00038 Datum       xpath_table(PG_FUNCTION_ARGS);
00039 
00040 /* exported for use by xslt_proc.c */
00041 
00042 PgXmlErrorContext *pgxml_parser_init(PgXmlStrictness strictness);
00043 
00044 /* workspace for pgxml_xpath() */
00045 
00046 typedef struct
00047 {
00048     xmlDocPtr   doctree;
00049     xmlXPathContextPtr ctxt;
00050     xmlXPathObjectPtr res;
00051 } xpath_workspace;
00052 
00053 /* local declarations */
00054 
00055 static xmlChar *pgxmlNodeSetToText(xmlNodeSetPtr nodeset,
00056                    xmlChar *toptagname, xmlChar *septagname,
00057                    xmlChar *plainsep);
00058 
00059 static text *pgxml_result_to_text(xmlXPathObjectPtr res, xmlChar *toptag,
00060                      xmlChar *septag, xmlChar *plainsep);
00061 
00062 static xmlChar *pgxml_texttoxmlchar(text *textstring);
00063 
00064 static xmlXPathObjectPtr pgxml_xpath(text *document, xmlChar *xpath,
00065             xpath_workspace *workspace);
00066 
00067 static void cleanup_workspace(xpath_workspace *workspace);
00068 
00069 
00070 /*
00071  * Initialize for xml parsing.
00072  *
00073  * As with the underlying pg_xml_init function, calls to this MUST be followed
00074  * by a PG_TRY block that guarantees that pg_xml_done is called.
00075  */
00076 PgXmlErrorContext *
00077 pgxml_parser_init(PgXmlStrictness strictness)
00078 {
00079     PgXmlErrorContext *xmlerrcxt;
00080 
00081     /* Set up error handling (we share the core's error handler) */
00082     xmlerrcxt = pg_xml_init(strictness);
00083 
00084     /* Note: we're assuming an elog cannot be thrown by the following calls */
00085 
00086     /* Initialize libxml */
00087     xmlInitParser();
00088 
00089     xmlSubstituteEntitiesDefault(1);
00090     xmlLoadExtDtdDefaultValue = 1;
00091 
00092     return xmlerrcxt;
00093 }
00094 
00095 
00096 /*
00097  * Returns true if document is well-formed
00098  *
00099  * Note: this has been superseded by a core function.  We still have to
00100  * have it in the contrib module so that existing SQL-level references
00101  * to the function won't fail; but in normal usage with up-to-date SQL
00102  * definitions for the contrib module, this won't be called.
00103  */
00104 
00105 PG_FUNCTION_INFO_V1(xml_is_well_formed);
00106 
00107 Datum
00108 xml_is_well_formed(PG_FUNCTION_ARGS)
00109 {
00110     text       *t = PG_GETARG_TEXT_P(0);        /* document buffer */
00111     bool        result = false;
00112     int32       docsize = VARSIZE(t) - VARHDRSZ;
00113     xmlDocPtr   doctree;
00114     PgXmlErrorContext *xmlerrcxt;
00115 
00116     xmlerrcxt = pgxml_parser_init(PG_XML_STRICTNESS_LEGACY);
00117 
00118     PG_TRY();
00119     {
00120         doctree = xmlParseMemory((char *) VARDATA(t), docsize);
00121 
00122         result = (doctree != NULL);
00123 
00124         if (doctree != NULL)
00125             xmlFreeDoc(doctree);
00126     }
00127     PG_CATCH();
00128     {
00129         pg_xml_done(xmlerrcxt, true);
00130 
00131         PG_RE_THROW();
00132     }
00133     PG_END_TRY();
00134 
00135     pg_xml_done(xmlerrcxt, false);
00136 
00137     PG_RETURN_BOOL(result);
00138 }
00139 
00140 
00141 /* Encodes special characters (<, >, &, " and \r) as XML entities */
00142 
00143 PG_FUNCTION_INFO_V1(xml_encode_special_chars);
00144 
00145 Datum
00146 xml_encode_special_chars(PG_FUNCTION_ARGS)
00147 {
00148     text       *tin = PG_GETARG_TEXT_P(0);
00149     text       *tout;
00150     xmlChar    *ts,
00151                *tt;
00152 
00153     ts = pgxml_texttoxmlchar(tin);
00154 
00155     tt = xmlEncodeSpecialChars(NULL, ts);
00156 
00157     pfree(ts);
00158 
00159     tout = cstring_to_text((char *) tt);
00160 
00161     xmlFree(tt);
00162 
00163     PG_RETURN_TEXT_P(tout);
00164 }
00165 
00166 /*
00167  * Function translates a nodeset into a text representation
00168  *
00169  * iterates over each node in the set and calls xmlNodeDump to write it to
00170  * an xmlBuffer -from which an xmlChar * string is returned.
00171  *
00172  * each representation is surrounded by <tagname> ... </tagname>
00173  *
00174  * plainsep is an ordinary (not tag) separator - if used, then nodes are
00175  * cast to string as output method
00176  */
00177 static xmlChar *
00178 pgxmlNodeSetToText(xmlNodeSetPtr nodeset,
00179                    xmlChar *toptagname,
00180                    xmlChar *septagname,
00181                    xmlChar *plainsep)
00182 {
00183     xmlBufferPtr buf;
00184     xmlChar    *result;
00185     int         i;
00186 
00187     buf = xmlBufferCreate();
00188 
00189     if ((toptagname != NULL) && (xmlStrlen(toptagname) > 0))
00190     {
00191         xmlBufferWriteChar(buf, "<");
00192         xmlBufferWriteCHAR(buf, toptagname);
00193         xmlBufferWriteChar(buf, ">");
00194     }
00195     if (nodeset != NULL)
00196     {
00197         for (i = 0; i < nodeset->nodeNr; i++)
00198         {
00199             if (plainsep != NULL)
00200             {
00201                 xmlBufferWriteCHAR(buf,
00202                               xmlXPathCastNodeToString(nodeset->nodeTab[i]));
00203 
00204                 /* If this isn't the last entry, write the plain sep. */
00205                 if (i < (nodeset->nodeNr) - 1)
00206                     xmlBufferWriteChar(buf, (char *) plainsep);
00207             }
00208             else
00209             {
00210                 if ((septagname != NULL) && (xmlStrlen(septagname) > 0))
00211                 {
00212                     xmlBufferWriteChar(buf, "<");
00213                     xmlBufferWriteCHAR(buf, septagname);
00214                     xmlBufferWriteChar(buf, ">");
00215                 }
00216                 xmlNodeDump(buf,
00217                             nodeset->nodeTab[i]->doc,
00218                             nodeset->nodeTab[i],
00219                             1, 0);
00220 
00221                 if ((septagname != NULL) && (xmlStrlen(septagname) > 0))
00222                 {
00223                     xmlBufferWriteChar(buf, "</");
00224                     xmlBufferWriteCHAR(buf, septagname);
00225                     xmlBufferWriteChar(buf, ">");
00226                 }
00227             }
00228         }
00229     }
00230 
00231     if ((toptagname != NULL) && (xmlStrlen(toptagname) > 0))
00232     {
00233         xmlBufferWriteChar(buf, "</");
00234         xmlBufferWriteCHAR(buf, toptagname);
00235         xmlBufferWriteChar(buf, ">");
00236     }
00237     result = xmlStrdup(buf->content);
00238     xmlBufferFree(buf);
00239     return result;
00240 }
00241 
00242 
00243 /* Translate a PostgreSQL "varlena" -i.e. a variable length parameter
00244  * into the libxml2 representation
00245  */
00246 static xmlChar *
00247 pgxml_texttoxmlchar(text *textstring)
00248 {
00249     return (xmlChar *) text_to_cstring(textstring);
00250 }
00251 
00252 /* Publicly visible XPath functions */
00253 
00254 /*
00255  * This is a "raw" xpath function. Check that it returns child elements
00256  * properly
00257  */
00258 PG_FUNCTION_INFO_V1(xpath_nodeset);
00259 
00260 Datum
00261 xpath_nodeset(PG_FUNCTION_ARGS)
00262 {
00263     text       *document = PG_GETARG_TEXT_P(0);
00264     text       *xpathsupp = PG_GETARG_TEXT_P(1);        /* XPath expression */
00265     xmlChar    *toptag = pgxml_texttoxmlchar(PG_GETARG_TEXT_P(2));
00266     xmlChar    *septag = pgxml_texttoxmlchar(PG_GETARG_TEXT_P(3));
00267     xmlChar    *xpath;
00268     text       *xpres;
00269     xmlXPathObjectPtr res;
00270     xpath_workspace workspace;
00271 
00272     xpath = pgxml_texttoxmlchar(xpathsupp);
00273 
00274     res = pgxml_xpath(document, xpath, &workspace);
00275 
00276     xpres = pgxml_result_to_text(res, toptag, septag, NULL);
00277 
00278     cleanup_workspace(&workspace);
00279 
00280     pfree(xpath);
00281 
00282     if (xpres == NULL)
00283         PG_RETURN_NULL();
00284     PG_RETURN_TEXT_P(xpres);
00285 }
00286 
00287 /*
00288  * The following function is almost identical, but returns the elements in
00289  * a list.
00290  */
00291 PG_FUNCTION_INFO_V1(xpath_list);
00292 
00293 Datum
00294 xpath_list(PG_FUNCTION_ARGS)
00295 {
00296     text       *document = PG_GETARG_TEXT_P(0);
00297     text       *xpathsupp = PG_GETARG_TEXT_P(1);        /* XPath expression */
00298     xmlChar    *plainsep = pgxml_texttoxmlchar(PG_GETARG_TEXT_P(2));
00299     xmlChar    *xpath;
00300     text       *xpres;
00301     xmlXPathObjectPtr res;
00302     xpath_workspace workspace;
00303 
00304     xpath = pgxml_texttoxmlchar(xpathsupp);
00305 
00306     res = pgxml_xpath(document, xpath, &workspace);
00307 
00308     xpres = pgxml_result_to_text(res, NULL, NULL, plainsep);
00309 
00310     cleanup_workspace(&workspace);
00311 
00312     pfree(xpath);
00313 
00314     if (xpres == NULL)
00315         PG_RETURN_NULL();
00316     PG_RETURN_TEXT_P(xpres);
00317 }
00318 
00319 
00320 PG_FUNCTION_INFO_V1(xpath_string);
00321 
00322 Datum
00323 xpath_string(PG_FUNCTION_ARGS)
00324 {
00325     text       *document = PG_GETARG_TEXT_P(0);
00326     text       *xpathsupp = PG_GETARG_TEXT_P(1);        /* XPath expression */
00327     xmlChar    *xpath;
00328     int32       pathsize;
00329     text       *xpres;
00330     xmlXPathObjectPtr res;
00331     xpath_workspace workspace;
00332 
00333     pathsize = VARSIZE(xpathsupp) - VARHDRSZ;
00334 
00335     /*
00336      * We encapsulate the supplied path with "string()" = 8 chars + 1 for NUL
00337      * at end
00338      */
00339     /* We could try casting to string using the libxml function? */
00340 
00341     xpath = (xmlChar *) palloc(pathsize + 9);
00342     strncpy((char *) xpath, "string(", 7);
00343     memcpy((char *) (xpath + 7), VARDATA(xpathsupp), pathsize);
00344     xpath[pathsize + 7] = ')';
00345     xpath[pathsize + 8] = '\0';
00346 
00347     res = pgxml_xpath(document, xpath, &workspace);
00348 
00349     xpres = pgxml_result_to_text(res, NULL, NULL, NULL);
00350 
00351     cleanup_workspace(&workspace);
00352 
00353     pfree(xpath);
00354 
00355     if (xpres == NULL)
00356         PG_RETURN_NULL();
00357     PG_RETURN_TEXT_P(xpres);
00358 }
00359 
00360 
00361 PG_FUNCTION_INFO_V1(xpath_number);
00362 
00363 Datum
00364 xpath_number(PG_FUNCTION_ARGS)
00365 {
00366     text       *document = PG_GETARG_TEXT_P(0);
00367     text       *xpathsupp = PG_GETARG_TEXT_P(1);        /* XPath expression */
00368     xmlChar    *xpath;
00369     float4      fRes;
00370     xmlXPathObjectPtr res;
00371     xpath_workspace workspace;
00372 
00373     xpath = pgxml_texttoxmlchar(xpathsupp);
00374 
00375     res = pgxml_xpath(document, xpath, &workspace);
00376 
00377     pfree(xpath);
00378 
00379     if (res == NULL)
00380         PG_RETURN_NULL();
00381 
00382     fRes = xmlXPathCastToNumber(res);
00383 
00384     cleanup_workspace(&workspace);
00385 
00386     if (xmlXPathIsNaN(fRes))
00387         PG_RETURN_NULL();
00388 
00389     PG_RETURN_FLOAT4(fRes);
00390 }
00391 
00392 
00393 PG_FUNCTION_INFO_V1(xpath_bool);
00394 
00395 Datum
00396 xpath_bool(PG_FUNCTION_ARGS)
00397 {
00398     text       *document = PG_GETARG_TEXT_P(0);
00399     text       *xpathsupp = PG_GETARG_TEXT_P(1);        /* XPath expression */
00400     xmlChar    *xpath;
00401     int         bRes;
00402     xmlXPathObjectPtr res;
00403     xpath_workspace workspace;
00404 
00405     xpath = pgxml_texttoxmlchar(xpathsupp);
00406 
00407     res = pgxml_xpath(document, xpath, &workspace);
00408 
00409     pfree(xpath);
00410 
00411     if (res == NULL)
00412         PG_RETURN_BOOL(false);
00413 
00414     bRes = xmlXPathCastToBoolean(res);
00415 
00416     cleanup_workspace(&workspace);
00417 
00418     PG_RETURN_BOOL(bRes);
00419 }
00420 
00421 
00422 
00423 /* Core function to evaluate XPath query */
00424 
00425 static xmlXPathObjectPtr
00426 pgxml_xpath(text *document, xmlChar *xpath, xpath_workspace *workspace)
00427 {
00428     int32       docsize = VARSIZE(document) - VARHDRSZ;
00429     PgXmlErrorContext *xmlerrcxt;
00430     xmlXPathCompExprPtr comppath;
00431 
00432     workspace->doctree = NULL;
00433     workspace->ctxt = NULL;
00434     workspace->res = NULL;
00435 
00436     xmlerrcxt = pgxml_parser_init(PG_XML_STRICTNESS_LEGACY);
00437 
00438     PG_TRY();
00439     {
00440         workspace->doctree = xmlParseMemory((char *) VARDATA(document),
00441                                             docsize);
00442         if (workspace->doctree != NULL)
00443         {
00444             workspace->ctxt = xmlXPathNewContext(workspace->doctree);
00445             workspace->ctxt->node = xmlDocGetRootElement(workspace->doctree);
00446 
00447             /* compile the path */
00448             comppath = xmlXPathCompile(xpath);
00449             if (comppath == NULL)
00450                 xml_ereport(xmlerrcxt, ERROR, ERRCODE_EXTERNAL_ROUTINE_EXCEPTION,
00451                             "XPath Syntax Error");
00452 
00453             /* Now evaluate the path expression. */
00454             workspace->res = xmlXPathCompiledEval(comppath, workspace->ctxt);
00455 
00456             xmlXPathFreeCompExpr(comppath);
00457         }
00458     }
00459     PG_CATCH();
00460     {
00461         cleanup_workspace(workspace);
00462 
00463         pg_xml_done(xmlerrcxt, true);
00464 
00465         PG_RE_THROW();
00466     }
00467     PG_END_TRY();
00468 
00469     if (workspace->res == NULL)
00470         cleanup_workspace(workspace);
00471 
00472     pg_xml_done(xmlerrcxt, false);
00473 
00474     return workspace->res;
00475 }
00476 
00477 /* Clean up after processing the result of pgxml_xpath() */
00478 static void
00479 cleanup_workspace(xpath_workspace *workspace)
00480 {
00481     if (workspace->res)
00482         xmlXPathFreeObject(workspace->res);
00483     workspace->res = NULL;
00484     if (workspace->ctxt)
00485         xmlXPathFreeContext(workspace->ctxt);
00486     workspace->ctxt = NULL;
00487     if (workspace->doctree)
00488         xmlFreeDoc(workspace->doctree);
00489     workspace->doctree = NULL;
00490 }
00491 
00492 static text *
00493 pgxml_result_to_text(xmlXPathObjectPtr res,
00494                      xmlChar *toptag,
00495                      xmlChar *septag,
00496                      xmlChar *plainsep)
00497 {
00498     xmlChar    *xpresstr;
00499     text       *xpres;
00500 
00501     if (res == NULL)
00502         return NULL;
00503 
00504     switch (res->type)
00505     {
00506         case XPATH_NODESET:
00507             xpresstr = pgxmlNodeSetToText(res->nodesetval,
00508                                           toptag,
00509                                           septag, plainsep);
00510             break;
00511 
00512         case XPATH_STRING:
00513             xpresstr = xmlStrdup(res->stringval);
00514             break;
00515 
00516         default:
00517             elog(NOTICE, "unsupported XQuery result: %d", res->type);
00518             xpresstr = xmlStrdup((const xmlChar *) "<unsupported/>");
00519     }
00520 
00521     /* Now convert this result back to text */
00522     xpres = cstring_to_text((char *) xpresstr);
00523 
00524     /* Free various storage */
00525     xmlFree(xpresstr);
00526 
00527     return xpres;
00528 }
00529 
00530 /*
00531  * xpath_table is a table function. It needs some tidying (as do the
00532  * other functions here!
00533  */
00534 PG_FUNCTION_INFO_V1(xpath_table);
00535 
00536 Datum
00537 xpath_table(PG_FUNCTION_ARGS)
00538 {
00539     /* Function parameters */
00540     char       *pkeyfield = text_to_cstring(PG_GETARG_TEXT_PP(0));
00541     char       *xmlfield = text_to_cstring(PG_GETARG_TEXT_PP(1));
00542     char       *relname = text_to_cstring(PG_GETARG_TEXT_PP(2));
00543     char       *xpathset = text_to_cstring(PG_GETARG_TEXT_PP(3));
00544     char       *condition = text_to_cstring(PG_GETARG_TEXT_PP(4));
00545 
00546     /* SPI (input tuple) support */
00547     SPITupleTable *tuptable;
00548     HeapTuple   spi_tuple;
00549     TupleDesc   spi_tupdesc;
00550 
00551     /* Output tuple (tuplestore) support */
00552     Tuplestorestate *tupstore = NULL;
00553     TupleDesc   ret_tupdesc;
00554     HeapTuple   ret_tuple;
00555 
00556     ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
00557     AttInMetadata *attinmeta;
00558     MemoryContext per_query_ctx;
00559     MemoryContext oldcontext;
00560 
00561     char      **values;
00562     xmlChar   **xpaths;
00563     char       *pos;
00564     const char *pathsep = "|";
00565 
00566     int         numpaths;
00567     int         ret;
00568     int         proc;
00569     int         i;
00570     int         j;
00571     int         rownr;          /* For issuing multiple rows from one original
00572                                  * document */
00573     bool        had_values;     /* To determine end of nodeset results */
00574     StringInfoData query_buf;
00575     PgXmlErrorContext *xmlerrcxt;
00576     volatile xmlDocPtr doctree = NULL;
00577 
00578     /* We only have a valid tuple description in table function mode */
00579     if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
00580         ereport(ERROR,
00581                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
00582                  errmsg("set-valued function called in context that cannot accept a set")));
00583     if (rsinfo->expectedDesc == NULL)
00584         ereport(ERROR,
00585                 (errcode(ERRCODE_SYNTAX_ERROR),
00586                  errmsg("xpath_table must be called as a table function")));
00587 
00588     /*
00589      * We want to materialise because it means that we don't have to carry
00590      * libxml2 parser state between invocations of this function
00591      */
00592     if (!(rsinfo->allowedModes & SFRM_Materialize))
00593         ereport(ERROR,
00594                 (errcode(ERRCODE_SYNTAX_ERROR),
00595                errmsg("xpath_table requires Materialize mode, but it is not "
00596                       "allowed in this context")));
00597 
00598     /*
00599      * The tuplestore must exist in a higher context than this function call
00600      * (per_query_ctx is used)
00601      */
00602     per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
00603     oldcontext = MemoryContextSwitchTo(per_query_ctx);
00604 
00605     /*
00606      * Create the tuplestore - work_mem is the max in-memory size before a
00607      * file is created on disk to hold it.
00608      */
00609     tupstore =
00610         tuplestore_begin_heap(rsinfo->allowedModes & SFRM_Materialize_Random,
00611                               false, work_mem);
00612 
00613     MemoryContextSwitchTo(oldcontext);
00614 
00615     /* get the requested return tuple description */
00616     ret_tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc);
00617 
00618     /* must have at least one output column (for the pkey) */
00619     if (ret_tupdesc->natts < 1)
00620         ereport(ERROR,
00621                 (errcode(ERRCODE_SYNTAX_ERROR),
00622                  errmsg("xpath_table must have at least one output column")));
00623 
00624     /*
00625      * At the moment we assume that the returned attributes make sense for the
00626      * XPath specififed (i.e. we trust the caller). It's not fatal if they get
00627      * it wrong - the input function for the column type will raise an error
00628      * if the path result can't be converted into the correct binary
00629      * representation.
00630      */
00631 
00632     attinmeta = TupleDescGetAttInMetadata(ret_tupdesc);
00633 
00634     /* Set return mode and allocate value space. */
00635     rsinfo->returnMode = SFRM_Materialize;
00636     rsinfo->setDesc = ret_tupdesc;
00637 
00638     values = (char **) palloc(ret_tupdesc->natts * sizeof(char *));
00639     xpaths = (xmlChar **) palloc(ret_tupdesc->natts * sizeof(xmlChar *));
00640 
00641     /*
00642      * Split XPaths. xpathset is a writable CString.
00643      *
00644      * Note that we stop splitting once we've done all needed for tupdesc
00645      */
00646     numpaths = 0;
00647     pos = xpathset;
00648     while (numpaths < (ret_tupdesc->natts - 1))
00649     {
00650         xpaths[numpaths++] = (xmlChar *) pos;
00651         pos = strstr(pos, pathsep);
00652         if (pos != NULL)
00653         {
00654             *pos = '\0';
00655             pos++;
00656         }
00657         else
00658             break;
00659     }
00660 
00661     /* Now build query */
00662     initStringInfo(&query_buf);
00663 
00664     /* Build initial sql statement */
00665     appendStringInfo(&query_buf, "SELECT %s, %s FROM %s WHERE %s",
00666                      pkeyfield,
00667                      xmlfield,
00668                      relname,
00669                      condition);
00670 
00671     if ((ret = SPI_connect()) < 0)
00672         elog(ERROR, "xpath_table: SPI_connect returned %d", ret);
00673 
00674     if ((ret = SPI_exec(query_buf.data, 0)) != SPI_OK_SELECT)
00675         elog(ERROR, "xpath_table: SPI execution failed for query %s",
00676              query_buf.data);
00677 
00678     proc = SPI_processed;
00679     /* elog(DEBUG1,"xpath_table: SPI returned %d rows",proc); */
00680     tuptable = SPI_tuptable;
00681     spi_tupdesc = tuptable->tupdesc;
00682 
00683     /* Switch out of SPI context */
00684     MemoryContextSwitchTo(oldcontext);
00685 
00686     /*
00687      * Check that SPI returned correct result. If you put a comma into one of
00688      * the function parameters, this will catch it when the SPI query returns
00689      * e.g. 3 columns.
00690      */
00691     if (spi_tupdesc->natts != 2)
00692     {
00693         ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
00694                         errmsg("expression returning multiple columns is not valid in parameter list"),
00695                         errdetail("Expected two columns in SPI result, got %d.", spi_tupdesc->natts)));
00696     }
00697 
00698     /*
00699      * Setup the parser.  This should happen after we are done evaluating the
00700      * query, in case it calls functions that set up libxml differently.
00701      */
00702     xmlerrcxt = pgxml_parser_init(PG_XML_STRICTNESS_LEGACY);
00703 
00704     PG_TRY();
00705     {
00706         /* For each row i.e. document returned from SPI */
00707         for (i = 0; i < proc; i++)
00708         {
00709             char       *pkey;
00710             char       *xmldoc;
00711             xmlXPathContextPtr ctxt;
00712             xmlXPathObjectPtr res;
00713             xmlChar    *resstr;
00714             xmlXPathCompExprPtr comppath;
00715 
00716             /* Extract the row data as C Strings */
00717             spi_tuple = tuptable->vals[i];
00718             pkey = SPI_getvalue(spi_tuple, spi_tupdesc, 1);
00719             xmldoc = SPI_getvalue(spi_tuple, spi_tupdesc, 2);
00720 
00721             /*
00722              * Clear the values array, so that not-well-formed documents
00723              * return NULL in all columns.  Note that this also means that
00724              * spare columns will be NULL.
00725              */
00726             for (j = 0; j < ret_tupdesc->natts; j++)
00727                 values[j] = NULL;
00728 
00729             /* Insert primary key */
00730             values[0] = pkey;
00731 
00732             /* Parse the document */
00733             if (xmldoc)
00734                 doctree = xmlParseMemory(xmldoc, strlen(xmldoc));
00735             else    /* treat NULL as not well-formed */
00736                 doctree = NULL;
00737 
00738             if (doctree == NULL)
00739             {
00740                 /* not well-formed, so output all-NULL tuple */
00741                 ret_tuple = BuildTupleFromCStrings(attinmeta, values);
00742                 tuplestore_puttuple(tupstore, ret_tuple);
00743                 heap_freetuple(ret_tuple);
00744             }
00745             else
00746             {
00747                 /* New loop here - we have to deal with nodeset results */
00748                 rownr = 0;
00749 
00750                 do
00751                 {
00752                     /* Now evaluate the set of xpaths. */
00753                     had_values = false;
00754                     for (j = 0; j < numpaths; j++)
00755                     {
00756                         ctxt = xmlXPathNewContext(doctree);
00757                         ctxt->node = xmlDocGetRootElement(doctree);
00758 
00759                         /* compile the path */
00760                         comppath = xmlXPathCompile(xpaths[j]);
00761                         if (comppath == NULL)
00762                             xml_ereport(xmlerrcxt, ERROR,
00763                                         ERRCODE_EXTERNAL_ROUTINE_EXCEPTION,
00764                                         "XPath Syntax Error");
00765 
00766                         /* Now evaluate the path expression. */
00767                         res = xmlXPathCompiledEval(comppath, ctxt);
00768                         xmlXPathFreeCompExpr(comppath);
00769 
00770                         if (res != NULL)
00771                         {
00772                             switch (res->type)
00773                             {
00774                                 case XPATH_NODESET:
00775                                     /* We see if this nodeset has enough nodes */
00776                                     if (res->nodesetval != NULL &&
00777                                         rownr < res->nodesetval->nodeNr)
00778                                     {
00779                                         resstr = xmlXPathCastNodeToString(res->nodesetval->nodeTab[rownr]);
00780                                         had_values = true;
00781                                     }
00782                                     else
00783                                         resstr = NULL;
00784 
00785                                     break;
00786 
00787                                 case XPATH_STRING:
00788                                     resstr = xmlStrdup(res->stringval);
00789                                     break;
00790 
00791                                 default:
00792                                     elog(NOTICE, "unsupported XQuery result: %d", res->type);
00793                                     resstr = xmlStrdup((const xmlChar *) "<unsupported/>");
00794                             }
00795 
00796                             /*
00797                              * Insert this into the appropriate column in the
00798                              * result tuple.
00799                              */
00800                             values[j + 1] = (char *) resstr;
00801                         }
00802                         xmlXPathFreeContext(ctxt);
00803                     }
00804 
00805                     /* Now add the tuple to the output, if there is one. */
00806                     if (had_values)
00807                     {
00808                         ret_tuple = BuildTupleFromCStrings(attinmeta, values);
00809                         tuplestore_puttuple(tupstore, ret_tuple);
00810                         heap_freetuple(ret_tuple);
00811                     }
00812 
00813                     rownr++;
00814                 } while (had_values);
00815             }
00816 
00817             if (doctree != NULL)
00818                 xmlFreeDoc(doctree);
00819             doctree = NULL;
00820 
00821             if (pkey)
00822                 pfree(pkey);
00823             if (xmldoc)
00824                 pfree(xmldoc);
00825         }
00826     }
00827     PG_CATCH();
00828     {
00829         if (doctree != NULL)
00830             xmlFreeDoc(doctree);
00831 
00832         pg_xml_done(xmlerrcxt, true);
00833 
00834         PG_RE_THROW();
00835     }
00836     PG_END_TRY();
00837 
00838     if (doctree != NULL)
00839         xmlFreeDoc(doctree);
00840 
00841     pg_xml_done(xmlerrcxt, false);
00842 
00843     tuplestore_donestoring(tupstore);
00844 
00845     SPI_finish();
00846 
00847     rsinfo->setResult = tupstore;
00848 
00849     /*
00850      * SFRM_Materialize mode expects us to return a NULL Datum. The actual
00851      * tuples are in our tuplestore and passed back through rsinfo->setResult.
00852      * rsinfo->setDesc is set to the tuple description that we actually used
00853      * to build our tuples with, so the caller can verify we did what it was
00854      * expecting.
00855      */
00856     return (Datum) 0;
00857 }