diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index 4cebea05c0f..13c3a0378b1 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -10,7 +10,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.256 2008/01/03 21:23:15 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.257 2008/01/15 18:56:59 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -45,6 +45,7 @@ #include "utils/inval.h" #include "utils/memutils.h" #include "utils/relcache.h" +#include "utils/xml.h" /* @@ -1671,6 +1672,7 @@ CommitTransaction(void) AtEOXact_GUC(true, 1); AtEOXact_SPI(true); + AtEOXact_xml(); AtEOXact_on_commit_actions(true); AtEOXact_Namespace(true); /* smgrcommit already done */ @@ -1880,6 +1882,7 @@ PrepareTransaction(void) /* PREPARE acts the same as COMMIT as far as GUC is concerned */ AtEOXact_GUC(true, 1); AtEOXact_SPI(true); + AtEOXact_xml(); AtEOXact_on_commit_actions(true); AtEOXact_Namespace(true); /* smgrcommit already done */ @@ -2021,6 +2024,7 @@ AbortTransaction(void) AtEOXact_GUC(false, 1); AtEOXact_SPI(false); + AtEOXact_xml(); AtEOXact_on_commit_actions(false); AtEOXact_Namespace(false); smgrabort(); @@ -3851,6 +3855,7 @@ AbortSubTransaction(void) AtEOXact_GUC(false, s->gucNestLevel); AtEOSubXact_SPI(false, s->subTransactionId); + AtEOXact_xml(); AtEOSubXact_on_commit_actions(false, s->subTransactionId, s->parent->subTransactionId); AtEOSubXact_Namespace(false, s->subTransactionId, diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c index 411cd6c22ed..0dc05f0d34c 100644 --- a/src/backend/utils/adt/xml.c +++ b/src/backend/utils/adt/xml.c @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.67 2008/01/12 21:14:08 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/utils/adt/xml.c,v 1.68 2008/01/15 18:56:59 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -24,18 +24,30 @@ */ /* - * Note on memory management: Via callbacks, libxml is told to use - * palloc and friends for memory management. Sometimes, libxml - * allocates global structures in the hope that it can reuse them - * later on, but if "later" is much later, the memory context - * management of PostgreSQL will have blown those structures away - * without telling libxml about it. Therefore, it is important to - * call xmlCleanupParser() or perhaps some other cleanup function - * after using such functions, for example something from - * libxml/parser.h or libxml/xmlsave.h. Unfortunately, you cannot - * readily tell from the API documentation when that happens, so - * careful evaluation is necessary when introducing new libxml APIs - * here. + * Notes on memory management: + * + * Via callbacks, libxml is told to use palloc and friends for memory + * management, within a context that we reset at transaction end (and also at + * subtransaction abort) to prevent memory leaks. Resetting at transaction or + * subtransaction abort is necessary since we might have thrown a longjmp + * while some data structures were not linked from anywhere persistent. + * Resetting at transaction commit might not be necessary, but seems a good + * idea to forestall long-term leaks. + * + * Sometimes libxml allocates global structures in the hope that it can reuse + * them later on. Therefore, before resetting LibxmlContext, we must tell + * libxml to discard any global data it has. The libxml API documentation is + * not very good about specifying this, but for now we assume that + * xmlCleanupParser() will get rid of anything we need to worry about. + * + * We use palloc --- which will throw a longjmp on error --- for allocation + * callbacks that officially should act like malloc, ie, return NULL on + * out-of-memory. This is a bit risky since there is a chance of leaving + * persistent libxml data structures in an inconsistent partially-constructed + * state, perhaps leading to crash in xmlCleanupParser(). However, as of + * early 2008 it is *known* that libxml can crash on out-of-memory due to + * inadequate checks for NULL returns, so this behavior seems the lesser + * of two evils. */ #include "postgres.h" @@ -80,8 +92,11 @@ XmlOptionType xmloption; #ifdef USE_LIBXML static StringInfo xml_err_buf = NULL; +static MemoryContext LibxmlContext = NULL; static void xml_init(void); +static void xml_memory_init(void); +static void xml_memory_cleanup(void); static void *xml_palloc(size_t size); static void *xml_repalloc(void *ptr, size_t size); static void xml_pfree(void *ptr); @@ -784,84 +799,53 @@ xmlvalidate(PG_FUNCTION_ARGS) text *data = PG_GETARG_TEXT_P(0); text *dtdOrUri = PG_GETARG_TEXT_P(1); bool result = false; - xmlParserCtxtPtr ctxt = NULL; - xmlDocPtr doc = NULL; - xmlDtdPtr dtd = NULL; + xmlParserCtxtPtr ctxt; + xmlDocPtr doc; + xmlDtdPtr dtd; xml_init(); + xmlInitParser(); + ctxt = xmlNewParserCtxt(); + if (ctxt == NULL) + xml_ereport(ERROR, ERRCODE_OUT_OF_MEMORY, + "could not allocate parser context"); - /* We use a PG_TRY block to ensure libxml parser is cleaned up on error */ - PG_TRY(); - { - xmlInitParser(); - ctxt = xmlNewParserCtxt(); - if (ctxt == NULL) - xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR, - "could not allocate parser context"); - - doc = xmlCtxtReadMemory(ctxt, (char *) VARDATA(data), - VARSIZE(data) - VARHDRSZ, - NULL, NULL, 0); - if (doc == NULL) - xml_ereport(ERROR, ERRCODE_INVALID_XML_DOCUMENT, - "could not parse XML data"); + doc = xmlCtxtReadMemory(ctxt, (char *) VARDATA(data), + VARSIZE(data) - VARHDRSZ, + NULL, NULL, 0); + if (doc == NULL) + xml_ereport(ERROR, ERRCODE_INVALID_XML_DOCUMENT, + "could not parse XML data"); #if 0 - uri = xmlCreateURI(); - elog(NOTICE, "dtd - %s", dtdOrUri); - dtd = palloc(sizeof(xmlDtdPtr)); - uri = xmlParseURI(dtdOrUri); - if (uri == NULL) - xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR, - "not implemented yet... (TODO)"); - else + uri = xmlCreateURI(); + elog(NOTICE, "dtd - %s", dtdOrUri); + dtd = palloc(sizeof(xmlDtdPtr)); + uri = xmlParseURI(dtdOrUri); + if (uri == NULL) + xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR, + "not implemented yet... (TODO)"); + else #endif - dtd = xmlParseDTD(NULL, xml_text2xmlChar(dtdOrUri)); + dtd = xmlParseDTD(NULL, xml_text2xmlChar(dtdOrUri)); - if (dtd == NULL) - xml_ereport(ERROR, ERRCODE_INVALID_XML_DOCUMENT, - "could not load DTD"); + if (dtd == NULL) + xml_ereport(ERROR, ERRCODE_INVALID_XML_DOCUMENT, + "could not load DTD"); - if (xmlValidateDtd(xmlNewValidCtxt(), doc, dtd) == 1) - result = true; + if (xmlValidateDtd(xmlNewValidCtxt(), doc, dtd) == 1) + result = true; - if (!result) - xml_ereport(NOTICE, ERRCODE_INVALID_XML_DOCUMENT, - "validation against DTD failed"); + if (!result) + xml_ereport(NOTICE, ERRCODE_INVALID_XML_DOCUMENT, + "validation against DTD failed"); #if 0 - if (uri) - xmlFreeURI(uri); - uri = NULL; + xmlFreeURI(uri); #endif - if (dtd) - xmlFreeDtd(dtd); - dtd = NULL; - if (doc) - xmlFreeDoc(doc); - doc = NULL; - if (ctxt) - xmlFreeParserCtxt(ctxt); - ctxt = NULL; - xmlCleanupParser(); - } - PG_CATCH(); - { -#if 0 - if (uri) - xmlFreeURI(uri); -#endif - if (dtd) - xmlFreeDtd(dtd); - if (doc) - xmlFreeDoc(doc); - if (ctxt) - xmlFreeParserCtxt(ctxt); - xmlCleanupParser(); - - PG_RE_THROW(); - } - PG_END_TRY(); + xmlFreeDtd(dtd); + xmlFreeDoc(doc); + xmlFreeParserCtxt(ctxt); PG_RETURN_BOOL(result); #else /* not USE_LIBXML */ @@ -915,6 +899,19 @@ xml_is_document(xmltype *arg) } +/* + * xml cleanup function for transaction end. This is also called on + * subtransaction abort; see notes at top of file for rationale. + */ +void +AtEOXact_xml(void) +{ +#ifdef USE_LIBXML + xml_memory_cleanup(); +#endif +} + + #ifdef USE_LIBXML /* @@ -953,14 +950,11 @@ xml_init(void) xmlSetGenericErrorFunc(NULL, xml_errorHandler); /* Set up memory allocation our way, too */ - xmlMemSetup(xml_pfree, xml_palloc, xml_repalloc, xml_pstrdup); + xml_memory_init(); /* Check library compatibility */ LIBXML_TEST_VERSION; - /* The above calls xmlInitParser(); must clean up dangling pointers */ - xmlCleanupParser(); - first_time = false; } else @@ -977,7 +971,7 @@ xml_init(void) * about, anyway. */ xmlSetGenericErrorFunc(NULL, xml_errorHandler); - xmlMemSetup(xml_pfree, xml_palloc, xml_repalloc, xml_pstrdup); + xml_memory_init(); } } @@ -1210,7 +1204,7 @@ print_xml_decl(StringInfo buf, const xmlChar * version, * Convert a C string to XML internal representation * * TODO maybe, libxml2's xmlreader is better? (do not construct DOM, - * yet do not use SAX - see xml_reader.c) + * yet do not use SAX - see xmlreader.c) */ static xmlDocPtr xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, @@ -1219,8 +1213,8 @@ xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, int32 len; xmlChar *string; xmlChar *utf8string; - xmlParserCtxtPtr ctxt = NULL; - xmlDocPtr doc = NULL; + xmlParserCtxtPtr ctxt; + xmlDocPtr doc; len = VARSIZE(data) - VARHDRSZ; /* will be useful later */ string = xml_text2xmlChar(data); @@ -1233,73 +1227,57 @@ xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace, PG_UTF8); xml_init(); + xmlInitParser(); + ctxt = xmlNewParserCtxt(); + if (ctxt == NULL) + xml_ereport(ERROR, ERRCODE_OUT_OF_MEMORY, + "could not allocate parser context"); - /* We use a PG_TRY block to ensure libxml parser is cleaned up on error */ - PG_TRY(); + if (xmloption_arg == XMLOPTION_DOCUMENT) { - xmlInitParser(); - ctxt = xmlNewParserCtxt(); - if (ctxt == NULL) - xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR, - "could not allocate parser context"); - - if (xmloption_arg == XMLOPTION_DOCUMENT) - { - /* - * Note, that here we try to apply DTD defaults - * (XML_PARSE_DTDATTR) according to SQL/XML:10.16.7.d: 'Default - * valies defined by internal DTD are applied'. As for external - * DTDs, we try to support them too, (see SQL/XML:10.16.7.e) - */ - doc = xmlCtxtReadDoc(ctxt, utf8string, - NULL, - "UTF-8", - XML_PARSE_NOENT | XML_PARSE_DTDATTR - | (preserve_whitespace ? 0 : XML_PARSE_NOBLANKS)); - if (doc == NULL) - xml_ereport(ERROR, ERRCODE_INVALID_XML_DOCUMENT, - "invalid XML document"); - } - else - { - int res_code; - size_t count; - xmlChar *version = NULL; - int standalone = -1; - - doc = xmlNewDoc(NULL); - - res_code = parse_xml_decl(utf8string, &count, &version, NULL, &standalone); - if (res_code != 0) - xml_ereport_by_code(ERROR, ERRCODE_INVALID_XML_CONTENT, - "invalid XML content: invalid XML declaration", res_code); - - res_code = xmlParseBalancedChunkMemory(doc, NULL, NULL, 0, utf8string + count, NULL); - if (res_code != 0) - xml_ereport(ERROR, ERRCODE_INVALID_XML_CONTENT, - "invalid XML content"); - - doc->version = xmlStrdup(version); - doc->encoding = xmlStrdup((xmlChar *) "UTF-8"); - doc->standalone = standalone; - } - - if (ctxt) - xmlFreeParserCtxt(ctxt); - ctxt = NULL; - xmlCleanupParser(); + /* + * Note, that here we try to apply DTD defaults + * (XML_PARSE_DTDATTR) according to SQL/XML:10.16.7.d: 'Default + * values defined by internal DTD are applied'. As for external + * DTDs, we try to support them too, (see SQL/XML:10.16.7.e) + */ + doc = xmlCtxtReadDoc(ctxt, utf8string, + NULL, + "UTF-8", + XML_PARSE_NOENT | XML_PARSE_DTDATTR + | (preserve_whitespace ? 0 : XML_PARSE_NOBLANKS)); + if (doc == NULL) + xml_ereport(ERROR, ERRCODE_INVALID_XML_DOCUMENT, + "invalid XML document"); } - PG_CATCH(); + else { - if (doc) - xmlFreeDoc(doc); - if (ctxt) - xmlFreeParserCtxt(ctxt); - xmlCleanupParser(); + int res_code; + size_t count; + xmlChar *version = NULL; + int standalone = -1; - PG_RE_THROW(); + doc = xmlNewDoc(NULL); + + res_code = parse_xml_decl(utf8string, + &count, &version, NULL, &standalone); + if (res_code != 0) + xml_ereport_by_code(ERROR, ERRCODE_INVALID_XML_CONTENT, + "invalid XML content: invalid XML declaration", + res_code); + + res_code = xmlParseBalancedChunkMemory(doc, NULL, NULL, 0, + utf8string + count, NULL); + if (res_code != 0) + xml_ereport(ERROR, ERRCODE_INVALID_XML_CONTENT, + "invalid XML content"); + + doc->version = xmlStrdup(version); + doc->encoding = xmlStrdup((xmlChar *) "UTF-8"); + doc->standalone = standalone; } - PG_END_TRY(); + + xmlFreeParserCtxt(ctxt); return doc; } @@ -1322,13 +1300,50 @@ xml_text2xmlChar(text *in) } +/* + * Manage the special context used for all libxml allocations + */ +static void +xml_memory_init(void) +{ + /* + * Create memory context if not there already. We make it a child of + * TopMemoryContext, even though our current policy is that it doesn't + * survive past transaction end, because we want to be really really + * sure it doesn't go away before we've called xmlCleanupParser(). + */ + if (LibxmlContext == NULL) + LibxmlContext = AllocSetContextCreate(TopMemoryContext, + "LibxmlContext", + ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, + ALLOCSET_DEFAULT_MAXSIZE); + + /* Re-establish the callbacks even if already set */ + xmlMemSetup(xml_pfree, xml_palloc, xml_repalloc, xml_pstrdup); +} + +static void +xml_memory_cleanup(void) +{ + if (LibxmlContext != NULL) + { + /* Give libxml a chance to clean up dangling pointers */ + xmlCleanupParser(); + + /* And flush the context */ + MemoryContextDelete(LibxmlContext); + LibxmlContext = NULL; + } +} + /* * Wrappers for memory management functions */ static void * xml_palloc(size_t size) { - return palloc(size); + return MemoryContextAlloc(LibxmlContext, size); } @@ -1349,7 +1364,7 @@ xml_pfree(void *ptr) static char * xml_pstrdup(const char *string) { - return pstrdup(string); + return MemoryContextStrdup(LibxmlContext, string); } @@ -3262,11 +3277,11 @@ xpath(PG_FUNCTION_ARGS) xmltype *data = PG_GETARG_XML_P(1); ArrayType *namespaces = PG_GETARG_ARRAYTYPE_P(2); ArrayBuildState *astate = NULL; - xmlParserCtxtPtr ctxt = NULL; - xmlDocPtr doc = NULL; - xmlXPathContextPtr xpathctx = NULL; - xmlXPathCompExprPtr xpathcomp = NULL; - xmlXPathObjectPtr xpathobj = NULL; + xmlParserCtxtPtr ctxt; + xmlDocPtr doc; + xmlXPathContextPtr xpathctx; + xmlXPathCompExprPtr xpathcomp; + xmlXPathObjectPtr xpathobj; char *datastr; int32 len; int32 xpath_len; @@ -3363,114 +3378,89 @@ xpath(PG_FUNCTION_ARGS) xpath_expr[xpath_len + 2] = '\0'; xpath_len += 2; - /* We use a PG_TRY block to ensure libxml parser is cleaned up on error */ - PG_TRY(); + xmlInitParser(); + + /* + * redundant XML parsing (two parsings for the same value during one + * command execution are possible) + */ + ctxt = xmlNewParserCtxt(); + if (ctxt == NULL) + xml_ereport(ERROR, ERRCODE_OUT_OF_MEMORY, + "could not allocate parser context"); + doc = xmlCtxtReadMemory(ctxt, (char *) string, len, NULL, NULL, 0); + if (doc == NULL) + xml_ereport(ERROR, ERRCODE_INVALID_XML_DOCUMENT, + "could not parse XML data"); + xpathctx = xmlXPathNewContext(doc); + if (xpathctx == NULL) + xml_ereport(ERROR, ERRCODE_OUT_OF_MEMORY, + "could not allocate XPath context"); + xpathctx->node = xmlDocGetRootElement(doc); + if (xpathctx->node == NULL) + xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR, + "could not find root XML element"); + + /* register namespaces, if any */ + if (ns_count > 0) { - xmlInitParser(); - - /* - * redundant XML parsing (two parsings for the same value during one - * command execution are possible) - */ - ctxt = xmlNewParserCtxt(); - if (ctxt == NULL) - xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR, - "could not allocate parser context"); - doc = xmlCtxtReadMemory(ctxt, (char *) string, len, NULL, NULL, 0); - if (doc == NULL) - xml_ereport(ERROR, ERRCODE_INVALID_XML_DOCUMENT, - "could not parse XML data"); - xpathctx = xmlXPathNewContext(doc); - if (xpathctx == NULL) - xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR, - "could not allocate XPath context"); - xpathctx->node = xmlDocGetRootElement(doc); - if (xpathctx->node == NULL) - xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR, - "could not find root XML element"); - - /* register namespaces, if any */ - if (ns_count > 0) + for (i = 0; i < ns_count; i++) { - for (i = 0; i < ns_count; i++) - { - char *ns_name; - char *ns_uri; + char *ns_name; + char *ns_uri; - if (ns_names_uris_nulls[i * 2] || - ns_names_uris_nulls[i * 2 + 1]) - ereport(ERROR, - (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), - errmsg("neither namespace name nor URI may be null"))); - ns_name = _textout(ns_names_uris[i * 2]); - ns_uri = _textout(ns_names_uris[i * 2 + 1]); - if (xmlXPathRegisterNs(xpathctx, - (xmlChar *) ns_name, - (xmlChar *) ns_uri) != 0) - ereport(ERROR, /* is this an internal error??? */ - (errmsg("could not register XML namespace with name \"%s\" and URI \"%s\"", - ns_name, ns_uri))); - } + if (ns_names_uris_nulls[i * 2] || + ns_names_uris_nulls[i * 2 + 1]) + ereport(ERROR, + (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("neither namespace name nor URI may be null"))); + ns_name = _textout(ns_names_uris[i * 2]); + ns_uri = _textout(ns_names_uris[i * 2 + 1]); + if (xmlXPathRegisterNs(xpathctx, + (xmlChar *) ns_name, + (xmlChar *) ns_uri) != 0) + ereport(ERROR, /* is this an internal error??? */ + (errmsg("could not register XML namespace with name \"%s\" and URI \"%s\"", + ns_name, ns_uri))); } - - xpathcomp = xmlXPathCompile(xpath_expr); - if (xpathcomp == NULL) /* TODO: show proper XPath error details */ - xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR, - "invalid XPath expression"); - - xpathobj = xmlXPathCompiledEval(xpathcomp, xpathctx); - if (xpathobj == NULL) /* TODO: reason? */ - ereport(ERROR, - (errmsg("could not create XPath object"))); - - xmlXPathFreeCompExpr(xpathcomp); - xpathcomp = NULL; - - /* return empty array in cases when nothing is found */ - if (xpathobj->nodesetval == NULL) - res_nitems = 0; - else - res_nitems = xpathobj->nodesetval->nodeNr; - - if (res_nitems) - for (i = 0; i < xpathobj->nodesetval->nodeNr; i++) - { - Datum elem; - bool elemisnull = false; - - elem = PointerGetDatum(xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[i])); - astate = accumArrayResult(astate, elem, - elemisnull, XMLOID, - CurrentMemoryContext); - } - - xmlXPathFreeObject(xpathobj); - xpathobj = NULL; - xmlXPathFreeContext(xpathctx); - xpathctx = NULL; - xmlFreeDoc(doc); - doc = NULL; - xmlFreeParserCtxt(ctxt); - ctxt = NULL; - xmlCleanupParser(); } - PG_CATCH(); + + xpathcomp = xmlXPathCompile(xpath_expr); + if (xpathcomp == NULL) /* TODO: show proper XPath error details */ + xml_ereport(ERROR, ERRCODE_INTERNAL_ERROR, + "invalid XPath expression"); + + xpathobj = xmlXPathCompiledEval(xpathcomp, xpathctx); + if (xpathobj == NULL) /* TODO: reason? */ + ereport(ERROR, + (errmsg("could not create XPath object"))); + + xmlXPathFreeCompExpr(xpathcomp); + + /* return empty array in cases when nothing is found */ + if (xpathobj->nodesetval == NULL) + res_nitems = 0; + else + res_nitems = xpathobj->nodesetval->nodeNr; + + if (res_nitems) { - if (xpathcomp) - xmlXPathFreeCompExpr(xpathcomp); - if (xpathobj) - xmlXPathFreeObject(xpathobj); - if (xpathctx) - xmlXPathFreeContext(xpathctx); - if (doc) - xmlFreeDoc(doc); - if (ctxt) - xmlFreeParserCtxt(ctxt); - xmlCleanupParser(); + for (i = 0; i < xpathobj->nodesetval->nodeNr; i++) + { + Datum elem; + bool elemisnull = false; - PG_RE_THROW(); + elem = PointerGetDatum(xml_xmlnodetoxmltype(xpathobj->nodesetval->nodeTab[i])); + astate = accumArrayResult(astate, elem, + elemisnull, XMLOID, + CurrentMemoryContext); + } } - PG_END_TRY(); + + xmlXPathFreeObject(xpathobj); + xmlXPathFreeContext(xpathctx); + xmlFreeDoc(doc); + xmlFreeParserCtxt(ctxt); if (res_nitems == 0) PG_RETURN_ARRAYTYPE_P(construct_empty_array(XMLOID)); diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h index 8d5b222f803..99a46f8ef76 100644 --- a/src/include/utils/xml.h +++ b/src/include/utils/xml.h @@ -7,7 +7,7 @@ * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/utils/xml.h,v 1.22 2008/01/01 19:45:59 momjian Exp $ + * $PostgreSQL: pgsql/src/include/utils/xml.h,v 1.23 2008/01/15 18:57:00 tgl Exp $ * *------------------------------------------------------------------------- */ @@ -75,6 +75,8 @@ extern char *map_sql_identifier_to_xml_name(char *ident, bool fully_escaped, boo extern char *map_xml_name_to_sql_identifier(char *name); extern char *map_sql_value_to_xml_value(Datum value, Oid type); +extern void AtEOXact_xml(void); + typedef enum { XMLBINARY_BASE64,