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