1 /*
2 * contrib/xml2/xpath.c
3 *
4 * Parser interface for DOM-based parser (libxml) rather than
5 * stream-based SAX-type parser
6 */
7 #include "postgres.h"
8
9 #include "access/htup_details.h"
10 #include "executor/spi.h"
11 #include "fmgr.h"
12 #include "funcapi.h"
13 #include "lib/stringinfo.h"
14 #include "miscadmin.h"
15 #include "utils/builtins.h"
16 #include "utils/xml.h"
17
18 /* libxml includes */
19
20 #include <libxml/xpath.h>
21 #include <libxml/tree.h>
22 #include <libxml/xmlmemory.h>
23 #include <libxml/xmlerror.h>
24 #include <libxml/parserInternals.h>
25
26 PG_MODULE_MAGIC;
27
28 /* exported for use by xslt_proc.c */
29
30 PgXmlErrorContext *pgxml_parser_init(PgXmlStrictness strictness);
31
32 /* workspace for pgxml_xpath() */
33
34 typedef struct
35 {
36 xmlDocPtr doctree;
37 xmlXPathContextPtr ctxt;
38 xmlXPathObjectPtr res;
39 } xpath_workspace;
40
41 /* local declarations */
42
43 static xmlChar *pgxmlNodeSetToText(xmlNodeSetPtr nodeset,
44 xmlChar *toptagname, xmlChar *septagname,
45 xmlChar *plainsep);
46
47 static text *pgxml_result_to_text(xmlXPathObjectPtr res, xmlChar *toptag,
48 xmlChar *septag, xmlChar *plainsep);
49
50 static xmlChar *pgxml_texttoxmlchar(text *textstring);
51
52 static xmlXPathObjectPtr pgxml_xpath(text *document, xmlChar *xpath,
53 xpath_workspace *workspace);
54
55 static void cleanup_workspace(xpath_workspace *workspace);
56
57
58 /*
59 * Initialize for xml parsing.
60 *
61 * As with the underlying pg_xml_init function, calls to this MUST be followed
62 * by a PG_TRY block that guarantees that pg_xml_done is called.
63 */
64 PgXmlErrorContext *
pgxml_parser_init(PgXmlStrictness strictness)65 pgxml_parser_init(PgXmlStrictness strictness)
66 {
67 PgXmlErrorContext *xmlerrcxt;
68
69 /* Set up error handling (we share the core's error handler) */
70 xmlerrcxt = pg_xml_init(strictness);
71
72 /* Note: we're assuming an elog cannot be thrown by the following calls */
73
74 /* Initialize libxml */
75 xmlInitParser();
76
77 xmlSubstituteEntitiesDefault(1);
78 xmlLoadExtDtdDefaultValue = 1;
79
80 return xmlerrcxt;
81 }
82
83
84 /*
85 * Returns true if document is well-formed
86 *
87 * Note: this has been superseded by a core function. We still have to
88 * have it in the contrib module so that existing SQL-level references
89 * to the function won't fail; but in normal usage with up-to-date SQL
90 * definitions for the contrib module, this won't be called.
91 */
92
93 PG_FUNCTION_INFO_V1(xml_is_well_formed);
94
95 Datum
xml_is_well_formed(PG_FUNCTION_ARGS)96 xml_is_well_formed(PG_FUNCTION_ARGS)
97 {
98 text *t = PG_GETARG_TEXT_PP(0); /* document buffer */
99 bool result = false;
100 int32 docsize = VARSIZE_ANY_EXHDR(t);
101 xmlDocPtr doctree;
102 PgXmlErrorContext *xmlerrcxt;
103
104 xmlerrcxt = pgxml_parser_init(PG_XML_STRICTNESS_LEGACY);
105
106 PG_TRY();
107 {
108 doctree = xmlParseMemory((char *) VARDATA_ANY(t), docsize);
109
110 result = (doctree != NULL);
111
112 if (doctree != NULL)
113 xmlFreeDoc(doctree);
114 }
115 PG_CATCH();
116 {
117 pg_xml_done(xmlerrcxt, true);
118
119 PG_RE_THROW();
120 }
121 PG_END_TRY();
122
123 pg_xml_done(xmlerrcxt, false);
124
125 PG_RETURN_BOOL(result);
126 }
127
128
129 /* Encodes special characters (<, >, &, " and \r) as XML entities */
130
131 PG_FUNCTION_INFO_V1(xml_encode_special_chars);
132
133 Datum
xml_encode_special_chars(PG_FUNCTION_ARGS)134 xml_encode_special_chars(PG_FUNCTION_ARGS)
135 {
136 text *tin = PG_GETARG_TEXT_PP(0);
137 text *tout;
138 xmlChar *ts,
139 *tt;
140
141 ts = pgxml_texttoxmlchar(tin);
142
143 tt = xmlEncodeSpecialChars(NULL, ts);
144
145 pfree(ts);
146
147 tout = cstring_to_text((char *) tt);
148
149 xmlFree(tt);
150
151 PG_RETURN_TEXT_P(tout);
152 }
153
154 /*
155 * Function translates a nodeset into a text representation
156 *
157 * iterates over each node in the set and calls xmlNodeDump to write it to
158 * an xmlBuffer -from which an xmlChar * string is returned.
159 *
160 * each representation is surrounded by <tagname> ... </tagname>
161 *
162 * plainsep is an ordinary (not tag) separator - if used, then nodes are
163 * cast to string as output method
164 */
165 static xmlChar *
pgxmlNodeSetToText(xmlNodeSetPtr nodeset,xmlChar * toptagname,xmlChar * septagname,xmlChar * plainsep)166 pgxmlNodeSetToText(xmlNodeSetPtr nodeset,
167 xmlChar *toptagname,
168 xmlChar *septagname,
169 xmlChar *plainsep)
170 {
171 xmlBufferPtr buf;
172 xmlChar *result;
173 int i;
174
175 buf = xmlBufferCreate();
176
177 if ((toptagname != NULL) && (xmlStrlen(toptagname) > 0))
178 {
179 xmlBufferWriteChar(buf, "<");
180 xmlBufferWriteCHAR(buf, toptagname);
181 xmlBufferWriteChar(buf, ">");
182 }
183 if (nodeset != NULL)
184 {
185 for (i = 0; i < nodeset->nodeNr; i++)
186 {
187 if (plainsep != NULL)
188 {
189 xmlBufferWriteCHAR(buf,
190 xmlXPathCastNodeToString(nodeset->nodeTab[i]));
191
192 /* If this isn't the last entry, write the plain sep. */
193 if (i < (nodeset->nodeNr) - 1)
194 xmlBufferWriteChar(buf, (char *) plainsep);
195 }
196 else
197 {
198 if ((septagname != NULL) && (xmlStrlen(septagname) > 0))
199 {
200 xmlBufferWriteChar(buf, "<");
201 xmlBufferWriteCHAR(buf, septagname);
202 xmlBufferWriteChar(buf, ">");
203 }
204 xmlNodeDump(buf,
205 nodeset->nodeTab[i]->doc,
206 nodeset->nodeTab[i],
207 1, 0);
208
209 if ((septagname != NULL) && (xmlStrlen(septagname) > 0))
210 {
211 xmlBufferWriteChar(buf, "</");
212 xmlBufferWriteCHAR(buf, septagname);
213 xmlBufferWriteChar(buf, ">");
214 }
215 }
216 }
217 }
218
219 if ((toptagname != NULL) && (xmlStrlen(toptagname) > 0))
220 {
221 xmlBufferWriteChar(buf, "</");
222 xmlBufferWriteCHAR(buf, toptagname);
223 xmlBufferWriteChar(buf, ">");
224 }
225 result = xmlStrdup(buf->content);
226 xmlBufferFree(buf);
227 return result;
228 }
229
230
231 /* Translate a PostgreSQL "varlena" -i.e. a variable length parameter
232 * into the libxml2 representation
233 */
234 static xmlChar *
pgxml_texttoxmlchar(text * textstring)235 pgxml_texttoxmlchar(text *textstring)
236 {
237 return (xmlChar *) text_to_cstring(textstring);
238 }
239
240 /* Publicly visible XPath functions */
241
242 /*
243 * This is a "raw" xpath function. Check that it returns child elements
244 * properly
245 */
246 PG_FUNCTION_INFO_V1(xpath_nodeset);
247
248 Datum
xpath_nodeset(PG_FUNCTION_ARGS)249 xpath_nodeset(PG_FUNCTION_ARGS)
250 {
251 text *document = PG_GETARG_TEXT_PP(0);
252 text *xpathsupp = PG_GETARG_TEXT_PP(1); /* XPath expression */
253 xmlChar *toptag = pgxml_texttoxmlchar(PG_GETARG_TEXT_PP(2));
254 xmlChar *septag = pgxml_texttoxmlchar(PG_GETARG_TEXT_PP(3));
255 xmlChar *xpath;
256 text *xpres;
257 xmlXPathObjectPtr res;
258 xpath_workspace workspace;
259
260 xpath = pgxml_texttoxmlchar(xpathsupp);
261
262 res = pgxml_xpath(document, xpath, &workspace);
263
264 xpres = pgxml_result_to_text(res, toptag, septag, NULL);
265
266 cleanup_workspace(&workspace);
267
268 pfree(xpath);
269
270 if (xpres == NULL)
271 PG_RETURN_NULL();
272 PG_RETURN_TEXT_P(xpres);
273 }
274
275 /*
276 * The following function is almost identical, but returns the elements in
277 * a list.
278 */
279 PG_FUNCTION_INFO_V1(xpath_list);
280
281 Datum
xpath_list(PG_FUNCTION_ARGS)282 xpath_list(PG_FUNCTION_ARGS)
283 {
284 text *document = PG_GETARG_TEXT_PP(0);
285 text *xpathsupp = PG_GETARG_TEXT_PP(1); /* XPath expression */
286 xmlChar *plainsep = pgxml_texttoxmlchar(PG_GETARG_TEXT_PP(2));
287 xmlChar *xpath;
288 text *xpres;
289 xmlXPathObjectPtr res;
290 xpath_workspace workspace;
291
292 xpath = pgxml_texttoxmlchar(xpathsupp);
293
294 res = pgxml_xpath(document, xpath, &workspace);
295
296 xpres = pgxml_result_to_text(res, NULL, NULL, plainsep);
297
298 cleanup_workspace(&workspace);
299
300 pfree(xpath);
301
302 if (xpres == NULL)
303 PG_RETURN_NULL();
304 PG_RETURN_TEXT_P(xpres);
305 }
306
307
308 PG_FUNCTION_INFO_V1(xpath_string);
309
310 Datum
xpath_string(PG_FUNCTION_ARGS)311 xpath_string(PG_FUNCTION_ARGS)
312 {
313 text *document = PG_GETARG_TEXT_PP(0);
314 text *xpathsupp = PG_GETARG_TEXT_PP(1); /* XPath expression */
315 xmlChar *xpath;
316 int32 pathsize;
317 text *xpres;
318 xmlXPathObjectPtr res;
319 xpath_workspace workspace;
320
321 pathsize = VARSIZE_ANY_EXHDR(xpathsupp);
322
323 /*
324 * We encapsulate the supplied path with "string()" = 8 chars + 1 for NUL
325 * at end
326 */
327 /* We could try casting to string using the libxml function? */
328
329 xpath = (xmlChar *) palloc(pathsize + 9);
330 memcpy((char *) xpath, "string(", 7);
331 memcpy((char *) (xpath + 7), VARDATA_ANY(xpathsupp), pathsize);
332 xpath[pathsize + 7] = ')';
333 xpath[pathsize + 8] = '\0';
334
335 res = pgxml_xpath(document, xpath, &workspace);
336
337 xpres = pgxml_result_to_text(res, NULL, NULL, NULL);
338
339 cleanup_workspace(&workspace);
340
341 pfree(xpath);
342
343 if (xpres == NULL)
344 PG_RETURN_NULL();
345 PG_RETURN_TEXT_P(xpres);
346 }
347
348
349 PG_FUNCTION_INFO_V1(xpath_number);
350
351 Datum
xpath_number(PG_FUNCTION_ARGS)352 xpath_number(PG_FUNCTION_ARGS)
353 {
354 text *document = PG_GETARG_TEXT_PP(0);
355 text *xpathsupp = PG_GETARG_TEXT_PP(1); /* XPath expression */
356 xmlChar *xpath;
357 float4 fRes;
358 xmlXPathObjectPtr res;
359 xpath_workspace workspace;
360
361 xpath = pgxml_texttoxmlchar(xpathsupp);
362
363 res = pgxml_xpath(document, xpath, &workspace);
364
365 pfree(xpath);
366
367 if (res == NULL)
368 PG_RETURN_NULL();
369
370 fRes = xmlXPathCastToNumber(res);
371
372 cleanup_workspace(&workspace);
373
374 if (xmlXPathIsNaN(fRes))
375 PG_RETURN_NULL();
376
377 PG_RETURN_FLOAT4(fRes);
378 }
379
380
381 PG_FUNCTION_INFO_V1(xpath_bool);
382
383 Datum
xpath_bool(PG_FUNCTION_ARGS)384 xpath_bool(PG_FUNCTION_ARGS)
385 {
386 text *document = PG_GETARG_TEXT_PP(0);
387 text *xpathsupp = PG_GETARG_TEXT_PP(1); /* XPath expression */
388 xmlChar *xpath;
389 int bRes;
390 xmlXPathObjectPtr res;
391 xpath_workspace workspace;
392
393 xpath = pgxml_texttoxmlchar(xpathsupp);
394
395 res = pgxml_xpath(document, xpath, &workspace);
396
397 pfree(xpath);
398
399 if (res == NULL)
400 PG_RETURN_BOOL(false);
401
402 bRes = xmlXPathCastToBoolean(res);
403
404 cleanup_workspace(&workspace);
405
406 PG_RETURN_BOOL(bRes);
407 }
408
409
410
411 /* Core function to evaluate XPath query */
412
413 static xmlXPathObjectPtr
pgxml_xpath(text * document,xmlChar * xpath,xpath_workspace * workspace)414 pgxml_xpath(text *document, xmlChar *xpath, xpath_workspace *workspace)
415 {
416 int32 docsize = VARSIZE_ANY_EXHDR(document);
417 PgXmlErrorContext *xmlerrcxt;
418 xmlXPathCompExprPtr comppath;
419
420 workspace->doctree = NULL;
421 workspace->ctxt = NULL;
422 workspace->res = NULL;
423
424 xmlerrcxt = pgxml_parser_init(PG_XML_STRICTNESS_LEGACY);
425
426 PG_TRY();
427 {
428 workspace->doctree = xmlParseMemory((char *) VARDATA_ANY(document),
429 docsize);
430 if (workspace->doctree != NULL)
431 {
432 workspace->ctxt = xmlXPathNewContext(workspace->doctree);
433 workspace->ctxt->node = xmlDocGetRootElement(workspace->doctree);
434
435 /* compile the path */
436 comppath = xmlXPathCompile(xpath);
437 if (comppath == NULL)
438 xml_ereport(xmlerrcxt, ERROR, ERRCODE_EXTERNAL_ROUTINE_EXCEPTION,
439 "XPath Syntax Error");
440
441 /* Now evaluate the path expression. */
442 workspace->res = xmlXPathCompiledEval(comppath, workspace->ctxt);
443
444 xmlXPathFreeCompExpr(comppath);
445 }
446 }
447 PG_CATCH();
448 {
449 cleanup_workspace(workspace);
450
451 pg_xml_done(xmlerrcxt, true);
452
453 PG_RE_THROW();
454 }
455 PG_END_TRY();
456
457 if (workspace->res == NULL)
458 cleanup_workspace(workspace);
459
460 pg_xml_done(xmlerrcxt, false);
461
462 return workspace->res;
463 }
464
465 /* Clean up after processing the result of pgxml_xpath() */
466 static void
cleanup_workspace(xpath_workspace * workspace)467 cleanup_workspace(xpath_workspace *workspace)
468 {
469 if (workspace->res)
470 xmlXPathFreeObject(workspace->res);
471 workspace->res = NULL;
472 if (workspace->ctxt)
473 xmlXPathFreeContext(workspace->ctxt);
474 workspace->ctxt = NULL;
475 if (workspace->doctree)
476 xmlFreeDoc(workspace->doctree);
477 workspace->doctree = NULL;
478 }
479
480 static text *
pgxml_result_to_text(xmlXPathObjectPtr res,xmlChar * toptag,xmlChar * septag,xmlChar * plainsep)481 pgxml_result_to_text(xmlXPathObjectPtr res,
482 xmlChar *toptag,
483 xmlChar *septag,
484 xmlChar *plainsep)
485 {
486 xmlChar *xpresstr;
487 text *xpres;
488
489 if (res == NULL)
490 return NULL;
491
492 switch (res->type)
493 {
494 case XPATH_NODESET:
495 xpresstr = pgxmlNodeSetToText(res->nodesetval,
496 toptag,
497 septag, plainsep);
498 break;
499
500 case XPATH_STRING:
501 xpresstr = xmlStrdup(res->stringval);
502 break;
503
504 default:
505 elog(NOTICE, "unsupported XQuery result: %d", res->type);
506 xpresstr = xmlStrdup((const xmlChar *) "<unsupported/>");
507 }
508
509 /* Now convert this result back to text */
510 xpres = cstring_to_text((char *) xpresstr);
511
512 /* Free various storage */
513 xmlFree(xpresstr);
514
515 return xpres;
516 }
517
518 /*
519 * xpath_table is a table function. It needs some tidying (as do the
520 * other functions here!
521 */
522 PG_FUNCTION_INFO_V1(xpath_table);
523
524 Datum
xpath_table(PG_FUNCTION_ARGS)525 xpath_table(PG_FUNCTION_ARGS)
526 {
527 /* Function parameters */
528 char *pkeyfield = text_to_cstring(PG_GETARG_TEXT_PP(0));
529 char *xmlfield = text_to_cstring(PG_GETARG_TEXT_PP(1));
530 char *relname = text_to_cstring(PG_GETARG_TEXT_PP(2));
531 char *xpathset = text_to_cstring(PG_GETARG_TEXT_PP(3));
532 char *condition = text_to_cstring(PG_GETARG_TEXT_PP(4));
533
534 /* SPI (input tuple) support */
535 SPITupleTable *tuptable;
536 HeapTuple spi_tuple;
537 TupleDesc spi_tupdesc;
538
539 /* Output tuple (tuplestore) support */
540 Tuplestorestate *tupstore = NULL;
541 TupleDesc ret_tupdesc;
542 HeapTuple ret_tuple;
543
544 ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
545 AttInMetadata *attinmeta;
546 MemoryContext per_query_ctx;
547 MemoryContext oldcontext;
548
549 char **values;
550 xmlChar **xpaths;
551 char *pos;
552 const char *pathsep = "|";
553
554 int numpaths;
555 int ret;
556 uint64 proc;
557 int j;
558 int rownr; /* For issuing multiple rows from one original
559 * document */
560 bool had_values; /* To determine end of nodeset results */
561 StringInfoData query_buf;
562 PgXmlErrorContext *xmlerrcxt;
563 volatile xmlDocPtr doctree = NULL;
564
565 /* We only have a valid tuple description in table function mode */
566 if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
567 ereport(ERROR,
568 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
569 errmsg("set-valued function called in context that cannot accept a set")));
570 if (rsinfo->expectedDesc == NULL)
571 ereport(ERROR,
572 (errcode(ERRCODE_SYNTAX_ERROR),
573 errmsg("xpath_table must be called as a table function")));
574
575 /*
576 * We want to materialise because it means that we don't have to carry
577 * libxml2 parser state between invocations of this function
578 */
579 if (!(rsinfo->allowedModes & SFRM_Materialize))
580 ereport(ERROR,
581 (errcode(ERRCODE_SYNTAX_ERROR),
582 errmsg("xpath_table requires Materialize mode, but it is not "
583 "allowed in this context")));
584
585 /*
586 * The tuplestore must exist in a higher context than this function call
587 * (per_query_ctx is used)
588 */
589 per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
590 oldcontext = MemoryContextSwitchTo(per_query_ctx);
591
592 /*
593 * Create the tuplestore - work_mem is the max in-memory size before a
594 * file is created on disk to hold it.
595 */
596 tupstore =
597 tuplestore_begin_heap(rsinfo->allowedModes & SFRM_Materialize_Random,
598 false, work_mem);
599
600 MemoryContextSwitchTo(oldcontext);
601
602 /* get the requested return tuple description */
603 ret_tupdesc = CreateTupleDescCopy(rsinfo->expectedDesc);
604
605 /* must have at least one output column (for the pkey) */
606 if (ret_tupdesc->natts < 1)
607 ereport(ERROR,
608 (errcode(ERRCODE_SYNTAX_ERROR),
609 errmsg("xpath_table must have at least one output column")));
610
611 /*
612 * At the moment we assume that the returned attributes make sense for the
613 * XPath specified (i.e. we trust the caller). It's not fatal if they get
614 * it wrong - the input function for the column type will raise an error
615 * if the path result can't be converted into the correct binary
616 * representation.
617 */
618
619 attinmeta = TupleDescGetAttInMetadata(ret_tupdesc);
620
621 /* Set return mode and allocate value space. */
622 rsinfo->returnMode = SFRM_Materialize;
623 rsinfo->setDesc = ret_tupdesc;
624
625 values = (char **) palloc(ret_tupdesc->natts * sizeof(char *));
626 xpaths = (xmlChar **) palloc(ret_tupdesc->natts * sizeof(xmlChar *));
627
628 /*
629 * Split XPaths. xpathset is a writable CString.
630 *
631 * Note that we stop splitting once we've done all needed for tupdesc
632 */
633 numpaths = 0;
634 pos = xpathset;
635 while (numpaths < (ret_tupdesc->natts - 1))
636 {
637 xpaths[numpaths++] = (xmlChar *) pos;
638 pos = strstr(pos, pathsep);
639 if (pos != NULL)
640 {
641 *pos = '\0';
642 pos++;
643 }
644 else
645 break;
646 }
647
648 /* Now build query */
649 initStringInfo(&query_buf);
650
651 /* Build initial sql statement */
652 appendStringInfo(&query_buf, "SELECT %s, %s FROM %s WHERE %s",
653 pkeyfield,
654 xmlfield,
655 relname,
656 condition);
657
658 if ((ret = SPI_connect()) < 0)
659 elog(ERROR, "xpath_table: SPI_connect returned %d", ret);
660
661 if ((ret = SPI_exec(query_buf.data, 0)) != SPI_OK_SELECT)
662 elog(ERROR, "xpath_table: SPI execution failed for query %s",
663 query_buf.data);
664
665 proc = SPI_processed;
666 tuptable = SPI_tuptable;
667 spi_tupdesc = tuptable->tupdesc;
668
669 /* Switch out of SPI context */
670 MemoryContextSwitchTo(oldcontext);
671
672 /*
673 * Check that SPI returned correct result. If you put a comma into one of
674 * the function parameters, this will catch it when the SPI query returns
675 * e.g. 3 columns.
676 */
677 if (spi_tupdesc->natts != 2)
678 {
679 ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
680 errmsg("expression returning multiple columns is not valid in parameter list"),
681 errdetail("Expected two columns in SPI result, got %d.", spi_tupdesc->natts)));
682 }
683
684 /*
685 * Setup the parser. This should happen after we are done evaluating the
686 * query, in case it calls functions that set up libxml differently.
687 */
688 xmlerrcxt = pgxml_parser_init(PG_XML_STRICTNESS_LEGACY);
689
690 PG_TRY();
691 {
692 /* For each row i.e. document returned from SPI */
693 uint64 i;
694
695 for (i = 0; i < proc; i++)
696 {
697 char *pkey;
698 char *xmldoc;
699 xmlXPathContextPtr ctxt;
700 xmlXPathObjectPtr res;
701 xmlChar *resstr;
702 xmlXPathCompExprPtr comppath;
703
704 /* Extract the row data as C Strings */
705 spi_tuple = tuptable->vals[i];
706 pkey = SPI_getvalue(spi_tuple, spi_tupdesc, 1);
707 xmldoc = SPI_getvalue(spi_tuple, spi_tupdesc, 2);
708
709 /*
710 * Clear the values array, so that not-well-formed documents
711 * return NULL in all columns. Note that this also means that
712 * spare columns will be NULL.
713 */
714 for (j = 0; j < ret_tupdesc->natts; j++)
715 values[j] = NULL;
716
717 /* Insert primary key */
718 values[0] = pkey;
719
720 /* Parse the document */
721 if (xmldoc)
722 doctree = xmlParseMemory(xmldoc, strlen(xmldoc));
723 else /* treat NULL as not well-formed */
724 doctree = NULL;
725
726 if (doctree == NULL)
727 {
728 /* not well-formed, so output all-NULL tuple */
729 ret_tuple = BuildTupleFromCStrings(attinmeta, values);
730 tuplestore_puttuple(tupstore, ret_tuple);
731 heap_freetuple(ret_tuple);
732 }
733 else
734 {
735 /* New loop here - we have to deal with nodeset results */
736 rownr = 0;
737
738 do
739 {
740 /* Now evaluate the set of xpaths. */
741 had_values = false;
742 for (j = 0; j < numpaths; j++)
743 {
744 ctxt = xmlXPathNewContext(doctree);
745 ctxt->node = xmlDocGetRootElement(doctree);
746
747 /* compile the path */
748 comppath = xmlXPathCompile(xpaths[j]);
749 if (comppath == NULL)
750 xml_ereport(xmlerrcxt, ERROR,
751 ERRCODE_EXTERNAL_ROUTINE_EXCEPTION,
752 "XPath Syntax Error");
753
754 /* Now evaluate the path expression. */
755 res = xmlXPathCompiledEval(comppath, ctxt);
756 xmlXPathFreeCompExpr(comppath);
757
758 if (res != NULL)
759 {
760 switch (res->type)
761 {
762 case XPATH_NODESET:
763 /* We see if this nodeset has enough nodes */
764 if (res->nodesetval != NULL &&
765 rownr < res->nodesetval->nodeNr)
766 {
767 resstr = xmlXPathCastNodeToString(res->nodesetval->nodeTab[rownr]);
768 had_values = true;
769 }
770 else
771 resstr = NULL;
772
773 break;
774
775 case XPATH_STRING:
776 resstr = xmlStrdup(res->stringval);
777 break;
778
779 default:
780 elog(NOTICE, "unsupported XQuery result: %d", res->type);
781 resstr = xmlStrdup((const xmlChar *) "<unsupported/>");
782 }
783
784 /*
785 * Insert this into the appropriate column in the
786 * result tuple.
787 */
788 values[j + 1] = (char *) resstr;
789 }
790 xmlXPathFreeContext(ctxt);
791 }
792
793 /* Now add the tuple to the output, if there is one. */
794 if (had_values)
795 {
796 ret_tuple = BuildTupleFromCStrings(attinmeta, values);
797 tuplestore_puttuple(tupstore, ret_tuple);
798 heap_freetuple(ret_tuple);
799 }
800
801 rownr++;
802 } while (had_values);
803 }
804
805 if (doctree != NULL)
806 xmlFreeDoc(doctree);
807 doctree = NULL;
808
809 if (pkey)
810 pfree(pkey);
811 if (xmldoc)
812 pfree(xmldoc);
813 }
814 }
815 PG_CATCH();
816 {
817 if (doctree != NULL)
818 xmlFreeDoc(doctree);
819
820 pg_xml_done(xmlerrcxt, true);
821
822 PG_RE_THROW();
823 }
824 PG_END_TRY();
825
826 if (doctree != NULL)
827 xmlFreeDoc(doctree);
828
829 pg_xml_done(xmlerrcxt, false);
830
831 tuplestore_donestoring(tupstore);
832
833 SPI_finish();
834
835 rsinfo->setResult = tupstore;
836
837 /*
838 * SFRM_Materialize mode expects us to return a NULL Datum. The actual
839 * tuples are in our tuplestore and passed back through rsinfo->setResult.
840 * rsinfo->setDesc is set to the tuple description that we actually used
841 * to build our tuples with, so the caller can verify we did what it was
842 * expecting.
843 */
844 return (Datum) 0;
845 }
846