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