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