Age Owner Branch data TLA Line data Source code
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 : :
6425 tgl@sss.pgh.pa.us 26 :CBC 1 : 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 *
4652 65 : 11 : pgxml_parser_init(PgXmlStrictness strictness)
66 : : {
67 : : PgXmlErrorContext *xmlerrcxt;
68 : :
69 : : /* Set up error handling (we share the core's error handler) */
70 : 11 : xmlerrcxt = pg_xml_init(strictness);
71 : :
72 : : /* Note: we're assuming an elog cannot be thrown by the following calls */
73 : :
74 : : /* Initialize libxml */
5159 75 : 11 : xmlInitParser();
76 : :
4652 77 : 11 : return xmlerrcxt;
78 : : }
79 : :
80 : :
81 : : /* Encodes special characters (<, >, &, " and \r) as XML entities */
82 : :
7073 bruce@momjian.us 83 : 1 : PG_FUNCTION_INFO_V1(xml_encode_special_chars);
84 : :
85 : : Datum
7073 bruce@momjian.us 86 :UBC 0 : xml_encode_special_chars(PG_FUNCTION_ARGS)
87 : : {
2590 noah@leadboat.com 88 : 0 : text *tin = PG_GETARG_TEXT_PP(0);
89 : : text *tout;
90 : : xmlChar *ts,
91 : : *tt;
92 : :
7073 bruce@momjian.us 93 : 0 : ts = pgxml_texttoxmlchar(tin);
94 : :
95 : 0 : tt = xmlEncodeSpecialChars(NULL, ts);
96 : :
97 : 0 : pfree(ts);
98 : :
5824 tgl@sss.pgh.pa.us 99 : 0 : tout = cstring_to_text((char *) tt);
100 : :
7073 bruce@momjian.us 101 : 0 : xmlFree(tt);
102 : :
103 : 0 : PG_RETURN_TEXT_P(tout);
104 : : }
105 : :
106 : : /*
107 : : * Function translates a nodeset into a text representation
108 : : *
109 : : * iterates over each node in the set and calls xmlNodeDump to write it to
110 : : * an xmlBuffer -from which an xmlChar * string is returned.
111 : : *
112 : : * each representation is surrounded by <tagname> ... </tagname>
113 : : *
114 : : * plainsep is an ordinary (not tag) separator - if used, then nodes are
115 : : * cast to string as output method
116 : : */
117 : : static xmlChar *
7345 118 : 0 : pgxmlNodeSetToText(xmlNodeSetPtr nodeset,
119 : : xmlChar *toptagname,
120 : : xmlChar *septagname,
121 : : xmlChar *plainsep)
122 : : {
123 : : xmlBufferPtr buf;
124 : : xmlChar *result;
125 : : int i;
126 : :
127 : 0 : buf = xmlBufferCreate();
128 : :
129 [ # # # # ]: 0 : if ((toptagname != NULL) && (xmlStrlen(toptagname) > 0))
130 : : {
131 : 0 : xmlBufferWriteChar(buf, "<");
132 : 0 : xmlBufferWriteCHAR(buf, toptagname);
133 : 0 : xmlBufferWriteChar(buf, ">");
134 : : }
135 [ # # ]: 0 : if (nodeset != NULL)
136 : : {
137 [ # # ]: 0 : for (i = 0; i < nodeset->nodeNr; i++)
138 : : {
7168 139 [ # # ]: 0 : if (plainsep != NULL)
140 : : {
141 : 0 : xmlBufferWriteCHAR(buf,
2489 tgl@sss.pgh.pa.us 142 : 0 : xmlXPathCastNodeToString(nodeset->nodeTab[i]));
143 : :
144 : : /* If this isn't the last entry, write the plain sep. */
7168 bruce@momjian.us 145 [ # # ]: 0 : if (i < (nodeset->nodeNr) - 1)
6118 tgl@sss.pgh.pa.us 146 : 0 : xmlBufferWriteChar(buf, (char *) plainsep);
147 : : }
148 : : else
149 : : {
7168 bruce@momjian.us 150 [ # # # # ]: 0 : if ((septagname != NULL) && (xmlStrlen(septagname) > 0))
151 : : {
152 : 0 : xmlBufferWriteChar(buf, "<");
153 : 0 : xmlBufferWriteCHAR(buf, septagname);
154 : 0 : xmlBufferWriteChar(buf, ">");
155 : : }
156 : 0 : xmlNodeDump(buf,
157 : 0 : nodeset->nodeTab[i]->doc,
158 : 0 : nodeset->nodeTab[i],
159 : : 1, 0);
160 : :
161 [ # # # # ]: 0 : if ((septagname != NULL) && (xmlStrlen(septagname) > 0))
162 : : {
163 : 0 : xmlBufferWriteChar(buf, "</");
164 : 0 : xmlBufferWriteCHAR(buf, septagname);
165 : 0 : xmlBufferWriteChar(buf, ">");
166 : : }
167 : : }
168 : : }
169 : : }
170 : :
7345 171 [ # # # # ]: 0 : if ((toptagname != NULL) && (xmlStrlen(toptagname) > 0))
172 : : {
173 : 0 : xmlBufferWriteChar(buf, "</");
174 : 0 : xmlBufferWriteCHAR(buf, toptagname);
175 : 0 : xmlBufferWriteChar(buf, ">");
176 : : }
177 : 0 : result = xmlStrdup(buf->content);
178 : 0 : xmlBufferFree(buf);
179 : 0 : return result;
180 : : }
181 : :
182 : :
183 : : /* Translate a PostgreSQL "varlena" -i.e. a variable length parameter
184 : : * into the libxml2 representation
185 : : */
186 : : static xmlChar *
187 : 0 : pgxml_texttoxmlchar(text *textstring)
188 : : {
5824 tgl@sss.pgh.pa.us 189 : 0 : return (xmlChar *) text_to_cstring(textstring);
190 : : }
191 : :
192 : : /* Publicly visible XPath functions */
193 : :
194 : : /*
195 : : * This is a "raw" xpath function. Check that it returns child elements
196 : : * properly
197 : : */
7345 bruce@momjian.us 198 :CBC 1 : PG_FUNCTION_INFO_V1(xpath_nodeset);
199 : :
200 : : Datum
7345 bruce@momjian.us 201 :UBC 0 : xpath_nodeset(PG_FUNCTION_ARGS)
202 : : {
2590 noah@leadboat.com 203 : 0 : text *document = PG_GETARG_TEXT_PP(0);
2489 tgl@sss.pgh.pa.us 204 : 0 : text *xpathsupp = PG_GETARG_TEXT_PP(1); /* XPath expression */
2590 noah@leadboat.com 205 : 0 : xmlChar *toptag = pgxml_texttoxmlchar(PG_GETARG_TEXT_PP(2));
206 : 0 : xmlChar *septag = pgxml_texttoxmlchar(PG_GETARG_TEXT_PP(3));
207 : : xmlChar *xpath;
208 : : text *xpres;
209 : : xmlXPathObjectPtr res;
210 : : xpath_workspace workspace;
211 : :
4888 tgl@sss.pgh.pa.us 212 : 0 : xpath = pgxml_texttoxmlchar(xpathsupp);
213 : :
214 : 0 : res = pgxml_xpath(document, xpath, &workspace);
215 : :
216 : 0 : xpres = pgxml_result_to_text(res, toptag, septag, NULL);
217 : :
218 : 0 : cleanup_workspace(&workspace);
219 : :
7123 neilc@samurai.com 220 : 0 : pfree(xpath);
221 : :
7168 bruce@momjian.us 222 [ # # ]: 0 : if (xpres == NULL)
223 : 0 : PG_RETURN_NULL();
7345 224 : 0 : PG_RETURN_TEXT_P(xpres);
225 : : }
226 : :
227 : : /*
228 : : * The following function is almost identical, but returns the elements in
229 : : * a list.
230 : : */
7345 bruce@momjian.us 231 :CBC 1 : PG_FUNCTION_INFO_V1(xpath_list);
232 : :
233 : : Datum
7345 bruce@momjian.us 234 :UBC 0 : xpath_list(PG_FUNCTION_ARGS)
235 : : {
2590 noah@leadboat.com 236 : 0 : text *document = PG_GETARG_TEXT_PP(0);
2489 tgl@sss.pgh.pa.us 237 : 0 : text *xpathsupp = PG_GETARG_TEXT_PP(1); /* XPath expression */
2590 noah@leadboat.com 238 : 0 : xmlChar *plainsep = pgxml_texttoxmlchar(PG_GETARG_TEXT_PP(2));
239 : : xmlChar *xpath;
240 : : text *xpres;
241 : : xmlXPathObjectPtr res;
242 : : xpath_workspace workspace;
243 : :
4888 tgl@sss.pgh.pa.us 244 : 0 : xpath = pgxml_texttoxmlchar(xpathsupp);
245 : :
246 : 0 : res = pgxml_xpath(document, xpath, &workspace);
247 : :
248 : 0 : xpres = pgxml_result_to_text(res, NULL, NULL, plainsep);
249 : :
250 : 0 : cleanup_workspace(&workspace);
251 : :
7123 neilc@samurai.com 252 : 0 : pfree(xpath);
253 : :
7168 bruce@momjian.us 254 [ # # ]: 0 : if (xpres == NULL)
255 : 0 : PG_RETURN_NULL();
7345 256 : 0 : PG_RETURN_TEXT_P(xpres);
257 : : }
258 : :
259 : :
7345 bruce@momjian.us 260 :CBC 2 : PG_FUNCTION_INFO_V1(xpath_string);
261 : :
262 : : Datum
263 : 1 : xpath_string(PG_FUNCTION_ARGS)
264 : : {
2590 noah@leadboat.com 265 : 1 : text *document = PG_GETARG_TEXT_PP(0);
2489 tgl@sss.pgh.pa.us 266 : 1 : text *xpathsupp = PG_GETARG_TEXT_PP(1); /* XPath expression */
267 : : xmlChar *xpath;
268 : : int32 pathsize;
269 : : text *xpres;
270 : : xmlXPathObjectPtr res;
271 : : xpath_workspace workspace;
272 : :
2590 noah@leadboat.com 273 [ - + - - : 1 : pathsize = VARSIZE_ANY_EXHDR(xpathsupp);
- - - - +
- ]
274 : :
275 : : /*
276 : : * We encapsulate the supplied path with "string()" = 8 chars + 1 for NUL
277 : : * at end
278 : : */
279 : : /* We could try casting to string using the libxml function? */
280 : :
7168 bruce@momjian.us 281 : 1 : xpath = (xmlChar *) palloc(pathsize + 9);
3368 tgl@sss.pgh.pa.us 282 : 1 : memcpy((char *) xpath, "string(", 7);
2590 noah@leadboat.com 283 [ + - ]: 1 : memcpy((char *) (xpath + 7), VARDATA_ANY(xpathsupp), pathsize);
7168 bruce@momjian.us 284 : 1 : xpath[pathsize + 7] = ')';
285 : 1 : xpath[pathsize + 8] = '\0';
286 : :
4888 tgl@sss.pgh.pa.us 287 : 1 : res = pgxml_xpath(document, xpath, &workspace);
288 : :
289 : 1 : xpres = pgxml_result_to_text(res, NULL, NULL, NULL);
290 : :
291 : 1 : cleanup_workspace(&workspace);
292 : :
7123 neilc@samurai.com 293 : 1 : pfree(xpath);
294 : :
7168 bruce@momjian.us 295 [ + - ]: 1 : if (xpres == NULL)
296 : 1 : PG_RETURN_NULL();
7345 bruce@momjian.us 297 :UBC 0 : PG_RETURN_TEXT_P(xpres);
298 : : }
299 : :
300 : :
7345 bruce@momjian.us 301 :CBC 1 : PG_FUNCTION_INFO_V1(xpath_number);
302 : :
303 : : Datum
7345 bruce@momjian.us 304 :UBC 0 : xpath_number(PG_FUNCTION_ARGS)
305 : : {
2590 noah@leadboat.com 306 : 0 : text *document = PG_GETARG_TEXT_PP(0);
2489 tgl@sss.pgh.pa.us 307 : 0 : text *xpathsupp = PG_GETARG_TEXT_PP(1); /* XPath expression */
308 : : xmlChar *xpath;
309 : : float4 fRes;
310 : : xmlXPathObjectPtr res;
311 : : xpath_workspace workspace;
312 : :
7345 bruce@momjian.us 313 : 0 : xpath = pgxml_texttoxmlchar(xpathsupp);
314 : :
4888 tgl@sss.pgh.pa.us 315 : 0 : res = pgxml_xpath(document, xpath, &workspace);
316 : :
7123 neilc@samurai.com 317 : 0 : pfree(xpath);
318 : :
7345 bruce@momjian.us 319 [ # # ]: 0 : if (res == NULL)
7168 320 : 0 : PG_RETURN_NULL();
321 : :
7345 322 : 0 : fRes = xmlXPathCastToNumber(res);
323 : :
4888 tgl@sss.pgh.pa.us 324 : 0 : cleanup_workspace(&workspace);
325 : :
7345 bruce@momjian.us 326 [ # # ]: 0 : if (xmlXPathIsNaN(fRes))
7168 327 : 0 : PG_RETURN_NULL();
328 : :
7345 329 : 0 : PG_RETURN_FLOAT4(fRes);
330 : : }
331 : :
332 : :
7345 bruce@momjian.us 333 :CBC 1 : PG_FUNCTION_INFO_V1(xpath_bool);
334 : :
335 : : Datum
7345 bruce@momjian.us 336 :UBC 0 : xpath_bool(PG_FUNCTION_ARGS)
337 : : {
2590 noah@leadboat.com 338 : 0 : text *document = PG_GETARG_TEXT_PP(0);
2489 tgl@sss.pgh.pa.us 339 : 0 : text *xpathsupp = PG_GETARG_TEXT_PP(1); /* XPath expression */
340 : : xmlChar *xpath;
341 : : int bRes;
342 : : xmlXPathObjectPtr res;
343 : : xpath_workspace workspace;
344 : :
7345 bruce@momjian.us 345 : 0 : xpath = pgxml_texttoxmlchar(xpathsupp);
346 : :
4888 tgl@sss.pgh.pa.us 347 : 0 : res = pgxml_xpath(document, xpath, &workspace);
348 : :
7123 neilc@samurai.com 349 : 0 : pfree(xpath);
350 : :
7345 bruce@momjian.us 351 [ # # ]: 0 : if (res == NULL)
7168 352 : 0 : PG_RETURN_BOOL(false);
353 : :
7345 354 : 0 : bRes = xmlXPathCastToBoolean(res);
355 : :
4888 tgl@sss.pgh.pa.us 356 : 0 : cleanup_workspace(&workspace);
357 : :
7345 bruce@momjian.us 358 : 0 : PG_RETURN_BOOL(bRes);
359 : : }
360 : :
361 : :
362 : :
363 : : /* Core function to evaluate XPath query */
364 : :
365 : : static xmlXPathObjectPtr
4888 tgl@sss.pgh.pa.us 366 :CBC 1 : pgxml_xpath(text *document, xmlChar *xpath, xpath_workspace *workspace)
367 : : {
2590 noah@leadboat.com 368 [ - + - - : 1 : int32 docsize = VARSIZE_ANY_EXHDR(document);
- - - - -
+ ]
369 : : PgXmlErrorContext *xmlerrcxt;
370 : : xmlXPathCompExprPtr comppath;
371 : :
4888 tgl@sss.pgh.pa.us 372 : 1 : workspace->doctree = NULL;
373 : 1 : workspace->ctxt = NULL;
374 : 1 : workspace->res = NULL;
375 : :
4652 376 : 1 : xmlerrcxt = pgxml_parser_init(PG_XML_STRICTNESS_LEGACY);
377 : :
378 [ + - ]: 1 : PG_TRY();
379 : : {
88 michael@paquier.xyz 380 [ - + ]:GNC 1 : workspace->doctree = xmlReadMemory((char *) VARDATA_ANY(document),
381 : : docsize, NULL, NULL,
382 : : XML_PARSE_NOENT);
4652 tgl@sss.pgh.pa.us 383 [ - + ]:CBC 1 : if (workspace->doctree != NULL)
384 : : {
4652 tgl@sss.pgh.pa.us 385 :UBC 0 : workspace->ctxt = xmlXPathNewContext(workspace->doctree);
386 : 0 : workspace->ctxt->node = xmlDocGetRootElement(workspace->doctree);
387 : :
388 : : /* compile the path */
389 : 0 : comppath = xmlXPathCompile(xpath);
390 [ # # ]: 0 : if (comppath == NULL)
391 : 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_EXTERNAL_ROUTINE_EXCEPTION,
392 : : "XPath Syntax Error");
393 : :
394 : : /* Now evaluate the path expression. */
395 : 0 : workspace->res = xmlXPathCompiledEval(comppath, workspace->ctxt);
396 : :
397 : 0 : xmlXPathFreeCompExpr(comppath);
398 : : }
399 : : }
400 : 0 : PG_CATCH();
401 : : {
4888 402 : 0 : cleanup_workspace(workspace);
403 : :
4652 404 : 0 : pg_xml_done(xmlerrcxt, true);
405 : :
406 : 0 : PG_RE_THROW();
407 : : }
4652 tgl@sss.pgh.pa.us 408 [ - + ]:CBC 1 : PG_END_TRY();
409 : :
410 [ + - ]: 1 : if (workspace->res == NULL)
4888 411 : 1 : cleanup_workspace(workspace);
412 : :
4652 413 : 1 : pg_xml_done(xmlerrcxt, false);
414 : :
415 : 1 : return workspace->res;
416 : : }
417 : :
418 : : /* Clean up after processing the result of pgxml_xpath() */
419 : : static void
4888 420 : 2 : cleanup_workspace(xpath_workspace *workspace)
421 : : {
422 [ - + ]: 2 : if (workspace->res)
4888 tgl@sss.pgh.pa.us 423 :UBC 0 : xmlXPathFreeObject(workspace->res);
4888 tgl@sss.pgh.pa.us 424 :CBC 2 : workspace->res = NULL;
425 [ - + ]: 2 : if (workspace->ctxt)
4888 tgl@sss.pgh.pa.us 426 :UBC 0 : xmlXPathFreeContext(workspace->ctxt);
4888 tgl@sss.pgh.pa.us 427 :CBC 2 : workspace->ctxt = NULL;
428 [ - + ]: 2 : if (workspace->doctree)
4888 tgl@sss.pgh.pa.us 429 :UBC 0 : xmlFreeDoc(workspace->doctree);
4888 tgl@sss.pgh.pa.us 430 :CBC 2 : workspace->doctree = NULL;
431 : 2 : }
432 : :
433 : : static text *
7168 bruce@momjian.us 434 : 1 : pgxml_result_to_text(xmlXPathObjectPtr res,
435 : : xmlChar *toptag,
436 : : xmlChar *septag,
437 : : xmlChar *plainsep)
438 : : {
439 : : xmlChar *xpresstr;
440 : : text *xpres;
441 : :
442 [ + - ]: 1 : if (res == NULL)
443 : 1 : return NULL;
444 : :
7345 bruce@momjian.us 445 [ # # # ]:UBC 0 : switch (res->type)
446 : : {
447 : 0 : case XPATH_NODESET:
448 : 0 : xpresstr = pgxmlNodeSetToText(res->nodesetval,
449 : : toptag,
450 : : septag, plainsep);
451 : 0 : break;
452 : :
453 : 0 : case XPATH_STRING:
454 : 0 : xpresstr = xmlStrdup(res->stringval);
455 : 0 : break;
456 : :
457 : 0 : default:
6619 neilc@samurai.com 458 [ # # ]: 0 : elog(NOTICE, "unsupported XQuery result: %d", res->type);
6118 tgl@sss.pgh.pa.us 459 : 0 : xpresstr = xmlStrdup((const xmlChar *) "<unsupported/>");
460 : : }
461 : :
462 : : /* Now convert this result back to text */
5824 463 : 0 : xpres = cstring_to_text((char *) xpresstr);
464 : :
465 : : /* Free various storage */
7345 bruce@momjian.us 466 : 0 : xmlFree(xpresstr);
467 : :
468 : 0 : return xpres;
469 : : }
470 : :
471 : : /*
472 : : * xpath_table is a table function. It needs some tidying (as do the
473 : : * other functions here!
474 : : */
7345 bruce@momjian.us 475 :CBC 2 : PG_FUNCTION_INFO_V1(xpath_table);
476 : :
477 : : Datum
7168 478 : 5 : xpath_table(PG_FUNCTION_ARGS)
479 : : {
480 : : /* Function parameters */
5159 tgl@sss.pgh.pa.us 481 : 5 : char *pkeyfield = text_to_cstring(PG_GETARG_TEXT_PP(0));
482 : 5 : char *xmlfield = text_to_cstring(PG_GETARG_TEXT_PP(1));
483 : 5 : char *relname = text_to_cstring(PG_GETARG_TEXT_PP(2));
484 : 5 : char *xpathset = text_to_cstring(PG_GETARG_TEXT_PP(3));
485 : 5 : char *condition = text_to_cstring(PG_GETARG_TEXT_PP(4));
486 : :
487 : : /* SPI (input tuple) support */
488 : : SPITupleTable *tuptable;
489 : : HeapTuple spi_tuple;
490 : : TupleDesc spi_tupdesc;
491 : :
492 : :
7168 bruce@momjian.us 493 : 5 : ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
494 : : AttInMetadata *attinmeta;
495 : :
496 : : char **values;
497 : : xmlChar **xpaths;
498 : : char *pos;
6118 tgl@sss.pgh.pa.us 499 : 5 : const char *pathsep = "|";
500 : :
501 : : int numpaths;
502 : : int ret;
503 : : uint64 proc;
504 : : int j;
505 : : int rownr; /* For issuing multiple rows from one original
506 : : * document */
507 : : bool had_values; /* To determine end of nodeset results */
508 : : StringInfoData query_buf;
509 : : PgXmlErrorContext *xmlerrcxt;
4652 510 : 5 : volatile xmlDocPtr doctree = NULL;
511 : :
544 michael@paquier.xyz 512 : 5 : InitMaterializedSRF(fcinfo, MAT_SRF_USE_EXPECTED_DESC);
513 : :
514 : : /* must have at least one output column (for the pkey) */
768 515 [ - + ]: 5 : if (rsinfo->setDesc->natts < 1)
5159 tgl@sss.pgh.pa.us 516 [ # # ]:UBC 0 : ereport(ERROR,
517 : : (errcode(ERRCODE_SYNTAX_ERROR),
518 : : errmsg("xpath_table must have at least one output column")));
519 : :
520 : : /*
521 : : * At the moment we assume that the returned attributes make sense for the
522 : : * XPath specified (i.e. we trust the caller). It's not fatal if they get
523 : : * it wrong - the input function for the column type will raise an error
524 : : * if the path result can't be converted into the correct binary
525 : : * representation.
526 : : */
527 : :
768 michael@paquier.xyz 528 :CBC 5 : attinmeta = TupleDescGetAttInMetadata(rsinfo->setDesc);
529 : :
530 : 5 : values = (char **) palloc(rsinfo->setDesc->natts * sizeof(char *));
531 : 5 : xpaths = (xmlChar **) palloc(rsinfo->setDesc->natts * sizeof(xmlChar *));
532 : :
533 : : /*
534 : : * Split XPaths. xpathset is a writable CString.
535 : : *
536 : : * Note that we stop splitting once we've done all needed for tupdesc
537 : : */
7168 bruce@momjian.us 538 : 5 : numpaths = 0;
539 : 5 : pos = xpathset;
768 michael@paquier.xyz 540 [ + + ]: 7 : while (numpaths < (rsinfo->setDesc->natts - 1))
541 : : {
5159 tgl@sss.pgh.pa.us 542 : 5 : xpaths[numpaths++] = (xmlChar *) pos;
7168 bruce@momjian.us 543 : 5 : pos = strstr(pos, pathsep);
544 [ + + ]: 5 : if (pos != NULL)
545 : : {
546 : 2 : *pos = '\0';
547 : 2 : pos++;
548 : : }
549 : : else
5159 tgl@sss.pgh.pa.us 550 : 3 : break;
551 : : }
552 : :
553 : : /* Now build query */
6619 neilc@samurai.com 554 : 5 : initStringInfo(&query_buf);
555 : :
556 : : /* Build initial sql statement */
557 : 5 : appendStringInfo(&query_buf, "SELECT %s, %s FROM %s WHERE %s",
558 : : pkeyfield,
559 : : xmlfield,
560 : : relname,
561 : : condition);
562 : :
7168 bruce@momjian.us 563 [ - + ]: 5 : if ((ret = SPI_connect()) < 0)
7168 bruce@momjian.us 564 [ # # ]:UBC 0 : elog(ERROR, "xpath_table: SPI_connect returned %d", ret);
565 : :
6619 neilc@samurai.com 566 [ - + ]:CBC 5 : if ((ret = SPI_exec(query_buf.data, 0)) != SPI_OK_SELECT)
5159 tgl@sss.pgh.pa.us 567 [ # # ]:UBC 0 : elog(ERROR, "xpath_table: SPI execution failed for query %s",
568 : : query_buf.data);
569 : :
7168 bruce@momjian.us 570 :CBC 5 : proc = SPI_processed;
571 : 5 : tuptable = SPI_tuptable;
572 : 5 : spi_tupdesc = tuptable->tupdesc;
573 : :
574 : : /*
575 : : * Check that SPI returned correct result. If you put a comma into one of
576 : : * the function parameters, this will catch it when the SPI query returns
577 : : * e.g. 3 columns.
578 : : */
579 [ - + ]: 5 : if (spi_tupdesc->natts != 2)
580 : : {
7168 bruce@momjian.us 581 [ # # ]:UBC 0 : ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
582 : : errmsg("expression returning multiple columns is not valid in parameter list"),
583 : : errdetail("Expected two columns in SPI result, got %d.", spi_tupdesc->natts)));
584 : : }
585 : :
586 : : /*
587 : : * Setup the parser. This should happen after we are done evaluating the
588 : : * query, in case it calls functions that set up libxml differently.
589 : : */
4652 tgl@sss.pgh.pa.us 590 :CBC 5 : xmlerrcxt = pgxml_parser_init(PG_XML_STRICTNESS_LEGACY);
591 : :
592 [ + - ]: 5 : PG_TRY();
593 : : {
594 : : /* For each row i.e. document returned from SPI */
595 : : uint64 i;
596 : :
4326 bruce@momjian.us 597 [ + + ]: 10 : for (i = 0; i < proc; i++)
598 : : {
599 : : char *pkey;
600 : : char *xmldoc;
601 : : xmlXPathContextPtr ctxt;
602 : : xmlXPathObjectPtr res;
603 : : xmlChar *resstr;
604 : : xmlXPathCompExprPtr comppath;
605 : : HeapTuple ret_tuple;
606 : :
607 : : /* Extract the row data as C Strings */
608 : 5 : spi_tuple = tuptable->vals[i];
609 : 5 : pkey = SPI_getvalue(spi_tuple, spi_tupdesc, 1);
610 : 5 : xmldoc = SPI_getvalue(spi_tuple, spi_tupdesc, 2);
611 : :
612 : : /*
613 : : * Clear the values array, so that not-well-formed documents
614 : : * return NULL in all columns. Note that this also means that
615 : : * spare columns will be NULL.
616 : : */
768 michael@paquier.xyz 617 [ + + ]: 15 : for (j = 0; j < rsinfo->setDesc->natts; j++)
4326 bruce@momjian.us 618 : 10 : values[j] = NULL;
619 : :
620 : : /* Insert primary key */
621 : 5 : values[0] = pkey;
622 : :
623 : : /* Parse the document */
624 [ + - ]: 5 : if (xmldoc)
88 michael@paquier.xyz 625 :GNC 5 : doctree = xmlReadMemory(xmldoc, strlen(xmldoc),
626 : : NULL, NULL,
627 : : XML_PARSE_NOENT);
628 : : else /* treat NULL as not well-formed */
4326 bruce@momjian.us 629 :UBC 0 : doctree = NULL;
630 : :
4326 bruce@momjian.us 631 [ - + ]:CBC 5 : if (doctree == NULL)
632 : : {
633 : : /* not well-formed, so output all-NULL tuple */
4326 bruce@momjian.us 634 :UBC 0 : ret_tuple = BuildTupleFromCStrings(attinmeta, values);
768 michael@paquier.xyz 635 : 0 : tuplestore_puttuple(rsinfo->setResult, ret_tuple);
4326 bruce@momjian.us 636 : 0 : heap_freetuple(ret_tuple);
637 : : }
638 : : else
639 : : {
640 : : /* New loop here - we have to deal with nodeset results */
4326 bruce@momjian.us 641 :CBC 5 : rownr = 0;
642 : :
643 : : do
644 : : {
645 : : /* Now evaluate the set of xpaths. */
646 : 8 : had_values = false;
647 [ + + ]: 18 : for (j = 0; j < numpaths; j++)
648 : : {
649 : 10 : ctxt = xmlXPathNewContext(doctree);
650 : 10 : ctxt->node = xmlDocGetRootElement(doctree);
651 : :
652 : : /* compile the path */
653 : 10 : comppath = xmlXPathCompile(xpaths[j]);
654 [ - + ]: 10 : if (comppath == NULL)
4326 bruce@momjian.us 655 :UBC 0 : xml_ereport(xmlerrcxt, ERROR,
656 : : ERRCODE_EXTERNAL_ROUTINE_EXCEPTION,
657 : : "XPath Syntax Error");
658 : :
659 : : /* Now evaluate the path expression. */
4326 bruce@momjian.us 660 :CBC 10 : res = xmlXPathCompiledEval(comppath, ctxt);
661 : 10 : xmlXPathFreeCompExpr(comppath);
662 : :
663 [ + - ]: 10 : if (res != NULL)
664 : : {
665 [ + - - ]: 10 : switch (res->type)
666 : : {
667 : 10 : case XPATH_NODESET:
668 : : /* We see if this nodeset has enough nodes */
669 [ + - ]: 10 : if (res->nodesetval != NULL &&
670 [ + + ]: 10 : rownr < res->nodesetval->nodeNr)
671 : : {
672 : 4 : resstr = xmlXPathCastNodeToString(res->nodesetval->nodeTab[rownr]);
673 : 4 : had_values = true;
674 : : }
675 : : else
676 : 6 : resstr = NULL;
677 : :
678 : 10 : break;
679 : :
4326 bruce@momjian.us 680 :UBC 0 : case XPATH_STRING:
681 : 0 : resstr = xmlStrdup(res->stringval);
682 : 0 : break;
683 : :
684 : 0 : default:
685 [ # # ]: 0 : elog(NOTICE, "unsupported XQuery result: %d", res->type);
686 : 0 : resstr = xmlStrdup((const xmlChar *) "<unsupported/>");
687 : : }
688 : :
689 : : /*
690 : : * Insert this into the appropriate column in the
691 : : * result tuple.
692 : : */
4326 bruce@momjian.us 693 :CBC 10 : values[j + 1] = (char *) resstr;
694 : : }
695 : 10 : xmlXPathFreeContext(ctxt);
696 : : }
697 : :
698 : : /* Now add the tuple to the output, if there is one. */
699 [ + + ]: 8 : if (had_values)
700 : : {
701 : 3 : ret_tuple = BuildTupleFromCStrings(attinmeta, values);
768 michael@paquier.xyz 702 : 3 : tuplestore_puttuple(rsinfo->setResult, ret_tuple);
4326 bruce@momjian.us 703 : 3 : heap_freetuple(ret_tuple);
704 : : }
705 : :
706 : 8 : rownr++;
707 [ + + ]: 8 : } while (had_values);
708 : : }
709 : :
710 [ + - ]: 5 : if (doctree != NULL)
711 : 5 : xmlFreeDoc(doctree);
712 : 5 : doctree = NULL;
713 : :
714 [ + - ]: 5 : if (pkey)
715 : 5 : pfree(pkey);
716 [ + - ]: 5 : if (xmldoc)
717 : 5 : pfree(xmldoc);
718 : : }
719 : : }
4652 tgl@sss.pgh.pa.us 720 :UBC 0 : PG_CATCH();
721 : : {
722 [ # # ]: 0 : if (doctree != NULL)
723 : 0 : xmlFreeDoc(doctree);
724 : :
725 : 0 : pg_xml_done(xmlerrcxt, true);
726 : :
727 : 0 : PG_RE_THROW();
728 : : }
4652 tgl@sss.pgh.pa.us 729 [ - + ]:CBC 5 : PG_END_TRY();
730 : :
731 [ - + ]: 5 : if (doctree != NULL)
4652 tgl@sss.pgh.pa.us 732 :UBC 0 : xmlFreeDoc(doctree);
733 : :
4652 tgl@sss.pgh.pa.us 734 :CBC 5 : pg_xml_done(xmlerrcxt, false);
735 : :
7168 bruce@momjian.us 736 : 5 : SPI_finish();
737 : :
738 : : /*
739 : : * SFRM_Materialize mode expects us to return a NULL Datum. The actual
740 : : * tuples are in our tuplestore and passed back through rsinfo->setResult.
741 : : * rsinfo->setDesc is set to the tuple description that we actually used
742 : : * to build our tuples with, so the caller can verify we did what it was
743 : : * expecting.
744 : : */
745 : 5 : return (Datum) 0;
746 : : }
|