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