Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * xml.c
4 : : * XML data type support.
5 : : *
6 : : *
7 : : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
8 : : * Portions Copyright (c) 1994, Regents of the University of California
9 : : *
10 : : * src/backend/utils/adt/xml.c
11 : : *
12 : : *-------------------------------------------------------------------------
13 : : */
14 : :
15 : : /*
16 : : * Generally, XML type support is only available when libxml use was
17 : : * configured during the build. But even if that is not done, the
18 : : * type and all the functions are available, but most of them will
19 : : * fail. For one thing, this avoids having to manage variant catalog
20 : : * installations. But it also has nice effects such as that you can
21 : : * dump a database containing XML type data even if the server is not
22 : : * linked with libxml. Thus, make sure xml_out() works even if nothing
23 : : * else does.
24 : : */
25 : :
26 : : /*
27 : : * Notes on memory management:
28 : : *
29 : : * Sometimes libxml allocates global structures in the hope that it can reuse
30 : : * them later on. This makes it impractical to change the xmlMemSetup
31 : : * functions on-the-fly; that is likely to lead to trying to pfree() chunks
32 : : * allocated with malloc() or vice versa. Since libxml might be used by
33 : : * loadable modules, eg libperl, our only safe choices are to change the
34 : : * functions at postmaster/backend launch or not at all. Since we'd rather
35 : : * not activate libxml in sessions that might never use it, the latter choice
36 : : * is the preferred one. However, for debugging purposes it can be awfully
37 : : * handy to constrain libxml's allocations to be done in a specific palloc
38 : : * context, where they're easy to track. Therefore there is code here that
39 : : * can be enabled in debug builds to redirect libxml's allocations into a
40 : : * special context LibxmlContext. It's not recommended to turn this on in
41 : : * a production build because of the possibility of bad interactions with
42 : : * external modules.
43 : : */
44 : : /* #define USE_LIBXMLCONTEXT */
45 : :
46 : : #include "postgres.h"
47 : :
48 : : #ifdef USE_LIBXML
49 : : #include <libxml/chvalid.h>
50 : : #include <libxml/entities.h>
51 : : #include <libxml/parser.h>
52 : : #include <libxml/parserInternals.h>
53 : : #include <libxml/tree.h>
54 : : #include <libxml/uri.h>
55 : : #include <libxml/xmlerror.h>
56 : : #include <libxml/xmlsave.h>
57 : : #include <libxml/xmlversion.h>
58 : : #include <libxml/xmlwriter.h>
59 : : #include <libxml/xpath.h>
60 : : #include <libxml/xpathInternals.h>
61 : :
62 : : /*
63 : : * We used to check for xmlStructuredErrorContext via a configure test; but
64 : : * that doesn't work on Windows, so instead use this grottier method of
65 : : * testing the library version number.
66 : : */
67 : : #if LIBXML_VERSION >= 20704
68 : : #define HAVE_XMLSTRUCTUREDERRORCONTEXT 1
69 : : #endif
70 : :
71 : : /*
72 : : * libxml2 2.12 decided to insert "const" into the error handler API.
73 : : */
74 : : #if LIBXML_VERSION >= 21200
75 : : #define PgXmlErrorPtr const xmlError *
76 : : #else
77 : : #define PgXmlErrorPtr xmlErrorPtr
78 : : #endif
79 : :
80 : : #endif /* USE_LIBXML */
81 : :
82 : : #include "access/htup_details.h"
83 : : #include "access/table.h"
84 : : #include "catalog/namespace.h"
85 : : #include "catalog/pg_class.h"
86 : : #include "catalog/pg_type.h"
87 : : #include "executor/spi.h"
88 : : #include "executor/tablefunc.h"
89 : : #include "fmgr.h"
90 : : #include "lib/stringinfo.h"
91 : : #include "libpq/pqformat.h"
92 : : #include "mb/pg_wchar.h"
93 : : #include "miscadmin.h"
94 : : #include "nodes/execnodes.h"
95 : : #include "nodes/miscnodes.h"
96 : : #include "nodes/nodeFuncs.h"
97 : : #include "utils/array.h"
98 : : #include "utils/builtins.h"
99 : : #include "utils/date.h"
100 : : #include "utils/datetime.h"
101 : : #include "utils/lsyscache.h"
102 : : #include "utils/rel.h"
103 : : #include "utils/syscache.h"
104 : : #include "utils/xml.h"
105 : :
106 : :
107 : : /* GUC variables */
108 : : int xmlbinary = XMLBINARY_BASE64;
109 : : int xmloption = XMLOPTION_CONTENT;
110 : :
111 : : #ifdef USE_LIBXML
112 : :
113 : : /* random number to identify PgXmlErrorContext */
114 : : #define ERRCXT_MAGIC 68275028
115 : :
116 : : struct PgXmlErrorContext
117 : : {
118 : : int magic;
119 : : /* strictness argument passed to pg_xml_init */
120 : : PgXmlStrictness strictness;
121 : : /* current error status and accumulated message, if any */
122 : : bool err_occurred;
123 : : StringInfoData err_buf;
124 : : /* previous libxml error handling state (saved by pg_xml_init) */
125 : : xmlStructuredErrorFunc saved_errfunc;
126 : : void *saved_errcxt;
127 : : /* previous libxml entity handler (saved by pg_xml_init) */
128 : : xmlExternalEntityLoader saved_entityfunc;
129 : : };
130 : :
131 : : static xmlParserInputPtr xmlPgEntityLoader(const char *URL, const char *ID,
132 : : xmlParserCtxtPtr ctxt);
133 : : static void xml_errsave(Node *escontext, PgXmlErrorContext *errcxt,
134 : : int sqlcode, const char *msg);
135 : : static void xml_errorHandler(void *data, PgXmlErrorPtr error);
136 : : static int errdetail_for_xml_code(int code);
137 : : static void chopStringInfoNewlines(StringInfo str);
138 : : static void appendStringInfoLineSeparator(StringInfo str);
139 : :
140 : : #ifdef USE_LIBXMLCONTEXT
141 : :
142 : : static MemoryContext LibxmlContext = NULL;
143 : :
144 : : static void xml_memory_init(void);
145 : : static void *xml_palloc(size_t size);
146 : : static void *xml_repalloc(void *ptr, size_t size);
147 : : static void xml_pfree(void *ptr);
148 : : static char *xml_pstrdup(const char *string);
149 : : #endif /* USE_LIBXMLCONTEXT */
150 : :
151 : : static xmlChar *xml_text2xmlChar(text *in);
152 : : static int parse_xml_decl(const xmlChar *str, size_t *lenp,
153 : : xmlChar **version, xmlChar **encoding, int *standalone);
154 : : static bool print_xml_decl(StringInfo buf, const xmlChar *version,
155 : : pg_enc encoding, int standalone);
156 : : static bool xml_doctype_in_content(const xmlChar *str);
157 : : static xmlDocPtr xml_parse(text *data, XmlOptionType xmloption_arg,
158 : : bool preserve_whitespace, int encoding,
159 : : XmlOptionType *parsed_xmloptiontype,
160 : : Node *escontext);
161 : : static text *xml_xmlnodetoxmltype(xmlNodePtr cur, PgXmlErrorContext *xmlerrcxt);
162 : : static int xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj,
163 : : ArrayBuildState *astate,
164 : : PgXmlErrorContext *xmlerrcxt);
165 : : static xmlChar *pg_xmlCharStrndup(const char *str, size_t len);
166 : : #endif /* USE_LIBXML */
167 : :
168 : : static void xmldata_root_element_start(StringInfo result, const char *eltname,
169 : : const char *xmlschema, const char *targetns,
170 : : bool top_level);
171 : : static void xmldata_root_element_end(StringInfo result, const char *eltname);
172 : : static StringInfo query_to_xml_internal(const char *query, char *tablename,
173 : : const char *xmlschema, bool nulls, bool tableforest,
174 : : const char *targetns, bool top_level);
175 : : static const char *map_sql_table_to_xmlschema(TupleDesc tupdesc, Oid relid,
176 : : bool nulls, bool tableforest, const char *targetns);
177 : : static const char *map_sql_schema_to_xmlschema_types(Oid nspid,
178 : : List *relid_list, bool nulls,
179 : : bool tableforest, const char *targetns);
180 : : static const char *map_sql_catalog_to_xmlschema_types(List *nspid_list,
181 : : bool nulls, bool tableforest,
182 : : const char *targetns);
183 : : static const char *map_sql_type_to_xml_name(Oid typeoid, int typmod);
184 : : static const char *map_sql_typecoll_to_xmlschema_types(List *tupdesc_list);
185 : : static const char *map_sql_type_to_xmlschema_type(Oid typeoid, int typmod);
186 : : static void SPI_sql_row_to_xmlelement(uint64 rownum, StringInfo result,
187 : : char *tablename, bool nulls, bool tableforest,
188 : : const char *targetns, bool top_level);
189 : :
190 : : /* XMLTABLE support */
191 : : #ifdef USE_LIBXML
192 : : /* random number to identify XmlTableContext */
193 : : #define XMLTABLE_CONTEXT_MAGIC 46922182
194 : : typedef struct XmlTableBuilderData
195 : : {
196 : : int magic;
197 : : int natts;
198 : : long int row_count;
199 : : PgXmlErrorContext *xmlerrcxt;
200 : : xmlParserCtxtPtr ctxt;
201 : : xmlDocPtr doc;
202 : : xmlXPathContextPtr xpathcxt;
203 : : xmlXPathCompExprPtr xpathcomp;
204 : : xmlXPathObjectPtr xpathobj;
205 : : xmlXPathCompExprPtr *xpathscomp;
206 : : } XmlTableBuilderData;
207 : : #endif
208 : :
209 : : static void XmlTableInitOpaque(struct TableFuncScanState *state, int natts);
210 : : static void XmlTableSetDocument(struct TableFuncScanState *state, Datum value);
211 : : static void XmlTableSetNamespace(struct TableFuncScanState *state, const char *name,
212 : : const char *uri);
213 : : static void XmlTableSetRowFilter(struct TableFuncScanState *state, const char *path);
214 : : static void XmlTableSetColumnFilter(struct TableFuncScanState *state,
215 : : const char *path, int colnum);
216 : : static bool XmlTableFetchRow(struct TableFuncScanState *state);
217 : : static Datum XmlTableGetValue(struct TableFuncScanState *state, int colnum,
218 : : Oid typid, int32 typmod, bool *isnull);
219 : : static void XmlTableDestroyOpaque(struct TableFuncScanState *state);
220 : :
221 : : const TableFuncRoutine XmlTableRoutine =
222 : : {
223 : : .InitOpaque = XmlTableInitOpaque,
224 : : .SetDocument = XmlTableSetDocument,
225 : : .SetNamespace = XmlTableSetNamespace,
226 : : .SetRowFilter = XmlTableSetRowFilter,
227 : : .SetColumnFilter = XmlTableSetColumnFilter,
228 : : .FetchRow = XmlTableFetchRow,
229 : : .GetValue = XmlTableGetValue,
230 : : .DestroyOpaque = XmlTableDestroyOpaque
231 : : };
232 : :
233 : : #define NO_XML_SUPPORT() \
234 : : ereport(ERROR, \
235 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), \
236 : : errmsg("unsupported XML feature"), \
237 : : errdetail("This functionality requires the server to be built with libxml support.")))
238 : :
239 : :
240 : : /* from SQL/XML:2008 section 4.9 */
241 : : #define NAMESPACE_XSD "http://www.w3.org/2001/XMLSchema"
242 : : #define NAMESPACE_XSI "http://www.w3.org/2001/XMLSchema-instance"
243 : : #define NAMESPACE_SQLXML "http://standards.iso.org/iso/9075/2003/sqlxml"
244 : :
245 : :
246 : : #ifdef USE_LIBXML
247 : :
248 : : static int
6385 tgl@sss.pgh.pa.us 249 :UBC 0 : xmlChar_to_encoding(const xmlChar *encoding_name)
250 : : {
251 : 0 : int encoding = pg_char_to_encoding((const char *) encoding_name);
252 : :
6963 253 [ # # ]: 0 : if (encoding < 0)
254 [ # # ]: 0 : ereport(ERROR,
255 : : (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
256 : : errmsg("invalid encoding name \"%s\"",
257 : : (const char *) encoding_name)));
258 : 0 : return encoding;
259 : : }
260 : : #endif
261 : :
262 : :
263 : : /*
264 : : * xml_in uses a plain C string to VARDATA conversion, so for the time being
265 : : * we use the conversion function for the text datatype.
266 : : *
267 : : * This is only acceptable so long as xmltype and text use the same
268 : : * representation.
269 : : */
270 : : Datum
7259 peter_e@gmx.net 271 :CBC 584 : xml_in(PG_FUNCTION_ARGS)
272 : : {
273 : : #ifdef USE_LIBXML
6930 bruce@momjian.us 274 : 584 : char *s = PG_GETARG_CSTRING(0);
275 : : xmltype *vardata;
276 : : xmlDocPtr doc;
277 : :
278 : : /* Build the result object. */
6799 tgl@sss.pgh.pa.us 279 : 584 : vardata = (xmltype *) cstring_to_text(s);
280 : :
281 : : /*
282 : : * Parse the data to check if it is well-formed XML data.
283 : : *
284 : : * Note: we don't need to worry about whether a soft error is detected.
285 : : */
1420 286 : 584 : doc = xml_parse(vardata, xmloption, true, GetDatabaseEncoding(),
287 : : NULL, fcinfo->context);
288 [ + + ]: 552 : if (doc != NULL)
289 : 544 : xmlFreeDoc(doc);
290 : :
7259 peter_e@gmx.net 291 : 552 : PG_RETURN_XML_P(vardata);
292 : : #else
293 : : NO_XML_SUPPORT();
294 : : return 0;
295 : : #endif
296 : : }
297 : :
298 : :
299 : : #define PG_XML_DEFAULT_VERSION "1.0"
300 : :
301 : :
302 : : /*
303 : : * xml_out_internal uses a plain VARDATA to C string conversion, so for the
304 : : * time being we use the conversion function for the text datatype.
305 : : *
306 : : * This is only acceptable so long as xmltype and text use the same
307 : : * representation.
308 : : */
309 : : static char *
6930 bruce@momjian.us 310 : 15912 : xml_out_internal(xmltype *x, pg_enc target_encoding)
311 : : {
6624 tgl@sss.pgh.pa.us 312 : 15912 : char *str = text_to_cstring((text *) x);
313 : :
314 : : #ifdef USE_LIBXML
315 : 15912 : size_t len = strlen(str);
316 : : xmlChar *version;
317 : : int standalone;
318 : : int res_code;
319 : :
6939 320 [ + - ]: 15912 : if ((res_code = parse_xml_decl((xmlChar *) str,
321 : : &len, &version, NULL, &standalone)) == 0)
322 : : {
323 : : StringInfoData buf;
324 : :
7231 peter_e@gmx.net 325 : 15912 : initStringInfo(&buf);
326 : :
7224 327 [ + + ]: 15912 : if (!print_xml_decl(&buf, version, target_encoding, standalone))
328 : : {
329 : : /*
330 : : * If we are not going to produce an XML declaration, eat a single
331 : : * newline in the original string to prevent empty first lines in
332 : : * the output.
333 : : */
7231 334 [ + + ]: 15880 : if (*(str + len) == '\n')
335 : 4 : len += 1;
336 : : }
337 : 15912 : appendStringInfoString(&buf, str + len);
338 : :
6624 tgl@sss.pgh.pa.us 339 : 15912 : pfree(str);
340 : :
7231 peter_e@gmx.net 341 : 15912 : return buf.data;
342 : : }
343 : :
1420 tgl@sss.pgh.pa.us 344 [ # # ]:UBC 0 : ereport(WARNING,
345 : : errcode(ERRCODE_DATA_CORRUPTED),
346 : : errmsg_internal("could not parse XML declaration in stored value"),
347 : : errdetail_for_xml_code(res_code));
348 : : #endif
7231 peter_e@gmx.net 349 : 0 : return str;
350 : : }
351 : :
352 : :
353 : : Datum
7259 peter_e@gmx.net 354 :CBC 15736 : xml_out(PG_FUNCTION_ARGS)
355 : : {
6930 bruce@momjian.us 356 : 15736 : xmltype *x = PG_GETARG_XML_P(0);
357 : :
358 : : /*
359 : : * xml_out removes the encoding property in all cases. This is because we
360 : : * cannot control from here whether the datum will be converted to a
361 : : * different client encoding, so we'd do more harm than good by including
362 : : * it.
363 : : */
7231 peter_e@gmx.net 364 : 15736 : PG_RETURN_CSTRING(xml_out_internal(x, 0));
365 : : }
366 : :
367 : :
368 : : Datum
7252 peter_e@gmx.net 369 :UBC 0 : xml_recv(PG_FUNCTION_ARGS)
370 : : {
371 : : #ifdef USE_LIBXML
372 : 0 : StringInfo buf = (StringInfo) PG_GETARG_POINTER(0);
373 : : xmltype *result;
374 : : const char *input;
375 : : char *str;
376 : : char *newstr;
377 : : int nbytes;
378 : : xmlDocPtr doc;
6271 heikki.linnakangas@i 379 : 0 : xmlChar *encodingStr = NULL;
380 : : int encoding;
381 : :
382 : : /*
383 : : * Read the data in raw format. We don't know yet what the encoding is, as
384 : : * that information is embedded in the xml declaration; so we have to
385 : : * parse that before converting to server encoding.
386 : : */
6983 tgl@sss.pgh.pa.us 387 : 0 : nbytes = buf->len - buf->cursor;
251 peter@eisentraut.org 388 : 0 : input = pq_getmsgbytes(buf, nbytes);
389 : :
390 : : /*
391 : : * We need a null-terminated string to pass to parse_xml_decl(). Rather
392 : : * than make a separate copy, make the temporary result one byte bigger
393 : : * than it needs to be.
394 : : */
6983 tgl@sss.pgh.pa.us 395 : 0 : result = palloc(nbytes + 1 + VARHDRSZ);
7191 396 : 0 : SET_VARSIZE(result, nbytes + VARHDRSZ);
251 peter@eisentraut.org 397 : 0 : memcpy(VARDATA(result), input, nbytes);
6983 tgl@sss.pgh.pa.us 398 : 0 : str = VARDATA(result);
399 : 0 : str[nbytes] = '\0';
400 : :
5364 peter_e@gmx.net 401 : 0 : parse_xml_decl((const xmlChar *) str, NULL, NULL, &encodingStr, NULL);
402 : :
403 : : /*
404 : : * If encoding wasn't explicitly specified in the XML header, treat it as
405 : : * UTF-8, as that's the default in XML. This is different from xml_in(),
406 : : * where the input has to go through the normal client to server encoding
407 : : * conversion.
408 : : */
6271 heikki.linnakangas@i 409 [ # # ]: 0 : encoding = encodingStr ? xmlChar_to_encoding(encodingStr) : PG_UTF8;
410 : :
411 : : /*
412 : : * Parse the data to check if it is well-formed XML data. Assume that
413 : : * xml_parse will throw ERROR if not.
414 : : */
21 tgl@sss.pgh.pa.us 415 :UNC 0 : doc = xml_parse(result, xmloption, true, encoding, NULL, NULL);
7243 peter_e@gmx.net 416 :UBC 0 : xmlFreeDoc(doc);
417 : :
418 : : /* Now that we know what we're dealing with, convert to server encoding */
4638 tgl@sss.pgh.pa.us 419 : 0 : newstr = pg_any_to_server(str, nbytes, encoding);
420 : :
7231 peter_e@gmx.net 421 [ # # ]: 0 : if (newstr != str)
422 : : {
6983 tgl@sss.pgh.pa.us 423 : 0 : pfree(result);
6759 424 : 0 : result = (xmltype *) cstring_to_text(newstr);
6983 425 : 0 : pfree(newstr);
426 : : }
427 : :
7252 peter_e@gmx.net 428 : 0 : PG_RETURN_XML_P(result);
429 : : #else
430 : : NO_XML_SUPPORT();
431 : : return 0;
432 : : #endif
433 : : }
434 : :
435 : :
436 : : Datum
437 : 0 : xml_send(PG_FUNCTION_ARGS)
438 : : {
6930 bruce@momjian.us 439 : 0 : xmltype *x = PG_GETARG_XML_P(0);
440 : : char *outval;
441 : : StringInfoData buf;
442 : :
443 : : /*
444 : : * xml_out_internal doesn't convert the encoding, it just prints the right
445 : : * declaration. pq_sendtext will do the conversion.
446 : : */
6983 tgl@sss.pgh.pa.us 447 : 0 : outval = xml_out_internal(x, pg_get_client_encoding());
448 : :
7252 peter_e@gmx.net 449 : 0 : pq_begintypsend(&buf);
6983 tgl@sss.pgh.pa.us 450 : 0 : pq_sendtext(&buf, outval, strlen(outval));
451 : 0 : pfree(outval);
7252 peter_e@gmx.net 452 : 0 : PG_RETURN_BYTEA_P(pq_endtypsend(&buf));
453 : : }
454 : :
455 : :
456 : : #ifdef USE_LIBXML
457 : : static void
7259 peter_e@gmx.net 458 :CBC 93 : appendStringInfoText(StringInfo str, const text *t)
459 : : {
3525 noah@leadboat.com 460 : 93 : appendBinaryStringInfo(str, VARDATA_ANY(t), VARSIZE_ANY_EXHDR(t));
7259 peter_e@gmx.net 461 : 93 : }
462 : : #endif
463 : :
464 : :
465 : : static xmltype *
466 : 15257 : stringinfo_to_xmltype(StringInfo buf)
467 : : {
6759 tgl@sss.pgh.pa.us 468 : 15257 : return (xmltype *) cstring_to_text_with_len(buf->data, buf->len);
469 : : }
470 : :
471 : :
472 : : static xmltype *
7231 peter_e@gmx.net 473 : 57 : cstring_to_xmltype(const char *string)
474 : : {
6759 tgl@sss.pgh.pa.us 475 : 57 : return (xmltype *) cstring_to_text(string);
476 : : }
477 : :
478 : :
479 : : #ifdef USE_LIBXML
480 : : static xmltype *
7243 peter_e@gmx.net 481 : 15331 : xmlBuffer_to_xmltype(xmlBufferPtr buf)
482 : : {
5534 483 : 15331 : return (xmltype *) cstring_to_text_with_len((const char *) xmlBufferContent(buf),
484 : : xmlBufferLength(buf));
485 : : }
486 : : #endif
487 : :
488 : :
489 : : Datum
7259 490 : 33 : xmlcomment(PG_FUNCTION_ARGS)
491 : : {
492 : : #ifdef USE_LIBXML
3525 noah@leadboat.com 493 : 33 : text *arg = PG_GETARG_TEXT_PP(0);
494 : 33 : char *argdata = VARDATA_ANY(arg);
495 : 33 : int len = VARSIZE_ANY_EXHDR(arg);
496 : : StringInfoData buf;
497 : : int i;
498 : :
499 : : /* check for "--" in string or "-" at the end */
7259 peter_e@gmx.net 500 [ + + ]: 144 : for (i = 1; i < len; i++)
501 : : {
6939 tgl@sss.pgh.pa.us 502 [ + + + + ]: 115 : if (argdata[i] == '-' && argdata[i - 1] == '-')
503 [ + - ]: 4 : ereport(ERROR,
504 : : (errcode(ERRCODE_INVALID_XML_COMMENT),
505 : : errmsg("invalid XML comment")));
506 : : }
507 [ + - + + ]: 29 : if (len > 0 && argdata[len - 1] == '-')
508 [ + - ]: 4 : ereport(ERROR,
509 : : (errcode(ERRCODE_INVALID_XML_COMMENT),
510 : : errmsg("invalid XML comment")));
511 : :
7259 peter_e@gmx.net 512 : 25 : initStringInfo(&buf);
4753 rhaas@postgresql.org 513 : 25 : appendStringInfoString(&buf, "<!--");
7259 peter_e@gmx.net 514 : 25 : appendStringInfoText(&buf, arg);
4753 rhaas@postgresql.org 515 : 25 : appendStringInfoString(&buf, "-->");
516 : :
7259 peter_e@gmx.net 517 : 25 : PG_RETURN_XML_P(stringinfo_to_xmltype(&buf));
518 : : #else
519 : : NO_XML_SUPPORT();
520 : : return 0;
521 : : #endif
522 : : }
523 : :
524 : :
525 : : Datum
1095 dgustafsson@postgres 526 : 25 : xmltext(PG_FUNCTION_ARGS)
527 : : {
528 : : #ifdef USE_LIBXML
529 : 25 : text *arg = PG_GETARG_TEXT_PP(0);
530 : : text *result;
240 michael@paquier.xyz 531 : 25 : xmlChar *volatile xmlbuf = NULL;
532 : : PgXmlErrorContext *xmlerrcxt;
533 : :
534 : : /* First we gotta spin up some error handling. */
492 535 : 25 : xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
536 : :
537 [ + - ]: 25 : PG_TRY();
538 : : {
539 : 25 : xmlbuf = xmlEncodeSpecialChars(NULL, xml_text2xmlChar(arg));
540 : :
541 [ + - - + ]: 25 : if (xmlbuf == NULL || xmlerrcxt->err_occurred)
492 michael@paquier.xyz 542 :UBC 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
543 : : "could not allocate xmlChar");
544 : :
492 michael@paquier.xyz 545 :CBC 25 : result = cstring_to_text_with_len((const char *) xmlbuf,
546 : : xmlStrlen(xmlbuf));
547 : : }
492 michael@paquier.xyz 548 :UBC 0 : PG_CATCH();
549 : : {
550 [ # # ]: 0 : if (xmlbuf)
240 551 : 0 : xmlFree(xmlbuf);
552 : :
492 553 : 0 : pg_xml_done(xmlerrcxt, true);
554 : 0 : PG_RE_THROW();
555 : : }
492 michael@paquier.xyz 556 [ - + ]:CBC 25 : PG_END_TRY();
557 : :
240 558 : 25 : xmlFree(xmlbuf);
492 559 : 25 : pg_xml_done(xmlerrcxt, false);
560 : :
1095 dgustafsson@postgres 561 : 25 : PG_RETURN_XML_P(result);
562 : : #else
563 : : NO_XML_SUPPORT();
564 : : return 0;
565 : : #endif /* not USE_LIBXML */
566 : : }
567 : :
568 : :
569 : : /*
570 : : * TODO: xmlconcat needs to merge the notations and unparsed entities
571 : : * of the argument values. Not very important in practice, though.
572 : : */
573 : : xmltype *
7229 peter_e@gmx.net 574 : 15070 : xmlconcat(List *args)
575 : : {
576 : : #ifdef USE_LIBXML
577 : 15070 : int global_standalone = 1;
6930 bruce@momjian.us 578 : 15070 : xmlChar *global_version = NULL;
7229 peter_e@gmx.net 579 : 15070 : bool global_version_no_value = false;
580 : : StringInfoData buf;
581 : : ListCell *v;
582 : :
583 : 15070 : initStringInfo(&buf);
584 [ + - + + : 45214 : foreach(v, args)
+ + ]
585 : : {
6930 bruce@momjian.us 586 : 30144 : xmltype *x = DatumGetXmlP(PointerGetDatum(lfirst(v)));
587 : : size_t len;
588 : : xmlChar *version;
589 : : int standalone;
590 : : char *str;
591 : :
7229 peter_e@gmx.net 592 : 30144 : len = VARSIZE(x) - VARHDRSZ;
6759 tgl@sss.pgh.pa.us 593 : 30144 : str = text_to_cstring((text *) x);
594 : :
7229 peter_e@gmx.net 595 : 30144 : parse_xml_decl((xmlChar *) str, &len, &version, NULL, &standalone);
596 : :
597 [ + + - + ]: 30144 : if (standalone == 0 && global_standalone == 1)
7229 peter_e@gmx.net 598 :UBC 0 : global_standalone = 0;
7229 peter_e@gmx.net 599 [ + + ]:CBC 30144 : if (standalone < 0)
600 : 30134 : global_standalone = -1;
601 : :
7224 602 [ + + ]: 30144 : if (!version)
603 : 30129 : global_version_no_value = true;
604 [ + + ]: 15 : else if (!global_version)
6386 tgl@sss.pgh.pa.us 605 : 10 : global_version = version;
7224 peter_e@gmx.net 606 [ - + ]: 5 : else if (xmlStrcmp(version, global_version) != 0)
7229 peter_e@gmx.net 607 :UBC 0 : global_version_no_value = true;
608 : :
7229 peter_e@gmx.net 609 :CBC 30144 : appendStringInfoString(&buf, str + len);
610 : 30144 : pfree(str);
611 : : }
612 : :
613 [ + + - + ]: 15070 : if (!global_version_no_value || global_standalone >= 0)
614 : : {
615 : : StringInfoData buf2;
616 : :
617 : 5 : initStringInfo(&buf2);
618 : :
7224 619 : 5 : print_xml_decl(&buf2,
6939 tgl@sss.pgh.pa.us 620 [ + - ]: 5 : (!global_version_no_value) ? global_version : NULL,
621 : : 0,
622 : : global_standalone);
623 : :
2662 drowley@postgresql.o 624 : 5 : appendBinaryStringInfo(&buf2, buf.data, buf.len);
7229 peter_e@gmx.net 625 : 5 : buf = buf2;
626 : : }
627 : :
628 : 15070 : return stringinfo_to_xmltype(&buf);
629 : : #else
630 : : NO_XML_SUPPORT();
631 : : return NULL;
632 : : #endif
633 : : }
634 : :
635 : :
636 : : /*
637 : : * XMLAGG support
638 : : */
639 : : Datum
640 : 15048 : xmlconcat2(PG_FUNCTION_ARGS)
641 : : {
642 [ + + ]: 15048 : if (PG_ARGISNULL(0))
643 : : {
644 [ - + ]: 12 : if (PG_ARGISNULL(1))
7229 peter_e@gmx.net 645 :UBC 0 : PG_RETURN_NULL();
646 : : else
7229 peter_e@gmx.net 647 :CBC 12 : PG_RETURN_XML_P(PG_GETARG_XML_P(1));
648 : : }
649 [ - + ]: 15036 : else if (PG_ARGISNULL(1))
7229 peter_e@gmx.net 650 :UBC 0 : PG_RETURN_XML_P(PG_GETARG_XML_P(0));
651 : : else
6939 tgl@sss.pgh.pa.us 652 :CBC 15036 : PG_RETURN_XML_P(xmlconcat(list_make2(PG_GETARG_XML_P(0),
653 : : PG_GETARG_XML_P(1))));
654 : : }
655 : :
656 : :
657 : : Datum
7256 658 : 4 : texttoxml(PG_FUNCTION_ARGS)
659 : : {
3525 noah@leadboat.com 660 : 4 : text *data = PG_GETARG_TEXT_PP(0);
661 : :
226 peter@eisentraut.org 662 : 4 : PG_RETURN_XML_P(xmlparse(data, xmloption, true, fcinfo->context));
663 : : }
664 : :
665 : :
666 : : Datum
7215 peter_e@gmx.net 667 :UBC 0 : xmltotext(PG_FUNCTION_ARGS)
668 : : {
6930 bruce@momjian.us 669 : 0 : xmltype *data = PG_GETARG_XML_P(0);
670 : :
671 : : /* It's actually binary compatible. */
6918 tgl@sss.pgh.pa.us 672 : 0 : PG_RETURN_TEXT_P((text *) data);
673 : : }
674 : :
675 : :
676 : : text *
1331 tgl@sss.pgh.pa.us 677 :CBC 158 : xmltotext_with_options(xmltype *data, XmlOptionType xmloption_arg, bool indent)
678 : : {
679 : : #ifdef USE_LIBXML
680 : : text *volatile result;
681 : : xmlDocPtr doc;
682 : : XmlOptionType parsed_xmloptiontype;
683 : 158 : volatile xmlBufferPtr buf = NULL;
1266 684 : 158 : volatile xmlSaveCtxtPtr ctxt = NULL;
1331 685 : 158 : ErrorSaveContext escontext = {T_ErrorSaveContext};
485 686 : 158 : PgXmlErrorContext *volatile xmlerrcxt = NULL;
687 : : #endif
688 : :
1331 689 [ + + + + ]: 158 : if (xmloption_arg != XMLOPTION_DOCUMENT && !indent)
690 : : {
691 : : /*
692 : : * We don't actually need to do anything, so just return the
693 : : * binary-compatible input. For backwards-compatibility reasons,
694 : : * allow such cases to succeed even without USE_LIBXML.
695 : : */
696 : 28 : return (text *) data;
697 : : }
698 : :
699 : : #ifdef USE_LIBXML
700 : :
701 : : /*
702 : : * Parse the input according to the xmloption.
703 : : *
704 : : * preserve_whitespace is set to false in case we are indenting, otherwise
705 : : * libxml2 will fail to indent elements that have whitespace between them.
706 : : */
786 707 : 130 : doc = xml_parse(data, xmloption_arg, !indent, GetDatabaseEncoding(),
708 : : &parsed_xmloptiontype,
1331 709 : 130 : (Node *) &escontext);
710 [ + + - + ]: 130 : if (doc == NULL || escontext.error_occurred)
711 : : {
712 [ - + ]: 20 : if (doc)
1331 tgl@sss.pgh.pa.us 713 :UBC 0 : xmlFreeDoc(doc);
714 : : /* A soft error must be failure to conform to XMLOPTION_DOCUMENT */
7215 peter_e@gmx.net 715 [ + - ]:CBC 20 : ereport(ERROR,
716 : : (errcode(ERRCODE_NOT_AN_XML_DOCUMENT),
717 : : errmsg("not an XML document")));
718 : : }
719 : :
720 : : /* If we weren't asked to indent, we're done. */
1331 tgl@sss.pgh.pa.us 721 [ + + ]: 110 : if (!indent)
722 : : {
723 : 15 : xmlFreeDoc(doc);
724 : 15 : return (text *) data;
725 : : }
726 : :
727 : : /*
728 : : * Otherwise, we gotta spin up some error handling. Unlike most other
729 : : * routines in this module, we already have a libxml "doc" structure to
730 : : * free, so we need to call pg_xml_init() inside the PG_TRY and be
731 : : * prepared for it to fail (typically due to palloc OOM).
732 : : */
733 [ + - ]: 95 : PG_TRY();
734 : : {
735 : 95 : size_t decl_len = 0;
736 : : int close_result;
737 : :
485 738 : 95 : xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
739 : :
740 : : /* The serialized data will go into this buffer. */
1331 741 : 95 : buf = xmlBufferCreate();
742 : :
743 [ + - - + ]: 95 : if (buf == NULL || xmlerrcxt->err_occurred)
1331 tgl@sss.pgh.pa.us 744 :UBC 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
745 : : "could not allocate xmlBuffer");
746 : :
747 : : /* Detect whether there's an XML declaration */
1331 tgl@sss.pgh.pa.us 748 :CBC 95 : parse_xml_decl(xml_text2xmlChar(data), &decl_len, NULL, NULL, NULL);
749 : :
750 : : /*
751 : : * Emit declaration only if the input had one. Note: some versions of
752 : : * xmlSaveToBuffer leak memory if a non-null encoding argument is
753 : : * passed, so don't do that. We don't want any encoding conversion
754 : : * anyway.
755 : : */
756 [ + + ]: 95 : if (decl_len == 0)
757 : 80 : ctxt = xmlSaveToBuffer(buf, NULL,
758 : : XML_SAVE_NO_DECL | XML_SAVE_FORMAT);
759 : : else
760 : 15 : ctxt = xmlSaveToBuffer(buf, NULL,
761 : : XML_SAVE_FORMAT);
762 : :
763 [ + - - + ]: 95 : if (ctxt == NULL || xmlerrcxt->err_occurred)
1331 tgl@sss.pgh.pa.us 764 :UBC 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
765 : : "could not allocate xmlSaveCtxt");
766 : :
1331 tgl@sss.pgh.pa.us 767 [ + + ]:CBC 95 : if (parsed_xmloptiontype == XMLOPTION_DOCUMENT)
768 : : {
769 : : /* If it's a document, saving is easy. */
770 [ + - - + ]: 35 : if (xmlSaveDoc(ctxt, doc) == -1 || xmlerrcxt->err_occurred)
772 tgl@sss.pgh.pa.us 771 :UBC 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
772 : : "could not save document to xmlBuffer");
773 : : }
774 : : else
775 : : {
776 : : /*
777 : : * When we have non-singly-rooted XML, we need to insert newlines
778 : : * between the content nodes, else the output is ugly.
779 : : */
21 tgl@sss.pgh.pa.us 780 :GNC 60 : xmlNodePtr root = xmlDocGetRootElement(doc);
781 : : xmlNodePtr newline;
782 : :
783 [ - + ]: 60 : Assert(root != NULL);
784 : :
785 : : /*
786 : : * We use this node to insert newlines in the dump. Note: in at
787 : : * least some libxml versions, xmlNewDocText would not attach the
788 : : * node to the document even if we passed it. Therefore, manage
789 : : * freeing of this node manually, and pass NULL here to make sure
790 : : * there's not a dangling link.
791 : : */
1331 tgl@sss.pgh.pa.us 792 :CBC 60 : newline = xmlNewDocText(NULL, (const xmlChar *) "\n");
793 [ + - - + ]: 60 : if (newline == NULL || xmlerrcxt->err_occurred)
1331 tgl@sss.pgh.pa.us 794 :UBC 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
795 : : "could not allocate xml node");
796 : :
1331 tgl@sss.pgh.pa.us 797 [ + + ]:CBC 140 : for (xmlNodePtr node = root->children; node; node = node->next)
798 : : {
799 : : /* insert newlines between nodes */
800 [ + + + + ]: 80 : if (node->type != XML_TEXT_NODE && node->prev != NULL)
801 : : {
802 [ + - - + ]: 20 : if (xmlSaveTree(ctxt, newline) == -1 || xmlerrcxt->err_occurred)
803 : : {
1331 tgl@sss.pgh.pa.us 804 :UBC 0 : xmlFreeNode(newline);
772 805 : 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
806 : : "could not save newline to xmlBuffer");
807 : : }
808 : : }
809 : :
1331 tgl@sss.pgh.pa.us 810 [ + - - + ]:CBC 80 : if (xmlSaveTree(ctxt, node) == -1 || xmlerrcxt->err_occurred)
811 : : {
1331 tgl@sss.pgh.pa.us 812 :UBC 0 : xmlFreeNode(newline);
772 813 : 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
814 : : "could not save content to xmlBuffer");
815 : : }
816 : : }
817 : :
1331 tgl@sss.pgh.pa.us 818 :CBC 60 : xmlFreeNode(newline);
819 : : }
820 : :
4 821 : 95 : close_result = xmlSaveClose(ctxt);
822 : : /* xmlSaveClose frees ctxt whether or not it succeeds */
823 : 95 : ctxt = NULL; /* don't try to close it again */
824 [ + - - + ]: 95 : if (close_result == -1 || xmlerrcxt->err_occurred)
1331 tgl@sss.pgh.pa.us 825 :UBC 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
826 : : "could not close xmlSaveCtxtPtr");
827 : :
828 : : /*
829 : : * xmlDocContentDumpOutput may add a trailing newline, so remove that.
830 : : */
786 tgl@sss.pgh.pa.us 831 [ + + ]:CBC 95 : if (xmloption_arg == XMLOPTION_DOCUMENT)
832 : : {
833 : 30 : const char *str = (const char *) xmlBufferContent(buf);
834 : 30 : int len = xmlBufferLength(buf);
835 : :
836 [ + - + + ]: 60 : while (len > 0 && (str[len - 1] == '\n' ||
837 [ - + ]: 30 : str[len - 1] == '\r'))
838 : 30 : len--;
839 : :
840 : 30 : result = cstring_to_text_with_len(str, len);
841 : : }
842 : : else
843 : 65 : result = (text *) xmlBuffer_to_xmltype(buf);
844 : : }
1331 tgl@sss.pgh.pa.us 845 :UBC 0 : PG_CATCH();
846 : : {
847 [ # # ]: 0 : if (ctxt)
848 : 0 : xmlSaveClose(ctxt);
849 [ # # ]: 0 : if (buf)
850 : 0 : xmlBufferFree(buf);
485 851 : 0 : xmlFreeDoc(doc);
852 : :
853 [ # # ]: 0 : if (xmlerrcxt)
854 : 0 : pg_xml_done(xmlerrcxt, true);
855 : :
1331 856 : 0 : PG_RE_THROW();
857 : : }
1331 tgl@sss.pgh.pa.us 858 [ - + ]:CBC 95 : PG_END_TRY();
859 : :
860 : 95 : xmlBufferFree(buf);
861 : 95 : xmlFreeDoc(doc);
862 : :
863 : 95 : pg_xml_done(xmlerrcxt, false);
864 : :
865 : 95 : return result;
866 : : #else
867 : : NO_XML_SUPPORT();
868 : : return NULL;
869 : : #endif
870 : : }
871 : :
872 : :
873 : : xmltype *
3523 andres@anarazel.de 874 : 15152 : xmlelement(XmlExpr *xexpr,
875 : : const Datum *named_argvalue, const bool *named_argnull,
876 : : const Datum *argvalue, const bool *argnull)
877 : : {
878 : : #ifdef USE_LIBXML
879 : : xmltype *result;
880 : : List *named_arg_strings;
881 : : List *arg_strings;
882 : : int i;
883 : : ListCell *arg;
884 : : ListCell *narg;
885 : : PgXmlErrorContext *xmlerrcxt;
5587 tgl@sss.pgh.pa.us 886 : 15152 : volatile xmlBufferPtr buf = NULL;
887 : 15152 : volatile xmlTextWriterPtr writer = NULL;
888 : :
889 : : /*
890 : : * All arguments are already evaluated, and their values are passed in the
891 : : * named_argvalue/named_argnull or argvalue/argnull arrays. This avoids
892 : : * issues if one of the arguments involves a call to some other function
893 : : * or subsystem that wants to use libxml on its own terms. We examine the
894 : : * original XmlExpr to identify the numbers and types of the arguments.
895 : : */
6940 896 : 15152 : named_arg_strings = NIL;
7239 peter_e@gmx.net 897 : 15152 : i = 0;
3523 andres@anarazel.de 898 [ + + + + : 15184 : foreach(arg, xexpr->named_args)
+ + ]
899 : : {
900 : 36 : Expr *e = (Expr *) lfirst(arg);
901 : : char *str;
902 : :
903 [ - + ]: 36 : if (named_argnull[i])
6940 tgl@sss.pgh.pa.us 904 :UBC 0 : str = NULL;
905 : : else
3523 andres@anarazel.de 906 :CBC 36 : str = map_sql_value_to_xml_value(named_argvalue[i],
907 : : exprType((Node *) e),
908 : : false);
6940 tgl@sss.pgh.pa.us 909 : 32 : named_arg_strings = lappend(named_arg_strings, str);
7239 peter_e@gmx.net 910 : 32 : i++;
911 : : }
912 : :
6940 tgl@sss.pgh.pa.us 913 : 15148 : arg_strings = NIL;
3523 andres@anarazel.de 914 : 15148 : i = 0;
915 [ + + + + : 30280 : foreach(arg, xexpr->args)
+ + ]
916 : : {
917 : 15132 : Expr *e = (Expr *) lfirst(arg);
918 : : char *str;
919 : :
920 : : /* here we can just forget NULL elements immediately */
921 [ + - ]: 15132 : if (!argnull[i])
922 : : {
923 : 15132 : str = map_sql_value_to_xml_value(argvalue[i],
924 : : exprType((Node *) e),
925 : : true);
6940 tgl@sss.pgh.pa.us 926 : 15132 : arg_strings = lappend(arg_strings, str);
927 : : }
3523 andres@anarazel.de 928 : 15132 : i++;
929 : : }
930 : :
5587 tgl@sss.pgh.pa.us 931 : 15148 : xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
932 : :
6385 933 [ + - ]: 15148 : PG_TRY();
934 : : {
6356 bruce@momjian.us 935 : 15148 : buf = xmlBufferCreate();
5587 tgl@sss.pgh.pa.us 936 [ + - - + ]: 15148 : if (buf == NULL || xmlerrcxt->err_occurred)
5587 tgl@sss.pgh.pa.us 937 :UBC 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
938 : : "could not allocate xmlBuffer");
6356 bruce@momjian.us 939 :CBC 15148 : writer = xmlNewTextWriterMemory(buf, 0);
5587 tgl@sss.pgh.pa.us 940 [ + - - + ]: 15148 : if (writer == NULL || xmlerrcxt->err_occurred)
5587 tgl@sss.pgh.pa.us 941 :UBC 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
942 : : "could not allocate xmlTextWriter");
943 : :
492 michael@paquier.xyz 944 [ + - ]:CBC 15148 : if (xmlTextWriterStartElement(writer, (xmlChar *) xexpr->name) < 0 ||
945 [ - + ]: 15148 : xmlerrcxt->err_occurred)
492 michael@paquier.xyz 946 :UBC 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
947 : : "could not start xml element");
948 : :
6356 bruce@momjian.us 949 [ + + + + :CBC 15180 : forboth(arg, named_arg_strings, narg, xexpr->arg_names)
+ + + + +
+ + - +
+ ]
950 : : {
951 : 32 : char *str = (char *) lfirst(arg);
952 : 32 : char *argname = strVal(lfirst(narg));
953 : :
954 [ + - ]: 32 : if (str)
955 : : {
492 michael@paquier.xyz 956 [ + - ]: 32 : if (xmlTextWriterWriteAttribute(writer,
957 : : (xmlChar *) argname,
958 : 32 : (xmlChar *) str) < 0 ||
959 [ - + ]: 32 : xmlerrcxt->err_occurred)
492 michael@paquier.xyz 960 :UBC 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
961 : : "could not write xml attribute");
962 : : }
963 : : }
964 : :
6356 bruce@momjian.us 965 [ + + + + :CBC 30280 : foreach(arg, arg_strings)
+ + ]
966 : : {
967 : 15132 : char *str = (char *) lfirst(arg);
968 : :
492 michael@paquier.xyz 969 [ + - ]: 15132 : if (xmlTextWriterWriteRaw(writer, (xmlChar *) str) < 0 ||
970 [ - + ]: 15132 : xmlerrcxt->err_occurred)
492 michael@paquier.xyz 971 :UBC 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
972 : : "could not write raw xml text");
973 : : }
974 : :
492 michael@paquier.xyz 975 [ + - ]:CBC 15148 : if (xmlTextWriterEndElement(writer) < 0 ||
976 [ - + ]: 15148 : xmlerrcxt->err_occurred)
492 michael@paquier.xyz 977 :UBC 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
978 : : "could not end xml element");
979 : :
980 : : /* we MUST do this now to flush data out to the buffer ... */
6356 bruce@momjian.us 981 :CBC 15148 : xmlFreeTextWriter(writer);
982 : 15148 : writer = NULL;
983 : :
984 : 15148 : result = xmlBuffer_to_xmltype(buf);
985 : : }
6385 tgl@sss.pgh.pa.us 986 :UBC 0 : PG_CATCH();
987 : : {
988 [ # # ]: 0 : if (writer)
989 : 0 : xmlFreeTextWriter(writer);
990 [ # # ]: 0 : if (buf)
991 : 0 : xmlBufferFree(buf);
992 : :
5587 993 : 0 : pg_xml_done(xmlerrcxt, true);
994 : :
6385 995 : 0 : PG_RE_THROW();
996 : : }
6385 tgl@sss.pgh.pa.us 997 [ - + ]:CBC 15148 : PG_END_TRY();
998 : :
7239 peter_e@gmx.net 999 : 15148 : xmlBufferFree(buf);
1000 : :
5587 tgl@sss.pgh.pa.us 1001 : 15148 : pg_xml_done(xmlerrcxt, false);
1002 : :
7239 peter_e@gmx.net 1003 : 15148 : return result;
1004 : : #else
1005 : : NO_XML_SUPPORT();
1006 : : return NULL;
1007 : : #endif
1008 : : }
1009 : :
1010 : :
1011 : : xmltype *
226 peter@eisentraut.org 1012 : 121 : xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, Node *escontext)
1013 : : {
1014 : : #ifdef USE_LIBXML
1015 : : xmlDocPtr doc;
1016 : :
6271 heikki.linnakangas@i 1017 : 121 : doc = xml_parse(data, xmloption_arg, preserve_whitespace,
1018 : : GetDatabaseEncoding(), NULL, escontext);
226 peter@eisentraut.org 1019 [ + - ]: 89 : if (doc)
1020 : 89 : xmlFreeDoc(doc);
1021 : :
1022 [ - + - - : 89 : if (SOFT_ERROR_OCCURRED(escontext))
- - ]
226 peter@eisentraut.org 1023 :UBC 0 : return NULL;
1024 : :
7256 tgl@sss.pgh.pa.us 1025 :CBC 89 : return (xmltype *) data;
1026 : : #else
1027 : : NO_XML_SUPPORT();
1028 : : return NULL;
1029 : : #endif
1030 : : }
1031 : :
1032 : :
1033 : : xmltype *
3292 peter_e@gmx.net 1034 : 57 : xmlpi(const char *target, text *arg, bool arg_is_null, bool *result_is_null)
1035 : : {
1036 : : #ifdef USE_LIBXML
1037 : : xmltype *result;
1038 : : StringInfoData buf;
1039 : :
6936 1040 [ + + ]: 57 : if (pg_strcasecmp(target, "xml") == 0)
7259 1041 [ + - ]: 8 : ereport(ERROR,
1042 : : (errcode(ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION),
1043 : : errmsg("invalid XML processing instruction"),
1044 : : errdetail("XML processing instruction target name cannot be \"%s\".", target)));
1045 : :
1046 : : /*
1047 : : * Following the SQL standard, the null check comes after the syntax check
1048 : : * above.
1049 : : */
7242 1050 : 49 : *result_is_null = arg_is_null;
1051 [ + + ]: 49 : if (*result_is_null)
6930 bruce@momjian.us 1052 : 10 : return NULL;
1053 : :
7259 peter_e@gmx.net 1054 : 39 : initStringInfo(&buf);
1055 : :
7256 tgl@sss.pgh.pa.us 1056 : 39 : appendStringInfo(&buf, "<?%s", target);
1057 : :
1058 [ + + ]: 39 : if (arg != NULL)
1059 : : {
1060 : : char *string;
1061 : :
6799 1062 : 19 : string = text_to_cstring(arg);
7256 1063 [ + + ]: 19 : if (strstr(string, "?>") != NULL)
6930 bruce@momjian.us 1064 [ + - ]: 4 : ereport(ERROR,
1065 : : (errcode(ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION),
1066 : : errmsg("invalid XML processing instruction"),
1067 : : errdetail("XML processing instruction cannot contain \"?>\".")));
1068 : :
7256 tgl@sss.pgh.pa.us 1069 : 15 : appendStringInfoChar(&buf, ' ');
7242 peter_e@gmx.net 1070 : 15 : appendStringInfoString(&buf, string + strspn(string, " "));
7256 tgl@sss.pgh.pa.us 1071 : 15 : pfree(string);
1072 : : }
7259 peter_e@gmx.net 1073 : 35 : appendStringInfoString(&buf, "?>");
1074 : :
7256 tgl@sss.pgh.pa.us 1075 : 35 : result = stringinfo_to_xmltype(&buf);
1076 : 35 : pfree(buf.data);
1077 : 35 : return result;
1078 : : #else
1079 : : NO_XML_SUPPORT();
1080 : : return NULL;
1081 : : #endif
1082 : : }
1083 : :
1084 : :
1085 : : xmltype *
6930 bruce@momjian.us 1086 : 49 : xmlroot(xmltype *data, text *version, int standalone)
1087 : : {
1088 : : #ifdef USE_LIBXML
1089 : : char *str;
1090 : : size_t len;
1091 : : xmlChar *orig_version;
1092 : : int orig_standalone;
1093 : : StringInfoData buf;
1094 : :
7224 peter_e@gmx.net 1095 : 49 : len = VARSIZE(data) - VARHDRSZ;
6759 tgl@sss.pgh.pa.us 1096 : 49 : str = text_to_cstring((text *) data);
1097 : :
7224 peter_e@gmx.net 1098 : 49 : parse_xml_decl((xmlChar *) str, &len, &orig_version, NULL, &orig_standalone);
1099 : :
7256 tgl@sss.pgh.pa.us 1100 [ + + ]: 49 : if (version)
7224 peter_e@gmx.net 1101 : 19 : orig_version = xml_text2xmlChar(version);
1102 : : else
1103 : 30 : orig_version = NULL;
1104 : :
7243 1105 [ + + + + : 49 : switch (standalone)
- ]
1106 : : {
7224 1107 : 14 : case XML_STANDALONE_YES:
1108 : 14 : orig_standalone = 1;
1109 : 14 : break;
1110 : 10 : case XML_STANDALONE_NO:
1111 : 10 : orig_standalone = 0;
7243 1112 : 10 : break;
7224 1113 : 10 : case XML_STANDALONE_NO_VALUE:
1114 : 10 : orig_standalone = -1;
7243 1115 : 10 : break;
7224 1116 : 15 : case XML_STANDALONE_OMITTED:
1117 : : /* leave original value */
7243 1118 : 15 : break;
1119 : : }
1120 : :
7224 1121 : 49 : initStringInfo(&buf);
1122 : 49 : print_xml_decl(&buf, orig_version, 0, orig_standalone);
1123 : 49 : appendStringInfoString(&buf, str + len);
1124 : :
1125 : 49 : return stringinfo_to_xmltype(&buf);
1126 : : #else
1127 : : NO_XML_SUPPORT();
1128 : : return NULL;
1129 : : #endif
1130 : : }
1131 : :
1132 : :
1133 : : /*
1134 : : * Validate document (given as string) against DTD (given as external link)
1135 : : *
1136 : : * This has been removed because it is a security hole: unprivileged users
1137 : : * should not be able to use Postgres to fetch arbitrary external files,
1138 : : * which unfortunately is exactly what libxml is willing to do with the DTD
1139 : : * parameter.
1140 : : */
1141 : : Datum
7259 peter_e@gmx.net 1142 :UBC 0 : xmlvalidate(PG_FUNCTION_ARGS)
1143 : : {
6823 tgl@sss.pgh.pa.us 1144 [ # # ]: 0 : ereport(ERROR,
1145 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1146 : : errmsg("xmlvalidate is not implemented")));
1147 : : return 0;
1148 : : }
1149 : :
1150 : :
1151 : : bool
6930 bruce@momjian.us 1152 :CBC 25 : xml_is_document(xmltype *arg)
1153 : : {
1154 : : #ifdef USE_LIBXML
1155 : : xmlDocPtr doc;
1420 tgl@sss.pgh.pa.us 1156 : 25 : ErrorSaveContext escontext = {T_ErrorSaveContext};
1157 : :
1158 : : /*
1159 : : * We'll report "true" if no soft error is reported by xml_parse().
1160 : : */
1161 : 25 : doc = xml_parse((text *) arg, XMLOPTION_DOCUMENT, true,
1162 : : GetDatabaseEncoding(), NULL, (Node *) &escontext);
7235 peter_e@gmx.net 1163 [ + + ]: 25 : if (doc)
1164 : 10 : xmlFreeDoc(doc);
1165 : :
1420 tgl@sss.pgh.pa.us 1166 : 25 : return !escontext.error_occurred;
1167 : : #else /* not USE_LIBXML */
1168 : : NO_XML_SUPPORT();
1169 : : return false;
1170 : : #endif /* not USE_LIBXML */
1171 : : }
1172 : :
1173 : :
1174 : : #ifdef USE_LIBXML
1175 : :
1176 : : /*
1177 : : * pg_xml_init_library --- set up for use of libxml
1178 : : *
1179 : : * This should be called by each function that is about to use libxml
1180 : : * facilities but doesn't require error handling. It initializes libxml
1181 : : * and verifies compatibility with the loaded libxml version. These are
1182 : : * once-per-session activities.
1183 : : *
1184 : : * TODO: xmlChar is utf8-char, make proper tuning (initdb with enc!=utf8 and
1185 : : * check)
1186 : : */
1187 : : void
5587 1188 : 64145 : pg_xml_init_library(void)
1189 : : {
1190 : : static bool first_time = true;
1191 : :
6940 1192 [ + + ]: 64145 : if (first_time)
1193 : : {
1194 : : /* Stuff we need do only once per session */
1195 : :
1196 : : /*
1197 : : * Currently, we have no pure UTF-8 support for internals -- check if
1198 : : * we can work.
1199 : : */
1200 : : if (sizeof(char) != sizeof(xmlChar))
1201 : : ereport(ERROR,
1202 : : (errmsg("could not initialize XML library"),
1203 : : errdetail("libxml2 has incompatible char type: sizeof(char)=%zu, sizeof(xmlChar)=%zu.",
1204 : : sizeof(char), sizeof(xmlChar))));
1205 : :
1206 : : #ifdef USE_LIBXMLCONTEXT
1207 : : /* Set up libxml's memory allocation our way */
1208 : : xml_memory_init();
1209 : : #endif
1210 : :
1211 : : /* Check library compatibility */
1212 : 15 : LIBXML_TEST_VERSION;
1213 : :
1214 : 15 : first_time = false;
1215 : : }
5587 1216 : 64145 : }
1217 : :
1218 : : /*
1219 : : * pg_xml_init --- set up for use of libxml and register an error handler
1220 : : *
1221 : : * This should be called by each function that is about to use libxml
1222 : : * facilities and requires error handling. It initializes libxml with
1223 : : * pg_xml_init_library() and establishes our libxml error handler.
1224 : : *
1225 : : * strictness determines which errors are reported and which are ignored.
1226 : : *
1227 : : * Calls to this function MUST be followed by a PG_TRY block that guarantees
1228 : : * that pg_xml_done() is called during either normal or error exit.
1229 : : *
1230 : : * This is exported for use by contrib/xml2, as well as other code that might
1231 : : * wish to share use of this module's libxml error handler.
1232 : : */
1233 : : PgXmlErrorContext *
1234 : 16830 : pg_xml_init(PgXmlStrictness strictness)
1235 : : {
1236 : : PgXmlErrorContext *errcxt;
1237 : : void *new_errcxt;
1238 : :
1239 : : /* Do one-time setup if needed */
1240 : 16830 : pg_xml_init_library();
1241 : :
1242 : : /* Create error handling context structure */
330 michael@paquier.xyz 1243 : 16830 : errcxt = palloc_object(PgXmlErrorContext);
5587 tgl@sss.pgh.pa.us 1244 : 16830 : errcxt->magic = ERRCXT_MAGIC;
1245 : 16830 : errcxt->strictness = strictness;
1246 : 16830 : errcxt->err_occurred = false;
1247 : 16830 : initStringInfo(&errcxt->err_buf);
1248 : :
1249 : : /*
1250 : : * Save original error handler and install ours. libxml originally didn't
1251 : : * distinguish between the contexts for generic and for structured error
1252 : : * handlers. If we're using an old libxml version, we must thus save the
1253 : : * generic error context, even though we're using a structured error
1254 : : * handler.
1255 : : */
1256 : 16830 : errcxt->saved_errfunc = xmlStructuredError;
1257 : :
1258 : : #ifdef HAVE_XMLSTRUCTUREDERRORCONTEXT
1259 : 16830 : errcxt->saved_errcxt = xmlStructuredErrorContext;
1260 : : #else
1261 : : errcxt->saved_errcxt = xmlGenericErrorContext;
1262 : : #endif
1263 : :
707 peter@eisentraut.org 1264 : 16830 : xmlSetStructuredErrorFunc(errcxt, xml_errorHandler);
1265 : :
1266 : : /*
1267 : : * Verify that xmlSetStructuredErrorFunc set the context variable we
1268 : : * expected it to. If not, the error context pointer we just saved is not
1269 : : * the correct thing to restore, and since that leaves us without a way to
1270 : : * restore the context in pg_xml_done, we must fail.
1271 : : *
1272 : : * The only known situation in which this test fails is if we compile with
1273 : : * headers from a libxml2 that doesn't track the structured error context
1274 : : * separately (< 2.7.4), but at runtime use a version that does, or vice
1275 : : * versa. The libxml2 authors did not treat that change as constituting
1276 : : * an ABI break, so the LIBXML_TEST_VERSION test in pg_xml_init_library
1277 : : * fails to protect us from this.
1278 : : */
1279 : :
1280 : : #ifdef HAVE_XMLSTRUCTUREDERRORCONTEXT
5581 tgl@sss.pgh.pa.us 1281 : 16830 : new_errcxt = xmlStructuredErrorContext;
1282 : : #else
1283 : : new_errcxt = xmlGenericErrorContext;
1284 : : #endif
1285 : :
707 peter@eisentraut.org 1286 [ - + ]: 16830 : if (new_errcxt != errcxt)
5581 tgl@sss.pgh.pa.us 1287 [ # # ]:UBC 0 : ereport(ERROR,
1288 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
1289 : : errmsg("could not set up XML error handler"),
1290 : : errhint("This probably indicates that the version of libxml2"
1291 : : " being used is not compatible with the libxml2"
1292 : : " header files that PostgreSQL was built with.")));
1293 : :
1294 : : /*
1295 : : * Also, install an entity loader to prevent unwanted fetches of external
1296 : : * files and URLs.
1297 : : */
5196 tgl@sss.pgh.pa.us 1298 :CBC 16830 : errcxt->saved_entityfunc = xmlGetExternalEntityLoader();
1299 : 16830 : xmlSetExternalEntityLoader(xmlPgEntityLoader);
1300 : :
5587 1301 : 16830 : return errcxt;
1302 : : }
1303 : :
1304 : :
1305 : : /*
1306 : : * pg_xml_done --- restore previous libxml error handling
1307 : : *
1308 : : * Resets libxml's global error-handling state to what it was before
1309 : : * pg_xml_init() was called.
1310 : : *
1311 : : * This routine verifies that all pending errors have been dealt with
1312 : : * (in assert-enabled builds, anyway).
1313 : : */
1314 : : void
1315 : 16830 : pg_xml_done(PgXmlErrorContext *errcxt, bool isError)
1316 : : {
1317 : : void *cur_errcxt;
1318 : :
1319 : : /* An assert seems like enough protection here */
1320 [ - + ]: 16830 : Assert(errcxt->magic == ERRCXT_MAGIC);
1321 : :
1322 : : /*
1323 : : * In a normal exit, there should be no un-handled libxml errors. But we
1324 : : * shouldn't try to enforce this during error recovery, since the longjmp
1325 : : * could have been thrown before xml_ereport had a chance to run.
1326 : : */
1327 [ - + - - ]: 16830 : Assert(!errcxt->err_occurred || isError);
1328 : :
1329 : : /*
1330 : : * Check that libxml's global state is correct, warn if not. This is a
1331 : : * real test and not an Assert because it has a higher probability of
1332 : : * happening.
1333 : : */
1334 : : #ifdef HAVE_XMLSTRUCTUREDERRORCONTEXT
1335 : 16830 : cur_errcxt = xmlStructuredErrorContext;
1336 : : #else
1337 : : cur_errcxt = xmlGenericErrorContext;
1338 : : #endif
1339 : :
707 peter@eisentraut.org 1340 [ - + ]: 16830 : if (cur_errcxt != errcxt)
5587 tgl@sss.pgh.pa.us 1341 [ # # ]:UBC 0 : elog(WARNING, "libxml error handling state is out of sync with xml.c");
1342 : :
1343 : : /* Restore the saved handlers */
5587 tgl@sss.pgh.pa.us 1344 :CBC 16830 : xmlSetStructuredErrorFunc(errcxt->saved_errcxt, errcxt->saved_errfunc);
5196 1345 : 16830 : xmlSetExternalEntityLoader(errcxt->saved_entityfunc);
1346 : :
1347 : : /*
1348 : : * Mark the struct as invalid, just in case somebody somehow manages to
1349 : : * call xml_errorHandler or xml_ereport with it.
1350 : : */
5587 1351 : 16830 : errcxt->magic = 0;
1352 : :
1353 : : /* Release memory */
1354 : 16830 : pfree(errcxt->err_buf.data);
1355 : 16830 : pfree(errcxt);
1356 : 16830 : }
1357 : :
1358 : :
1359 : : /*
1360 : : * pg_xml_error_occurred() --- test the error flag
1361 : : */
1362 : : bool
1363 : 47 : pg_xml_error_occurred(PgXmlErrorContext *errcxt)
1364 : : {
1365 : 47 : return errcxt->err_occurred;
1366 : : }
1367 : :
1368 : :
1369 : : /*
1370 : : * SQL/XML allows storing "XML documents" or "XML content". "XML
1371 : : * documents" are specified by the XML specification and are parsed
1372 : : * easily by libxml. "XML content" is specified by SQL/XML as the
1373 : : * production "XMLDecl? content". But libxml can only parse the
1374 : : * "content" part, so we have to parse the XML declaration ourselves
1375 : : * to complete this.
1376 : : */
1377 : :
1378 : : #define CHECK_XML_SPACE(p) \
1379 : : do { \
1380 : : if (!xmlIsBlank_ch(*(p))) \
1381 : : return XML_ERR_SPACE_REQUIRED; \
1382 : : } while (0)
1383 : :
1384 : : #define SKIP_XML_SPACE(p) \
1385 : : while (xmlIsBlank_ch(*(p))) (p)++
1386 : :
1387 : : /* Letter | Digit | '.' | '-' | '_' | ':' | CombiningChar | Extender */
1388 : : /* Beware of multiple evaluations of argument! */
1389 : : #define PG_XMLISNAMECHAR(c) \
1390 : : (xmlIsBaseChar_ch(c) || xmlIsIdeographicQ(c) \
1391 : : || xmlIsDigit_ch(c) \
1392 : : || c == '.' || c == '-' || c == '_' || c == ':' \
1393 : : || xmlIsCombiningQ(c) \
1394 : : || xmlIsExtender_ch(c))
1395 : :
1396 : : /* pnstrdup, but deal with xmlChar not char; len is measured in xmlChars */
1397 : : static xmlChar *
6386 1398 : 146 : xml_pnstrdup(const xmlChar *str, size_t len)
1399 : : {
1400 : : xmlChar *result;
1401 : :
80 michael@paquier.xyz 1402 :GNC 146 : result = palloc_array(xmlChar, len + 1);
6386 tgl@sss.pgh.pa.us 1403 :CBC 146 : memcpy(result, str, len * sizeof(xmlChar));
1404 : 146 : result[len] = 0;
1405 : 146 : return result;
1406 : : }
1407 : :
1408 : : /* Ditto, except input is char* */
1409 : : static xmlChar *
3292 peter_e@gmx.net 1410 : 1676 : pg_xmlCharStrndup(const char *str, size_t len)
1411 : : {
1412 : : xmlChar *result;
1413 : :
80 michael@paquier.xyz 1414 :GNC 1676 : result = palloc_array(xmlChar, len + 1);
3529 alvherre@alvh.no-ip. 1415 :CBC 1676 : memcpy(result, str, len);
1416 : 1676 : result[len] = '\0';
1417 : :
1418 : 1676 : return result;
1419 : : }
1420 : :
1421 : : /*
1422 : : * Copy xmlChar string to PostgreSQL-owned memory, freeing the input.
1423 : : *
1424 : : * The input xmlChar is freed regardless of success of the copy.
1425 : : */
1426 : : static char *
2800 1427 : 76201 : xml_pstrdup_and_free(xmlChar *str)
1428 : : {
1429 : : char *result;
1430 : :
1431 [ + - ]: 76201 : if (str)
1432 : : {
1433 [ + - ]: 76201 : PG_TRY();
1434 : : {
1435 : 76201 : result = pstrdup((char *) str);
1436 : : }
2561 peter@eisentraut.org 1437 :UBC 0 : PG_FINALLY();
1438 : : {
2800 alvherre@alvh.no-ip. 1439 :CBC 76201 : xmlFree(str);
1440 : : }
1441 [ - + ]: 76201 : PG_END_TRY();
1442 : : }
1443 : : else
2800 alvherre@alvh.no-ip. 1444 :UBC 0 : result = NULL;
1445 : :
2800 alvherre@alvh.no-ip. 1446 :CBC 76201 : return result;
1447 : : }
1448 : :
1449 : : /*
1450 : : * str is the null-terminated input string. Remaining arguments are
1451 : : * output arguments; each can be NULL if value is not wanted.
1452 : : * version and encoding are returned as locally-palloc'd strings.
1453 : : * Result is 0 if OK, an error code if not.
1454 : : */
1455 : : static int
6356 bruce@momjian.us 1456 : 47315 : parse_xml_decl(const xmlChar *str, size_t *lenp,
1457 : : xmlChar **version, xmlChar **encoding, int *standalone)
1458 : : {
1459 : : const xmlChar *p;
1460 : : const xmlChar *save_p;
1461 : : size_t len;
1462 : : int utf8char;
1463 : : int utf8len;
1464 : :
1465 : : /*
1466 : : * Only initialize libxml. We don't need error handling here, but we do
1467 : : * need to make sure libxml is initialized before calling any of its
1468 : : * functions. Note that this is safe (and a no-op) if caller has already
1469 : : * done pg_xml_init().
1470 : : */
5587 tgl@sss.pgh.pa.us 1471 : 47315 : pg_xml_init_library();
1472 : :
1473 : : /* Initialize output arguments to "not present" */
7231 peter_e@gmx.net 1474 [ + + ]: 47315 : if (version)
1475 : 46822 : *version = NULL;
1476 [ - + ]: 47315 : if (encoding)
7231 peter_e@gmx.net 1477 :UBC 0 : *encoding = NULL;
7231 peter_e@gmx.net 1478 [ + + ]:CBC 47315 : if (standalone)
1479 : 46822 : *standalone = -1;
1480 : :
6939 tgl@sss.pgh.pa.us 1481 : 47315 : p = str;
1482 : :
6930 bruce@momjian.us 1483 [ + + ]: 47315 : if (xmlStrncmp(p, (xmlChar *) "<?xml", 5) != 0)
7242 peter_e@gmx.net 1484 : 47142 : goto finished;
1485 : :
1486 : : /*
1487 : : * If next char is a name char, it's a PI like <?xml-stylesheet ...?>
1488 : : * rather than an XMLDecl, so we have done what we came to do and found no
1489 : : * XMLDecl.
1490 : : *
1491 : : * We need an input length value for xmlGetUTF8Char, but there's no need
1492 : : * to count the whole document size, so use strnlen not strlen.
1493 : : */
2784 tgl@sss.pgh.pa.us 1494 : 173 : utf8len = strnlen((const char *) (p + 5), MAX_MULTIBYTE_CHAR_LEN);
6930 bruce@momjian.us 1495 : 173 : utf8char = xmlGetUTF8Char(p + 5, &utf8len);
6935 tgl@sss.pgh.pa.us 1496 [ + + + - : 173 : if (PG_XMLISNAMECHAR(utf8char))
+ + - + -
+ - - - +
- - + - -
+ - - - -
- - - - -
- - + - -
+ - + + +
- + - - +
- - - + ]
6936 peter_e@gmx.net 1497 : 8 : goto finished;
1498 : :
7242 1499 : 165 : p += 5;
1500 : :
1501 : : /* version */
1502 [ - + - - : 165 : CHECK_XML_SPACE(p);
- - - - ]
1503 [ + + + - : 330 : SKIP_XML_SPACE(p);
- + - + ]
6930 bruce@momjian.us 1504 [ - + ]: 165 : if (xmlStrncmp(p, (xmlChar *) "version", 7) != 0)
7242 peter_e@gmx.net 1505 :UBC 0 : return XML_ERR_VERSION_MISSING;
7242 peter_e@gmx.net 1506 :CBC 165 : p += 7;
1507 [ - + + - : 165 : SKIP_XML_SPACE(p);
- + - + ]
1508 [ - + ]: 165 : if (*p != '=')
7242 peter_e@gmx.net 1509 :UBC 0 : return XML_ERR_VERSION_MISSING;
7242 peter_e@gmx.net 1510 :CBC 165 : p += 1;
1511 [ - + + - : 165 : SKIP_XML_SPACE(p);
- + - + ]
1512 : :
7231 1513 [ + - + - ]: 165 : if (*p == '\'' || *p == '"')
1514 : 165 : {
1515 : : const xmlChar *q;
1516 : :
1517 : 165 : q = xmlStrchr(p + 1, *p);
1518 [ - + ]: 165 : if (!q)
7231 peter_e@gmx.net 1519 :UBC 0 : return XML_ERR_VERSION_MISSING;
1520 : :
7231 peter_e@gmx.net 1521 [ + + ]:CBC 165 : if (version)
6386 tgl@sss.pgh.pa.us 1522 : 146 : *version = xml_pnstrdup(p + 1, q - p - 1);
7231 peter_e@gmx.net 1523 : 165 : p = q + 1;
1524 : : }
1525 : : else
7242 peter_e@gmx.net 1526 :UBC 0 : return XML_ERR_VERSION_MISSING;
1527 : :
1528 : : /* encoding */
7242 peter_e@gmx.net 1529 :CBC 165 : save_p = p;
1530 [ + + + - : 281 : SKIP_XML_SPACE(p);
- + - + ]
6930 bruce@momjian.us 1531 [ + + ]: 165 : if (xmlStrncmp(p, (xmlChar *) "encoding", 8) == 0)
1532 : : {
7242 peter_e@gmx.net 1533 [ - + - - : 39 : CHECK_XML_SPACE(save_p);
- - - - ]
1534 : 39 : p += 8;
1535 [ - + + - : 39 : SKIP_XML_SPACE(p);
- + - + ]
1536 [ - + ]: 39 : if (*p != '=')
7242 peter_e@gmx.net 1537 :UBC 0 : return XML_ERR_MISSING_ENCODING;
7242 peter_e@gmx.net 1538 :CBC 39 : p += 1;
1539 [ - + + - : 39 : SKIP_XML_SPACE(p);
- + - + ]
1540 : :
1541 [ + - + - ]: 39 : if (*p == '\'' || *p == '"')
1542 : 39 : {
1543 : : const xmlChar *q;
1544 : :
1545 : 39 : q = xmlStrchr(p + 1, *p);
1546 [ - + ]: 39 : if (!q)
7242 peter_e@gmx.net 1547 :UBC 0 : return XML_ERR_MISSING_ENCODING;
1548 : :
7231 peter_e@gmx.net 1549 [ - + ]:CBC 39 : if (encoding)
6386 tgl@sss.pgh.pa.us 1550 :UBC 0 : *encoding = xml_pnstrdup(p + 1, q - p - 1);
7242 peter_e@gmx.net 1551 :CBC 39 : p = q + 1;
1552 : : }
1553 : : else
7242 peter_e@gmx.net 1554 :UBC 0 : return XML_ERR_MISSING_ENCODING;
1555 : : }
1556 : : else
1557 : : {
7242 peter_e@gmx.net 1558 :CBC 126 : p = save_p;
1559 : : }
1560 : :
1561 : : /* standalone */
1562 : 165 : save_p = p;
1563 [ + + + - : 242 : SKIP_XML_SPACE(p);
- + - + ]
6930 bruce@momjian.us 1564 [ + + ]: 165 : if (xmlStrncmp(p, (xmlChar *) "standalone", 10) == 0)
1565 : : {
7242 peter_e@gmx.net 1566 [ - + - - : 77 : CHECK_XML_SPACE(save_p);
- - - - ]
1567 : 77 : p += 10;
1568 [ - + + - : 77 : SKIP_XML_SPACE(p);
- + - + ]
1569 [ - + ]: 77 : if (*p != '=')
7242 peter_e@gmx.net 1570 :UBC 0 : return XML_ERR_STANDALONE_VALUE;
7242 peter_e@gmx.net 1571 :CBC 77 : p += 1;
1572 [ - + + - : 77 : SKIP_XML_SPACE(p);
- + - + ]
6925 tgl@sss.pgh.pa.us 1573 [ + - + + ]: 154 : if (xmlStrncmp(p, (xmlChar *) "'yes'", 5) == 0 ||
1574 : 77 : xmlStrncmp(p, (xmlChar *) "\"yes\"", 5) == 0)
1575 : : {
5640 1576 [ + - ]: 43 : if (standalone)
1577 : 43 : *standalone = 1;
7242 peter_e@gmx.net 1578 : 43 : p += 5;
1579 : : }
6925 tgl@sss.pgh.pa.us 1580 [ + - + + ]: 68 : else if (xmlStrncmp(p, (xmlChar *) "'no'", 4) == 0 ||
1581 : 34 : xmlStrncmp(p, (xmlChar *) "\"no\"", 4) == 0)
1582 : : {
5640 1583 [ + - ]: 26 : if (standalone)
1584 : 26 : *standalone = 0;
7242 peter_e@gmx.net 1585 : 26 : p += 4;
1586 : : }
1587 : : else
1588 : 8 : return XML_ERR_STANDALONE_VALUE;
1589 : : }
1590 : : else
1591 : : {
1592 : 88 : p = save_p;
1593 : : }
1594 : :
1595 [ - + + - : 157 : SKIP_XML_SPACE(p);
- + - + ]
6930 bruce@momjian.us 1596 [ - + ]: 157 : if (xmlStrncmp(p, (xmlChar *) "?>", 2) != 0)
7242 peter_e@gmx.net 1597 :UBC 0 : return XML_ERR_XMLDECL_NOT_FINISHED;
7242 peter_e@gmx.net 1598 :CBC 157 : p += 2;
1599 : :
1600 : 47307 : finished:
7231 1601 : 47307 : len = p - str;
1602 : :
1603 [ + + ]: 52454 : for (p = str; p < str + len; p++)
1604 [ - + ]: 5147 : if (*p > 127)
7231 peter_e@gmx.net 1605 :UBC 0 : return XML_ERR_INVALID_CHAR;
1606 : :
7231 peter_e@gmx.net 1607 [ + - ]:CBC 47307 : if (lenp)
1608 : 47307 : *lenp = len;
1609 : :
7242 1610 : 47307 : return XML_ERR_OK;
1611 : : }
1612 : :
1613 : :
1614 : : /*
1615 : : * Write an XML declaration. On output, we adjust the XML declaration
1616 : : * as follows. (These rules are the moral equivalent of the clause
1617 : : * "Serialization of an XML value" in the SQL standard.)
1618 : : *
1619 : : * We try to avoid generating an XML declaration if possible. This is
1620 : : * so that you don't get trivial things like xml '<foo/>' resulting in
1621 : : * '<?xml version="1.0"?><foo/>', which would surely be annoying. We
1622 : : * must provide a declaration if the standalone property is specified
1623 : : * or if we include an encoding declaration. If we have a
1624 : : * declaration, we must specify a version (XML requires this).
1625 : : * Otherwise we only make a declaration if the version is not "1.0",
1626 : : * which is the default version specified in SQL:2003.
1627 : : */
1628 : : static bool
6356 bruce@momjian.us 1629 : 15966 : print_xml_decl(StringInfo buf, const xmlChar *version,
1630 : : pg_enc encoding, int standalone)
1631 : : {
5534 peter_e@gmx.net 1632 [ + + + + ]: 15966 : if ((version && strcmp((const char *) version, PG_XML_DEFAULT_VERSION) != 0)
7224 1633 [ - + - - ]: 15939 : || (encoding && encoding != PG_UTF8)
1634 [ + + ]: 15939 : || standalone != -1)
1635 : : {
1636 : 71 : appendStringInfoString(buf, "<?xml");
1637 : :
1638 [ + + ]: 71 : if (version)
1639 : 51 : appendStringInfo(buf, " version=\"%s\"", version);
1640 : : else
1641 : 20 : appendStringInfo(buf, " version=\"%s\"", PG_XML_DEFAULT_VERSION);
1642 : :
1643 [ - + - - ]: 71 : if (encoding && encoding != PG_UTF8)
1644 : : {
1645 : : /*
1646 : : * XXX might be useful to convert this to IANA names (ISO-8859-1
1647 : : * instead of LATIN1 etc.); needs field experience
1648 : : */
6939 tgl@sss.pgh.pa.us 1649 :UBC 0 : appendStringInfo(buf, " encoding=\"%s\"",
1650 : : pg_encoding_to_char(encoding));
1651 : : }
1652 : :
7224 peter_e@gmx.net 1653 [ + + ]:CBC 71 : if (standalone == 1)
1654 : 35 : appendStringInfoString(buf, " standalone=\"yes\"");
1655 [ + + ]: 36 : else if (standalone == 0)
1656 : 18 : appendStringInfoString(buf, " standalone=\"no\"");
1657 : 71 : appendStringInfoString(buf, "?>");
1658 : :
1659 : 71 : return true;
1660 : : }
1661 : : else
1662 : 15895 : return false;
1663 : : }
1664 : :
1665 : : /*
1666 : : * Test whether an input that is to be parsed as CONTENT contains a DTD.
1667 : : *
1668 : : * The SQL/XML:2003 definition of CONTENT ("XMLDecl? content") is not
1669 : : * satisfied by a document with a DTD, which is a bit of a wart, as it means
1670 : : * the CONTENT type is not a proper superset of DOCUMENT. SQL/XML:2006 and
1671 : : * later fix that, by redefining content with reference to the "more
1672 : : * permissive" Document Node of the XQuery/XPath Data Model, such that any
1673 : : * DOCUMENT value is indeed also a CONTENT value. That definition is more
1674 : : * useful, as CONTENT becomes usable for parsing input of unknown form (think
1675 : : * pg_restore).
1676 : : *
1677 : : * As used below in parse_xml when parsing for CONTENT, libxml does not give
1678 : : * us the 2006+ behavior, but only the 2003; it will choke if the input has
1679 : : * a DTD. But we can provide the 2006+ definition of CONTENT easily enough,
1680 : : * by detecting this case first and simply doing the parse as DOCUMENT.
1681 : : *
1682 : : * A DTD can be found arbitrarily far in, but that would be a contrived case;
1683 : : * it will ordinarily start within a few dozen characters. The only things
1684 : : * that can precede it are an XMLDecl (here, the caller will have called
1685 : : * parse_xml_decl already), whitespace, comments, and processing instructions.
1686 : : * This function need only return true if it sees a valid sequence of such
1687 : : * things leading to <!DOCTYPE. It can simply return false in any other
1688 : : * cases, including malformed input; that will mean the input gets parsed as
1689 : : * CONTENT as originally planned, with libxml reporting any errors.
1690 : : *
1691 : : * This is only to be called from xml_parse, when pg_xml_init has already
1692 : : * been called. The input is already in UTF8 encoding.
1693 : : */
1694 : : static bool
2784 tgl@sss.pgh.pa.us 1695 : 709 : xml_doctype_in_content(const xmlChar *str)
1696 : : {
1697 : 709 : const xmlChar *p = str;
1698 : :
1699 : : for (;;)
1700 : 24 : {
1701 : : const xmlChar *e;
1702 : :
1703 [ + + + + : 797 : SKIP_XML_SPACE(p);
+ + - + ]
1704 [ + + ]: 733 : if (*p != '<')
1705 : 176 : return false;
1706 : 557 : p++;
1707 : :
1708 [ + + ]: 557 : if (*p == '!')
1709 : : {
1710 : 49 : p++;
1711 : :
1712 : : /* if we see <!DOCTYPE, we can return true */
1713 [ + + ]: 49 : if (xmlStrncmp(p, (xmlChar *) "DOCTYPE", 7) == 0)
1714 : 29 : return true;
1715 : :
1716 : : /* otherwise, if it's not a comment, fail */
1717 [ - + ]: 20 : if (xmlStrncmp(p, (xmlChar *) "--", 2) != 0)
2784 tgl@sss.pgh.pa.us 1718 :UBC 0 : return false;
1719 : : /* find end of comment: find -- and a > must follow */
2784 tgl@sss.pgh.pa.us 1720 :CBC 20 : p = xmlStrstr(p + 2, (xmlChar *) "--");
1721 [ + - - + ]: 20 : if (!p || p[2] != '>')
2784 tgl@sss.pgh.pa.us 1722 :UBC 0 : return false;
1723 : : /* advance over comment, and keep scanning */
2784 tgl@sss.pgh.pa.us 1724 :CBC 20 : p += 3;
1725 : 20 : continue;
1726 : : }
1727 : :
1728 : : /* otherwise, if it's not a PI <?target something?>, fail */
1729 [ + + ]: 508 : if (*p != '?')
1730 : 504 : return false;
1731 : 4 : p++;
1732 : :
1733 : : /* find end of PI (the string ?> is forbidden within a PI) */
1734 : 4 : e = xmlStrstr(p, (xmlChar *) "?>");
1735 [ - + ]: 4 : if (!e)
2784 tgl@sss.pgh.pa.us 1736 :UBC 0 : return false;
1737 : :
1738 : : /* advance over PI, keep scanning */
2784 tgl@sss.pgh.pa.us 1739 :CBC 4 : p = e + 2;
1740 : : }
1741 : : }
1742 : :
1743 : :
1744 : : /*
1745 : : * Convert a text object to XML internal representation
1746 : : *
1747 : : * data is the source data (must not be toasted!), encoding is its encoding,
1748 : : * and xmloption_arg and preserve_whitespace are options for the
1749 : : * transformation.
1750 : : *
1751 : : * If parsed_xmloptiontype isn't NULL, *parsed_xmloptiontype is set to the
1752 : : * XmlOptionType actually used to parse the input (typically the same as
1753 : : * xmloption_arg, but a DOCTYPE node in the input can force DOCUMENT mode).
1754 : : *
1755 : : * When we parse in CONTENT mode, the resulting document has a dummy root
1756 : : * node with zero or more child nodes, reflecting the contents found.
1757 : : *
1758 : : * Errors normally result in ereport(ERROR), but if escontext is an
1759 : : * ErrorSaveContext, then "safe" errors are reported there instead, and the
1760 : : * caller must check SOFT_ERROR_OCCURRED() to see whether that happened.
1761 : : *
1762 : : * Note: it is caller's responsibility to xmlFreeDoc() the result,
1763 : : * else a permanent memory leak will ensue! But note the result could
1764 : : * be NULL after a soft error.
1765 : : *
1766 : : * TODO maybe libxml2's xmlreader is better? (do not construct DOM,
1767 : : * yet do not use SAX - see xmlreader.c)
1768 : : */
1769 : : static xmlDocPtr
1331 1770 : 940 : xml_parse(text *data, XmlOptionType xmloption_arg,
1771 : : bool preserve_whitespace, int encoding,
1772 : : XmlOptionType *parsed_xmloptiontype,
1773 : : Node *escontext)
1774 : : {
1775 : : int32 len;
1776 : : xmlChar *string;
1777 : : xmlChar *utf8string;
1778 : : PgXmlErrorContext *xmlerrcxt;
5587 1779 : 940 : volatile xmlParserCtxtPtr ctxt = NULL;
1780 : 940 : volatile xmlDocPtr doc = NULL;
1781 : :
1782 : : /*
1783 : : * This step looks annoyingly redundant, but we must do it to have a
1784 : : * null-terminated string in case encoding conversion isn't required.
1785 : : */
3424 1786 : 940 : len = VARSIZE_ANY_EXHDR(data); /* will be useful later */
7259 peter_e@gmx.net 1787 : 940 : string = xml_text2xmlChar(data);
1788 : :
1789 : : /*
1790 : : * If the data isn't UTF8, we must translate before giving it to libxml.
1791 : : *
1792 : : * XXX ideally, we'd catch any encoding conversion failure and return a
1793 : : * soft error. However, failure to convert to UTF8 should be pretty darn
1794 : : * rare, so for now this is left undone.
1795 : : */
7231 1796 : 940 : utf8string = pg_do_encoding_conversion(string,
1797 : : len,
1798 : : encoding,
1799 : : PG_UTF8);
1800 : :
1801 : : /* Start up libxml and its parser */
5587 tgl@sss.pgh.pa.us 1802 : 940 : xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_WELLFORMED);
1803 : :
1804 : : /* Use a TRY block to ensure we clean up correctly */
6385 1805 [ + + ]: 940 : PG_TRY();
1806 : : {
2784 1807 : 940 : bool parse_as_document = false;
1808 : 940 : size_t count = 0;
1809 : 940 : xmlChar *version = NULL;
1810 : 940 : int standalone = 0;
1811 : : int options;
1812 : :
1813 : : /* Any errors here are reported as hard ereport's */
5587 1814 : 940 : xmlInitParser();
1815 : :
1816 : : /* Decide whether to parse as document or content */
6356 bruce@momjian.us 1817 [ + + ]: 940 : if (xmloption_arg == XMLOPTION_DOCUMENT)
2784 tgl@sss.pgh.pa.us 1818 : 223 : parse_as_document = true;
1819 : : else
1820 : : {
1821 : : /* Parse and skip over the XML declaration, if any */
1822 : : int res_code;
1823 : :
1824 : 717 : res_code = parse_xml_decl(utf8string,
1825 : : &count, &version, NULL, &standalone);
1826 [ + + ]: 717 : if (res_code != 0)
1827 : : {
1420 1828 [ + + ]: 8 : errsave(escontext,
1829 : : errcode(ERRCODE_INVALID_XML_CONTENT),
1830 : : errmsg_internal("invalid XML content: invalid XML declaration"),
1831 : : errdetail_for_xml_code(res_code));
1832 : 8 : goto fail;
1833 : : }
1834 : :
1835 : : /* Is there a DOCTYPE element? */
2784 1836 [ + + ]: 709 : if (xml_doctype_in_content(utf8string + count))
1837 : 29 : parse_as_document = true;
1838 : : }
1839 : :
1840 : : /*
1841 : : * Select parse options.
1842 : : *
1843 : : * Note that here we try to apply DTD defaults (XML_PARSE_DTDATTR)
1844 : : * according to SQL/XML:2008 GR 10.16.7.d: 'Default values defined by
1845 : : * internal DTD are applied'. As for external DTDs, we try to support
1846 : : * them too (see SQL/XML:2008 GR 10.16.7.e), but that doesn't really
1847 : : * happen because xmlPgEntityLoader prevents it.
1848 : : */
21 tgl@sss.pgh.pa.us 1849 :GNC 932 : options = XML_PARSE_NOENT | XML_PARSE_DTDATTR
1850 [ + + ]: 932 : | (preserve_whitespace ? 0 : XML_PARSE_NOBLANKS);
1851 : :
1852 : : /* initialize output parameter */
1331 tgl@sss.pgh.pa.us 1853 [ + + ]:CBC 932 : if (parsed_xmloptiontype != NULL)
1854 : 130 : *parsed_xmloptiontype = parse_as_document ? XMLOPTION_DOCUMENT :
1855 : : XMLOPTION_CONTENT;
1856 : :
2784 1857 [ + + ]: 932 : if (parse_as_document)
1858 : : {
1859 : : /* set up parser context used by xmlCtxtReadDoc */
850 1860 : 252 : ctxt = xmlNewParserCtxt();
1861 [ + - - + ]: 252 : if (ctxt == NULL || xmlerrcxt->err_occurred)
850 tgl@sss.pgh.pa.us 1862 :UBC 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
1863 : : "could not allocate parser context");
1864 : :
6356 bruce@momjian.us 1865 :CBC 252 : doc = xmlCtxtReadDoc(ctxt, utf8string,
1866 : : NULL, /* no URL */
1867 : : "UTF-8",
1868 : : options);
1869 : :
5587 tgl@sss.pgh.pa.us 1870 [ + + - + ]: 252 : if (doc == NULL || xmlerrcxt->err_occurred)
1871 : : {
1872 : : /* Use original option to decide which error code to report */
2784 1873 [ + + ]: 104 : if (xmloption_arg == XMLOPTION_DOCUMENT)
1420 1874 : 100 : xml_errsave(escontext, xmlerrcxt,
1875 : : ERRCODE_INVALID_XML_DOCUMENT,
1876 : : "invalid XML document");
1877 : : else
1878 : 4 : xml_errsave(escontext, xmlerrcxt,
1879 : : ERRCODE_INVALID_XML_CONTENT,
1880 : : "invalid XML content");
1881 : 72 : goto fail;
1882 : : }
1883 : : }
1884 : : else
1885 : : {
1886 : : xmlNodePtr root;
1887 : :
1888 : : /*
1889 : : * Create our result document and set up its dummy root node,
1890 : : * which we'll use as the context node for xmlParseInNodeContext.
1891 : : */
6356 bruce@momjian.us 1892 : 680 : doc = xmlNewDoc(version);
1420 tgl@sss.pgh.pa.us 1893 [ + - - + ]: 680 : if (doc == NULL || xmlerrcxt->err_occurred)
1420 tgl@sss.pgh.pa.us 1894 :UBC 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
1895 : : "could not allocate XML document");
1896 : :
21 tgl@sss.pgh.pa.us 1897 :GNC 680 : root = xmlNewNode(NULL, (const xmlChar *) "content-root");
1898 [ + - - + ]: 680 : if (root == NULL || xmlerrcxt->err_occurred)
1420 tgl@sss.pgh.pa.us 1899 :UBC 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
1900 : : "could not allocate xml node");
21 tgl@sss.pgh.pa.us 1901 :GNC 680 : xmlDocSetRootElement(doc, root);
1902 : :
1903 : : /*
1904 : : * Leave doc->encoding unset while parsing the UTF-8 content.
1905 : : * Older libxml2 versions otherwise strip a leading U+FEFF as a
1906 : : * BOM, even when it followed the XML declaration in the original
1907 : : * input. libxml2 will treat the data as UTF-8 anyway, and there
1908 : : * do not seem to be any negative side-effects during parsing.
1909 : : */
1910 [ - + ]: 680 : Assert(doc->encoding == NULL);
1911 : 680 : doc->standalone = standalone;
1912 : :
1913 : : /* allow empty content */
4440 peter_e@gmx.net 1914 [ + + ]:CBC 680 : if (*(utf8string + count))
1915 : : {
1916 : : /* for some reason we need "char *" not "xmlChar *" here */
21 tgl@sss.pgh.pa.us 1917 :GNC 662 : const char *content = (const char *) (utf8string + count);
1918 : 662 : xmlNodePtr nodelist = NULL;
1919 : : xmlParserErrors res_code;
1920 : :
1921 : 1324 : res_code = xmlParseInNodeContext(root,
1922 : : content,
1923 : 662 : strlen(content),
1924 : : options,
1925 : : &nodelist);
1926 : :
1927 : : /* immediately attach the resulting nodelist to the root */
1928 [ + + ]: 662 : if (nodelist)
1929 : : {
1930 [ - + ]: 622 : if (xmlAddChildList(root, nodelist) == NULL)
1931 : : {
1932 : : /* don't leak the nodelist if we can't attach it */
21 tgl@sss.pgh.pa.us 1933 :UNC 0 : xmlFreeNodeList(nodelist);
1934 : 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
1935 : : "could not append xml node list");
1936 : : }
1937 : : /* now, freeing the document will release the nodelist */
1938 : : }
1939 : :
21 tgl@sss.pgh.pa.us 1940 [ + + - + ]:GNC 662 : if (res_code != XML_ERR_OK || xmlerrcxt->err_occurred)
1941 : : {
1420 tgl@sss.pgh.pa.us 1942 :CBC 40 : xml_errsave(escontext, xmlerrcxt,
1943 : : ERRCODE_INVALID_XML_CONTENT,
1944 : : "invalid XML content");
1945 : 8 : goto fail;
1946 : : }
1947 : : }
1948 : :
1949 : : /*
1950 : : * Now install the correct encoding setting. If we don't, some
1951 : : * aspects of document dumping don't do what we want.
1952 : : */
21 tgl@sss.pgh.pa.us 1953 [ - + ]:GNC 640 : Assert(doc->encoding == NULL);
1954 : 640 : doc->encoding = xmlStrdup((const xmlChar *) "UTF-8");
1955 [ + - + - ]: 640 : if (doc->encoding == NULL || xmlerrcxt->err_occurred)
21 tgl@sss.pgh.pa.us 1956 :UNC 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
1957 : : "could not allocate XML document");
1958 : : }
1959 : :
1420 tgl@sss.pgh.pa.us 1960 :CBC 876 : fail:
1961 : : ;
1962 : : }
6385 1963 : 64 : PG_CATCH();
1964 : : {
5587 1965 [ + + ]: 64 : if (doc != NULL)
1966 : 32 : xmlFreeDoc(doc);
1967 [ + + ]: 64 : if (ctxt != NULL)
1968 : 32 : xmlFreeParserCtxt(ctxt);
1969 : :
1970 : 64 : pg_xml_done(xmlerrcxt, true);
1971 : :
6385 1972 : 64 : PG_RE_THROW();
1973 : : }
1974 [ - + ]: 876 : PG_END_TRY();
1975 : :
850 1976 [ + + ]: 876 : if (ctxt != NULL)
1977 : 220 : xmlFreeParserCtxt(ctxt);
1978 : :
5587 1979 : 876 : pg_xml_done(xmlerrcxt, false);
1980 : :
7259 peter_e@gmx.net 1981 : 876 : return doc;
1982 : : }
1983 : :
1984 : :
1985 : : /*
1986 : : * xmlChar<->text conversions
1987 : : */
1988 : : static xmlChar *
1989 : 1079 : xml_text2xmlChar(text *in)
1990 : : {
6759 tgl@sss.pgh.pa.us 1991 : 1079 : return (xmlChar *) text_to_cstring(in);
1992 : : }
1993 : :
1994 : :
1995 : : #ifdef USE_LIBXMLCONTEXT
1996 : :
1997 : : /*
1998 : : * Manage the special context used for all libxml allocations (but only
1999 : : * in special debug builds; see notes at top of file)
2000 : : */
2001 : : static void
2002 : : xml_memory_init(void)
2003 : : {
2004 : : /* Create memory context if not there already */
2005 : : if (LibxmlContext == NULL)
2006 : : LibxmlContext = AllocSetContextCreate(TopMemoryContext,
2007 : : "Libxml context",
2008 : : ALLOCSET_DEFAULT_SIZES);
2009 : :
2010 : : /* Re-establish the callbacks even if already set */
2011 : : xmlMemSetup(xml_pfree, xml_palloc, xml_repalloc, xml_pstrdup);
2012 : : }
2013 : :
2014 : : /*
2015 : : * Wrappers for memory management functions
2016 : : */
2017 : : static void *
2018 : : xml_palloc(size_t size)
2019 : : {
2020 : : return MemoryContextAlloc(LibxmlContext, size);
2021 : : }
2022 : :
2023 : :
2024 : : static void *
2025 : : xml_repalloc(void *ptr, size_t size)
2026 : : {
2027 : : return repalloc(ptr, size);
2028 : : }
2029 : :
2030 : :
2031 : : static void
2032 : : xml_pfree(void *ptr)
2033 : : {
2034 : : /* At least some parts of libxml assume xmlFree(NULL) is allowed */
2035 : : if (ptr)
2036 : : pfree(ptr);
2037 : : }
2038 : :
2039 : :
2040 : : static char *
2041 : : xml_pstrdup(const char *string)
2042 : : {
2043 : : return MemoryContextStrdup(LibxmlContext, string);
2044 : : }
2045 : : #endif /* USE_LIBXMLCONTEXT */
2046 : :
2047 : :
2048 : : /*
2049 : : * xmlPgEntityLoader --- entity loader callback function
2050 : : *
2051 : : * Silently prevent any external entity URL from being loaded. We don't want
2052 : : * to throw an error, so instead make the entity appear to expand to an empty
2053 : : * string.
2054 : : *
2055 : : * We would prefer to allow loading entities that exist in the system's
2056 : : * global XML catalog; but the available libxml2 APIs make that a complex
2057 : : * and fragile task. For now, just shut down all external access.
2058 : : */
2059 : : static xmlParserInputPtr
5196 2060 : 15 : xmlPgEntityLoader(const char *URL, const char *ID,
2061 : : xmlParserCtxtPtr ctxt)
2062 : : {
2063 : 15 : return xmlNewStringInputStream(ctxt, (const xmlChar *) "");
2064 : : }
2065 : :
2066 : :
2067 : : /*
2068 : : * xml_ereport --- report an XML-related error
2069 : : *
2070 : : * The "msg" is the SQL-level message; some can be adopted from the SQL/XML
2071 : : * standard. This function adds libxml's native error message, if any, as
2072 : : * detail.
2073 : : *
2074 : : * This is exported for modules that want to share the core libxml error
2075 : : * handler. Note that pg_xml_init() *must* have been called previously.
2076 : : */
2077 : : void
5587 2078 : 8 : xml_ereport(PgXmlErrorContext *errcxt, int level, int sqlcode, const char *msg)
2079 : : {
2080 : : char *detail;
2081 : :
2082 : : /* Defend against someone passing us a bogus context struct */
2083 [ - + ]: 8 : if (errcxt->magic != ERRCXT_MAGIC)
5587 tgl@sss.pgh.pa.us 2084 [ # # ]:UBC 0 : elog(ERROR, "xml_ereport called with invalid PgXmlErrorContext");
2085 : :
2086 : : /* Flag that the current libxml error has been reported */
5587 tgl@sss.pgh.pa.us 2087 :CBC 8 : errcxt->err_occurred = false;
2088 : :
2089 : : /* Include detail only if we have some text from libxml */
2090 [ + - ]: 8 : if (errcxt->err_buf.len > 0)
2091 : 8 : detail = errcxt->err_buf.data;
2092 : : else
7226 peter_e@gmx.net 2093 :UBC 0 : detail = NULL;
2094 : :
5587 tgl@sss.pgh.pa.us 2095 [ + - + - ]:CBC 8 : ereport(level,
2096 : : (errcode(sqlcode),
2097 : : errmsg_internal("%s", msg),
2098 : : detail ? errdetail_internal("%s", detail) : 0));
5587 tgl@sss.pgh.pa.us 2099 :UBC 0 : }
2100 : :
2101 : :
2102 : : /*
2103 : : * xml_errsave --- save an XML-related error
2104 : : *
2105 : : * If escontext is an ErrorSaveContext, error details are saved into it,
2106 : : * and control returns normally.
2107 : : *
2108 : : * Otherwise, the error is thrown, so that this is equivalent to
2109 : : * xml_ereport() with level == ERROR.
2110 : : *
2111 : : * This should be used only for errors that we're sure we do not need
2112 : : * a transaction abort to clean up after.
2113 : : */
2114 : : static void
1420 tgl@sss.pgh.pa.us 2115 :CBC 144 : xml_errsave(Node *escontext, PgXmlErrorContext *errcxt,
2116 : : int sqlcode, const char *msg)
2117 : : {
2118 : : char *detail;
2119 : :
2120 : : /* Defend against someone passing us a bogus context struct */
2121 [ - + ]: 144 : if (errcxt->magic != ERRCXT_MAGIC)
1420 tgl@sss.pgh.pa.us 2122 [ # # ]:UBC 0 : elog(ERROR, "xml_errsave called with invalid PgXmlErrorContext");
2123 : :
2124 : : /* Flag that the current libxml error has been reported */
1420 tgl@sss.pgh.pa.us 2125 :CBC 144 : errcxt->err_occurred = false;
2126 : :
2127 : : /* Include detail only if we have some text from libxml */
2128 [ + - ]: 144 : if (errcxt->err_buf.len > 0)
2129 : 144 : detail = errcxt->err_buf.data;
2130 : : else
1420 tgl@sss.pgh.pa.us 2131 :UBC 0 : detail = NULL;
2132 : :
1420 tgl@sss.pgh.pa.us 2133 [ + + + - ]:CBC 144 : errsave(escontext,
2134 : : (errcode(sqlcode),
2135 : : errmsg_internal("%s", msg),
2136 : : detail ? errdetail_internal("%s", detail) : 0));
2137 : 80 : }
2138 : :
2139 : :
2140 : : /*
2141 : : * Error handler for libxml errors and warnings
2142 : : */
2143 : : static void
1011 2144 : 289 : xml_errorHandler(void *data, PgXmlErrorPtr error)
2145 : : {
5587 2146 : 289 : PgXmlErrorContext *xmlerrcxt = (PgXmlErrorContext *) data;
2147 : 289 : xmlParserCtxtPtr ctxt = (xmlParserCtxtPtr) error->ctxt;
2148 [ + - ]: 289 : xmlParserInputPtr input = (ctxt != NULL) ? ctxt->input : NULL;
5261 bruce@momjian.us 2149 : 289 : xmlNodePtr node = error->node;
5587 tgl@sss.pgh.pa.us 2150 : 289 : const xmlChar *name = (node != NULL &&
3424 2151 [ - + - - ]: 289 : node->type == XML_ELEMENT_NODE) ? node->name : NULL;
5587 2152 : 289 : int domain = error->domain;
2153 : 289 : int level = error->level;
2154 : : StringInfoData errorBuf;
2155 : :
2156 : : /*
2157 : : * Defend against someone passing us a bogus context struct.
2158 : : *
2159 : : * We force a backend exit if this check fails because longjmp'ing out of
2160 : : * libxml would likely render it unsafe to use further.
2161 : : */
2162 [ - + ]: 289 : if (xmlerrcxt->magic != ERRCXT_MAGIC)
5581 tgl@sss.pgh.pa.us 2163 [ # # ]:UBC 0 : elog(FATAL, "xml_errorHandler called with invalid PgXmlErrorContext");
2164 : :
2165 : : /*----------
2166 : : * Older libxml versions report some errors differently.
2167 : : * First, some errors were previously reported as coming from the parser
2168 : : * domain but are now reported as coming from the namespace domain.
2169 : : * Second, some warnings were upgraded to errors.
2170 : : * We attempt to compensate for that here.
2171 : : *----------
2172 : : */
5587 tgl@sss.pgh.pa.us 2173 [ + + + ]:CBC 289 : switch (error->code)
2174 : : {
2175 : 22 : case XML_WAR_NS_URI:
2176 : 22 : level = XML_ERR_ERROR;
2177 : 22 : domain = XML_FROM_NAMESPACE;
2178 : 22 : break;
2179 : :
2180 : 41 : case XML_ERR_NS_DECL_ERROR:
2181 : : case XML_WAR_NS_URI_RELATIVE:
2182 : : case XML_WAR_NS_COLUMN:
2183 : : case XML_NS_ERR_XML_NAMESPACE:
2184 : : case XML_NS_ERR_UNDEFINED_NAMESPACE:
2185 : : case XML_NS_ERR_QNAME:
2186 : : case XML_NS_ERR_ATTRIBUTE_REDEFINED:
2187 : : case XML_NS_ERR_EMPTY:
2188 : 41 : domain = XML_FROM_NAMESPACE;
2189 : 41 : break;
2190 : : }
2191 : :
2192 : : /* Decide whether to act on the error or not */
2193 [ + - + ]: 289 : switch (domain)
2194 : : {
2195 : 226 : case XML_FROM_PARSER:
2196 : :
2197 : : /*
2198 : : * XML_ERR_NOT_WELL_BALANCED is typically reported after some
2199 : : * other, more on-point error. Furthermore, libxml2 2.13 reports
2200 : : * it under a completely different set of rules than prior
2201 : : * versions. To avoid cross-version behavioral differences,
2202 : : * suppress it so long as we already logged some error.
2203 : : */
849 2204 [ + + ]: 226 : if (error->code == XML_ERR_NOT_WELL_BALANCED &&
2205 [ + - ]: 28 : xmlerrcxt->err_occurred)
2206 : 84 : return;
2207 : : pg_fallthrough;
2208 : :
2209 : : case XML_FROM_NONE:
2210 : : case XML_FROM_MEMORY:
2211 : : case XML_FROM_IO:
2212 : :
2213 : : /*
2214 : : * Suppress warnings about undeclared entities. We need to do
2215 : : * this to avoid problems due to not loading DTD definitions.
2216 : : */
5196 2217 [ + + ]: 198 : if (error->code == XML_WAR_UNDECLARED_ENTITY)
2218 : 5 : return;
2219 : :
2220 : : /* Otherwise, accept error regardless of the parsing purpose */
5587 2221 : 193 : break;
2222 : :
2223 : 63 : default:
2224 : : /* Ignore error if only doing well-formedness check */
2225 [ + + ]: 63 : if (xmlerrcxt->strictness == PG_XML_STRICTNESS_WELLFORMED)
2226 : 50 : return;
2227 : 13 : break;
2228 : : }
2229 : :
2230 : : /* Prepare error message in errorBuf */
364 drowley@postgresql.o 2231 : 206 : initStringInfo(&errorBuf);
2232 : :
5587 tgl@sss.pgh.pa.us 2233 [ + - ]: 206 : if (error->line > 0)
364 drowley@postgresql.o 2234 : 206 : appendStringInfo(&errorBuf, "line %d: ", error->line);
5587 tgl@sss.pgh.pa.us 2235 [ - + ]: 206 : if (name != NULL)
364 drowley@postgresql.o 2236 :UBC 0 : appendStringInfo(&errorBuf, "element %s: ", name);
2827 tgl@sss.pgh.pa.us 2237 [ + - ]:CBC 206 : if (error->message != NULL)
364 drowley@postgresql.o 2238 : 206 : appendStringInfoString(&errorBuf, error->message);
2239 : : else
364 drowley@postgresql.o 2240 :UBC 0 : appendStringInfoString(&errorBuf, "(no message provided)");
2241 : :
2242 : : /*
2243 : : * Append context information to errorBuf.
2244 : : *
2245 : : * xmlParserPrintFileContext() uses libxml's "generic" error handler to
2246 : : * write the context. Since we don't want to duplicate libxml
2247 : : * functionality here, we set up a generic error handler temporarily.
2248 : : *
2249 : : * We use appendStringInfo() directly as libxml's generic error handler.
2250 : : * This should work because it has essentially the same signature as
2251 : : * libxml expects, namely (void *ptr, const char *msg, ...).
2252 : : */
5587 tgl@sss.pgh.pa.us 2253 [ + - ]:CBC 206 : if (input != NULL)
2254 : : {
2255 : 206 : xmlGenericErrorFunc errFuncSaved = xmlGenericError;
5261 bruce@momjian.us 2256 : 206 : void *errCtxSaved = xmlGenericErrorContext;
2257 : :
364 drowley@postgresql.o 2258 : 206 : xmlSetGenericErrorFunc(&errorBuf,
2259 : : (xmlGenericErrorFunc) appendStringInfo);
2260 : :
2261 : : /* Add context information to errorBuf */
2262 : 206 : appendStringInfoLineSeparator(&errorBuf);
2263 : :
5587 tgl@sss.pgh.pa.us 2264 : 206 : xmlParserPrintFileContext(input);
2265 : :
2266 : : /* Restore generic error func */
2267 : 206 : xmlSetGenericErrorFunc(errCtxSaved, errFuncSaved);
2268 : : }
2269 : :
2270 : : /* Get rid of any trailing newlines in errorBuf */
364 drowley@postgresql.o 2271 : 206 : chopStringInfoNewlines(&errorBuf);
2272 : :
2273 : : /*
2274 : : * Legacy error handling mode. err_occurred is never set, we just add the
2275 : : * message to err_buf. This mode exists because the xml2 contrib module
2276 : : * uses our error-handling infrastructure, but we don't want to change its
2277 : : * behaviour since it's deprecated anyway. This is also why we don't
2278 : : * distinguish between notices, warnings and errors here --- the old-style
2279 : : * generic error handler wouldn't have done that either.
2280 : : */
5587 tgl@sss.pgh.pa.us 2281 [ + + ]: 206 : if (xmlerrcxt->strictness == PG_XML_STRICTNESS_LEGACY)
2282 : : {
2283 : 1 : appendStringInfoLineSeparator(&xmlerrcxt->err_buf);
364 drowley@postgresql.o 2284 : 1 : appendBinaryStringInfo(&xmlerrcxt->err_buf, errorBuf.data,
2285 : : errorBuf.len);
2286 : :
2287 : 1 : pfree(errorBuf.data);
5587 tgl@sss.pgh.pa.us 2288 : 1 : return;
2289 : : }
2290 : :
2291 : : /*
2292 : : * We don't want to ereport() here because that'd probably leave libxml in
2293 : : * an inconsistent state. Instead, we remember the error and ereport()
2294 : : * from xml_ereport().
2295 : : *
2296 : : * Warnings and notices can be reported immediately since they won't cause
2297 : : * a longjmp() out of libxml.
2298 : : */
2299 [ + + ]: 205 : if (level >= XML_ERR_ERROR)
2300 : : {
2301 : 200 : appendStringInfoLineSeparator(&xmlerrcxt->err_buf);
364 drowley@postgresql.o 2302 : 200 : appendBinaryStringInfo(&xmlerrcxt->err_buf, errorBuf.data,
2303 : : errorBuf.len);
2304 : :
5587 tgl@sss.pgh.pa.us 2305 : 200 : xmlerrcxt->err_occurred = true;
2306 : : }
2307 [ + - ]: 5 : else if (level >= XML_ERR_WARNING)
2308 : : {
2309 [ + - ]: 5 : ereport(WARNING,
2310 : : (errmsg_internal("%s", errorBuf.data)));
2311 : : }
2312 : : else
2313 : : {
5587 tgl@sss.pgh.pa.us 2314 [ # # ]:UBC 0 : ereport(NOTICE,
2315 : : (errmsg_internal("%s", errorBuf.data)));
2316 : : }
2317 : :
364 drowley@postgresql.o 2318 :CBC 205 : pfree(errorBuf.data);
2319 : : }
2320 : :
2321 : :
2322 : : /*
2323 : : * Convert libxml error codes into textual errdetail messages.
2324 : : *
2325 : : * This should be called within an ereport or errsave invocation,
2326 : : * just as errdetail would be.
2327 : : *
2328 : : * At the moment, we only need to cover those codes that we
2329 : : * may raise in this file.
2330 : : */
2331 : : static int
1420 tgl@sss.pgh.pa.us 2332 : 4 : errdetail_for_xml_code(int code)
2333 : : {
2334 : : const char *det;
2335 : :
6930 bruce@momjian.us 2336 [ - - + - : 4 : switch (code)
- - - ]
2337 : : {
7226 peter_e@gmx.net 2338 :UBC 0 : case XML_ERR_INVALID_CHAR:
6800 tgl@sss.pgh.pa.us 2339 : 0 : det = gettext_noop("Invalid character value.");
7226 peter_e@gmx.net 2340 : 0 : break;
2341 : 0 : case XML_ERR_SPACE_REQUIRED:
6800 tgl@sss.pgh.pa.us 2342 : 0 : det = gettext_noop("Space required.");
7226 peter_e@gmx.net 2343 : 0 : break;
7226 peter_e@gmx.net 2344 :CBC 4 : case XML_ERR_STANDALONE_VALUE:
6800 tgl@sss.pgh.pa.us 2345 : 4 : det = gettext_noop("standalone accepts only 'yes' or 'no'.");
7226 peter_e@gmx.net 2346 : 4 : break;
7226 peter_e@gmx.net 2347 :UBC 0 : case XML_ERR_VERSION_MISSING:
6800 tgl@sss.pgh.pa.us 2348 : 0 : det = gettext_noop("Malformed declaration: missing version.");
7226 peter_e@gmx.net 2349 : 0 : break;
2350 : 0 : case XML_ERR_MISSING_ENCODING:
6800 tgl@sss.pgh.pa.us 2351 : 0 : det = gettext_noop("Missing encoding in text declaration.");
7226 peter_e@gmx.net 2352 : 0 : break;
2353 : 0 : case XML_ERR_XMLDECL_NOT_FINISHED:
6800 tgl@sss.pgh.pa.us 2354 : 0 : det = gettext_noop("Parsing XML declaration: '?>' expected.");
7226 peter_e@gmx.net 2355 : 0 : break;
6930 bruce@momjian.us 2356 : 0 : default:
6800 tgl@sss.pgh.pa.us 2357 : 0 : det = gettext_noop("Unrecognized libxml error code: %d.");
7256 2358 : 0 : break;
2359 : : }
2360 : :
1420 tgl@sss.pgh.pa.us 2361 :CBC 4 : return errdetail(det, code);
2362 : : }
2363 : :
2364 : :
2365 : : /*
2366 : : * Remove all trailing newlines from a StringInfo string
2367 : : */
2368 : : static void
5587 2369 : 613 : chopStringInfoNewlines(StringInfo str)
2370 : : {
2371 [ + + + + ]: 1025 : while (str->len > 0 && str->data[str->len - 1] == '\n')
2372 : 412 : str->data[--str->len] = '\0';
2373 : 613 : }
2374 : :
2375 : :
2376 : : /*
2377 : : * Append a newline after removing any existing trailing newlines
2378 : : */
2379 : : static void
2380 : 407 : appendStringInfoLineSeparator(StringInfo str)
2381 : : {
2382 : 407 : chopStringInfoNewlines(str);
2383 [ + + ]: 407 : if (str->len > 0)
2384 : 254 : appendStringInfoChar(str, '\n');
2385 : 407 : }
2386 : :
2387 : :
2388 : : /*
2389 : : * Convert one char in the current server encoding to a Unicode codepoint.
2390 : : */
2391 : : static pg_wchar
3292 peter_e@gmx.net 2392 : 12173 : sqlchar_to_unicode(const char *s)
2393 : : {
2394 : : char *utf8string;
2395 : : pg_wchar ret[2]; /* need space for trailing zero */
2396 : :
302 tmunro@postgresql.or 2397 : 12173 : utf8string = pg_server_to_any(s, pg_mblen_cstr(s), PG_UTF8);
2398 : :
6569 tgl@sss.pgh.pa.us 2399 : 12173 : pg_encoding_mb2wchar_with_len(PG_UTF8, utf8string, ret,
2400 : : pg_encoding_mblen(PG_UTF8, utf8string));
2401 : :
2402 [ - + ]: 12173 : if (utf8string != s)
6569 tgl@sss.pgh.pa.us 2403 :UBC 0 : pfree(utf8string);
2404 : :
7256 tgl@sss.pgh.pa.us 2405 :CBC 12173 : return ret[0];
2406 : : }
2407 : :
2408 : :
2409 : : static bool
7259 peter_e@gmx.net 2410 : 2421 : is_valid_xml_namefirst(pg_wchar c)
2411 : : {
2412 : : /* (Letter | '_' | ':') */
2413 [ + + + + : 2425 : return (xmlIsBaseCharQ(c) || xmlIsIdeographicQ(c)
+ + - + -
+ - - - +
- - - + -
- - + - -
- - - - -
- - - ]
2414 [ + - + + : 4846 : || c == '_' || c == ':');
+ - - + ]
2415 : : }
2416 : :
2417 : :
2418 : : static bool
2419 : 9752 : is_valid_xml_namechar(pg_wchar c)
2420 : : {
2421 : : /* Letter | Digit | '.' | '-' | '_' | ':' | CombiningChar | Extender */
2422 [ + + + + : 10345 : return (xmlIsBaseCharQ(c) || xmlIsIdeographicQ(c)
+ + - + -
+ - - - +
- - - + -
- - + - -
- - - - -
- - - ]
2423 [ + + + + : 593 : || xmlIsDigitQ(c)
+ + - - ]
2424 [ + + + + : 169 : || c == '.' || c == '-' || c == '_' || c == ':'
+ + + + ]
2425 [ - + - - ]: 8 : || xmlIsCombiningQ(c)
2426 [ + - + + : 20690 : || xmlIsExtenderQ(c));
+ - + - -
+ - - ]
2427 : : }
2428 : : #endif /* USE_LIBXML */
2429 : :
2430 : :
2431 : : /*
2432 : : * Map SQL identifier to XML name; see SQL/XML:2008 section 9.1.
2433 : : */
2434 : : char *
3292 2435 : 2430 : map_sql_identifier_to_xml_name(const char *ident, bool fully_escaped,
2436 : : bool escape_period)
2437 : : {
2438 : : #ifdef USE_LIBXML
2439 : : StringInfoData buf;
2440 : : const char *p;
2441 : :
2442 : : /*
2443 : : * SQL/XML doesn't make use of this case anywhere, so it's probably a
2444 : : * mistake.
2445 : : */
7207 2446 [ + + - + ]: 2430 : Assert(fully_escaped || !escape_period);
2447 : :
7259 2448 : 2430 : initStringInfo(&buf);
2449 : :
302 tmunro@postgresql.or 2450 [ + + ]: 14616 : for (p = ident; *p; p += pg_mblen_cstr(p))
2451 : : {
7259 peter_e@gmx.net 2452 [ + + + + : 12186 : if (*p == ':' && (p == ident || fully_escaped))
- + ]
4753 rhaas@postgresql.org 2453 : 9 : appendStringInfoString(&buf, "_x003A_");
6930 bruce@momjian.us 2454 [ + + + + ]: 12177 : else if (*p == '_' && *(p + 1) == 'x')
4753 rhaas@postgresql.org 2455 : 4 : appendStringInfoString(&buf, "_x005F_");
7256 tgl@sss.pgh.pa.us 2456 [ + + + + : 14363 : else if (fully_escaped && p == ident &&
- + ]
2457 : 2190 : pg_strncasecmp(p, "xml", 3) == 0)
2458 : : {
7259 peter_e@gmx.net 2459 [ # # ]:UBC 0 : if (*p == 'x')
4753 rhaas@postgresql.org 2460 : 0 : appendStringInfoString(&buf, "_x0078_");
2461 : : else
2462 : 0 : appendStringInfoString(&buf, "_x0058_");
2463 : : }
7207 peter_e@gmx.net 2464 [ + + - + ]:CBC 12173 : else if (escape_period && *p == '.')
4753 rhaas@postgresql.org 2465 :UBC 0 : appendStringInfoString(&buf, "_x002E_");
2466 : : else
2467 : : {
6930 bruce@momjian.us 2468 :CBC 12173 : pg_wchar u = sqlchar_to_unicode(p);
2469 : :
7256 tgl@sss.pgh.pa.us 2470 [ + + + + ]: 24346 : if ((p == ident)
2471 : 2421 : ? !is_valid_xml_namefirst(u)
2472 : 9752 : : !is_valid_xml_namechar(u))
2473 : 12 : appendStringInfo(&buf, "_x%04X_", (unsigned int) u);
2474 : : else
302 tmunro@postgresql.or 2475 : 12161 : appendBinaryStringInfo(&buf, p, pg_mblen_cstr(p));
2476 : : }
2477 : : }
2478 : :
7259 peter_e@gmx.net 2479 : 2430 : return buf.data;
2480 : : #else /* not USE_LIBXML */
2481 : : NO_XML_SUPPORT();
2482 : : return NULL;
2483 : : #endif /* not USE_LIBXML */
2484 : : }
2485 : :
2486 : :
2487 : : /*
2488 : : * Map XML name to SQL identifier; see SQL/XML:2008 section 9.3.
2489 : : */
2490 : : char *
3292 2491 : 72 : map_xml_name_to_sql_identifier(const char *name)
2492 : : {
2493 : : StringInfoData buf;
2494 : : const char *p;
2495 : :
7251 2496 : 72 : initStringInfo(&buf);
2497 : :
302 tmunro@postgresql.or 2498 [ + + ]: 396 : for (p = name; *p; p += pg_mblen_cstr(p))
2499 : : {
6930 bruce@momjian.us 2500 [ + + + - ]: 324 : if (*p == '_' && *(p + 1) == 'x'
2501 [ + - ]: 9 : && isxdigit((unsigned char) *(p + 2))
2502 [ + - ]: 9 : && isxdigit((unsigned char) *(p + 3))
2503 [ + - ]: 9 : && isxdigit((unsigned char) *(p + 4))
2504 [ + - ]: 9 : && isxdigit((unsigned char) *(p + 5))
2505 [ + - ]: 9 : && *(p + 6) == '_')
7251 peter_e@gmx.net 2506 : 9 : {
2507 : : char cbuf[MAX_UNICODE_EQUIVALENT_STRING + 1];
2508 : : unsigned int u;
2509 : :
2510 : 9 : sscanf(p + 2, "%X", &u);
2435 tgl@sss.pgh.pa.us 2511 : 9 : pg_unicode_to_server(u, (unsigned char *) cbuf);
2512 : 9 : appendStringInfoString(&buf, cbuf);
7251 peter_e@gmx.net 2513 : 9 : p += 6;
2514 : : }
2515 : : else
302 tmunro@postgresql.or 2516 : 315 : appendBinaryStringInfo(&buf, p, pg_mblen_cstr(p));
2517 : : }
2518 : :
7251 peter_e@gmx.net 2519 : 72 : return buf.data;
2520 : : }
2521 : :
2522 : : /*
2523 : : * Map SQL value to XML value; see SQL/XML:2008 section 9.8.
2524 : : *
2525 : : * When xml_escape_strings is true, then certain characters in string
2526 : : * values are replaced by entity references (< etc.), as specified
2527 : : * in SQL/XML:2008 section 9.8 GR 9) a) iii). This is normally what is
2528 : : * wanted. The false case is mainly useful when the resulting value
2529 : : * is used with xmlTextWriterWriteAttribute() to write out an
2530 : : * attribute, because that function does the escaping itself.
2531 : : */
2532 : : char *
6358 2533 : 91116 : map_sql_value_to_xml_value(Datum value, Oid type, bool xml_escape_strings)
2534 : : {
5859 tgl@sss.pgh.pa.us 2535 [ + + ]: 91116 : if (type_is_array_domain(type))
2536 : : {
2537 : : ArrayType *array;
2538 : : Oid elmtype;
2539 : : int16 elmlen;
2540 : : bool elmbyval;
2541 : : char elmalign;
2542 : : int num_elems;
2543 : : Datum *elem_values;
2544 : : bool *elem_nulls;
2545 : : StringInfoData buf;
2546 : : int i;
2547 : :
7237 peter_e@gmx.net 2548 : 4 : array = DatumGetArrayTypeP(value);
2549 : 4 : elmtype = ARR_ELEMTYPE(array);
2550 : 4 : get_typlenbyvalalign(elmtype, &elmlen, &elmbyval, &elmalign);
2551 : :
6939 tgl@sss.pgh.pa.us 2552 : 4 : deconstruct_array(array, elmtype,
2553 : : elmlen, elmbyval, elmalign,
2554 : : &elem_values, &elem_nulls,
2555 : : &num_elems);
2556 : :
6601 2557 : 4 : initStringInfo(&buf);
2558 : :
6939 2559 [ + + ]: 16 : for (i = 0; i < num_elems; i++)
2560 : : {
2561 [ - + ]: 12 : if (elem_nulls[i])
6939 tgl@sss.pgh.pa.us 2562 :UBC 0 : continue;
7237 peter_e@gmx.net 2563 :CBC 12 : appendStringInfoString(&buf, "<element>");
6939 tgl@sss.pgh.pa.us 2564 : 12 : appendStringInfoString(&buf,
2565 : 12 : map_sql_value_to_xml_value(elem_values[i],
2566 : : elmtype, true));
7237 peter_e@gmx.net 2567 : 12 : appendStringInfoString(&buf, "</element>");
2568 : : }
2569 : :
6939 tgl@sss.pgh.pa.us 2570 : 4 : pfree(elem_values);
2571 : 4 : pfree(elem_nulls);
2572 : :
6601 2573 : 4 : return buf.data;
2574 : : }
2575 : : else
2576 : : {
2577 : : Oid typeOut;
2578 : : bool isvarlena;
2579 : : char *str;
2580 : :
2581 : : /*
2582 : : * Flatten domains; the special-case treatments below should apply to,
2583 : : * eg, domains over boolean not just boolean.
2584 : : */
4995 2585 : 91112 : type = getBaseType(type);
2586 : :
2587 : : /*
2588 : : * Special XSD formatting for some data types
2589 : : */
7189 peter_e@gmx.net 2590 [ + + + + : 91112 : switch (type)
+ + ]
2591 : : {
2592 : 46 : case BOOLOID:
2593 [ + + ]: 46 : if (DatumGetBool(value))
2594 : 41 : return "true";
2595 : : else
2596 : 5 : return "false";
2597 : :
2598 : 32 : case DATEOID:
2599 : : {
2600 : : DateADT date;
2601 : : struct pg_tm tm;
2602 : : char buf[MAXDATELEN + 1];
2603 : :
6930 bruce@momjian.us 2604 : 32 : date = DatumGetDateADT(value);
2605 : : /* XSD doesn't support infinite values */
6596 tgl@sss.pgh.pa.us 2606 [ + - - + ]: 32 : if (DATE_NOT_FINITE(date))
6596 tgl@sss.pgh.pa.us 2607 [ # # ]:UBC 0 : ereport(ERROR,
2608 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
2609 : : errmsg("date out of range"),
2610 : : errdetail("XML does not support infinite date values.")));
6930 bruce@momjian.us 2611 :CBC 32 : j2date(date + POSTGRES_EPOCH_JDATE,
2612 : : &(tm.tm_year), &(tm.tm_mon), &(tm.tm_mday));
2613 : 32 : EncodeDateOnly(&tm, USE_XSD_DATES, buf);
2614 : :
2615 : 32 : return pstrdup(buf);
2616 : : }
2617 : :
7189 peter_e@gmx.net 2618 : 24 : case TIMESTAMPOID:
2619 : : {
2620 : : Timestamp timestamp;
2621 : : struct pg_tm tm;
2622 : : fsec_t fsec;
2623 : : char buf[MAXDATELEN + 1];
2624 : :
6930 bruce@momjian.us 2625 : 24 : timestamp = DatumGetTimestamp(value);
2626 : :
2627 : : /* XSD doesn't support infinite values */
2628 [ + - + + ]: 24 : if (TIMESTAMP_NOT_FINITE(timestamp))
2629 [ + - ]: 4 : ereport(ERROR,
2630 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
2631 : : errmsg("timestamp out of range"),
2632 : : errdetail("XML does not support infinite timestamp values.")));
2633 [ + - ]: 20 : else if (timestamp2tm(timestamp, NULL, &tm, &fsec, NULL, NULL) == 0)
5349 peter_e@gmx.net 2634 : 20 : EncodeDateTime(&tm, fsec, false, 0, NULL, USE_XSD_DATES, buf);
2635 : : else
6930 bruce@momjian.us 2636 [ # # ]:UBC 0 : ereport(ERROR,
2637 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
2638 : : errmsg("timestamp out of range")));
2639 : :
6930 bruce@momjian.us 2640 :CBC 20 : return pstrdup(buf);
2641 : : }
2642 : :
7189 peter_e@gmx.net 2643 : 16 : case TIMESTAMPTZOID:
2644 : : {
2645 : : TimestampTz timestamp;
2646 : : struct pg_tm tm;
2647 : : int tz;
2648 : : fsec_t fsec;
5348 2649 : 16 : const char *tzn = NULL;
2650 : : char buf[MAXDATELEN + 1];
2651 : :
6930 bruce@momjian.us 2652 : 16 : timestamp = DatumGetTimestamp(value);
2653 : :
2654 : : /* XSD doesn't support infinite values */
2655 [ + - - + ]: 16 : if (TIMESTAMP_NOT_FINITE(timestamp))
6930 bruce@momjian.us 2656 [ # # ]:UBC 0 : ereport(ERROR,
2657 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
2658 : : errmsg("timestamp out of range"),
2659 : : errdetail("XML does not support infinite timestamp values.")));
6930 bruce@momjian.us 2660 [ + - ]:CBC 16 : else if (timestamp2tm(timestamp, &tz, &tm, &fsec, &tzn, NULL) == 0)
5349 peter_e@gmx.net 2661 : 16 : EncodeDateTime(&tm, fsec, true, tz, tzn, USE_XSD_DATES, buf);
2662 : : else
6930 bruce@momjian.us 2663 [ # # ]:UBC 0 : ereport(ERROR,
2664 : : (errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
2665 : : errmsg("timestamp out of range")));
2666 : :
6930 bruce@momjian.us 2667 :CBC 16 : return pstrdup(buf);
2668 : : }
2669 : :
2670 : : #ifdef USE_LIBXML
6601 tgl@sss.pgh.pa.us 2671 : 24 : case BYTEAOID:
2672 : : {
2673 : 24 : bytea *bstr = DatumGetByteaPP(value);
2674 : : PgXmlErrorContext *xmlerrcxt;
5587 2675 : 24 : volatile xmlBufferPtr buf = NULL;
2676 : 24 : volatile xmlTextWriterPtr writer = NULL;
2677 : : char *result;
2678 : :
2679 : 24 : xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
2680 : :
6385 2681 [ + - ]: 24 : PG_TRY();
2682 : : {
2683 : 24 : buf = xmlBufferCreate();
5587 2684 [ + - - + ]: 24 : if (buf == NULL || xmlerrcxt->err_occurred)
5587 tgl@sss.pgh.pa.us 2685 :UBC 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
2686 : : "could not allocate xmlBuffer");
6385 tgl@sss.pgh.pa.us 2687 :CBC 24 : writer = xmlNewTextWriterMemory(buf, 0);
5587 2688 [ + - - + ]: 24 : if (writer == NULL || xmlerrcxt->err_occurred)
5587 tgl@sss.pgh.pa.us 2689 :UBC 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
2690 : : "could not allocate xmlTextWriter");
2691 : :
6385 tgl@sss.pgh.pa.us 2692 [ + + ]:CBC 24 : if (xmlbinary == XMLBINARY_BASE64)
2693 : 20 : xmlTextWriterWriteBase64(writer, VARDATA_ANY(bstr),
3424 2694 : 20 : 0, VARSIZE_ANY_EXHDR(bstr));
2695 : : else
6385 2696 : 4 : xmlTextWriterWriteBinHex(writer, VARDATA_ANY(bstr),
3424 2697 : 4 : 0, VARSIZE_ANY_EXHDR(bstr));
2698 : :
2699 : : /* we MUST do this now to flush data out to the buffer */
6385 2700 : 24 : xmlFreeTextWriter(writer);
2701 : 24 : writer = NULL;
2702 : :
2703 : 24 : result = pstrdup((const char *) xmlBufferContent(buf));
2704 : : }
6385 tgl@sss.pgh.pa.us 2705 :UBC 0 : PG_CATCH();
2706 : : {
2707 [ # # ]: 0 : if (writer)
2708 : 0 : xmlFreeTextWriter(writer);
2709 [ # # ]: 0 : if (buf)
2710 : 0 : xmlBufferFree(buf);
2711 : :
5587 2712 : 0 : pg_xml_done(xmlerrcxt, true);
2713 : :
6385 2714 : 0 : PG_RE_THROW();
2715 : : }
6385 tgl@sss.pgh.pa.us 2716 [ - + ]:CBC 24 : PG_END_TRY();
2717 : :
6601 2718 : 24 : xmlBufferFree(buf);
2719 : :
5587 2720 : 24 : pg_xml_done(xmlerrcxt, false);
2721 : :
6601 2722 : 24 : return result;
2723 : : }
2724 : : #endif /* USE_LIBXML */
2725 : :
2726 : : }
2727 : :
2728 : : /*
2729 : : * otherwise, just use the type's native text representation
2730 : : */
7237 peter_e@gmx.net 2731 : 90970 : getTypeOutputInfo(type, &typeOut, &isvarlena);
2732 : 90970 : str = OidOutputFunctionCall(typeOut, value);
2733 : :
2734 : : /* ... exactly as-is for XML, and when escaping is not wanted */
6358 2735 [ + + + + ]: 90970 : if (type == XMLOID || !xml_escape_strings)
7237 2736 : 15092 : return str;
2737 : :
2738 : : /* otherwise, translate special characters as needed */
6296 tgl@sss.pgh.pa.us 2739 : 75878 : return escape_xml(str);
2740 : : }
2741 : : }
2742 : :
2743 : :
2744 : : /*
2745 : : * Escape characters in text that have special meanings in XML.
2746 : : *
2747 : : * Returns a palloc'd string.
2748 : : *
2749 : : * NB: this is intentionally not dependent on libxml.
2750 : : */
2751 : : char *
2752 : 76196 : escape_xml(const char *str)
2753 : : {
2754 : : StringInfoData buf;
2755 : : const char *p;
2756 : :
2757 : 76196 : initStringInfo(&buf);
2758 [ + + ]: 481565 : for (p = str; *p; p++)
2759 : : {
2760 [ - + + - : 405369 : switch (*p)
+ ]
2761 : : {
6296 tgl@sss.pgh.pa.us 2762 :UBC 0 : case '&':
2763 : 0 : appendStringInfoString(&buf, "&");
2764 : 0 : break;
6296 tgl@sss.pgh.pa.us 2765 :CBC 28 : case '<':
2766 : 28 : appendStringInfoString(&buf, "<");
2767 : 28 : break;
2768 : 18 : case '>':
2769 : 18 : appendStringInfoString(&buf, ">");
2770 : 18 : break;
6296 tgl@sss.pgh.pa.us 2771 :UBC 0 : case '\r':
2772 : 0 : appendStringInfoString(&buf, "
");
2773 : 0 : break;
6296 tgl@sss.pgh.pa.us 2774 :CBC 405323 : default:
2775 [ - + ]: 405323 : appendStringInfoCharMacro(&buf, *p);
2776 : 405323 : break;
2777 : : }
2778 : : }
2779 : 76196 : return buf.data;
2780 : : }
2781 : :
2782 : :
2783 : : static char *
7202 peter_e@gmx.net 2784 : 16 : _SPI_strdup(const char *s)
2785 : : {
6872 neilc@samurai.com 2786 : 16 : size_t len = strlen(s) + 1;
2787 : 16 : char *ret = SPI_palloc(len);
2788 : :
2789 : 16 : memcpy(ret, s, len);
7202 peter_e@gmx.net 2790 : 16 : return ret;
2791 : : }
2792 : :
2793 : :
2794 : : /*
2795 : : * SQL to XML mapping functions
2796 : : *
2797 : : * What follows below was at one point intentionally organized so that
2798 : : * you can read along in the SQL/XML standard. The functions are
2799 : : * mostly split up the way the clauses lay out in the standards
2800 : : * document, and the identifiers are also aligned with the standard
2801 : : * text. Unfortunately, SQL/XML:2006 reordered the clauses
2802 : : * differently than SQL/XML:2003, so the order below doesn't make much
2803 : : * sense anymore.
2804 : : *
2805 : : * There are many things going on there:
2806 : : *
2807 : : * There are two kinds of mappings: Mapping SQL data (table contents)
2808 : : * to XML documents, and mapping SQL structure (the "schema") to XML
2809 : : * Schema. And there are functions that do both at the same time.
2810 : : *
2811 : : * Then you can map a database, a schema, or a table, each in both
2812 : : * ways. This breaks down recursively: Mapping a database invokes
2813 : : * mapping schemas, which invokes mapping tables, which invokes
2814 : : * mapping rows, which invokes mapping columns, although you can't
2815 : : * call the last two from the outside. Because of this, there are a
2816 : : * number of xyz_internal() functions which are to be called both from
2817 : : * the function manager wrapper and from some upper layer in a
2818 : : * recursive call.
2819 : : *
2820 : : * See the documentation about what the common function arguments
2821 : : * nulls, tableforest, and targetns mean.
2822 : : *
2823 : : * Some style guidelines for XML output: Use double quotes for quoting
2824 : : * XML attributes. Indent XML elements by two spaces, but remember
2825 : : * that a lot of code is called recursively at different levels, so
2826 : : * it's better not to indent rather than create output that indents
2827 : : * and outdents weirdly. Add newlines to make the output look nice.
2828 : : */
2829 : :
2830 : :
2831 : : /*
2832 : : * Visibility of objects for XML mappings; see SQL/XML:2008 section
2833 : : * 4.10.8.
2834 : : */
2835 : :
2836 : : /*
2837 : : * Given a query, which must return type oid as first column, produce
2838 : : * a list of Oids with the query results.
2839 : : */
2840 : : static List *
7158 2841 : 24 : query_to_oid_list(const char *query)
2842 : : {
2843 : : uint64 i;
2844 : 24 : List *list = NIL;
2845 : : int spi_result;
2846 : :
2555 michael@paquier.xyz 2847 : 24 : spi_result = SPI_execute(query, true, 0);
2848 [ - + ]: 24 : if (spi_result != SPI_OK_SELECT)
2555 michael@paquier.xyz 2849 [ # # ]:UBC 0 : elog(ERROR, "SPI_execute returned %s for %s",
2850 : : SPI_result_code_string(spi_result), query);
2851 : :
7158 peter_e@gmx.net 2852 [ + + ]:CBC 72 : for (i = 0; i < SPI_processed; i++)
2853 : : {
2854 : : Datum oid;
2855 : : bool isnull;
2856 : :
6939 tgl@sss.pgh.pa.us 2857 : 48 : oid = SPI_getbinval(SPI_tuptable->vals[i],
2858 : 48 : SPI_tuptable->tupdesc,
2859 : : 1,
2860 : : &isnull);
2861 [ + - ]: 48 : if (!isnull)
2862 : 48 : list = lappend_oid(list, DatumGetObjectId(oid));
2863 : : }
2864 : :
7158 peter_e@gmx.net 2865 : 24 : return list;
2866 : : }
2867 : :
2868 : :
2869 : : static List *
2870 : 24 : schema_get_xml_visible_tables(Oid nspid)
2871 : : {
2872 : : StringInfoData query;
2873 : :
2874 : 24 : initStringInfo(&query);
3528 tgl@sss.pgh.pa.us 2875 : 24 : appendStringInfo(&query, "SELECT oid FROM pg_catalog.pg_class"
2876 : : " WHERE relnamespace = %u AND relkind IN ("
2877 : : CppAsString2(RELKIND_RELATION) ","
2878 : : CppAsString2(RELKIND_MATVIEW) ","
2879 : : CppAsString2(RELKIND_VIEW) ")"
2880 : : " AND pg_catalog.has_table_privilege (oid, 'SELECT')"
2881 : : " ORDER BY relname;", nspid);
2882 : :
7158 peter_e@gmx.net 2883 : 24 : return query_to_oid_list(query.data);
2884 : : }
2885 : :
2886 : :
2887 : : /*
2888 : : * Including the system schemas is probably not useful for a database
2889 : : * mapping.
2890 : : */
2891 : : #define XML_VISIBLE_SCHEMAS_EXCLUDE "(nspname ~ '^pg_' OR nspname = 'information_schema')"
2892 : :
2893 : : #define XML_VISIBLE_SCHEMAS "SELECT oid FROM pg_catalog.pg_namespace WHERE pg_catalog.has_schema_privilege (oid, 'USAGE') AND NOT " XML_VISIBLE_SCHEMAS_EXCLUDE
2894 : :
2895 : :
2896 : : static List *
7158 peter_e@gmx.net 2897 :UBC 0 : database_get_xml_visible_schemas(void)
2898 : : {
2899 : 0 : return query_to_oid_list(XML_VISIBLE_SCHEMAS " ORDER BY nspname;");
2900 : : }
2901 : :
2902 : :
2903 : : static List *
2904 : 0 : database_get_xml_visible_tables(void)
2905 : : {
2906 : : /* At the moment there is no order required here. */
3528 tgl@sss.pgh.pa.us 2907 : 0 : return query_to_oid_list("SELECT oid FROM pg_catalog.pg_class"
2908 : : " WHERE relkind IN ("
2909 : : CppAsString2(RELKIND_RELATION) ","
2910 : : CppAsString2(RELKIND_MATVIEW) ","
2911 : : CppAsString2(RELKIND_VIEW) ")"
2912 : : " AND pg_catalog.has_table_privilege(pg_class.oid, 'SELECT')"
2913 : : " AND relnamespace IN (" XML_VISIBLE_SCHEMAS ");");
2914 : : }
2915 : :
2916 : :
2917 : : /*
2918 : : * Map SQL table to XML and/or XML Schema document; see SQL/XML:2008
2919 : : * section 9.11.
2920 : : */
2921 : :
2922 : : static StringInfo
6939 tgl@sss.pgh.pa.us 2923 :CBC 64 : table_to_xml_internal(Oid relid,
2924 : : const char *xmlschema, bool nulls, bool tableforest,
2925 : : const char *targetns, bool top_level)
2926 : : {
2927 : : StringInfoData query;
2928 : :
7158 peter_e@gmx.net 2929 : 64 : initStringInfo(&query);
6939 tgl@sss.pgh.pa.us 2930 : 64 : appendStringInfo(&query, "SELECT * FROM %s",
2931 : : DatumGetCString(DirectFunctionCall1(regclassout,
2932 : : ObjectIdGetDatum(relid))));
2933 : 64 : return query_to_xml_internal(query.data, get_rel_name(relid),
2934 : : xmlschema, nulls, tableforest,
2935 : : targetns, top_level);
2936 : : }
2937 : :
2938 : :
2939 : : Datum
7202 peter_e@gmx.net 2940 : 24 : table_to_xml(PG_FUNCTION_ARGS)
2941 : : {
2942 : 24 : Oid relid = PG_GETARG_OID(0);
2943 : 24 : bool nulls = PG_GETARG_BOOL(1);
2944 : 24 : bool tableforest = PG_GETARG_BOOL(2);
6799 tgl@sss.pgh.pa.us 2945 : 24 : const char *targetns = text_to_cstring(PG_GETARG_TEXT_PP(3));
2946 : :
6939 2947 : 24 : PG_RETURN_XML_P(stringinfo_to_xmltype(table_to_xml_internal(relid, NULL,
2948 : : nulls, tableforest,
2949 : : targetns, true)));
2950 : : }
2951 : :
2952 : :
2953 : : Datum
7202 peter_e@gmx.net 2954 : 6 : query_to_xml(PG_FUNCTION_ARGS)
2955 : : {
6799 tgl@sss.pgh.pa.us 2956 : 6 : char *query = text_to_cstring(PG_GETARG_TEXT_PP(0));
7202 peter_e@gmx.net 2957 : 6 : bool nulls = PG_GETARG_BOOL(1);
2958 : 6 : bool tableforest = PG_GETARG_BOOL(2);
6799 tgl@sss.pgh.pa.us 2959 : 6 : const char *targetns = text_to_cstring(PG_GETARG_TEXT_PP(3));
2960 : :
6939 2961 : 6 : PG_RETURN_XML_P(stringinfo_to_xmltype(query_to_xml_internal(query, NULL,
2962 : : NULL, nulls, tableforest,
2963 : : targetns, true)));
2964 : : }
2965 : :
2966 : :
2967 : : Datum
7202 peter_e@gmx.net 2968 : 8 : cursor_to_xml(PG_FUNCTION_ARGS)
2969 : : {
6799 tgl@sss.pgh.pa.us 2970 : 8 : char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
7202 peter_e@gmx.net 2971 : 8 : int32 count = PG_GETARG_INT32(1);
2972 : 8 : bool nulls = PG_GETARG_BOOL(2);
2973 : 8 : bool tableforest = PG_GETARG_BOOL(3);
6799 tgl@sss.pgh.pa.us 2974 : 8 : const char *targetns = text_to_cstring(PG_GETARG_TEXT_PP(4));
2975 : :
2976 : : StringInfoData result;
2977 : : Portal portal;
2978 : : uint64 i;
2979 : :
7202 peter_e@gmx.net 2980 : 8 : initStringInfo(&result);
2981 : :
3473 2982 [ + + ]: 8 : if (!tableforest)
2983 : : {
2984 : 4 : xmldata_root_element_start(&result, "table", NULL, targetns, true);
2985 : 4 : appendStringInfoChar(&result, '\n');
2986 : : }
2987 : :
7202 2988 : 8 : SPI_connect();
2989 : 8 : portal = SPI_cursor_find(name);
2990 [ - + ]: 8 : if (portal == NULL)
7202 peter_e@gmx.net 2991 [ # # ]:UBC 0 : ereport(ERROR,
2992 : : (errcode(ERRCODE_UNDEFINED_CURSOR),
2993 : : errmsg("cursor \"%s\" does not exist", name)));
2994 : :
7202 peter_e@gmx.net 2995 :CBC 8 : SPI_cursor_fetch(portal, true, count);
2996 [ + + ]: 32 : for (i = 0; i < SPI_processed; i++)
6939 tgl@sss.pgh.pa.us 2997 : 24 : SPI_sql_row_to_xmlelement(i, &result, NULL, nulls,
2998 : : tableforest, targetns, true);
2999 : :
7202 peter_e@gmx.net 3000 : 8 : SPI_finish();
3001 : :
3473 3002 [ + + ]: 8 : if (!tableforest)
3003 : 4 : xmldata_root_element_end(&result, "table");
3004 : :
7202 3005 : 8 : PG_RETURN_XML_P(stringinfo_to_xmltype(&result));
3006 : : }
3007 : :
3008 : :
3009 : : /*
3010 : : * Write the start tag of the root element of a data mapping.
3011 : : *
3012 : : * top_level means that this is the very top level of the eventual
3013 : : * output. For example, when the user calls table_to_xml, then a call
3014 : : * with a table name to this function is the top level. When the user
3015 : : * calls database_to_xml, then a call with a schema name to this
3016 : : * function is not the top level. If top_level is false, then the XML
3017 : : * namespace declarations are omitted, because they supposedly already
3018 : : * appeared earlier in the output. Repeating them is not wrong, but
3019 : : * it looks ugly.
3020 : : */
3021 : : static void
6939 tgl@sss.pgh.pa.us 3022 : 158 : xmldata_root_element_start(StringInfo result, const char *eltname,
3023 : : const char *xmlschema, const char *targetns,
3024 : : bool top_level)
3025 : : {
3026 : : /* This isn't really wrong but currently makes no sense. */
7158 peter_e@gmx.net 3027 [ + + - + ]: 158 : Assert(top_level || !xmlschema);
3028 : :
3029 : 158 : appendStringInfo(result, "<%s", eltname);
3030 [ + + ]: 158 : if (top_level)
3031 : : {
3032 : 118 : appendStringInfoString(result, " xmlns:xsi=\"" NAMESPACE_XSI "\"");
3033 [ + + ]: 118 : if (strlen(targetns) > 0)
3034 : 20 : appendStringInfo(result, " xmlns=\"%s\"", targetns);
3035 : : }
3036 [ + + ]: 158 : if (xmlschema)
3037 : : {
3038 : : /* FIXME: better targets */
3039 [ + + ]: 12 : if (strlen(targetns) > 0)
3040 : 4 : appendStringInfo(result, " xsi:schemaLocation=\"%s #\"", targetns);
3041 : : else
4753 rhaas@postgresql.org 3042 : 8 : appendStringInfoString(result, " xsi:noNamespaceSchemaLocation=\"#\"");
3043 : : }
3044 : 158 : appendStringInfoString(result, ">\n");
7158 peter_e@gmx.net 3045 : 158 : }
3046 : :
3047 : :
3048 : : static void
3049 : 158 : xmldata_root_element_end(StringInfo result, const char *eltname)
3050 : : {
3051 : 158 : appendStringInfo(result, "</%s>\n", eltname);
3052 : 158 : }
3053 : :
3054 : :
3055 : : static StringInfo
6939 tgl@sss.pgh.pa.us 3056 : 74 : query_to_xml_internal(const char *query, char *tablename,
3057 : : const char *xmlschema, bool nulls, bool tableforest,
3058 : : const char *targetns, bool top_level)
3059 : : {
3060 : : StringInfo result;
3061 : : char *xmltn;
3062 : : uint64 i;
3063 : :
7202 peter_e@gmx.net 3064 [ + + ]: 74 : if (tablename)
3065 : 64 : xmltn = map_sql_identifier_to_xml_name(tablename, true, false);
3066 : : else
3067 : 10 : xmltn = "table";
3068 : :
3069 : 74 : result = makeStringInfo();
3070 : :
3071 : 74 : SPI_connect();
3072 [ - + ]: 74 : if (SPI_execute(query, true, 0) != SPI_OK_SELECT)
7202 peter_e@gmx.net 3073 [ # # ]:UBC 0 : ereport(ERROR,
3074 : : (errcode(ERRCODE_DATA_EXCEPTION),
3075 : : errmsg("invalid query")));
3076 : :
7202 peter_e@gmx.net 3077 [ + + ]:CBC 74 : if (!tableforest)
3078 : : {
6939 tgl@sss.pgh.pa.us 3079 : 34 : xmldata_root_element_start(result, xmltn, xmlschema,
3080 : : targetns, top_level);
4144 heikki.linnakangas@i 3081 : 34 : appendStringInfoChar(result, '\n');
3082 : : }
3083 : :
7202 peter_e@gmx.net 3084 [ + + ]: 74 : if (xmlschema)
3085 : 20 : appendStringInfo(result, "%s\n\n", xmlschema);
3086 : :
6930 bruce@momjian.us 3087 [ + + ]: 256 : for (i = 0; i < SPI_processed; i++)
6939 tgl@sss.pgh.pa.us 3088 : 182 : SPI_sql_row_to_xmlelement(i, result, tablename, nulls,
3089 : : tableforest, targetns, top_level);
3090 : :
7202 peter_e@gmx.net 3091 [ + + ]: 74 : if (!tableforest)
7158 3092 : 34 : xmldata_root_element_end(result, xmltn);
3093 : :
7202 3094 : 74 : SPI_finish();
3095 : :
3096 : 74 : return result;
3097 : : }
3098 : :
3099 : :
3100 : : Datum
3101 : 20 : table_to_xmlschema(PG_FUNCTION_ARGS)
3102 : : {
3103 : 20 : Oid relid = PG_GETARG_OID(0);
3104 : 20 : bool nulls = PG_GETARG_BOOL(1);
3105 : 20 : bool tableforest = PG_GETARG_BOOL(2);
6799 tgl@sss.pgh.pa.us 3106 : 20 : const char *targetns = text_to_cstring(PG_GETARG_TEXT_PP(3));
3107 : : const char *result;
3108 : : Relation rel;
3109 : :
2845 andres@anarazel.de 3110 : 20 : rel = table_open(relid, AccessShareLock);
6939 tgl@sss.pgh.pa.us 3111 : 20 : result = map_sql_table_to_xmlschema(rel->rd_att, relid, nulls,
3112 : : tableforest, targetns);
2845 andres@anarazel.de 3113 : 20 : table_close(rel, NoLock);
3114 : :
7202 peter_e@gmx.net 3115 : 20 : PG_RETURN_XML_P(cstring_to_xmltype(result));
3116 : : }
3117 : :
3118 : :
3119 : : Datum
3120 : 4 : query_to_xmlschema(PG_FUNCTION_ARGS)
3121 : : {
6799 tgl@sss.pgh.pa.us 3122 : 4 : char *query = text_to_cstring(PG_GETARG_TEXT_PP(0));
7202 peter_e@gmx.net 3123 : 4 : bool nulls = PG_GETARG_BOOL(1);
3124 : 4 : bool tableforest = PG_GETARG_BOOL(2);
6799 tgl@sss.pgh.pa.us 3125 : 4 : const char *targetns = text_to_cstring(PG_GETARG_TEXT_PP(3));
3126 : : const char *result;
3127 : : SPIPlanPtr plan;
3128 : : Portal portal;
3129 : :
7202 peter_e@gmx.net 3130 : 4 : SPI_connect();
3131 : :
6872 neilc@samurai.com 3132 [ - + ]: 4 : if ((plan = SPI_prepare(query, 0, NULL)) == NULL)
6872 neilc@samurai.com 3133 [ # # ]:UBC 0 : elog(ERROR, "SPI_prepare(\"%s\") failed", query);
3134 : :
6872 neilc@samurai.com 3135 [ - + ]:CBC 4 : if ((portal = SPI_cursor_open(NULL, plan, NULL, NULL, true)) == NULL)
6872 neilc@samurai.com 3136 [ # # ]:UBC 0 : elog(ERROR, "SPI_cursor_open(\"%s\") failed", query);
3137 : :
6939 tgl@sss.pgh.pa.us 3138 :CBC 4 : result = _SPI_strdup(map_sql_table_to_xmlschema(portal->tupDesc,
3139 : : InvalidOid, nulls,
3140 : : tableforest, targetns));
7202 peter_e@gmx.net 3141 : 4 : SPI_cursor_close(portal);
3142 : 4 : SPI_finish();
3143 : :
3144 : 4 : PG_RETURN_XML_P(cstring_to_xmltype(result));
3145 : : }
3146 : :
3147 : :
3148 : : Datum
3149 : 8 : cursor_to_xmlschema(PG_FUNCTION_ARGS)
3150 : : {
6799 tgl@sss.pgh.pa.us 3151 : 8 : char *name = text_to_cstring(PG_GETARG_TEXT_PP(0));
7202 peter_e@gmx.net 3152 : 8 : bool nulls = PG_GETARG_BOOL(1);
3153 : 8 : bool tableforest = PG_GETARG_BOOL(2);
6799 tgl@sss.pgh.pa.us 3154 : 8 : const char *targetns = text_to_cstring(PG_GETARG_TEXT_PP(3));
3155 : : const char *xmlschema;
3156 : : Portal portal;
3157 : :
7202 peter_e@gmx.net 3158 : 8 : SPI_connect();
3159 : 8 : portal = SPI_cursor_find(name);
3160 [ - + ]: 8 : if (portal == NULL)
7202 peter_e@gmx.net 3161 [ # # ]:UBC 0 : ereport(ERROR,
3162 : : (errcode(ERRCODE_UNDEFINED_CURSOR),
3163 : : errmsg("cursor \"%s\" does not exist", name)));
1144 tgl@sss.pgh.pa.us 3164 [ - + ]:CBC 8 : if (portal->tupDesc == NULL)
1144 tgl@sss.pgh.pa.us 3165 [ # # ]:UBC 0 : ereport(ERROR,
3166 : : (errcode(ERRCODE_INVALID_CURSOR_STATE),
3167 : : errmsg("portal \"%s\" does not return tuples", name)));
3168 : :
6939 tgl@sss.pgh.pa.us 3169 :CBC 8 : xmlschema = _SPI_strdup(map_sql_table_to_xmlschema(portal->tupDesc,
3170 : : InvalidOid, nulls,
3171 : : tableforest, targetns));
7202 peter_e@gmx.net 3172 : 8 : SPI_finish();
3173 : :
3174 : 8 : PG_RETURN_XML_P(cstring_to_xmltype(xmlschema));
3175 : : }
3176 : :
3177 : :
3178 : : Datum
3179 : 16 : table_to_xml_and_xmlschema(PG_FUNCTION_ARGS)
3180 : : {
3181 : 16 : Oid relid = PG_GETARG_OID(0);
3182 : 16 : bool nulls = PG_GETARG_BOOL(1);
3183 : 16 : bool tableforest = PG_GETARG_BOOL(2);
6799 tgl@sss.pgh.pa.us 3184 : 16 : const char *targetns = text_to_cstring(PG_GETARG_TEXT_PP(3));
3185 : : Relation rel;
3186 : : const char *xmlschema;
3187 : :
2845 andres@anarazel.de 3188 : 16 : rel = table_open(relid, AccessShareLock);
6939 tgl@sss.pgh.pa.us 3189 : 16 : xmlschema = map_sql_table_to_xmlschema(rel->rd_att, relid, nulls,
3190 : : tableforest, targetns);
2845 andres@anarazel.de 3191 : 16 : table_close(rel, NoLock);
3192 : :
6939 tgl@sss.pgh.pa.us 3193 : 16 : PG_RETURN_XML_P(stringinfo_to_xmltype(table_to_xml_internal(relid,
3194 : : xmlschema, nulls, tableforest,
3195 : : targetns, true)));
3196 : : }
3197 : :
3198 : :
3199 : : Datum
7202 peter_e@gmx.net 3200 : 4 : query_to_xml_and_xmlschema(PG_FUNCTION_ARGS)
3201 : : {
6799 tgl@sss.pgh.pa.us 3202 : 4 : char *query = text_to_cstring(PG_GETARG_TEXT_PP(0));
7202 peter_e@gmx.net 3203 : 4 : bool nulls = PG_GETARG_BOOL(1);
3204 : 4 : bool tableforest = PG_GETARG_BOOL(2);
6799 tgl@sss.pgh.pa.us 3205 : 4 : const char *targetns = text_to_cstring(PG_GETARG_TEXT_PP(3));
3206 : :
3207 : : const char *xmlschema;
3208 : : SPIPlanPtr plan;
3209 : : Portal portal;
3210 : :
7202 peter_e@gmx.net 3211 : 4 : SPI_connect();
3212 : :
6872 neilc@samurai.com 3213 [ - + ]: 4 : if ((plan = SPI_prepare(query, 0, NULL)) == NULL)
6872 neilc@samurai.com 3214 [ # # ]:UBC 0 : elog(ERROR, "SPI_prepare(\"%s\") failed", query);
3215 : :
6872 neilc@samurai.com 3216 [ - + ]:CBC 4 : if ((portal = SPI_cursor_open(NULL, plan, NULL, NULL, true)) == NULL)
6872 neilc@samurai.com 3217 [ # # ]:UBC 0 : elog(ERROR, "SPI_cursor_open(\"%s\") failed", query);
3218 : :
6939 tgl@sss.pgh.pa.us 3219 :CBC 4 : xmlschema = _SPI_strdup(map_sql_table_to_xmlschema(portal->tupDesc,
3220 : : InvalidOid, nulls, tableforest, targetns));
7202 peter_e@gmx.net 3221 : 4 : SPI_cursor_close(portal);
3222 : 4 : SPI_finish();
3223 : :
6939 tgl@sss.pgh.pa.us 3224 : 4 : PG_RETURN_XML_P(stringinfo_to_xmltype(query_to_xml_internal(query, NULL,
3225 : : xmlschema, nulls, tableforest,
3226 : : targetns, true)));
3227 : : }
3228 : :
3229 : :
3230 : : /*
3231 : : * Map SQL schema to XML and/or XML Schema document; see SQL/XML:2008
3232 : : * sections 9.13, 9.14.
3233 : : */
3234 : :
3235 : : static StringInfo
3236 : 12 : schema_to_xml_internal(Oid nspid, const char *xmlschema, bool nulls,
3237 : : bool tableforest, const char *targetns, bool top_level)
3238 : : {
3239 : : StringInfo result;
3240 : : char *nspname;
3241 : : char *xmlsn;
3242 : : List *relid_list;
3243 : : ListCell *cell;
3244 : :
3245 : : /*
3246 : : * The schema may have been dropped concurrently, as we hold no lock on
3247 : : * it. Report that rather than passing a NULL name below.
3248 : : */
5 heikki.linnakangas@i 3249 : 12 : nspname = get_namespace_name(nspid);
3250 [ - + ]: 12 : if (nspname == NULL)
5 heikki.linnakangas@i 3251 [ # # ]:UBC 0 : ereport(ERROR,
3252 : : (errcode(ERRCODE_UNDEFINED_SCHEMA),
3253 : : errmsg("schema with OID %u does not exist", nspid)));
3254 : :
5 heikki.linnakangas@i 3255 :CBC 12 : xmlsn = map_sql_identifier_to_xml_name(nspname, true, false);
7158 peter_e@gmx.net 3256 : 12 : result = makeStringInfo();
3257 : :
3258 : 12 : xmldata_root_element_start(result, xmlsn, xmlschema, targetns, top_level);
4144 heikki.linnakangas@i 3259 : 12 : appendStringInfoChar(result, '\n');
3260 : :
7158 peter_e@gmx.net 3261 [ + + ]: 12 : if (xmlschema)
3262 : 4 : appendStringInfo(result, "%s\n\n", xmlschema);
3263 : :
3264 : 12 : SPI_connect();
3265 : :
3266 : 12 : relid_list = schema_get_xml_visible_tables(nspid);
3267 : :
3268 [ + - + + : 36 : foreach(cell, relid_list)
+ + ]
3269 : : {
6930 bruce@momjian.us 3270 : 24 : Oid relid = lfirst_oid(cell);
3271 : : StringInfo subres;
3272 : :
6939 tgl@sss.pgh.pa.us 3273 : 24 : subres = table_to_xml_internal(relid, NULL, nulls, tableforest,
3274 : : targetns, false);
3275 : :
2662 drowley@postgresql.o 3276 : 24 : appendBinaryStringInfo(result, subres->data, subres->len);
7158 peter_e@gmx.net 3277 : 24 : appendStringInfoChar(result, '\n');
3278 : : }
3279 : :
3280 : 12 : SPI_finish();
3281 : :
3282 : 12 : xmldata_root_element_end(result, xmlsn);
3283 : :
3284 : 12 : return result;
3285 : : }
3286 : :
3287 : :
3288 : : Datum
3289 : 8 : schema_to_xml(PG_FUNCTION_ARGS)
3290 : : {
3291 : 8 : Name name = PG_GETARG_NAME(0);
3292 : 8 : bool nulls = PG_GETARG_BOOL(1);
3293 : 8 : bool tableforest = PG_GETARG_BOOL(2);
6799 tgl@sss.pgh.pa.us 3294 : 8 : const char *targetns = text_to_cstring(PG_GETARG_TEXT_PP(3));
3295 : :
3296 : : char *schemaname;
3297 : : Oid nspid;
3298 : :
7158 peter_e@gmx.net 3299 : 8 : schemaname = NameStr(*name);
5031 bruce@momjian.us 3300 : 8 : nspid = LookupExplicitNamespace(schemaname, false);
3301 : :
6939 tgl@sss.pgh.pa.us 3302 : 8 : PG_RETURN_XML_P(stringinfo_to_xmltype(schema_to_xml_internal(nspid, NULL,
3303 : : nulls, tableforest, targetns, true)));
3304 : : }
3305 : :
3306 : :
3307 : : /*
3308 : : * Write the start element of the root element of an XML Schema mapping.
3309 : : */
3310 : : static void
7158 peter_e@gmx.net 3311 : 64 : xsd_schema_element_start(StringInfo result, const char *targetns)
3312 : : {
3313 : 64 : appendStringInfoString(result,
3314 : : "<xsd:schema\n"
3315 : : " xmlns:xsd=\"" NAMESPACE_XSD "\"");
3316 [ + + ]: 64 : if (strlen(targetns) > 0)
3317 : 12 : appendStringInfo(result,
3318 : : "\n"
3319 : : " targetNamespace=\"%s\"\n"
3320 : : " elementFormDefault=\"qualified\"",
3321 : : targetns);
3322 : 64 : appendStringInfoString(result,
3323 : : ">\n\n");
3324 : 64 : }
3325 : :
3326 : :
3327 : : static void
3328 : 64 : xsd_schema_element_end(StringInfo result)
3329 : : {
6939 tgl@sss.pgh.pa.us 3330 : 64 : appendStringInfoString(result, "</xsd:schema>");
7158 peter_e@gmx.net 3331 : 64 : }
3332 : :
3333 : :
3334 : : static StringInfo
6939 tgl@sss.pgh.pa.us 3335 : 12 : schema_to_xmlschema_internal(const char *schemaname, bool nulls,
3336 : : bool tableforest, const char *targetns)
3337 : : {
3338 : : Oid nspid;
3339 : : List *relid_list;
3340 : : List *tupdesc_list;
3341 : : ListCell *cell;
3342 : : StringInfo result;
3343 : :
7158 peter_e@gmx.net 3344 : 12 : result = makeStringInfo();
3345 : :
5031 bruce@momjian.us 3346 : 12 : nspid = LookupExplicitNamespace(schemaname, false);
3347 : :
7158 peter_e@gmx.net 3348 : 12 : xsd_schema_element_start(result, targetns);
3349 : :
3350 : 12 : SPI_connect();
3351 : :
3352 : 12 : relid_list = schema_get_xml_visible_tables(nspid);
3353 : :
3354 : 12 : tupdesc_list = NIL;
6930 bruce@momjian.us 3355 [ + - + + : 36 : foreach(cell, relid_list)
+ + ]
3356 : : {
3357 : : Relation rel;
3358 : :
2845 andres@anarazel.de 3359 : 24 : rel = table_open(lfirst_oid(cell), AccessShareLock);
6939 tgl@sss.pgh.pa.us 3360 : 24 : tupdesc_list = lappend(tupdesc_list, CreateTupleDescCopy(rel->rd_att));
2845 andres@anarazel.de 3361 : 24 : table_close(rel, NoLock);
3362 : : }
3363 : :
7158 peter_e@gmx.net 3364 : 12 : appendStringInfoString(result,
3365 : : map_sql_typecoll_to_xmlschema_types(tupdesc_list));
3366 : :
3367 : 12 : appendStringInfoString(result,
3368 : : map_sql_schema_to_xmlschema_types(nspid, relid_list,
3369 : : nulls, tableforest, targetns));
3370 : :
3371 : 12 : xsd_schema_element_end(result);
3372 : :
3373 : 12 : SPI_finish();
3374 : :
3375 : 12 : return result;
3376 : : }
3377 : :
3378 : :
3379 : : Datum
3380 : 8 : schema_to_xmlschema(PG_FUNCTION_ARGS)
3381 : : {
3382 : 8 : Name name = PG_GETARG_NAME(0);
3383 : 8 : bool nulls = PG_GETARG_BOOL(1);
3384 : 8 : bool tableforest = PG_GETARG_BOOL(2);
6799 tgl@sss.pgh.pa.us 3385 : 8 : const char *targetns = text_to_cstring(PG_GETARG_TEXT_PP(3));
3386 : :
6939 3387 : 8 : PG_RETURN_XML_P(stringinfo_to_xmltype(schema_to_xmlschema_internal(NameStr(*name),
3388 : : nulls, tableforest, targetns)));
3389 : : }
3390 : :
3391 : :
3392 : : Datum
7158 peter_e@gmx.net 3393 : 4 : schema_to_xml_and_xmlschema(PG_FUNCTION_ARGS)
3394 : : {
3395 : 4 : Name name = PG_GETARG_NAME(0);
3396 : 4 : bool nulls = PG_GETARG_BOOL(1);
3397 : 4 : bool tableforest = PG_GETARG_BOOL(2);
6799 tgl@sss.pgh.pa.us 3398 : 4 : const char *targetns = text_to_cstring(PG_GETARG_TEXT_PP(3));
3399 : : char *schemaname;
3400 : : Oid nspid;
3401 : : StringInfo xmlschema;
3402 : :
7158 peter_e@gmx.net 3403 : 4 : schemaname = NameStr(*name);
5031 bruce@momjian.us 3404 : 4 : nspid = LookupExplicitNamespace(schemaname, false);
3405 : :
6939 tgl@sss.pgh.pa.us 3406 : 4 : xmlschema = schema_to_xmlschema_internal(schemaname, nulls,
3407 : : tableforest, targetns);
3408 : :
3409 : 4 : PG_RETURN_XML_P(stringinfo_to_xmltype(schema_to_xml_internal(nspid,
3410 : : xmlschema->data, nulls,
3411 : : tableforest, targetns, true)));
3412 : : }
3413 : :
3414 : :
3415 : : /*
3416 : : * Map SQL database to XML and/or XML Schema document; see SQL/XML:2008
3417 : : * sections 9.16, 9.17.
3418 : : */
3419 : :
3420 : : static StringInfo
6939 tgl@sss.pgh.pa.us 3421 :UBC 0 : database_to_xml_internal(const char *xmlschema, bool nulls,
3422 : : bool tableforest, const char *targetns)
3423 : : {
3424 : : StringInfo result;
3425 : : List *nspid_list;
3426 : : ListCell *cell;
3427 : : char *xmlcn;
3428 : :
3429 : 0 : xmlcn = map_sql_identifier_to_xml_name(get_database_name(MyDatabaseId),
3430 : : true, false);
7158 peter_e@gmx.net 3431 : 0 : result = makeStringInfo();
3432 : :
3433 : 0 : xmldata_root_element_start(result, xmlcn, xmlschema, targetns, true);
4144 heikki.linnakangas@i 3434 : 0 : appendStringInfoChar(result, '\n');
3435 : :
7158 peter_e@gmx.net 3436 [ # # ]: 0 : if (xmlschema)
3437 : 0 : appendStringInfo(result, "%s\n\n", xmlschema);
3438 : :
3439 : 0 : SPI_connect();
3440 : :
3441 : 0 : nspid_list = database_get_xml_visible_schemas();
3442 : :
3443 [ # # # # : 0 : foreach(cell, nspid_list)
# # ]
3444 : : {
6930 bruce@momjian.us 3445 : 0 : Oid nspid = lfirst_oid(cell);
3446 : : StringInfo subres;
3447 : :
6939 tgl@sss.pgh.pa.us 3448 : 0 : subres = schema_to_xml_internal(nspid, NULL, nulls,
3449 : : tableforest, targetns, false);
3450 : :
2662 drowley@postgresql.o 3451 : 0 : appendBinaryStringInfo(result, subres->data, subres->len);
7158 peter_e@gmx.net 3452 : 0 : appendStringInfoChar(result, '\n');
3453 : : }
3454 : :
3455 : 0 : SPI_finish();
3456 : :
3457 : 0 : xmldata_root_element_end(result, xmlcn);
3458 : :
3459 : 0 : return result;
3460 : : }
3461 : :
3462 : :
3463 : : Datum
3464 : 0 : database_to_xml(PG_FUNCTION_ARGS)
3465 : : {
3466 : 0 : bool nulls = PG_GETARG_BOOL(0);
3467 : 0 : bool tableforest = PG_GETARG_BOOL(1);
6799 tgl@sss.pgh.pa.us 3468 : 0 : const char *targetns = text_to_cstring(PG_GETARG_TEXT_PP(2));
3469 : :
6939 3470 : 0 : PG_RETURN_XML_P(stringinfo_to_xmltype(database_to_xml_internal(NULL, nulls,
3471 : : tableforest, targetns)));
3472 : : }
3473 : :
3474 : :
3475 : : static StringInfo
3476 : 0 : database_to_xmlschema_internal(bool nulls, bool tableforest,
3477 : : const char *targetns)
3478 : : {
3479 : : List *relid_list;
3480 : : List *nspid_list;
3481 : : List *tupdesc_list;
3482 : : ListCell *cell;
3483 : : StringInfo result;
3484 : :
7158 peter_e@gmx.net 3485 : 0 : result = makeStringInfo();
3486 : :
3487 : 0 : xsd_schema_element_start(result, targetns);
3488 : :
3489 : 0 : SPI_connect();
3490 : :
3491 : 0 : relid_list = database_get_xml_visible_tables();
3492 : 0 : nspid_list = database_get_xml_visible_schemas();
3493 : :
3494 : 0 : tupdesc_list = NIL;
6930 bruce@momjian.us 3495 [ # # # # : 0 : foreach(cell, relid_list)
# # ]
3496 : : {
3497 : : Relation rel;
3498 : :
2845 andres@anarazel.de 3499 : 0 : rel = table_open(lfirst_oid(cell), AccessShareLock);
6939 tgl@sss.pgh.pa.us 3500 : 0 : tupdesc_list = lappend(tupdesc_list, CreateTupleDescCopy(rel->rd_att));
2845 andres@anarazel.de 3501 : 0 : table_close(rel, NoLock);
3502 : : }
3503 : :
7158 peter_e@gmx.net 3504 : 0 : appendStringInfoString(result,
3505 : : map_sql_typecoll_to_xmlschema_types(tupdesc_list));
3506 : :
3507 : 0 : appendStringInfoString(result,
3508 : : map_sql_catalog_to_xmlschema_types(nspid_list, nulls, tableforest, targetns));
3509 : :
3510 : 0 : xsd_schema_element_end(result);
3511 : :
3512 : 0 : SPI_finish();
3513 : :
3514 : 0 : return result;
3515 : : }
3516 : :
3517 : :
3518 : : Datum
3519 : 0 : database_to_xmlschema(PG_FUNCTION_ARGS)
3520 : : {
3521 : 0 : bool nulls = PG_GETARG_BOOL(0);
3522 : 0 : bool tableforest = PG_GETARG_BOOL(1);
6799 tgl@sss.pgh.pa.us 3523 : 0 : const char *targetns = text_to_cstring(PG_GETARG_TEXT_PP(2));
3524 : :
6939 3525 : 0 : PG_RETURN_XML_P(stringinfo_to_xmltype(database_to_xmlschema_internal(nulls,
3526 : : tableforest, targetns)));
3527 : : }
3528 : :
3529 : :
3530 : : Datum
7158 peter_e@gmx.net 3531 : 0 : database_to_xml_and_xmlschema(PG_FUNCTION_ARGS)
3532 : : {
3533 : 0 : bool nulls = PG_GETARG_BOOL(0);
3534 : 0 : bool tableforest = PG_GETARG_BOOL(1);
6799 tgl@sss.pgh.pa.us 3535 : 0 : const char *targetns = text_to_cstring(PG_GETARG_TEXT_PP(2));
3536 : : StringInfo xmlschema;
3537 : :
7158 peter_e@gmx.net 3538 : 0 : xmlschema = database_to_xmlschema_internal(nulls, tableforest, targetns);
3539 : :
6939 tgl@sss.pgh.pa.us 3540 : 0 : PG_RETURN_XML_P(stringinfo_to_xmltype(database_to_xml_internal(xmlschema->data,
3541 : : nulls, tableforest, targetns)));
3542 : : }
3543 : :
3544 : :
3545 : : /*
3546 : : * Map a multi-part SQL name to an XML name; see SQL/XML:2008 section
3547 : : * 9.2.
3548 : : */
3549 : : static char *
3292 peter_e@gmx.net 3550 :CBC 256 : map_multipart_sql_identifier_to_xml_name(const char *a, const char *b, const char *c, const char *d)
3551 : : {
3552 : : StringInfoData result;
3553 : :
7202 3554 : 256 : initStringInfo(&result);
3555 : :
3556 [ + - ]: 256 : if (a)
4753 rhaas@postgresql.org 3557 : 256 : appendStringInfoString(&result,
4566 bruce@momjian.us 3558 : 256 : map_sql_identifier_to_xml_name(a, true, true));
7202 peter_e@gmx.net 3559 [ + - ]: 256 : if (b)
6939 tgl@sss.pgh.pa.us 3560 : 256 : appendStringInfo(&result, ".%s",
3561 : : map_sql_identifier_to_xml_name(b, true, true));
7202 peter_e@gmx.net 3562 [ + - ]: 256 : if (c)
6939 tgl@sss.pgh.pa.us 3563 : 256 : appendStringInfo(&result, ".%s",
3564 : : map_sql_identifier_to_xml_name(c, true, true));
7202 peter_e@gmx.net 3565 [ + + ]: 256 : if (d)
6939 tgl@sss.pgh.pa.us 3566 : 244 : appendStringInfo(&result, ".%s",
3567 : : map_sql_identifier_to_xml_name(d, true, true));
3568 : :
7202 peter_e@gmx.net 3569 : 256 : return result.data;
3570 : : }
3571 : :
3572 : :
3573 : : /*
3574 : : * Map an SQL table to an XML Schema document; see SQL/XML:2008
3575 : : * section 9.11.
3576 : : *
3577 : : * Map an SQL table to XML Schema data types; see SQL/XML:2008 section
3578 : : * 9.9.
3579 : : */
3580 : : static const char *
6939 tgl@sss.pgh.pa.us 3581 : 52 : map_sql_table_to_xmlschema(TupleDesc tupdesc, Oid relid, bool nulls,
3582 : : bool tableforest, const char *targetns)
3583 : : {
3584 : : int i;
3585 : : char *xmltn;
3586 : : char *tabletypename;
3587 : : char *rowtypename;
3588 : : StringInfoData result;
3589 : :
7202 peter_e@gmx.net 3590 : 52 : initStringInfo(&result);
3591 : :
6939 tgl@sss.pgh.pa.us 3592 [ + + ]: 52 : if (OidIsValid(relid))
3593 : : {
3594 : : HeapTuple tuple;
3595 : : Form_pg_class reltuple;
3596 : :
6108 rhaas@postgresql.org 3597 : 36 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
6939 tgl@sss.pgh.pa.us 3598 [ - + ]: 36 : if (!HeapTupleIsValid(tuple))
6939 tgl@sss.pgh.pa.us 3599 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for relation %u", relid);
6939 tgl@sss.pgh.pa.us 3600 :CBC 36 : reltuple = (Form_pg_class) GETSTRUCT(tuple);
3601 : :
3602 : 36 : xmltn = map_sql_identifier_to_xml_name(NameStr(reltuple->relname),
3603 : : true, false);
3604 : :
7202 peter_e@gmx.net 3605 : 36 : tabletypename = map_multipart_sql_identifier_to_xml_name("TableType",
3424 tgl@sss.pgh.pa.us 3606 : 36 : get_database_name(MyDatabaseId),
3607 : 36 : get_namespace_name(reltuple->relnamespace),
3608 : 36 : NameStr(reltuple->relname));
3609 : :
7202 peter_e@gmx.net 3610 : 36 : rowtypename = map_multipart_sql_identifier_to_xml_name("RowType",
3424 tgl@sss.pgh.pa.us 3611 : 36 : get_database_name(MyDatabaseId),
3612 : 36 : get_namespace_name(reltuple->relnamespace),
3613 : 36 : NameStr(reltuple->relname));
3614 : :
7202 peter_e@gmx.net 3615 : 36 : ReleaseSysCache(tuple);
3616 : : }
3617 : : else
3618 : : {
3619 [ + + ]: 16 : if (tableforest)
3620 : 8 : xmltn = "row";
3621 : : else
3622 : 8 : xmltn = "table";
3623 : :
3624 : 16 : tabletypename = "TableType";
3625 : 16 : rowtypename = "RowType";
3626 : : }
3627 : :
7158 3628 : 52 : xsd_schema_element_start(&result, targetns);
3629 : :
7202 3630 : 52 : appendStringInfoString(&result,
3631 : : map_sql_typecoll_to_xmlschema_types(list_make1(tupdesc)));
3632 : :
3633 : 52 : appendStringInfo(&result,
3634 : : "<xsd:complexType name=\"%s\">\n"
3635 : : " <xsd:sequence>\n",
3636 : : rowtypename);
3637 : :
3638 [ + + ]: 216 : for (i = 0; i < tupdesc->natts; i++)
3639 : : {
3364 andres@anarazel.de 3640 : 164 : Form_pg_attribute att = TupleDescAttr(tupdesc, i);
3641 : :
3642 [ + + ]: 164 : if (att->attisdropped)
6359 peter_e@gmx.net 3643 : 4 : continue;
7202 3644 [ + + ]: 320 : appendStringInfo(&result,
3645 : : " <xsd:element name=\"%s\" type=\"%s\"%s></xsd:element>\n",
3364 andres@anarazel.de 3646 : 160 : map_sql_identifier_to_xml_name(NameStr(att->attname),
3647 : : true, false),
3648 : : map_sql_type_to_xml_name(att->atttypid, -1),
3649 : : nulls ? " nillable=\"true\"" : " minOccurs=\"0\"");
3650 : : }
3651 : :
7202 peter_e@gmx.net 3652 : 52 : appendStringInfoString(&result,
3653 : : " </xsd:sequence>\n"
3654 : : "</xsd:complexType>\n\n");
3655 : :
3656 [ + + ]: 52 : if (!tableforest)
3657 : : {
3658 : 28 : appendStringInfo(&result,
3659 : : "<xsd:complexType name=\"%s\">\n"
3660 : : " <xsd:sequence>\n"
3661 : : " <xsd:element name=\"row\" type=\"%s\" minOccurs=\"0\" maxOccurs=\"unbounded\"/>\n"
3662 : : " </xsd:sequence>\n"
3663 : : "</xsd:complexType>\n\n",
3664 : : tabletypename, rowtypename);
3665 : :
3666 : 28 : appendStringInfo(&result,
3667 : : "<xsd:element name=\"%s\" type=\"%s\"/>\n\n",
3668 : : xmltn, tabletypename);
3669 : : }
3670 : : else
3671 : 24 : appendStringInfo(&result,
3672 : : "<xsd:element name=\"%s\" type=\"%s\"/>\n\n",
3673 : : xmltn, rowtypename);
3674 : :
7158 3675 : 52 : xsd_schema_element_end(&result);
3676 : :
3677 : 52 : return result.data;
3678 : : }
3679 : :
3680 : :
3681 : : /*
3682 : : * Map an SQL schema to XML Schema data types; see SQL/XML:2008
3683 : : * section 9.12.
3684 : : */
3685 : : static const char *
6939 tgl@sss.pgh.pa.us 3686 : 12 : map_sql_schema_to_xmlschema_types(Oid nspid, List *relid_list, bool nulls,
3687 : : bool tableforest, const char *targetns)
3688 : : {
3689 : : char *dbname;
3690 : : char *nspname;
3691 : : char *xmlsn;
3692 : : char *schematypename;
3693 : : StringInfoData result;
3694 : : ListCell *cell;
3695 : :
3696 : 12 : dbname = get_database_name(MyDatabaseId);
3697 : 12 : nspname = get_namespace_name(nspid);
3698 : :
3699 : : /*
3700 : : * The schema may have been dropped concurrently, as we hold no lock on
3701 : : * it. Report that rather than passing a NULL name below.
3702 : : */
5 heikki.linnakangas@i 3703 [ - + ]: 12 : if (nspname == NULL)
5 heikki.linnakangas@i 3704 [ # # ]:UBC 0 : ereport(ERROR,
3705 : : (errcode(ERRCODE_UNDEFINED_SCHEMA),
3706 : : errmsg("schema with OID %u does not exist", nspid)));
3707 : :
7158 peter_e@gmx.net 3708 :CBC 12 : initStringInfo(&result);
3709 : :
6939 tgl@sss.pgh.pa.us 3710 : 12 : xmlsn = map_sql_identifier_to_xml_name(nspname, true, false);
3711 : :
7158 peter_e@gmx.net 3712 : 12 : schematypename = map_multipart_sql_identifier_to_xml_name("SchemaType",
3713 : : dbname,
3714 : : nspname,
3715 : : NULL);
3716 : :
3717 : 12 : appendStringInfo(&result,
3718 : : "<xsd:complexType name=\"%s\">\n", schematypename);
3719 [ + + ]: 12 : if (!tableforest)
3720 : 4 : appendStringInfoString(&result,
3721 : : " <xsd:all>\n");
3722 : : else
3723 : 8 : appendStringInfoString(&result,
3724 : : " <xsd:sequence>\n");
3725 : :
6930 bruce@momjian.us 3726 [ + - + + : 36 : foreach(cell, relid_list)
+ + ]
3727 : : {
3728 : 24 : Oid relid = lfirst_oid(cell);
3729 : 24 : char *relname = get_rel_name(relid);
3730 : 24 : char *xmltn = map_sql_identifier_to_xml_name(relname, true, false);
3731 [ + + ]: 24 : char *tabletypename = map_multipart_sql_identifier_to_xml_name(tableforest ? "RowType" : "TableType",
3732 : : dbname,
3733 : : nspname,
3734 : : relname);
3735 : :
7158 peter_e@gmx.net 3736 [ + + ]: 24 : if (!tableforest)
3737 : 8 : appendStringInfo(&result,
3738 : : " <xsd:element name=\"%s\" type=\"%s\"/>\n",
3739 : : xmltn, tabletypename);
3740 : : else
3741 : 16 : appendStringInfo(&result,
3742 : : " <xsd:element name=\"%s\" type=\"%s\" minOccurs=\"0\" maxOccurs=\"unbounded\"/>\n",
3743 : : xmltn, tabletypename);
3744 : : }
3745 : :
3746 [ + + ]: 12 : if (!tableforest)
3747 : 4 : appendStringInfoString(&result,
3748 : : " </xsd:all>\n");
3749 : : else
3750 : 8 : appendStringInfoString(&result,
3751 : : " </xsd:sequence>\n");
7202 3752 : 12 : appendStringInfoString(&result,
3753 : : "</xsd:complexType>\n\n");
3754 : :
7158 3755 : 12 : appendStringInfo(&result,
3756 : : "<xsd:element name=\"%s\" type=\"%s\"/>\n\n",
3757 : : xmlsn, schematypename);
3758 : :
3759 : 12 : return result.data;
3760 : : }
3761 : :
3762 : :
3763 : : /*
3764 : : * Map an SQL catalog to XML Schema data types; see SQL/XML:2008
3765 : : * section 9.15.
3766 : : */
3767 : : static const char *
6939 tgl@sss.pgh.pa.us 3768 :UBC 0 : map_sql_catalog_to_xmlschema_types(List *nspid_list, bool nulls,
3769 : : bool tableforest, const char *targetns)
3770 : : {
3771 : : char *dbname;
3772 : : char *xmlcn;
3773 : : char *catalogtypename;
3774 : : StringInfoData result;
3775 : : ListCell *cell;
3776 : :
3777 : 0 : dbname = get_database_name(MyDatabaseId);
3778 : :
7158 peter_e@gmx.net 3779 : 0 : initStringInfo(&result);
3780 : :
6939 tgl@sss.pgh.pa.us 3781 : 0 : xmlcn = map_sql_identifier_to_xml_name(dbname, true, false);
3782 : :
7158 peter_e@gmx.net 3783 : 0 : catalogtypename = map_multipart_sql_identifier_to_xml_name("CatalogType",
3784 : : dbname,
3785 : : NULL,
3786 : : NULL);
3787 : :
3788 : 0 : appendStringInfo(&result,
3789 : : "<xsd:complexType name=\"%s\">\n", catalogtypename);
3790 : 0 : appendStringInfoString(&result,
3791 : : " <xsd:all>\n");
3792 : :
6930 bruce@momjian.us 3793 [ # # # # : 0 : foreach(cell, nspid_list)
# # ]
3794 : : {
3795 : 0 : Oid nspid = lfirst_oid(cell);
6939 tgl@sss.pgh.pa.us 3796 : 0 : char *nspname = get_namespace_name(nspid);
3797 : : char *xmlsn;
3798 : : char *schematypename;
3799 : :
3800 : : /* Skip a schema that was concurrently dropped; it was not scanned. */
5 heikki.linnakangas@i 3801 [ # # ]: 0 : if (nspname == NULL)
3802 : 0 : continue;
3803 : :
3804 : 0 : xmlsn = map_sql_identifier_to_xml_name(nspname, true, false);
3805 : 0 : schematypename = map_multipart_sql_identifier_to_xml_name("SchemaType",
3806 : : dbname,
3807 : : nspname,
3808 : : NULL);
3809 : :
7158 peter_e@gmx.net 3810 : 0 : appendStringInfo(&result,
3811 : : " <xsd:element name=\"%s\" type=\"%s\"/>\n",
3812 : : xmlsn, schematypename);
3813 : : }
3814 : :
3815 : 0 : appendStringInfoString(&result,
3816 : : " </xsd:all>\n");
3817 : 0 : appendStringInfoString(&result,
3818 : : "</xsd:complexType>\n\n");
3819 : :
3820 : 0 : appendStringInfo(&result,
3821 : : "<xsd:element name=\"%s\" type=\"%s\"/>\n\n",
3822 : : xmlcn, catalogtypename);
3823 : :
7202 3824 : 0 : return result.data;
3825 : : }
3826 : :
3827 : :
3828 : : /*
3829 : : * Map an SQL data type to an XML name; see SQL/XML:2008 section 9.4.
3830 : : */
3831 : : static const char *
7202 peter_e@gmx.net 3832 :CBC 540 : map_sql_type_to_xml_name(Oid typeoid, int typmod)
3833 : : {
3834 : : StringInfoData result;
3835 : :
3836 : 540 : initStringInfo(&result);
3837 : :
6930 bruce@momjian.us 3838 [ + + + + : 540 : switch (typeoid)
+ + + - +
+ + + + +
+ + ]
3839 : : {
7202 peter_e@gmx.net 3840 : 20 : case BPCHAROID:
3841 [ + - ]: 20 : if (typmod == -1)
4753 rhaas@postgresql.org 3842 : 20 : appendStringInfoString(&result, "CHAR");
3843 : : else
7202 peter_e@gmx.net 3844 :UBC 0 : appendStringInfo(&result, "CHAR_%d", typmod - VARHDRSZ);
7202 peter_e@gmx.net 3845 :CBC 20 : break;
3846 : 36 : case VARCHAROID:
3847 [ + - ]: 36 : if (typmod == -1)
4753 rhaas@postgresql.org 3848 : 36 : appendStringInfoString(&result, "VARCHAR");
3849 : : else
7202 peter_e@gmx.net 3850 :UBC 0 : appendStringInfo(&result, "VARCHAR_%d", typmod - VARHDRSZ);
7202 peter_e@gmx.net 3851 :CBC 36 : break;
3852 : 20 : case NUMERICOID:
3853 [ + - ]: 20 : if (typmod == -1)
4753 rhaas@postgresql.org 3854 : 20 : appendStringInfoString(&result, "NUMERIC");
3855 : : else
7202 peter_e@gmx.net 3856 :UBC 0 : appendStringInfo(&result, "NUMERIC_%d_%d",
3857 : 0 : ((typmod - VARHDRSZ) >> 16) & 0xffff,
3858 : 0 : (typmod - VARHDRSZ) & 0xffff);
7202 peter_e@gmx.net 3859 :CBC 20 : break;
3860 : 116 : case INT4OID:
4753 rhaas@postgresql.org 3861 : 116 : appendStringInfoString(&result, "INTEGER");
7202 peter_e@gmx.net 3862 : 116 : break;
3863 : 20 : case INT2OID:
4753 rhaas@postgresql.org 3864 : 20 : appendStringInfoString(&result, "SMALLINT");
7202 peter_e@gmx.net 3865 : 20 : break;
3866 : 20 : case INT8OID:
4753 rhaas@postgresql.org 3867 : 20 : appendStringInfoString(&result, "BIGINT");
7202 peter_e@gmx.net 3868 : 20 : break;
3869 : 20 : case FLOAT4OID:
4753 rhaas@postgresql.org 3870 : 20 : appendStringInfoString(&result, "REAL");
7202 peter_e@gmx.net 3871 : 20 : break;
7202 peter_e@gmx.net 3872 :UBC 0 : case FLOAT8OID:
4753 rhaas@postgresql.org 3873 : 0 : appendStringInfoString(&result, "DOUBLE");
7202 peter_e@gmx.net 3874 : 0 : break;
7202 peter_e@gmx.net 3875 :CBC 20 : case BOOLOID:
4753 rhaas@postgresql.org 3876 : 20 : appendStringInfoString(&result, "BOOLEAN");
7202 peter_e@gmx.net 3877 : 20 : break;
3878 : 20 : case TIMEOID:
3879 [ + - ]: 20 : if (typmod == -1)
4753 rhaas@postgresql.org 3880 : 20 : appendStringInfoString(&result, "TIME");
3881 : : else
7202 peter_e@gmx.net 3882 :UBC 0 : appendStringInfo(&result, "TIME_%d", typmod);
7202 peter_e@gmx.net 3883 :CBC 20 : break;
3884 : 20 : case TIMETZOID:
3885 [ + - ]: 20 : if (typmod == -1)
4753 rhaas@postgresql.org 3886 : 20 : appendStringInfoString(&result, "TIME_WTZ");
3887 : : else
7202 peter_e@gmx.net 3888 :UBC 0 : appendStringInfo(&result, "TIME_WTZ_%d", typmod);
7202 peter_e@gmx.net 3889 :CBC 20 : break;
3890 : 20 : case TIMESTAMPOID:
3891 [ + - ]: 20 : if (typmod == -1)
4753 rhaas@postgresql.org 3892 : 20 : appendStringInfoString(&result, "TIMESTAMP");
3893 : : else
7202 peter_e@gmx.net 3894 :UBC 0 : appendStringInfo(&result, "TIMESTAMP_%d", typmod);
7202 peter_e@gmx.net 3895 :CBC 20 : break;
3896 : 20 : case TIMESTAMPTZOID:
3897 [ + - ]: 20 : if (typmod == -1)
4753 rhaas@postgresql.org 3898 : 20 : appendStringInfoString(&result, "TIMESTAMP_WTZ");
3899 : : else
7202 peter_e@gmx.net 3900 :UBC 0 : appendStringInfo(&result, "TIMESTAMP_WTZ_%d", typmod);
7202 peter_e@gmx.net 3901 :CBC 20 : break;
3902 : 20 : case DATEOID:
4753 rhaas@postgresql.org 3903 : 20 : appendStringInfoString(&result, "DATE");
7202 peter_e@gmx.net 3904 : 20 : break;
3905 : 20 : case XMLOID:
4753 rhaas@postgresql.org 3906 : 20 : appendStringInfoString(&result, "XML");
7202 peter_e@gmx.net 3907 : 20 : break;
3908 : 148 : default:
3909 : : {
3910 : : HeapTuple tuple;
3911 : : Form_pg_type typtuple;
3912 : :
6108 rhaas@postgresql.org 3913 : 148 : tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typeoid));
6930 bruce@momjian.us 3914 [ - + ]: 148 : if (!HeapTupleIsValid(tuple))
6930 bruce@momjian.us 3915 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for type %u", typeoid);
6930 bruce@momjian.us 3916 :CBC 148 : typtuple = (Form_pg_type) GETSTRUCT(tuple);
3917 : :
3918 : 148 : appendStringInfoString(&result,
3919 [ + + ]: 148 : map_multipart_sql_identifier_to_xml_name((typtuple->typtype == TYPTYPE_DOMAIN) ? "Domain" : "UDT",
3424 tgl@sss.pgh.pa.us 3920 : 148 : get_database_name(MyDatabaseId),
3921 : 148 : get_namespace_name(typtuple->typnamespace),
3922 : 148 : NameStr(typtuple->typname)));
3923 : :
6930 bruce@momjian.us 3924 : 148 : ReleaseSysCache(tuple);
3925 : : }
3926 : : }
3927 : :
7202 peter_e@gmx.net 3928 : 540 : return result.data;
3929 : : }
3930 : :
3931 : :
3932 : : /*
3933 : : * Map a collection of SQL data types to XML Schema data types; see
3934 : : * SQL/XML:2008 section 9.7.
3935 : : */
3936 : : static const char *
7158 3937 : 64 : map_sql_typecoll_to_xmlschema_types(List *tupdesc_list)
3938 : : {
3939 : 64 : List *uniquetypes = NIL;
3940 : : int i;
3941 : : StringInfoData result;
3942 : : ListCell *cell0;
3943 : :
3944 : : /* extract all column types used in the set of TupleDescs */
7055 tgl@sss.pgh.pa.us 3945 [ + - + + : 140 : foreach(cell0, tupdesc_list)
+ + ]
3946 : : {
6930 bruce@momjian.us 3947 : 76 : TupleDesc tupdesc = (TupleDesc) lfirst(cell0);
3948 : :
7055 tgl@sss.pgh.pa.us 3949 [ + + ]: 468 : for (i = 0; i < tupdesc->natts; i++)
3950 : : {
3364 andres@anarazel.de 3951 : 392 : Form_pg_attribute att = TupleDescAttr(tupdesc, i);
3952 : :
3953 [ + + ]: 392 : if (att->attisdropped)
7158 peter_e@gmx.net 3954 : 16 : continue;
3364 andres@anarazel.de 3955 : 376 : uniquetypes = list_append_unique_oid(uniquetypes, att->atttypid);
3956 : : }
3957 : : }
3958 : :
3959 : : /* add base types of domains */
7055 tgl@sss.pgh.pa.us 3960 [ + - + + : 428 : foreach(cell0, uniquetypes)
+ + ]
3961 : : {
6930 bruce@momjian.us 3962 : 364 : Oid typid = lfirst_oid(cell0);
3963 : 364 : Oid basetypid = getBaseType(typid);
3964 : :
7055 tgl@sss.pgh.pa.us 3965 [ + + ]: 364 : if (basetypid != typid)
3966 : 16 : uniquetypes = list_append_unique_oid(uniquetypes, basetypid);
3967 : : }
3968 : :
3969 : : /* Convert to textual form */
7158 peter_e@gmx.net 3970 : 64 : initStringInfo(&result);
3971 : :
7055 tgl@sss.pgh.pa.us 3972 [ + - + + : 428 : foreach(cell0, uniquetypes)
+ + ]
3973 : : {
3974 : 364 : appendStringInfo(&result, "%s\n",
3975 : : map_sql_type_to_xmlschema_type(lfirst_oid(cell0),
3976 : : -1));
3977 : : }
3978 : :
7202 peter_e@gmx.net 3979 : 64 : return result.data;
3980 : : }
3981 : :
3982 : :
3983 : : /*
3984 : : * Map an SQL data type to a named XML Schema data type; see
3985 : : * SQL/XML:2008 sections 9.5 and 9.6.
3986 : : *
3987 : : * (The distinction between 9.5 and 9.6 is basically that 9.6 adds
3988 : : * a name attribute, which this function does. The name-less version
3989 : : * 9.5 doesn't appear to be required anywhere.)
3990 : : */
3991 : : static const char *
3992 : 364 : map_sql_type_to_xmlschema_type(Oid typeoid, int typmod)
3993 : : {
3994 : : StringInfoData result;
3995 : 364 : const char *typename = map_sql_type_to_xml_name(typeoid, typmod);
3996 : :
3997 : 364 : initStringInfo(&result);
3998 : :
3999 [ + + ]: 364 : if (typeoid == XMLOID)
4000 : : {
4753 rhaas@postgresql.org 4001 : 16 : appendStringInfoString(&result,
4002 : : "<xsd:complexType mixed=\"true\">\n"
4003 : : " <xsd:sequence>\n"
4004 : : " <xsd:any name=\"element\" minOccurs=\"0\" maxOccurs=\"unbounded\" processContents=\"skip\"/>\n"
4005 : : " </xsd:sequence>\n"
4006 : : "</xsd:complexType>\n");
4007 : : }
4008 : : else
4009 : : {
7202 peter_e@gmx.net 4010 : 348 : appendStringInfo(&result,
4011 : : "<xsd:simpleType name=\"%s\">\n", typename);
4012 : :
6930 bruce@momjian.us 4013 [ + + + + : 348 : switch (typeoid)
+ + + - +
+ + + + ]
4014 : : {
7202 peter_e@gmx.net 4015 : 92 : case BPCHAROID:
4016 : : case VARCHAROID:
4017 : : case TEXTOID:
3369 4018 : 92 : appendStringInfoString(&result,
4019 : : " <xsd:restriction base=\"xsd:string\">\n");
7202 4020 [ - + ]: 92 : if (typmod != -1)
7202 peter_e@gmx.net 4021 :UBC 0 : appendStringInfo(&result,
4022 : : " <xsd:maxLength value=\"%d\"/>\n",
4023 : : typmod - VARHDRSZ);
4753 rhaas@postgresql.org 4024 :CBC 92 : appendStringInfoString(&result, " </xsd:restriction>\n");
7202 peter_e@gmx.net 4025 : 92 : break;
4026 : :
4027 : 16 : case BYTEAOID:
4028 : 16 : appendStringInfo(&result,
4029 : : " <xsd:restriction base=\"xsd:%s\">\n"
4030 : : " </xsd:restriction>\n",
3424 tgl@sss.pgh.pa.us 4031 [ + - ]: 16 : xmlbinary == XMLBINARY_BASE64 ? "base64Binary" : "hexBinary");
6917 peter_e@gmx.net 4032 : 16 : break;
4033 : :
7202 4034 : 16 : case NUMERICOID:
4035 [ - + ]: 16 : if (typmod != -1)
7202 peter_e@gmx.net 4036 :UBC 0 : appendStringInfo(&result,
4037 : : " <xsd:restriction base=\"xsd:decimal\">\n"
4038 : : " <xsd:totalDigits value=\"%d\"/>\n"
4039 : : " <xsd:fractionDigits value=\"%d\"/>\n"
4040 : : " </xsd:restriction>\n",
4041 : 0 : ((typmod - VARHDRSZ) >> 16) & 0xffff,
4042 : 0 : (typmod - VARHDRSZ) & 0xffff);
7202 peter_e@gmx.net 4043 :CBC 16 : break;
4044 : :
4045 : 16 : case INT2OID:
4046 : 16 : appendStringInfo(&result,
4047 : : " <xsd:restriction base=\"xsd:short\">\n"
4048 : : " <xsd:maxInclusive value=\"%d\"/>\n"
4049 : : " <xsd:minInclusive value=\"%d\"/>\n"
4050 : : " </xsd:restriction>\n",
4051 : : SHRT_MAX, SHRT_MIN);
4052 : 16 : break;
4053 : :
4054 : 64 : case INT4OID:
4055 : 64 : appendStringInfo(&result,
4056 : : " <xsd:restriction base=\"xsd:int\">\n"
4057 : : " <xsd:maxInclusive value=\"%d\"/>\n"
4058 : : " <xsd:minInclusive value=\"%d\"/>\n"
4059 : : " </xsd:restriction>\n",
4060 : : INT_MAX, INT_MIN);
4061 : 64 : break;
4062 : :
4063 : 16 : case INT8OID:
4064 : 16 : appendStringInfo(&result,
4065 : : " <xsd:restriction base=\"xsd:long\">\n"
4066 : : " <xsd:maxInclusive value=\"" INT64_FORMAT "\"/>\n"
4067 : : " <xsd:minInclusive value=\"" INT64_FORMAT "\"/>\n"
4068 : : " </xsd:restriction>\n",
4069 : : PG_INT64_MAX,
4070 : : PG_INT64_MIN);
4071 : 16 : break;
4072 : :
4073 : 16 : case FLOAT4OID:
4753 rhaas@postgresql.org 4074 : 16 : appendStringInfoString(&result,
4075 : : " <xsd:restriction base=\"xsd:float\"></xsd:restriction>\n");
7202 peter_e@gmx.net 4076 : 16 : break;
4077 : :
7202 peter_e@gmx.net 4078 :UBC 0 : case FLOAT8OID:
4753 rhaas@postgresql.org 4079 : 0 : appendStringInfoString(&result,
4080 : : " <xsd:restriction base=\"xsd:double\"></xsd:restriction>\n");
7202 peter_e@gmx.net 4081 : 0 : break;
4082 : :
7202 peter_e@gmx.net 4083 :CBC 16 : case BOOLOID:
4753 rhaas@postgresql.org 4084 : 16 : appendStringInfoString(&result,
4085 : : " <xsd:restriction base=\"xsd:boolean\"></xsd:restriction>\n");
7202 peter_e@gmx.net 4086 : 16 : break;
4087 : :
4088 : 32 : case TIMEOID:
4089 : : case TIMETZOID:
4090 : : {
1693 tgl@sss.pgh.pa.us 4091 [ + + ]: 32 : const char *tz = (typeoid == TIMETZOID ? "(\\+|-)\\p{Nd}{2}:\\p{Nd}{2}" : "");
4092 : :
6930 bruce@momjian.us 4093 [ + - ]: 32 : if (typmod == -1)
4094 : 32 : appendStringInfo(&result,
4095 : : " <xsd:restriction base=\"xsd:time\">\n"
4096 : : " <xsd:pattern value=\"\\p{Nd}{2}:\\p{Nd}{2}:\\p{Nd}{2}(.\\p{Nd}+)?%s\"/>\n"
4097 : : " </xsd:restriction>\n", tz);
6930 bruce@momjian.us 4098 [ # # ]:UBC 0 : else if (typmod == 0)
4099 : 0 : appendStringInfo(&result,
4100 : : " <xsd:restriction base=\"xsd:time\">\n"
4101 : : " <xsd:pattern value=\"\\p{Nd}{2}:\\p{Nd}{2}:\\p{Nd}{2}%s\"/>\n"
4102 : : " </xsd:restriction>\n", tz);
4103 : : else
4104 : 0 : appendStringInfo(&result,
4105 : : " <xsd:restriction base=\"xsd:time\">\n"
4106 : : " <xsd:pattern value=\"\\p{Nd}{2}:\\p{Nd}{2}:\\p{Nd}{2}.\\p{Nd}{%d}%s\"/>\n"
4107 : : " </xsd:restriction>\n", typmod - VARHDRSZ, tz);
6930 bruce@momjian.us 4108 :CBC 32 : break;
4109 : : }
4110 : :
7202 peter_e@gmx.net 4111 : 32 : case TIMESTAMPOID:
4112 : : case TIMESTAMPTZOID:
4113 : : {
1693 tgl@sss.pgh.pa.us 4114 [ + + ]: 32 : const char *tz = (typeoid == TIMESTAMPTZOID ? "(\\+|-)\\p{Nd}{2}:\\p{Nd}{2}" : "");
4115 : :
6930 bruce@momjian.us 4116 [ + - ]: 32 : if (typmod == -1)
4117 : 32 : appendStringInfo(&result,
4118 : : " <xsd:restriction base=\"xsd:dateTime\">\n"
4119 : : " <xsd:pattern value=\"\\p{Nd}{4}-\\p{Nd}{2}-\\p{Nd}{2}T\\p{Nd}{2}:\\p{Nd}{2}:\\p{Nd}{2}(.\\p{Nd}+)?%s\"/>\n"
4120 : : " </xsd:restriction>\n", tz);
6930 bruce@momjian.us 4121 [ # # ]:UBC 0 : else if (typmod == 0)
4122 : 0 : appendStringInfo(&result,
4123 : : " <xsd:restriction base=\"xsd:dateTime\">\n"
4124 : : " <xsd:pattern value=\"\\p{Nd}{4}-\\p{Nd}{2}-\\p{Nd}{2}T\\p{Nd}{2}:\\p{Nd}{2}:\\p{Nd}{2}%s\"/>\n"
4125 : : " </xsd:restriction>\n", tz);
4126 : : else
4127 : 0 : appendStringInfo(&result,
4128 : : " <xsd:restriction base=\"xsd:dateTime\">\n"
4129 : : " <xsd:pattern value=\"\\p{Nd}{4}-\\p{Nd}{2}-\\p{Nd}{2}T\\p{Nd}{2}:\\p{Nd}{2}:\\p{Nd}{2}.\\p{Nd}{%d}%s\"/>\n"
4130 : : " </xsd:restriction>\n", typmod - VARHDRSZ, tz);
6930 bruce@momjian.us 4131 :CBC 32 : break;
4132 : : }
4133 : :
7202 peter_e@gmx.net 4134 : 16 : case DATEOID:
4753 rhaas@postgresql.org 4135 : 16 : appendStringInfoString(&result,
4136 : : " <xsd:restriction base=\"xsd:date\">\n"
4137 : : " <xsd:pattern value=\"\\p{Nd}{4}-\\p{Nd}{2}-\\p{Nd}{2}\"/>\n"
4138 : : " </xsd:restriction>\n");
6939 tgl@sss.pgh.pa.us 4139 : 16 : break;
4140 : :
7202 peter_e@gmx.net 4141 : 16 : default:
7157 tgl@sss.pgh.pa.us 4142 [ + - ]: 16 : if (get_typtype(typeoid) == TYPTYPE_DOMAIN)
4143 : : {
4144 : : Oid base_typeoid;
6930 bruce@momjian.us 4145 : 16 : int32 base_typmod = -1;
4146 : :
7202 peter_e@gmx.net 4147 : 16 : base_typeoid = getBaseTypeAndTypmod(typeoid, &base_typmod);
4148 : :
4149 : 16 : appendStringInfo(&result,
4150 : : " <xsd:restriction base=\"%s\"/>\n",
4151 : : map_sql_type_to_xml_name(base_typeoid, base_typmod));
4152 : : }
6939 tgl@sss.pgh.pa.us 4153 : 16 : break;
4154 : : }
4753 rhaas@postgresql.org 4155 : 348 : appendStringInfoString(&result, "</xsd:simpleType>\n");
4156 : : }
4157 : :
7202 peter_e@gmx.net 4158 : 364 : return result.data;
4159 : : }
4160 : :
4161 : :
4162 : : /*
4163 : : * Map an SQL row to an XML element, taking the row from the active
4164 : : * SPI cursor. See also SQL/XML:2008 section 9.10.
4165 : : */
4166 : : static void
3890 tgl@sss.pgh.pa.us 4167 : 206 : SPI_sql_row_to_xmlelement(uint64 rownum, StringInfo result, char *tablename,
4168 : : bool nulls, bool tableforest,
4169 : : const char *targetns, bool top_level)
4170 : : {
4171 : : int i;
4172 : : char *xmltn;
4173 : :
7202 peter_e@gmx.net 4174 [ + + ]: 206 : if (tablename)
4175 : 152 : xmltn = map_sql_identifier_to_xml_name(tablename, true, false);
4176 : : else
4177 : : {
4178 [ + + ]: 54 : if (tableforest)
4179 : 24 : xmltn = "row";
4180 : : else
4181 : 30 : xmltn = "table";
4182 : : }
4183 : :
4184 [ + + ]: 206 : if (tableforest)
7158 4185 : 108 : xmldata_root_element_start(result, xmltn, NULL, targetns, top_level);
4186 : : else
7202 4187 : 98 : appendStringInfoString(result, "<row>\n");
4188 : :
6930 bruce@momjian.us 4189 [ + + ]: 844 : for (i = 1; i <= SPI_tuptable->tupdesc->natts; i++)
4190 : : {
4191 : : char *colname;
4192 : : Datum colval;
4193 : : bool isnull;
4194 : :
6939 tgl@sss.pgh.pa.us 4195 : 638 : colname = map_sql_identifier_to_xml_name(SPI_fname(SPI_tuptable->tupdesc, i),
4196 : : true, false);
4197 : 638 : colval = SPI_getbinval(SPI_tuptable->vals[rownum],
4198 : 638 : SPI_tuptable->tupdesc,
4199 : : i,
4200 : : &isnull);
7202 peter_e@gmx.net 4201 [ + + ]: 638 : if (isnull)
4202 : : {
4203 [ + + ]: 76 : if (nulls)
6920 4204 : 40 : appendStringInfo(result, " <%s xsi:nil=\"true\"/>\n", colname);
4205 : : }
4206 : : else
7202 4207 : 562 : appendStringInfo(result, " <%s>%s</%s>\n",
4208 : : colname,
4209 : : map_sql_value_to_xml_value(colval,
3424 tgl@sss.pgh.pa.us 4210 : 562 : SPI_gettypeid(SPI_tuptable->tupdesc, i), true),
4211 : : colname);
4212 : : }
4213 : :
7202 peter_e@gmx.net 4214 [ + + ]: 206 : if (tableforest)
4215 : : {
7158 4216 : 108 : xmldata_root_element_end(result, xmltn);
4217 : 108 : appendStringInfoChar(result, '\n');
4218 : : }
4219 : : else
7202 4220 : 98 : appendStringInfoString(result, "</row>\n\n");
4221 : 206 : }
4222 : :
4223 : :
4224 : : /*
4225 : : * XPath related functions
4226 : : */
4227 : :
4228 : : #ifdef USE_LIBXML
4229 : :
4230 : : /*
4231 : : * Convert XML node to text.
4232 : : *
4233 : : * For attribute and text nodes, return the escaped text. For anything else,
4234 : : * dump the whole subtree.
4235 : : */
4236 : : static text *
4321 4237 : 149 : xml_xmlnodetoxmltype(xmlNodePtr cur, PgXmlErrorContext *xmlerrcxt)
4238 : : {
2558 peter@eisentraut.org 4239 : 149 : xmltype *result = NULL;
4240 : :
146 michael@paquier.xyz 4241 [ + + ]: 149 : if (cur->type != XML_ATTRIBUTE_NODE &&
4242 [ + + ]: 134 : cur->type != XML_TEXT_NODE &&
4243 [ + + ]: 123 : cur->type != XML_NAMESPACE_DECL)
7168 bruce@momjian.us 4244 : 118 : {
2775 tgl@sss.pgh.pa.us 4245 : 118 : void (*volatile nodefree) (xmlNodePtr) = NULL;
2799 alvherre@alvh.no-ip. 4246 : 118 : volatile xmlBufferPtr buf = NULL;
4247 : 118 : volatile xmlNodePtr cur_copy = NULL;
4248 : :
4249 [ + - ]: 118 : PG_TRY();
4250 : : {
4251 : : int bytes;
4252 : :
4253 : 118 : buf = xmlBufferCreate();
4254 [ + - - + ]: 118 : if (buf == NULL || xmlerrcxt->err_occurred)
2799 alvherre@alvh.no-ip. 4255 :UBC 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
4256 : : "could not allocate xmlBuffer");
4257 : :
4258 : : /*
4259 : : * Produce a dump of the node that we can serialize. xmlNodeDump
4260 : : * does that, but the result of that function won't contain
4261 : : * namespace definitions from ancestor nodes, so we first do a
4262 : : * xmlCopyNode() which duplicates the node along with its required
4263 : : * namespace definitions.
4264 : : *
4265 : : * Some old libxml2 versions such as 2.7.6 produce partially
4266 : : * broken XML_DOCUMENT_NODE nodes (unset content field) when
4267 : : * copying them. xmlNodeDump of such a node works fine, but
4268 : : * xmlFreeNode crashes; set us up to call xmlFreeDoc instead.
4269 : : */
2799 alvherre@alvh.no-ip. 4270 :CBC 118 : cur_copy = xmlCopyNode(cur, 1);
4271 [ + - - + ]: 118 : if (cur_copy == NULL || xmlerrcxt->err_occurred)
2799 alvherre@alvh.no-ip. 4272 :UBC 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
4273 : : "could not copy node");
2799 alvherre@alvh.no-ip. 4274 :CBC 236 : nodefree = (cur_copy->type == XML_DOCUMENT_NODE) ?
4275 [ + + ]: 118 : (void (*) (xmlNodePtr)) xmlFreeDoc : xmlFreeNode;
4276 : :
2753 tgl@sss.pgh.pa.us 4277 : 118 : bytes = xmlNodeDump(buf, NULL, cur_copy, 0, 0);
2799 alvherre@alvh.no-ip. 4278 [ + - - + ]: 118 : if (bytes == -1 || xmlerrcxt->err_occurred)
2799 alvherre@alvh.no-ip. 4279 :UBC 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
4280 : : "could not dump node");
4281 : :
6385 tgl@sss.pgh.pa.us 4282 :CBC 118 : result = xmlBuffer_to_xmltype(buf);
4283 : : }
2555 peter@eisentraut.org 4284 :UBC 0 : PG_FINALLY();
4285 : : {
2799 alvherre@alvh.no-ip. 4286 [ + - ]:CBC 118 : if (nodefree)
4287 : 118 : nodefree(cur_copy);
4288 [ + - ]: 118 : if (buf)
4289 : 118 : xmlBufferFree(buf);
4290 : : }
6385 tgl@sss.pgh.pa.us 4291 [ - + ]: 118 : PG_END_TRY();
4292 : : }
4293 : : else
4294 : : {
240 michael@paquier.xyz 4295 : 31 : xmlChar *volatile str = NULL;
4296 : :
6385 tgl@sss.pgh.pa.us 4297 [ + - ]: 31 : PG_TRY();
4298 : : {
4299 : : char *escaped;
4300 : :
492 michael@paquier.xyz 4301 : 31 : str = xmlXPathCastNodeToString(cur);
4302 [ + - - + ]: 31 : if (str == NULL || xmlerrcxt->err_occurred)
492 michael@paquier.xyz 4303 :UBC 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
4304 : : "could not allocate xmlChar");
4305 : :
4306 : : /* Here we rely on XML having the same representation as TEXT */
492 michael@paquier.xyz 4307 :CBC 31 : escaped = escape_xml((char *) str);
4308 : :
5587 tgl@sss.pgh.pa.us 4309 : 31 : result = (xmltype *) cstring_to_text(escaped);
4310 : 31 : pfree(escaped);
4311 : : }
2561 peter@eisentraut.org 4312 :UBC 0 : PG_FINALLY();
4313 : : {
492 michael@paquier.xyz 4314 [ + - ]:CBC 31 : if (str)
240 4315 : 31 : xmlFree(str);
4316 : : }
6385 tgl@sss.pgh.pa.us 4317 [ - + ]: 31 : PG_END_TRY();
4318 : : }
4319 : :
7168 bruce@momjian.us 4320 : 149 : return result;
4321 : : }
4322 : :
4323 : : /*
4324 : : * Convert an XML XPath object (the result of evaluating an XPath expression)
4325 : : * to an array of xml values, which are appended to astate. The function
4326 : : * result value is the number of elements in the array.
4327 : : *
4328 : : * If "astate" is NULL then we don't generate the array value, but we still
4329 : : * return the number of elements it would have had.
4330 : : *
4331 : : * Nodesets are converted to an array containing the nodes' textual
4332 : : * representations. Primitive values (float, double, string) are converted
4333 : : * to a single-element array containing the value's string representation.
4334 : : */
4335 : : static int
5586 tgl@sss.pgh.pa.us 4336 : 390 : xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj,
4337 : : ArrayBuildState *astate,
4338 : : PgXmlErrorContext *xmlerrcxt)
4339 : : {
4340 : 390 : int result = 0;
4341 : : Datum datum;
4342 : : Oid datumtype;
4343 : : char *result_str;
4344 : :
4345 [ + + + + : 390 : switch (xpathobj->type)
- ]
4346 : : {
4347 : 355 : case XPATH_NODESET:
4348 [ + + ]: 355 : if (xpathobj->nodesetval != NULL)
4349 : : {
4350 : 259 : result = xpathobj->nodesetval->nodeNr;
4351 [ + + ]: 259 : if (astate != NULL)
4352 : : {
4353 : : int i;
4354 : :
4355 [ + + ]: 150 : for (i = 0; i < result; i++)
4356 : : {
4321 peter_e@gmx.net 4357 : 81 : datum = PointerGetDatum(xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[i],
4358 : : xmlerrcxt));
4363 tgl@sss.pgh.pa.us 4359 : 81 : (void) accumArrayResult(astate, datum, false,
4360 : : XMLOID, CurrentMemoryContext);
4361 : : }
4362 : : }
4363 : : }
5586 4364 : 355 : return result;
4365 : :
4366 : 10 : case XPATH_BOOLEAN:
4367 [ - + ]: 10 : if (astate == NULL)
5586 tgl@sss.pgh.pa.us 4368 :UBC 0 : return 1;
5586 tgl@sss.pgh.pa.us 4369 :CBC 10 : datum = BoolGetDatum(xpathobj->boolval);
4370 : 10 : datumtype = BOOLOID;
4371 : 10 : break;
4372 : :
4373 : 15 : case XPATH_NUMBER:
4374 [ + + ]: 15 : if (astate == NULL)
4375 : 10 : return 1;
4376 : 5 : datum = Float8GetDatum(xpathobj->floatval);
4377 : 5 : datumtype = FLOAT8OID;
4378 : 5 : break;
4379 : :
4380 : 10 : case XPATH_STRING:
4381 [ - + ]: 10 : if (astate == NULL)
5586 tgl@sss.pgh.pa.us 4382 :UBC 0 : return 1;
5586 tgl@sss.pgh.pa.us 4383 :CBC 10 : datum = CStringGetDatum((char *) xpathobj->stringval);
4384 : 10 : datumtype = CSTRINGOID;
4385 : 10 : break;
4386 : :
5586 tgl@sss.pgh.pa.us 4387 :UBC 0 : default:
4388 [ # # ]: 0 : elog(ERROR, "xpath expression result type %d is unsupported",
4389 : : xpathobj->type);
4390 : : return 0; /* keep compiler quiet */
4391 : : }
4392 : :
4393 : : /* Common code for scalar-value cases */
5586 tgl@sss.pgh.pa.us 4394 :CBC 25 : result_str = map_sql_value_to_xml_value(datum, datumtype, true);
4395 : 25 : datum = PointerGetDatum(cstring_to_xmltype(result_str));
4363 4396 : 25 : (void) accumArrayResult(astate, datum, false,
4397 : : XMLOID, CurrentMemoryContext);
5586 4398 : 25 : return 1;
4399 : : }
4400 : :
4401 : :
4402 : : /*
4403 : : * Common code for xpath() and xmlexists()
4404 : : *
4405 : : * Evaluate XPath expression and return number of nodes in res_nitems
4406 : : * and array of XML values in astate. Either of those pointers can be
4407 : : * NULL if the corresponding result isn't wanted.
4408 : : *
4409 : : * It is up to the user to ensure that the XML passed is in fact
4410 : : * an XML document - XPath doesn't work easily on fragments without
4411 : : * a context node being known.
4412 : : */
4413 : : static void
5936 peter_e@gmx.net 4414 : 402 : xpath_internal(text *xpath_expr_text, xmltype *data, ArrayType *namespaces,
4415 : : int *res_nitems, ArrayBuildState *astate)
4416 : : {
4417 : : PgXmlErrorContext *xmlerrcxt;
5587 tgl@sss.pgh.pa.us 4418 : 402 : volatile xmlParserCtxtPtr ctxt = NULL;
4419 : 402 : volatile xmlDocPtr doc = NULL;
4420 : 402 : volatile xmlXPathContextPtr xpathctx = NULL;
4421 : 402 : volatile xmlXPathCompExprPtr xpathcomp = NULL;
4422 : 402 : volatile xmlXPathObjectPtr xpathobj = NULL;
4423 : : char *datastr;
4424 : : int32 len;
4425 : : int32 xpath_len;
4426 : : xmlChar *string;
4427 : : xmlChar *xpath_expr;
3281 noah@leadboat.com 4428 : 402 : size_t xmldecl_len = 0;
4429 : : int i;
4430 : : int ndim;
4431 : : Datum *ns_names_uris;
4432 : : bool *ns_names_uris_nulls;
4433 : : int ns_count;
4434 : :
4435 : : /*
4436 : : * Namespace mappings are passed as text[]. If an empty array is passed
4437 : : * (ndim = 0, "0-dimensional"), then there are no namespace mappings.
4438 : : * Else, a 2-dimensional array with length of the second axis being equal
4439 : : * to 2 should be passed, i.e., every subarray contains 2 elements, the
4440 : : * first element defining the name, the second one the URI. Example:
4441 : : * ARRAY[ARRAY['myns', 'http://example.com'], ARRAY['myns2',
4442 : : * 'http://example2.com']].
4443 : : */
5936 peter_e@gmx.net 4444 [ + + ]: 402 : ndim = namespaces ? ARR_NDIM(namespaces) : 0;
7108 4445 [ + + ]: 402 : if (ndim != 0)
4446 : : {
4447 : : int *dims;
4448 : :
7168 bruce@momjian.us 4449 : 87 : dims = ARR_DIMS(namespaces);
4450 : :
7108 peter_e@gmx.net 4451 [ + - - + ]: 87 : if (ndim != 2 || dims[1] != 2)
6939 tgl@sss.pgh.pa.us 4452 [ # # ]:UBC 0 : ereport(ERROR,
4453 : : (errcode(ERRCODE_DATA_EXCEPTION),
4454 : : errmsg("invalid array for XML namespace mapping"),
4455 : : errdetail("The array must be two-dimensional with length of the second axis equal to 2.")));
4456 : :
7168 bruce@momjian.us 4457 [ - + ]:CBC 87 : Assert(ARR_ELEMTYPE(namespaces) == TEXTOID);
4458 : :
1588 peter@eisentraut.org 4459 : 87 : deconstruct_array_builtin(namespaces, TEXTOID,
4460 : : &ns_names_uris, &ns_names_uris_nulls,
4461 : : &ns_count);
4462 : :
6930 bruce@momjian.us 4463 [ - + ]: 87 : Assert((ns_count % 2) == 0); /* checked above */
6939 tgl@sss.pgh.pa.us 4464 : 87 : ns_count /= 2; /* count pairs only */
4465 : : }
4466 : : else
4467 : : {
4468 : 315 : ns_names_uris = NULL;
4469 : 315 : ns_names_uris_nulls = NULL;
7108 peter_e@gmx.net 4470 : 315 : ns_count = 0;
4471 : : }
4472 : :
6939 tgl@sss.pgh.pa.us 4473 : 402 : datastr = VARDATA(data);
7168 bruce@momjian.us 4474 : 402 : len = VARSIZE(data) - VARHDRSZ;
3525 noah@leadboat.com 4475 : 402 : xpath_len = VARSIZE_ANY_EXHDR(xpath_expr_text);
7168 bruce@momjian.us 4476 [ + + ]: 402 : if (xpath_len == 0)
6939 tgl@sss.pgh.pa.us 4477 [ + - ]: 4 : ereport(ERROR,
4478 : : (errcode(ERRCODE_INVALID_ARGUMENT_FOR_XQUERY),
4479 : : errmsg("empty XPath expression")));
4480 : :
3529 alvherre@alvh.no-ip. 4481 : 398 : string = pg_xmlCharStrndup(datastr, len);
3525 noah@leadboat.com 4482 : 398 : xpath_expr = pg_xmlCharStrndup(VARDATA_ANY(xpath_expr_text), xpath_len);
4483 : :
4484 : : /*
4485 : : * In a UTF8 database, skip any xml declaration, which might assert
4486 : : * another encoding. Ignore parse_xml_decl() failure, letting
4487 : : * xmlCtxtReadMemory() report parse errors. Documentation disclaims
4488 : : * xpath() support for non-ASCII data in non-UTF8 databases, so leave
4489 : : * those scenarios bug-compatible with historical behavior.
4490 : : */
3281 4491 [ + - ]: 398 : if (GetDatabaseEncoding() == PG_UTF8)
4492 : 398 : parse_xml_decl(string, &xmldecl_len, NULL, NULL, NULL);
4493 : :
5587 tgl@sss.pgh.pa.us 4494 : 398 : xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
4495 : :
6385 4496 [ + + ]: 398 : PG_TRY();
4497 : : {
5587 4498 : 398 : xmlInitParser();
4499 : :
4500 : : /*
4501 : : * redundant XML parsing (two parsings for the same value during one
4502 : : * command execution are possible)
4503 : : */
6356 bruce@momjian.us 4504 : 398 : ctxt = xmlNewParserCtxt();
5587 tgl@sss.pgh.pa.us 4505 [ + - - + ]: 398 : if (ctxt == NULL || xmlerrcxt->err_occurred)
5587 tgl@sss.pgh.pa.us 4506 :UBC 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
4507 : : "could not allocate parser context");
3281 noah@leadboat.com 4508 :CBC 796 : doc = xmlCtxtReadMemory(ctxt, (char *) string + xmldecl_len,
1014 michael@paquier.xyz 4509 : 398 : len - xmldecl_len, NULL, NULL, 0);
5587 tgl@sss.pgh.pa.us 4510 [ + - + + ]: 398 : if (doc == NULL || xmlerrcxt->err_occurred)
4511 : 8 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
4512 : : "could not parse XML document");
6356 bruce@momjian.us 4513 : 390 : xpathctx = xmlXPathNewContext(doc);
5587 tgl@sss.pgh.pa.us 4514 [ + - - + ]: 390 : if (xpathctx == NULL || xmlerrcxt->err_occurred)
5587 tgl@sss.pgh.pa.us 4515 :UBC 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
4516 : : "could not allocate XPath context");
3059 alvherre@alvh.no-ip. 4517 :CBC 390 : xpathctx->node = (xmlNodePtr) doc;
4518 : :
4519 : : /* register namespaces, if any */
6356 bruce@momjian.us 4520 [ + + ]: 390 : if (ns_count > 0)
4521 : : {
4522 [ + + ]: 174 : for (i = 0; i < ns_count; i++)
4523 : : {
4524 : : char *ns_name;
4525 : : char *ns_uri;
4526 : :
4527 [ + - ]: 87 : if (ns_names_uris_nulls[i * 2] ||
4528 [ - + ]: 87 : ns_names_uris_nulls[i * 2 + 1])
6356 bruce@momjian.us 4529 [ # # ]:UBC 0 : ereport(ERROR,
4530 : : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
4531 : : errmsg("neither namespace name nor URI may be null")));
6356 bruce@momjian.us 4532 :CBC 87 : ns_name = TextDatumGetCString(ns_names_uris[i * 2]);
4533 : 87 : ns_uri = TextDatumGetCString(ns_names_uris[i * 2 + 1]);
4534 [ - + ]: 87 : if (xmlXPathRegisterNs(xpathctx,
4535 : : (xmlChar *) ns_name,
4536 : : (xmlChar *) ns_uri) != 0)
3424 tgl@sss.pgh.pa.us 4537 [ # # ]:UBC 0 : ereport(ERROR, /* is this an internal error??? */
4538 : : (errmsg("could not register XML namespace with name \"%s\" and URI \"%s\"",
4539 : : ns_name, ns_uri)));
4540 : : }
4541 : : }
4542 : :
4543 : : /*
4544 : : * Note: here and elsewhere, be careful to use xmlXPathCtxtCompile not
4545 : : * xmlXPathCompile. In libxml2 2.13.3 and older, the latter function
4546 : : * fails to defend itself against recursion-to-stack-overflow. See
4547 : : * https://gitlab.gnome.org/GNOME/libxml2/-/issues/799
4548 : : */
781 tgl@sss.pgh.pa.us 4549 :CBC 390 : xpathcomp = xmlXPathCtxtCompile(xpathctx, xpath_expr);
5587 4550 [ + - - + ]: 390 : if (xpathcomp == NULL || xmlerrcxt->err_occurred)
772 tgl@sss.pgh.pa.us 4551 :UBC 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_INVALID_ARGUMENT_FOR_XQUERY,
4552 : : "invalid XPath expression");
4553 : :
4554 : : /*
4555 : : * Version 2.6.27 introduces a function named
4556 : : * xmlXPathCompiledEvalToBoolean, which would be enough for xmlexists,
4557 : : * but we can derive the existence by whether any nodes are returned,
4558 : : * thereby preventing a library version upgrade and keeping the code
4559 : : * the same.
4560 : : */
6356 bruce@momjian.us 4561 :CBC 390 : xpathobj = xmlXPathCompiledEval(xpathcomp, xpathctx);
5587 tgl@sss.pgh.pa.us 4562 [ + - - + ]: 390 : if (xpathobj == NULL || xmlerrcxt->err_occurred)
772 tgl@sss.pgh.pa.us 4563 :UBC 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_INVALID_ARGUMENT_FOR_XQUERY,
4564 : : "could not create XPath object");
4565 : :
4566 : : /*
4567 : : * Extract the results as requested.
4568 : : */
5586 tgl@sss.pgh.pa.us 4569 [ + + ]:CBC 390 : if (res_nitems != NULL)
4321 peter_e@gmx.net 4570 : 296 : *res_nitems = xml_xpathobjtoxmlarray(xpathobj, astate, xmlerrcxt);
4571 : : else
4572 : 94 : (void) xml_xpathobjtoxmlarray(xpathobj, astate, xmlerrcxt);
4573 : : }
6385 tgl@sss.pgh.pa.us 4574 : 8 : PG_CATCH();
4575 : : {
4576 [ - + ]: 8 : if (xpathobj)
6385 tgl@sss.pgh.pa.us 4577 :UBC 0 : xmlXPathFreeObject(xpathobj);
6385 tgl@sss.pgh.pa.us 4578 [ - + ]:CBC 8 : if (xpathcomp)
6385 tgl@sss.pgh.pa.us 4579 :UBC 0 : xmlXPathFreeCompExpr(xpathcomp);
6385 tgl@sss.pgh.pa.us 4580 [ - + ]:CBC 8 : if (xpathctx)
6385 tgl@sss.pgh.pa.us 4581 :UBC 0 : xmlXPathFreeContext(xpathctx);
6385 tgl@sss.pgh.pa.us 4582 [ + - ]:CBC 8 : if (doc)
4583 : 8 : xmlFreeDoc(doc);
4584 [ + - ]: 8 : if (ctxt)
4585 : 8 : xmlFreeParserCtxt(ctxt);
4586 : :
5587 4587 : 8 : pg_xml_done(xmlerrcxt, true);
4588 : :
6385 4589 : 8 : PG_RE_THROW();
4590 : : }
4591 [ - + ]: 390 : PG_END_TRY();
4592 : :
6869 4593 : 390 : xmlXPathFreeObject(xpathobj);
6385 4594 : 390 : xmlXPathFreeCompExpr(xpathcomp);
6869 4595 : 390 : xmlXPathFreeContext(xpathctx);
4596 : 390 : xmlFreeDoc(doc);
4597 : 390 : xmlFreeParserCtxt(ctxt);
4598 : :
5587 4599 : 390 : pg_xml_done(xmlerrcxt, false);
5936 peter_e@gmx.net 4600 : 390 : }
4601 : : #endif /* USE_LIBXML */
4602 : :
4603 : : /*
4604 : : * Evaluate XPath expression and return array of XML values.
4605 : : *
4606 : : * As we have no support of XQuery sequences yet, this function seems
4607 : : * to be the most useful one (array of XML functions plays a role of
4608 : : * some kind of substitution for XQuery sequences).
4609 : : */
4610 : : Datum
4611 : 106 : xpath(PG_FUNCTION_ARGS)
4612 : : {
4613 : : #ifdef USE_LIBXML
3525 noah@leadboat.com 4614 : 106 : text *xpath_expr_text = PG_GETARG_TEXT_PP(0);
5936 peter_e@gmx.net 4615 : 106 : xmltype *data = PG_GETARG_XML_P(1);
4616 : 106 : ArrayType *namespaces = PG_GETARG_ARRAYTYPE_P(2);
4617 : : ArrayBuildState *astate;
4618 : :
4275 jdavis@postgresql.or 4619 : 106 : astate = initArrayResult(XMLOID, CurrentMemoryContext, true);
5936 peter_e@gmx.net 4620 : 106 : xpath_internal(xpath_expr_text, data, namespaces,
4621 : : NULL, astate);
1530 peter@eisentraut.org 4622 : 94 : PG_RETURN_DATUM(makeArrayResult(astate, CurrentMemoryContext));
4623 : : #else
4624 : : NO_XML_SUPPORT();
4625 : : return 0;
4626 : : #endif
4627 : : }
4628 : :
4629 : : /*
4630 : : * Determines if the node specified by the supplied XPath exists
4631 : : * in a given XML document, returning a boolean.
4632 : : */
4633 : : Datum
5688 bruce@momjian.us 4634 : 135 : xmlexists(PG_FUNCTION_ARGS)
4635 : : {
4636 : : #ifdef USE_LIBXML
3525 noah@leadboat.com 4637 : 135 : text *xpath_expr_text = PG_GETARG_TEXT_PP(0);
5936 peter_e@gmx.net 4638 : 135 : xmltype *data = PG_GETARG_XML_P(1);
4639 : : int res_nitems;
4640 : :
4641 : 135 : xpath_internal(xpath_expr_text, data, NULL,
4642 : : &res_nitems, NULL);
4643 : :
4644 : 135 : PG_RETURN_BOOL(res_nitems > 0);
4645 : : #else
4646 : : NO_XML_SUPPORT();
4647 : : return 0;
4648 : : #endif
4649 : : }
4650 : :
4651 : : /*
4652 : : * Determines if the node specified by the supplied XPath exists
4653 : : * in a given XML document, returning a boolean. Differs from
4654 : : * xmlexists as it supports namespaces and is not defined in SQL/XML.
4655 : : */
4656 : : Datum
5933 tgl@sss.pgh.pa.us 4657 : 161 : xpath_exists(PG_FUNCTION_ARGS)
4658 : : {
4659 : : #ifdef USE_LIBXML
3525 noah@leadboat.com 4660 : 161 : text *xpath_expr_text = PG_GETARG_TEXT_PP(0);
5933 tgl@sss.pgh.pa.us 4661 : 161 : xmltype *data = PG_GETARG_XML_P(1);
4662 : 161 : ArrayType *namespaces = PG_GETARG_ARRAYTYPE_P(2);
4663 : : int res_nitems;
4664 : :
4665 : 161 : xpath_internal(xpath_expr_text, data, namespaces,
4666 : : &res_nitems, NULL);
4667 : :
4668 : 161 : PG_RETURN_BOOL(res_nitems > 0);
4669 : : #else
4670 : : NO_XML_SUPPORT();
4671 : : return 0;
4672 : : #endif
4673 : : }
4674 : :
4675 : : /*
4676 : : * Functions for checking well-formed-ness
4677 : : */
4678 : :
4679 : : #ifdef USE_LIBXML
4680 : : static bool
5928 4681 : 80 : wellformed_xml(text *data, XmlOptionType xmloption_arg)
4682 : : {
4683 : : xmlDocPtr doc;
1420 4684 : 80 : ErrorSaveContext escontext = {T_ErrorSaveContext};
4685 : :
4686 : : /*
4687 : : * We'll report "true" if no soft error is reported by xml_parse().
4688 : : */
4689 : 80 : doc = xml_parse(data, xmloption_arg, true,
4690 : : GetDatabaseEncoding(), NULL, (Node *) &escontext);
5928 4691 [ + + ]: 80 : if (doc)
4692 : 43 : xmlFreeDoc(doc);
4693 : :
1420 4694 : 80 : return !escontext.error_occurred;
4695 : : }
4696 : : #endif
4697 : :
4698 : : Datum
5928 4699 : 60 : xml_is_well_formed(PG_FUNCTION_ARGS)
4700 : : {
4701 : : #ifdef USE_LIBXML
3525 noah@leadboat.com 4702 : 60 : text *data = PG_GETARG_TEXT_PP(0);
4703 : :
5928 tgl@sss.pgh.pa.us 4704 : 60 : PG_RETURN_BOOL(wellformed_xml(data, xmloption));
4705 : : #else
4706 : : NO_XML_SUPPORT();
4707 : : return 0;
4708 : : #endif /* not USE_LIBXML */
4709 : : }
4710 : :
4711 : : Datum
4712 : 10 : xml_is_well_formed_document(PG_FUNCTION_ARGS)
4713 : : {
4714 : : #ifdef USE_LIBXML
3525 noah@leadboat.com 4715 : 10 : text *data = PG_GETARG_TEXT_PP(0);
4716 : :
5928 tgl@sss.pgh.pa.us 4717 : 10 : PG_RETURN_BOOL(wellformed_xml(data, XMLOPTION_DOCUMENT));
4718 : : #else
4719 : : NO_XML_SUPPORT();
4720 : : return 0;
4721 : : #endif /* not USE_LIBXML */
4722 : : }
4723 : :
4724 : : Datum
4725 : 10 : xml_is_well_formed_content(PG_FUNCTION_ARGS)
4726 : : {
4727 : : #ifdef USE_LIBXML
3525 noah@leadboat.com 4728 : 10 : text *data = PG_GETARG_TEXT_PP(0);
4729 : :
5928 tgl@sss.pgh.pa.us 4730 : 10 : PG_RETURN_BOOL(wellformed_xml(data, XMLOPTION_CONTENT));
4731 : : #else
4732 : : NO_XML_SUPPORT();
4733 : : return 0;
4734 : : #endif /* not USE_LIBXML */
4735 : : }
4736 : :
4737 : : /*
4738 : : * support functions for XMLTABLE
4739 : : *
4740 : : */
4741 : : #ifdef USE_LIBXML
4742 : :
4743 : : /*
4744 : : * Returns private data from executor state. Ensure validity by check with
4745 : : * MAGIC number.
4746 : : */
4747 : : static inline XmlTableBuilderData *
3529 alvherre@alvh.no-ip. 4748 : 108144 : GetXmlTableBuilderPrivateData(TableFuncScanState *state, const char *fname)
4749 : : {
4750 : : XmlTableBuilderData *result;
4751 : :
4752 [ - + ]: 108144 : if (!IsA(state, TableFuncScanState))
3529 alvherre@alvh.no-ip. 4753 [ # # ]:UBC 0 : elog(ERROR, "%s called with invalid TableFuncScanState", fname);
3529 alvherre@alvh.no-ip. 4754 :CBC 108144 : result = (XmlTableBuilderData *) state->opaque;
4755 [ - + ]: 108144 : if (result->magic != XMLTABLE_CONTEXT_MAGIC)
3529 alvherre@alvh.no-ip. 4756 [ # # ]:UBC 0 : elog(ERROR, "%s called with invalid TableFuncScanState", fname);
4757 : :
3529 alvherre@alvh.no-ip. 4758 :CBC 108144 : return result;
4759 : : }
4760 : : #endif
4761 : :
4762 : : /*
4763 : : * XmlTableInitOpaque
4764 : : * Fill in TableFuncScanState->opaque for XmlTable processor; initialize
4765 : : * the XML parser.
4766 : : *
4767 : : * Note: Because we call pg_xml_init() here and pg_xml_done() in
4768 : : * XmlTableDestroyOpaque, it is critical for robustness that no other
4769 : : * executor nodes run until this node is processed to completion. Caller
4770 : : * must execute this to completion (probably filling a tuplestore to exhaust
4771 : : * this node in a single pass) instead of using row-per-call mode.
4772 : : */
4773 : : static void
4774 : 176 : XmlTableInitOpaque(TableFuncScanState *state, int natts)
4775 : : {
4776 : : #ifdef USE_LIBXML
4777 : 176 : volatile xmlParserCtxtPtr ctxt = NULL;
4778 : : XmlTableBuilderData *xtCxt;
4779 : : PgXmlErrorContext *xmlerrcxt;
4780 : :
330 michael@paquier.xyz 4781 : 176 : xtCxt = palloc0_object(XmlTableBuilderData);
3529 alvherre@alvh.no-ip. 4782 : 176 : xtCxt->magic = XMLTABLE_CONTEXT_MAGIC;
4783 : 176 : xtCxt->natts = natts;
330 michael@paquier.xyz 4784 : 176 : xtCxt->xpathscomp = palloc0_array(xmlXPathCompExprPtr, natts);
4785 : :
3529 alvherre@alvh.no-ip. 4786 : 176 : xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
4787 : :
4788 [ + - ]: 176 : PG_TRY();
4789 : : {
4790 : 176 : xmlInitParser();
4791 : :
4792 : 176 : ctxt = xmlNewParserCtxt();
4793 [ + - - + ]: 176 : if (ctxt == NULL || xmlerrcxt->err_occurred)
3529 alvherre@alvh.no-ip. 4794 :UBC 0 : xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
4795 : : "could not allocate parser context");
4796 : : }
4797 : 0 : PG_CATCH();
4798 : : {
4799 [ # # ]: 0 : if (ctxt != NULL)
4800 : 0 : xmlFreeParserCtxt(ctxt);
4801 : :
4802 : 0 : pg_xml_done(xmlerrcxt, true);
4803 : :
4804 : 0 : PG_RE_THROW();
4805 : : }
3529 alvherre@alvh.no-ip. 4806 [ - + ]:CBC 176 : PG_END_TRY();
4807 : :
4808 : 176 : xtCxt->xmlerrcxt = xmlerrcxt;
4809 : 176 : xtCxt->ctxt = ctxt;
4810 : :
4811 : 176 : state->opaque = xtCxt;
4812 : : #else
4813 : : NO_XML_SUPPORT();
4814 : : #endif /* not USE_LIBXML */
4815 : 176 : }
4816 : :
4817 : : /*
4818 : : * XmlTableSetDocument
4819 : : * Install the input document
4820 : : */
4821 : : static void
4822 : 176 : XmlTableSetDocument(TableFuncScanState *state, Datum value)
4823 : : {
4824 : : #ifdef USE_LIBXML
4825 : : XmlTableBuilderData *xtCxt;
4826 : 176 : xmltype *xmlval = DatumGetXmlP(value);
4827 : : char *str;
4828 : : xmlChar *xstr;
4829 : : int length;
4830 : 176 : volatile xmlDocPtr doc = NULL;
4831 : 176 : volatile xmlXPathContextPtr xpathcxt = NULL;
4832 : :
4833 : 176 : xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableSetDocument");
4834 : :
4835 : : /*
4836 : : * Use out function for casting to string (remove encoding property). See
4837 : : * comment in xml_out.
4838 : : */
4839 : 176 : str = xml_out_internal(xmlval, 0);
4840 : :
4841 : 176 : length = strlen(str);
4842 : 176 : xstr = pg_xmlCharStrndup(str, length);
4843 : :
4844 [ + - ]: 176 : PG_TRY();
4845 : : {
1014 michael@paquier.xyz 4846 : 176 : doc = xmlCtxtReadMemory(xtCxt->ctxt, (char *) xstr, length, NULL, NULL, 0);
3529 alvherre@alvh.no-ip. 4847 [ + - - + ]: 176 : if (doc == NULL || xtCxt->xmlerrcxt->err_occurred)
3529 alvherre@alvh.no-ip. 4848 :UBC 0 : xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INVALID_XML_DOCUMENT,
4849 : : "could not parse XML document");
3529 alvherre@alvh.no-ip. 4850 :CBC 176 : xpathcxt = xmlXPathNewContext(doc);
4851 [ + - - + ]: 176 : if (xpathcxt == NULL || xtCxt->xmlerrcxt->err_occurred)
3529 alvherre@alvh.no-ip. 4852 :UBC 0 : xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
4853 : : "could not allocate XPath context");
3059 alvherre@alvh.no-ip. 4854 :CBC 176 : xpathcxt->node = (xmlNodePtr) doc;
4855 : : }
3529 alvherre@alvh.no-ip. 4856 :UBC 0 : PG_CATCH();
4857 : : {
4858 [ # # ]: 0 : if (xpathcxt != NULL)
4859 : 0 : xmlXPathFreeContext(xpathcxt);
4860 [ # # ]: 0 : if (doc != NULL)
4861 : 0 : xmlFreeDoc(doc);
4862 : :
4863 : 0 : PG_RE_THROW();
4864 : : }
3529 alvherre@alvh.no-ip. 4865 [ - + ]:CBC 176 : PG_END_TRY();
4866 : :
4867 : 176 : xtCxt->doc = doc;
4868 : 176 : xtCxt->xpathcxt = xpathcxt;
4869 : : #else
4870 : : NO_XML_SUPPORT();
4871 : : #endif /* not USE_LIBXML */
4872 : 176 : }
4873 : :
4874 : : /*
4875 : : * XmlTableSetNamespace
4876 : : * Add a namespace declaration
4877 : : */
4878 : : static void
3292 peter_e@gmx.net 4879 : 12 : XmlTableSetNamespace(TableFuncScanState *state, const char *name, const char *uri)
4880 : : {
4881 : : #ifdef USE_LIBXML
4882 : : XmlTableBuilderData *xtCxt;
4883 : :
3529 alvherre@alvh.no-ip. 4884 [ + + ]: 12 : if (name == NULL)
4885 [ + - ]: 4 : ereport(ERROR,
4886 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
4887 : : errmsg("DEFAULT namespace is not supported")));
4888 : 8 : xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableSetNamespace");
4889 : :
4890 [ - + ]: 8 : if (xmlXPathRegisterNs(xtCxt->xpathcxt,
4891 : 8 : pg_xmlCharStrndup(name, strlen(name)),
4892 : 8 : pg_xmlCharStrndup(uri, strlen(uri))))
772 tgl@sss.pgh.pa.us 4893 :UBC 0 : xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INVALID_ARGUMENT_FOR_XQUERY,
4894 : : "could not set XML namespace");
4895 : : #else
4896 : : NO_XML_SUPPORT();
4897 : : #endif /* not USE_LIBXML */
3529 alvherre@alvh.no-ip. 4898 :CBC 8 : }
4899 : :
4900 : : /*
4901 : : * XmlTableSetRowFilter
4902 : : * Install the row-filter Xpath expression.
4903 : : */
4904 : : static void
3292 peter_e@gmx.net 4905 : 172 : XmlTableSetRowFilter(TableFuncScanState *state, const char *path)
4906 : : {
4907 : : #ifdef USE_LIBXML
4908 : : XmlTableBuilderData *xtCxt;
4909 : : xmlChar *xstr;
4910 : :
3529 alvherre@alvh.no-ip. 4911 : 172 : xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableSetRowFilter");
4912 : :
4913 [ - + ]: 172 : if (*path == '\0')
3529 alvherre@alvh.no-ip. 4914 [ # # ]:UBC 0 : ereport(ERROR,
4915 : : (errcode(ERRCODE_INVALID_ARGUMENT_FOR_XQUERY),
4916 : : errmsg("row path filter must not be empty string")));
4917 : :
3529 alvherre@alvh.no-ip. 4918 :CBC 172 : xstr = pg_xmlCharStrndup(path, strlen(path));
4919 : :
4920 : : /* We require XmlTableSetDocument to have been done already */
781 tgl@sss.pgh.pa.us 4921 [ - + ]: 172 : Assert(xtCxt->xpathcxt != NULL);
4922 : :
4923 : 172 : xtCxt->xpathcomp = xmlXPathCtxtCompile(xtCxt->xpathcxt, xstr);
3529 alvherre@alvh.no-ip. 4924 [ + - - + ]: 172 : if (xtCxt->xpathcomp == NULL || xtCxt->xmlerrcxt->err_occurred)
772 tgl@sss.pgh.pa.us 4925 :UBC 0 : xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INVALID_ARGUMENT_FOR_XQUERY,
4926 : : "invalid XPath expression");
4927 : : #else
4928 : : NO_XML_SUPPORT();
4929 : : #endif /* not USE_LIBXML */
3529 alvherre@alvh.no-ip. 4930 :CBC 172 : }
4931 : :
4932 : : /*
4933 : : * XmlTableSetColumnFilter
4934 : : * Install the column-filter Xpath expression, for the given column.
4935 : : */
4936 : : static void
3292 peter_e@gmx.net 4937 : 516 : XmlTableSetColumnFilter(TableFuncScanState *state, const char *path, int colnum)
4938 : : {
4939 : : #ifdef USE_LIBXML
4940 : : XmlTableBuilderData *xtCxt;
4941 : : xmlChar *xstr;
4942 : :
407 peter@eisentraut.org 4943 [ - + ]: 516 : Assert(path);
4944 : :
3529 alvherre@alvh.no-ip. 4945 : 516 : xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableSetColumnFilter");
4946 : :
4947 [ - + ]: 516 : if (*path == '\0')
3529 alvherre@alvh.no-ip. 4948 [ # # ]:UBC 0 : ereport(ERROR,
4949 : : (errcode(ERRCODE_INVALID_ARGUMENT_FOR_XQUERY),
4950 : : errmsg("column path filter must not be empty string")));
4951 : :
3529 alvherre@alvh.no-ip. 4952 :CBC 516 : xstr = pg_xmlCharStrndup(path, strlen(path));
4953 : :
4954 : : /* We require XmlTableSetDocument to have been done already */
781 tgl@sss.pgh.pa.us 4955 [ - + ]: 516 : Assert(xtCxt->xpathcxt != NULL);
4956 : :
4957 : 516 : xtCxt->xpathscomp[colnum] = xmlXPathCtxtCompile(xtCxt->xpathcxt, xstr);
3529 alvherre@alvh.no-ip. 4958 [ + - - + ]: 516 : if (xtCxt->xpathscomp[colnum] == NULL || xtCxt->xmlerrcxt->err_occurred)
772 tgl@sss.pgh.pa.us 4959 :UBC 0 : xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INVALID_ARGUMENT_FOR_XQUERY,
4960 : : "invalid XPath expression");
4961 : : #else
4962 : : NO_XML_SUPPORT();
4963 : : #endif /* not USE_LIBXML */
3529 alvherre@alvh.no-ip. 4964 :CBC 516 : }
4965 : :
4966 : : /*
4967 : : * XmlTableFetchRow
4968 : : * Prepare the next "current" tuple for upcoming GetValue calls.
4969 : : * Returns false if the row-filter expression returned no more rows.
4970 : : */
4971 : : static bool
4972 : 15540 : XmlTableFetchRow(TableFuncScanState *state)
4973 : : {
4974 : : #ifdef USE_LIBXML
4975 : : XmlTableBuilderData *xtCxt;
4976 : :
4977 : 15540 : xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableFetchRow");
4978 : :
4979 : : /* Propagate our own error context to libxml2 */
707 peter@eisentraut.org 4980 : 15540 : xmlSetStructuredErrorFunc(xtCxt->xmlerrcxt, xml_errorHandler);
4981 : :
3529 alvherre@alvh.no-ip. 4982 [ + + ]: 15540 : if (xtCxt->xpathobj == NULL)
4983 : : {
4984 : 172 : xtCxt->xpathobj = xmlXPathCompiledEval(xtCxt->xpathcomp, xtCxt->xpathcxt);
4985 [ + - - + ]: 172 : if (xtCxt->xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
772 tgl@sss.pgh.pa.us 4986 :UBC 0 : xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INVALID_ARGUMENT_FOR_XQUERY,
4987 : : "could not create XPath object");
4988 : :
3529 alvherre@alvh.no-ip. 4989 :CBC 172 : xtCxt->row_count = 0;
4990 : : }
4991 : :
4992 [ + - ]: 15540 : if (xtCxt->xpathobj->type == XPATH_NODESET)
4993 : : {
4994 [ + - ]: 15540 : if (xtCxt->xpathobj->nodesetval != NULL)
4995 : : {
4996 [ + + ]: 15540 : if (xtCxt->row_count++ < xtCxt->xpathobj->nodesetval->nodeNr)
4997 : 15376 : return true;
4998 : : }
4999 : : }
5000 : :
5001 : 164 : return false;
5002 : : #else
5003 : : NO_XML_SUPPORT();
5004 : : return false;
5005 : : #endif /* not USE_LIBXML */
5006 : : }
5007 : :
5008 : : /*
5009 : : * XmlTableGetValue
5010 : : * Return the value for column number 'colnum' for the current row. If
5011 : : * column -1 is requested, return representation of the whole row.
5012 : : *
5013 : : * This leaks memory, so be sure to reset often the context in which it's
5014 : : * called.
5015 : : */
5016 : : static Datum
5017 : 91556 : XmlTableGetValue(TableFuncScanState *state, int colnum,
5018 : : Oid typid, int32 typmod, bool *isnull)
5019 : : {
5020 : : #ifdef USE_LIBXML
5021 : 91556 : Datum result = (Datum) 0;
5022 : : XmlTableBuilderData *xtCxt;
5023 : 91556 : volatile xmlXPathObjectPtr xpathobj = NULL;
5024 : :
5025 : 91556 : xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableGetValue");
5026 : :
5027 [ + - + - : 91556 : Assert(xtCxt->xpathobj &&
- + ]
5028 : : xtCxt->xpathobj->type == XPATH_NODESET &&
5029 : : xtCxt->xpathobj->nodesetval != NULL);
5030 : :
5031 : : /* Propagate our own error context to libxml2 */
707 peter@eisentraut.org 5032 : 91556 : xmlSetStructuredErrorFunc(xtCxt->xmlerrcxt, xml_errorHandler);
5033 : :
3529 alvherre@alvh.no-ip. 5034 : 91556 : *isnull = false;
5035 : :
5036 [ - + ]: 91556 : Assert(xtCxt->xpathscomp[colnum] != NULL);
5037 : :
5038 [ + + ]: 91556 : PG_TRY();
5039 : : {
5040 : : xmlNodePtr cur;
690 tgl@sss.pgh.pa.us 5041 : 91556 : char *cstr = NULL;
5042 : :
5043 : : /* Set current node as entry point for XPath evaluation */
5044 : 91556 : cur = xtCxt->xpathobj->nodesetval->nodeTab[xtCxt->row_count - 1];
3529 alvherre@alvh.no-ip. 5045 : 91556 : xtCxt->xpathcxt->node = cur;
5046 : :
5047 : : /* Evaluate column path */
5048 : 91556 : xpathobj = xmlXPathCompiledEval(xtCxt->xpathscomp[colnum], xtCxt->xpathcxt);
5049 [ + - - + ]: 91556 : if (xpathobj == NULL || xtCxt->xmlerrcxt->err_occurred)
772 tgl@sss.pgh.pa.us 5050 :UBC 0 : xml_ereport(xtCxt->xmlerrcxt, ERROR, ERRCODE_INVALID_ARGUMENT_FOR_XQUERY,
5051 : : "could not create XPath object");
5052 : :
5053 : : /*
5054 : : * There are four possible cases, depending on the number of nodes
5055 : : * returned by the XPath expression and the type of the target column:
5056 : : * a) XPath returns no nodes. b) The target type is XML (return all
5057 : : * as XML). For non-XML return types: c) One node (return content).
5058 : : * d) Multiple nodes (error).
5059 : : */
3529 alvherre@alvh.no-ip. 5060 [ + + ]:CBC 91556 : if (xpathobj->type == XPATH_NODESET)
5061 : : {
5062 : 91536 : int count = 0;
5063 : :
5064 [ + + ]: 91536 : if (xpathobj->nodesetval != NULL)
5065 : 91396 : count = xpathobj->nodesetval->nodeNr;
5066 : :
5067 [ + + + + ]: 91536 : if (xpathobj->nodesetval == NULL || count == 0)
5068 : : {
5069 : 15291 : *isnull = true;
5070 : : }
5071 : : else
5072 : : {
2800 5073 [ + + ]: 76245 : if (typid == XMLOID)
5074 : : {
5075 : : text *textstr;
5076 : : StringInfoData str;
5077 : :
5078 : : /* Concatenate serialized values */
5079 : 48 : initStringInfo(&str);
5080 [ + + ]: 116 : for (int i = 0; i < count; i++)
5081 : : {
5082 : : textstr =
5083 : 68 : xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[i],
5084 : : xtCxt->xmlerrcxt);
5085 : :
5086 : 68 : appendStringInfoText(&str, textstr);
5087 : : }
5088 : 48 : cstr = str.data;
5089 : : }
5090 : : else
5091 : : {
5092 : : xmlChar *str;
5093 : :
5094 [ + + ]: 76197 : if (count > 1)
5095 [ + - ]: 4 : ereport(ERROR,
5096 : : (errcode(ERRCODE_CARDINALITY_VIOLATION),
5097 : : errmsg("more than one value returned by column XPath expression")));
5098 : :
5099 : 76193 : str = xmlXPathCastNodeSetToString(xpathobj->nodesetval);
5100 [ + - ]: 76193 : cstr = str ? xml_pstrdup_and_free(str) : "";
5101 : : }
5102 : : }
5103 : : }
5104 [ + + ]: 20 : else if (xpathobj->type == XPATH_STRING)
5105 : : {
5106 : : /* Content should be escaped when target will be XML */
5107 [ + + ]: 12 : if (typid == XMLOID)
5108 : 4 : cstr = escape_xml((char *) xpathobj->stringval);
5109 : : else
5110 : 8 : cstr = (char *) xpathobj->stringval;
5111 : : }
5112 [ + + ]: 8 : else if (xpathobj->type == XPATH_BOOLEAN)
5113 : : {
5114 : : char typcategory;
5115 : : bool typispreferred;
5116 : : xmlChar *str;
5117 : :
5118 : : /* Allow implicit casting from boolean to numbers */
5119 : 4 : get_type_category_preferred(typid, &typcategory, &typispreferred);
5120 : :
5121 [ + - ]: 4 : if (typcategory != TYPCATEGORY_NUMERIC)
5122 : 4 : str = xmlXPathCastBooleanToString(xpathobj->boolval);
5123 : : else
2800 alvherre@alvh.no-ip. 5124 :UBC 0 : str = xmlXPathCastNumberToString(xmlXPathCastBooleanToNumber(xpathobj->boolval));
5125 : :
2800 alvherre@alvh.no-ip. 5126 :CBC 4 : cstr = xml_pstrdup_and_free(str);
5127 : : }
5128 [ + - ]: 4 : else if (xpathobj->type == XPATH_NUMBER)
5129 : : {
5130 : : xmlChar *str;
5131 : :
5132 : 4 : str = xmlXPathCastNumberToString(xpathobj->floatval);
5133 : 4 : cstr = xml_pstrdup_and_free(str);
5134 : : }
5135 : : else
3529 alvherre@alvh.no-ip. 5136 [ # # ]:UBC 0 : elog(ERROR, "unexpected XPath object type %u", xpathobj->type);
5137 : :
5138 : : /*
5139 : : * By here, either cstr contains the result value, or the isnull flag
5140 : : * has been set.
5141 : : */
3529 alvherre@alvh.no-ip. 5142 [ + + - + ]:CBC 91552 : Assert(cstr || *isnull);
5143 : :
5144 [ + + ]: 91552 : if (!*isnull)
5145 : 76261 : result = InputFunctionCall(&state->in_functions[colnum],
5146 : : cstr,
5147 : 76261 : state->typioparams[colnum],
5148 : : typmod);
5149 : : }
2561 peter@eisentraut.org 5150 : 4 : PG_FINALLY();
5151 : : {
3529 alvherre@alvh.no-ip. 5152 [ + - ]: 91556 : if (xpathobj != NULL)
5153 : 91556 : xmlXPathFreeObject(xpathobj);
5154 : : }
5155 [ + + ]: 91556 : PG_END_TRY();
5156 : :
5157 : 91552 : return result;
5158 : : #else
5159 : : NO_XML_SUPPORT();
5160 : : return 0;
5161 : : #endif /* not USE_LIBXML */
5162 : : }
5163 : :
5164 : : /*
5165 : : * XmlTableDestroyOpaque
5166 : : * Release all libxml2 resources
5167 : : */
5168 : : static void
5169 : 176 : XmlTableDestroyOpaque(TableFuncScanState *state)
5170 : : {
5171 : : #ifdef USE_LIBXML
5172 : : XmlTableBuilderData *xtCxt;
5173 : :
5174 : 176 : xtCxt = GetXmlTableBuilderPrivateData(state, "XmlTableDestroyOpaque");
5175 : :
5176 : : /* Propagate our own error context to libxml2 */
707 peter@eisentraut.org 5177 : 176 : xmlSetStructuredErrorFunc(xtCxt->xmlerrcxt, xml_errorHandler);
5178 : :
3529 alvherre@alvh.no-ip. 5179 [ + - ]: 176 : if (xtCxt->xpathscomp != NULL)
5180 : : {
5181 : : int i;
5182 : :
5183 [ + + ]: 744 : for (i = 0; i < xtCxt->natts; i++)
5184 [ + + ]: 568 : if (xtCxt->xpathscomp[i] != NULL)
5185 : 516 : xmlXPathFreeCompExpr(xtCxt->xpathscomp[i]);
5186 : : }
5187 : :
5188 [ + + ]: 176 : if (xtCxt->xpathobj != NULL)
5189 : 172 : xmlXPathFreeObject(xtCxt->xpathobj);
5190 [ + + ]: 176 : if (xtCxt->xpathcomp != NULL)
5191 : 172 : xmlXPathFreeCompExpr(xtCxt->xpathcomp);
5192 [ + - ]: 176 : if (xtCxt->xpathcxt != NULL)
5193 : 176 : xmlXPathFreeContext(xtCxt->xpathcxt);
5194 [ + - ]: 176 : if (xtCxt->doc != NULL)
5195 : 176 : xmlFreeDoc(xtCxt->doc);
5196 [ + - ]: 176 : if (xtCxt->ctxt != NULL)
5197 : 176 : xmlFreeParserCtxt(xtCxt->ctxt);
5198 : :
5199 : 176 : pg_xml_done(xtCxt->xmlerrcxt, true);
5200 : :
5201 : : /* not valid anymore */
5202 : 176 : xtCxt->magic = 0;
5203 : 176 : state->opaque = NULL;
5204 : :
5205 : : #else
5206 : : NO_XML_SUPPORT();
5207 : : #endif /* not USE_LIBXML */
5208 : 176 : }
|