Support [NO] INDENT option in XMLSERIALIZE().
authorTom Lane
Wed, 15 Mar 2023 20:58:59 +0000 (16:58 -0400)
committerTom Lane
Wed, 15 Mar 2023 20:59:09 +0000 (16:59 -0400)
This adds the ability to pretty-print XML documents ... according to
libxml's somewhat idiosyncratic notions of what's pretty, anyway.
One notable divergence from a strict reading of the spec is that
libxml is willing to collapse empty nodes "" to just
"", whereas SQL and the underlying XML spec say that this
option should only result in whitespace tweaks.  Nonetheless,
it seems close enough to justify using the SQL-standard syntax.

Jim Jones, reviewed by Peter Smith and myself

Discussion: https://postgr.es/m/2f5df461-dad8-6d7d-4568-08e10608a69b@uni-muenster.de

15 files changed:
doc/src/sgml/datatype.sgml
src/backend/catalog/sql_features.txt
src/backend/executor/execExprInterp.c
src/backend/parser/gram.y
src/backend/parser/parse_expr.c
src/backend/utils/adt/xml.c
src/include/catalog/catversion.h
src/include/nodes/parsenodes.h
src/include/nodes/primnodes.h
src/include/parser/kwlist.h
src/include/utils/xml.h
src/test/regress/expected/xml.out
src/test/regress/expected/xml_1.out
src/test/regress/expected/xml_2.out
src/test/regress/sql/xml.sql

index 467b49b199f5a605b871b4b4212d7fb79ee09036..4df8bd1b641702d77bc77f247a41e8befb81ad62 100644 (file)
@@ -4460,7 +4460,7 @@ xml 'bar'
     xml, uses the function
     xmlserialize:xmlserialize
 
-XMLSERIALIZE ( { DOCUMENT | CONTENT } value AS type )
+XMLSERIALIZE ( { DOCUMENT | CONTENT } value AS type [ [ NO ] INDENT ] )
 
     type can be
     charactercharacter varying, or
@@ -4470,6 +4470,13 @@ XMLSERIALIZE ( { DOCUMENT | CONTENT } value AS 
     you to simply cast the value.
    
 
+   
+    The INDENT option causes the result to be
+    pretty-printed, while NO INDENT (which is the
+    default) just emits the original input string.  Casting to a character
+    type likewise produces the original string.
+   
+
    
     When a character string value is cast to or from type
     xml without going through XMLPARSE or
index 0fb9ab75335d19baaef00f6040ed458e089f648e..bb4c135a7f6cdf5224208ad37e9314094ffe368a 100644 (file)
@@ -621,7 +621,7 @@ X061    XMLParse: character string input and DOCUMENT option            YES
 X065   XMLParse: binary string input and CONTENT option            NO  
 X066   XMLParse: binary string input and DOCUMENT option           NO  
 X068   XMLSerialize: BOM           NO  
-X069   XMLSerialize: INDENT            NO  
+X069   XMLSerialize: INDENT            YES 
 X070   XMLSerialize: character string serialization and CONTENT option         YES 
 X071   XMLSerialize: character string serialization and DOCUMENT option            YES 
 X072   XMLSerialize: character string serialization            YES 
index 19351fe34bf44f9599e2b23528893aa913ea192d..9cb9625ce91bb98fdb20bd4a9e5c900899d7d611 100644 (file)
@@ -3837,8 +3837,10 @@ ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op)
                    return;
                value = argvalue[0];
 
-               *op->resvalue = PointerGetDatum(xmltotext_with_xmloption(DatumGetXmlP(value),
-                                                                        xexpr->xmloption));
+               *op->resvalue =
+                   PointerGetDatum(xmltotext_with_options(DatumGetXmlP(value),
+                                                          xexpr->xmloption,
+                                                          xexpr->indent));
                *op->resnull = false;
            }
            break;
index a0138382a16c873ed77d1d7e571dac5026f79eb9..efe88ccf9da7cae72c1439aaebcc31e5905c0244 100644 (file)
@@ -613,7 +613,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %type    xml_root_version opt_xml_root_standalone
 %type    xmlexists_argument
 %type    document_or_content
-%type  xml_whitespace_option
+%type     xml_indent_option xml_whitespace_option
 %type    xmltable_column_list xmltable_column_option_list
 %type    xmltable_column_el
 %type  xmltable_column_option_el
@@ -702,7 +702,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
    HANDLER HAVING HEADER_P HOLD HOUR_P
 
    IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P INCLUDE
-   INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
+   INCLUDING INCREMENT INDENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P
    INNER_P INOUT INPUT_P INSENSITIVE INSERT INSTEAD INT_P INTEGER
    INTERSECT INTERVAL INTO INVOKER IS ISNULL ISOLATION
 
@@ -15532,13 +15532,14 @@ func_expr_common_subexpr:
                    $$ = makeXmlExpr(IS_XMLROOT, NULL, NIL,
                                     list_make3($3, $5, $6), @1);
                }
-           | XMLSERIALIZE '(' document_or_content a_expr AS SimpleTypename ')'
+           | XMLSERIALIZE '(' document_or_content a_expr AS SimpleTypename xml_indent_option ')'
                {
                    XmlSerialize *n = makeNode(XmlSerialize);
 
                    n->xmloption = $3;
                    n->expr = $4;
                    n->typeName = $6;
+                   n->indent = $7;
                    n->location = @1;
                    $$ = (Node *) n;
                }
@@ -15592,6 +15593,11 @@ document_or_content: DOCUMENT_P                        { $$ = XMLOPTION_DOCUMENT; }
            | CONTENT_P                             { $$ = XMLOPTION_CONTENT; }
        ;
 
+xml_indent_option: INDENT                          { $$ = true; }
+           | NO INDENT                             { $$ = false; }
+           | /*EMPTY*/                             { $$ = false; }
+       ;
+
 xml_whitespace_option: PRESERVE WHITESPACE_P       { $$ = true; }
            | STRIP_P WHITESPACE_P                  { $$ = false; }
            | /*EMPTY*/                             { $$ = false; }
@@ -16828,6 +16834,7 @@ unreserved_keyword:
            | INCLUDE
            | INCLUDING
            | INCREMENT
+           | INDENT
            | INDEX
            | INDEXES
            | INHERIT
@@ -17384,6 +17391,7 @@ bare_label_keyword:
            | INCLUDE
            | INCLUDING
            | INCREMENT
+           | INDENT
            | INDEX
            | INDEXES
            | INHERIT
index 78221d2e0f7a863d60d2d664faa3d69d48190688..233141755225b0f9787207c972af61d9b3c37335 100644 (file)
@@ -2331,6 +2331,7 @@ transformXmlSerialize(ParseState *pstate, XmlSerialize *xs)
    typenameTypeIdAndMod(pstate, xs->typeName, &targetType, &targetTypmod);
 
    xexpr->xmloption = xs->xmloption;
+   xexpr->indent = xs->indent;
    xexpr->location = xs->location;
    /* We actually only need these to be able to parse back the expression. */
    xexpr->type = targetType;
index 079bcb120850458b6ebe6670b15ab3e4d1bc32cf..15adbd6a016260f8b48e2b167603c7f6bd31e555 100644 (file)
@@ -52,6 +52,7 @@
 #include 
 #include 
 #include 
+#include 
 #include 
 #include 
 #include 
@@ -146,6 +147,8 @@ static bool print_xml_decl(StringInfo buf, const xmlChar *version,
 static bool xml_doctype_in_content(const xmlChar *str);
 static xmlDocPtr xml_parse(text *data, XmlOptionType xmloption_arg,
                           bool preserve_whitespace, int encoding,
+                          XmlOptionType *parsed_xmloptiontype,
+                          xmlNodePtr *parsed_nodes,
                           Node *escontext);
 static text *xml_xmlnodetoxmltype(xmlNodePtr cur, PgXmlErrorContext *xmlerrcxt);
 static int xml_xpathobjtoxmlarray(xmlXPathObjectPtr xpathobj,
@@ -273,7 +276,7 @@ xml_in(PG_FUNCTION_ARGS)
     * Note: we don't need to worry about whether a soft error is detected.
     */
    doc = xml_parse(vardata, xmloption, true, GetDatabaseEncoding(),
-                   fcinfo->context);
+                   NULL, NULL, fcinfo->context);
    if (doc != NULL)
        xmlFreeDoc(doc);
 
@@ -400,7 +403,7 @@ xml_recv(PG_FUNCTION_ARGS)
     * Parse the data to check if it is well-formed XML data.  Assume that
     * xml_parse will throw ERROR if not.
     */
-   doc = xml_parse(result, xmloption, true, encoding, NULL);
+   doc = xml_parse(result, xmloption, true, encoding, NULL, NULL, NULL);
    xmlFreeDoc(doc);
 
    /* Now that we know what we're dealing with, convert to server encoding */
@@ -619,15 +622,182 @@ xmltotext(PG_FUNCTION_ARGS)
 
 
 text *
-xmltotext_with_xmloption(xmltype *data, XmlOptionType xmloption_arg)
+xmltotext_with_options(xmltype *data, XmlOptionType xmloption_arg, bool indent)
 {
-   if (xmloption_arg == XMLOPTION_DOCUMENT && !xml_is_document(data))
+#ifdef USE_LIBXML
+   text       *volatile result;
+   xmlDocPtr   doc;
+   XmlOptionType parsed_xmloptiontype;
+   xmlNodePtr  content_nodes;
+   volatile xmlBufferPtr buf = NULL;
+   volatile    xmlSaveCtxtPtr ctxt = NULL;
+   ErrorSaveContext escontext = {T_ErrorSaveContext};
+   PgXmlErrorContext *xmlerrcxt;
+#endif
+
+   if (xmloption_arg != XMLOPTION_DOCUMENT && !indent)
+   {
+       /*
+        * We don't actually need to do anything, so just return the
+        * binary-compatible input.  For backwards-compatibility reasons,
+        * allow such cases to succeed even without USE_LIBXML.
+        */
+       return (text *) data;
+   }
+
+#ifdef USE_LIBXML
+   /* Parse the input according to the xmloption */
+   doc = xml_parse(data, xmloption_arg, true, GetDatabaseEncoding(),
+                   &parsed_xmloptiontype, &content_nodes,
+                   (Node *) &escontext);
+   if (doc == NULL || escontext.error_occurred)
+   {
+       if (doc)
+           xmlFreeDoc(doc);
+       /* A soft error must be failure to conform to XMLOPTION_DOCUMENT */
        ereport(ERROR,
                (errcode(ERRCODE_NOT_AN_XML_DOCUMENT),
                 errmsg("not an XML document")));
+   }
+
+   /* If we weren't asked to indent, we're done. */
+   if (!indent)
+   {
+       xmlFreeDoc(doc);
+       return (text *) data;
+   }
+
+   /* Otherwise, we gotta spin up some error handling. */
+   xmlerrcxt = pg_xml_init(PG_XML_STRICTNESS_ALL);
+
+   PG_TRY();
+   {
+       size_t      decl_len = 0;
+
+       /* The serialized data will go into this buffer. */
+       buf = xmlBufferCreate();
+
+       if (buf == NULL || xmlerrcxt->err_occurred)
+           xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+                       "could not allocate xmlBuffer");
+
+       /* Detect whether there's an XML declaration */
+       parse_xml_decl(xml_text2xmlChar(data), &decl_len, NULL, NULL, NULL);
+
+       /*
+        * Emit declaration only if the input had one.  Note: some versions of
+        * xmlSaveToBuffer leak memory if a non-null encoding argument is
+        * passed, so don't do that.  We don't want any encoding conversion
+        * anyway.
+        */
+       if (decl_len == 0)
+           ctxt = xmlSaveToBuffer(buf, NULL,
+                                  XML_SAVE_NO_DECL | XML_SAVE_FORMAT);
+       else
+           ctxt = xmlSaveToBuffer(buf, NULL,
+                                  XML_SAVE_FORMAT);
+
+       if (ctxt == NULL || xmlerrcxt->err_occurred)
+           xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+                       "could not allocate xmlSaveCtxt");
+
+       if (parsed_xmloptiontype == XMLOPTION_DOCUMENT)
+       {
+           /* If it's a document, saving is easy. */
+           if (xmlSaveDoc(ctxt, doc) == -1 || xmlerrcxt->err_occurred)
+               xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+                           "could not save document to xmlBuffer");
+       }
+       else if (content_nodes != NULL)
+       {
+           /*
+            * Deal with the case where we have non-singly-rooted XML.
+            * libxml's dump functions don't work well for that without help.
+            * We build a fake root node that serves as a container for the
+            * content nodes, and then iterate over the nodes.
+            */
+           xmlNodePtr  root;
+           xmlNodePtr  newline;
+
+           root = xmlNewNode(NULL, (const xmlChar *) "content-root");
+           if (root == NULL || xmlerrcxt->err_occurred)
+               xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+                           "could not allocate xml node");
+
+           /* This attaches root to doc, so we need not free it separately. */
+           xmlDocSetRootElement(doc, root);
+           xmlAddChild(root, content_nodes);
 
-   /* It's actually binary compatible, save for the above check. */
-   return (text *) data;
+           /*
+            * We use this node to insert newlines in the dump.  Note: in at
+            * least some libxml versions, xmlNewDocText would not attach the
+            * node to the document even if we passed it.  Therefore, manage
+            * freeing of this node manually, and pass NULL here to make sure
+            * there's not a dangling link.
+            */
+           newline = xmlNewDocText(NULL, (const xmlChar *) "\n");
+           if (newline == NULL || xmlerrcxt->err_occurred)
+               xml_ereport(xmlerrcxt, ERROR, ERRCODE_OUT_OF_MEMORY,
+                           "could not allocate xml node");
+
+           for (xmlNodePtr node = root->children; node; node = node->next)
+           {
+               /* insert newlines between nodes */
+               if (node->type != XML_TEXT_NODE && node->prev != NULL)
+               {
+                   if (xmlSaveTree(ctxt, newline) == -1 || xmlerrcxt->err_occurred)
+                   {
+                       xmlFreeNode(newline);
+                       xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+                                   "could not save newline to xmlBuffer");
+                   }
+               }
+
+               if (xmlSaveTree(ctxt, node) == -1 || xmlerrcxt->err_occurred)
+               {
+                   xmlFreeNode(newline);
+                   xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+                               "could not save content to xmlBuffer");
+               }
+           }
+
+           xmlFreeNode(newline);
+       }
+
+       if (xmlSaveClose(ctxt) == -1 || xmlerrcxt->err_occurred)
+       {
+           ctxt = NULL;        /* don't try to close it again */
+           xml_ereport(xmlerrcxt, ERROR, ERRCODE_INTERNAL_ERROR,
+                       "could not close xmlSaveCtxtPtr");
+       }
+
+       result = (text *) xmlBuffer_to_xmltype(buf);
+   }
+   PG_CATCH();
+   {
+       if (ctxt)
+           xmlSaveClose(ctxt);
+       if (buf)
+           xmlBufferFree(buf);
+       if (doc)
+           xmlFreeDoc(doc);
+
+       pg_xml_done(xmlerrcxt, true);
+
+       PG_RE_THROW();
+   }
+   PG_END_TRY();
+
+   xmlBufferFree(buf);
+   xmlFreeDoc(doc);
+
+   pg_xml_done(xmlerrcxt, false);
+
+   return result;
+#else
+   NO_XML_SUPPORT();
+   return NULL;
+#endif
 }
 
 
@@ -762,7 +932,7 @@ xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace)
    xmlDocPtr   doc;
 
    doc = xml_parse(data, xmloption_arg, preserve_whitespace,
-                   GetDatabaseEncoding(), NULL);
+                   GetDatabaseEncoding(), NULL, NULL, NULL);
    xmlFreeDoc(doc);
 
    return (xmltype *) data;
@@ -902,7 +1072,7 @@ xml_is_document(xmltype *arg)
     * We'll report "true" if no soft error is reported by xml_parse().
     */
    doc = xml_parse((text *) arg, XMLOPTION_DOCUMENT, true,
-                   GetDatabaseEncoding(), (Node *) &escontext);
+                   GetDatabaseEncoding(), NULL, NULL, (Node *) &escontext);
    if (doc)
        xmlFreeDoc(doc);
 
@@ -1491,6 +1661,14 @@ xml_doctype_in_content(const xmlChar *str)
  * and xmloption_arg and preserve_whitespace are options for the
  * transformation.
  *
+ * If parsed_xmloptiontype isn't NULL, *parsed_xmloptiontype is set to the
+ * XmlOptionType actually used to parse the input (typically the same as
+ * xmloption_arg, but a DOCTYPE node in the input can force DOCUMENT mode).
+ *
+ * If parsed_nodes isn't NULL and the input is not an XML document, the list
+ * of parsed nodes from the xmlParseBalancedChunkMemory call will be returned
+ * to *parsed_nodes.
+ *
  * Errors normally result in ereport(ERROR), but if escontext is an
  * ErrorSaveContext, then "safe" errors are reported there instead, and the
  * caller must check SOFT_ERROR_OCCURRED() to see whether that happened.
@@ -1503,8 +1681,10 @@ xml_doctype_in_content(const xmlChar *str)
  * yet do not use SAX - see xmlreader.c)
  */
 static xmlDocPtr
-xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace,
-         int encoding, Node *escontext)
+xml_parse(text *data, XmlOptionType xmloption_arg,
+         bool preserve_whitespace, int encoding,
+         XmlOptionType *parsed_xmloptiontype, xmlNodePtr *parsed_nodes,
+         Node *escontext)
 {
    int32       len;
    xmlChar    *string;
@@ -1574,6 +1754,13 @@ xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace,
                parse_as_document = true;
        }
 
+       /* initialize output parameters */
+       if (parsed_xmloptiontype != NULL)
+           *parsed_xmloptiontype = parse_as_document ? XMLOPTION_DOCUMENT :
+               XMLOPTION_CONTENT;
+       if (parsed_nodes != NULL)
+           *parsed_nodes = NULL;
+
        if (parse_as_document)
        {
            /*
@@ -1620,7 +1807,8 @@ xml_parse(text *data, XmlOptionType xmloption_arg, bool preserve_whitespace,
            if (*(utf8string + count))
            {
                res_code = xmlParseBalancedChunkMemory(doc, NULL, NULL, 0,
-                                                      utf8string + count, NULL);
+                                                      utf8string + count,
+                                                      parsed_nodes);
                if (res_code != 0 || xmlerrcxt->err_occurred)
                {
                    xml_errsave(escontext, xmlerrcxt,
@@ -4305,7 +4493,7 @@ wellformed_xml(text *data, XmlOptionType xmloption_arg)
     * We'll report "true" if no soft error is reported by xml_parse().
     */
    doc = xml_parse(data, xmloption_arg, true,
-                   GetDatabaseEncoding(), (Node *) &escontext);
+                   GetDatabaseEncoding(), NULL, NULL, (Node *) &escontext);
    if (doc)
        xmlFreeDoc(doc);
 
index 309aed370361f133093706251e8b8aa50aea32b7..b2eed22d46c9c8c6cfd1048528ed0fd43f86e0d6 100644 (file)
@@ -57,6 +57,6 @@
  */
 
 /*                         yyyymmddN */
-#define CATALOG_VERSION_NO 202303141
+#define CATALOG_VERSION_NO 202303151
 
 #endif
index 371aa0ffc56b64a0cc268ec268529161afc84378..028588fb3300d266e74bf1a00707f3a5e6aed884 100644 (file)
@@ -840,6 +840,7 @@ typedef struct XmlSerialize
    XmlOptionType xmloption;    /* DOCUMENT or CONTENT */
    Node       *expr;
    TypeName   *typeName;
+   bool        indent;         /* [NO] INDENT */
    int         location;       /* token location, or -1 if unknown */
 } XmlSerialize;
 
index 4220c63ab726aba7535f19433683f12ac335924e..8fb5b4b919b837b2fd39058774ff5b17f1447f21 100644 (file)
@@ -1464,7 +1464,7 @@ typedef enum XmlExprOp
    IS_XMLPARSE,                /* XMLPARSE(text, is_doc, preserve_ws) */
    IS_XMLPI,                   /* XMLPI(name [, args]) */
    IS_XMLROOT,                 /* XMLROOT(xml, version, standalone) */
-   IS_XMLSERIALIZE,            /* XMLSERIALIZE(is_document, xmlval) */
+   IS_XMLSERIALIZE,            /* XMLSERIALIZE(is_document, xmlval, indent) */
    IS_DOCUMENT                 /* xmlval IS DOCUMENT */
 } XmlExprOp;
 
@@ -1489,6 +1489,8 @@ typedef struct XmlExpr
    List       *args;
    /* DOCUMENT or CONTENT */
    XmlOptionType xmloption pg_node_attr(query_jumble_ignore);
+   /* INDENT option for XMLSERIALIZE */
+   bool        indent;
    /* target type/typmod for XMLSERIALIZE */
    Oid         type pg_node_attr(query_jumble_ignore);
    int32       typmod pg_node_attr(query_jumble_ignore);
index bb36213e6f4eff55b0384229e21524a4e906b6a1..753e9ee174250a9ea976e1c7b1403aa5e538d7d7 100644 (file)
@@ -205,6 +205,7 @@ PG_KEYWORD("in", IN_P, RESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("include", INCLUDE, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("including", INCLUDING, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("increment", INCREMENT, UNRESERVED_KEYWORD, BARE_LABEL)
+PG_KEYWORD("indent", INDENT, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("index", INDEX, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("indexes", INDEXES, UNRESERVED_KEYWORD, BARE_LABEL)
 PG_KEYWORD("inherit", INHERIT, UNRESERVED_KEYWORD, BARE_LABEL)
index 311da06cd6f22655f5d05d4217700133f25bde68..224f6d75ffde32f96286dc4e3f99bbfaa2bd7f3b 100644 (file)
@@ -77,7 +77,8 @@ extern xmltype *xmlparse(text *data, XmlOptionType xmloption_arg, bool preserve_
 extern xmltype *xmlpi(const char *target, text *arg, bool arg_is_null, bool *result_is_null);
 extern xmltype *xmlroot(xmltype *data, text *version, int standalone);
 extern bool xml_is_document(xmltype *arg);
-extern text *xmltotext_with_xmloption(xmltype *data, XmlOptionType xmloption_arg);
+extern text *xmltotext_with_options(xmltype *data, XmlOptionType xmloption_arg,
+                                   bool indent);
 extern char *escape_xml(const char *str);
 
 extern char *map_sql_identifier_to_xml_name(const char *ident, bool fully_escaped, bool escape_period);
index ad852dc2f718334a36985472570303c7445a276b..398345ca67fe69939fec69b6ee6650fd708818a8 100644 (file)
@@ -486,6 +486,192 @@ SELECT xmlserialize(content 'good' as char(10));
 
 SELECT xmlserialize(document 'bad' as text);
 ERROR:  not an XML document
+-- indent
+SELECT xmlserialize(DOCUMENT '42' AS text INDENT);
+      xmlserialize       
+-------------------------
                  +
+                   +
+     42+
+                  +
+                  +
+(1 row)
+
+SELECT xmlserialize(CONTENT  '42' AS text INDENT);
+      xmlserialize       
+-------------------------
                  +
+                   +
+     42+
+                  +
+(1 row)
+
+-- no indent
+SELECT xmlserialize(DOCUMENT '42' AS text NO INDENT);
+               xmlserialize                
+-------------------------------------------
42
+(1 row)
+
+SELECT xmlserialize(CONTENT  '42' AS text NO INDENT);
+               xmlserialize                
+-------------------------------------------
42
+(1 row)
+
+-- indent non singly-rooted xml
+SELECT xmlserialize(DOCUMENT '7342' AS text INDENT);
+ERROR:  not an XML document
+SELECT xmlserialize(CONTENT  '7342' AS text INDENT);
+     xmlserialize      
+-----------------------
73        +
                +
+   42+
+(1 row)
+
+-- indent non singly-rooted xml with mixed contents
+SELECT xmlserialize(DOCUMENT 'text node73text node42' AS text INDENT);
+ERROR:  not an XML document
+SELECT xmlserialize(CONTENT  'text node73text node42' AS text INDENT);
+      xmlserialize      
+------------------------
+ text node             +
73text node+
                 +
+   42 +
+(1 row)
+
+-- indent singly-rooted xml with mixed contents
+SELECT xmlserialize(DOCUMENT '42text node73' AS text INDENT);
+                xmlserialize                 
+---------------------------------------------
                                      +
+                                       +
+     42                    +
+     text node73+
+                                      +
+                                      +
+(1 row)
+
+SELECT xmlserialize(CONTENT  '42text node73' AS text INDENT);
+                xmlserialize                 
+---------------------------------------------
                                      +
+                                       +
+     42                    +
+     text node73+
+                                      +
+(1 row)
+
+-- indent empty string
+SELECT xmlserialize(DOCUMENT '' AS text INDENT);
+ERROR:  not an XML document
+SELECT xmlserialize(CONTENT  '' AS text INDENT);
+ xmlserialize 
+--------------
+(1 row)
+
+-- whitespaces
+SELECT xmlserialize(DOCUMENT '  ' AS text INDENT);
+ERROR:  not an XML document
+SELECT xmlserialize(CONTENT  '  ' AS text INDENT);
+ xmlserialize 
+--------------
+   
+(1 row)
+
+-- indent null
+SELECT xmlserialize(DOCUMENT NULL AS text INDENT);
+ xmlserialize 
+--------------
+(1 row)
+
+SELECT xmlserialize(CONTENT  NULL AS text INDENT);
+ xmlserialize 
+--------------
+(1 row)
+
+-- indent with XML declaration
+SELECT xmlserialize(DOCUMENT '73' AS text INDENT);
+              xmlserialize              
+----------------------------------------
+
                                 +
+                                  +
+     73                     +
+                                 +
+                                 +
+(1 row)
+
+SELECT xmlserialize(CONTENT  '73' AS text INDENT);
+   xmlserialize    
+-------------------
            +
+             +
+     73+
+            +
+(1 row)
+
+-- indent containing DOCTYPE declaration
+SELECT xmlserialize(DOCUMENT '' AS text INDENT);
+ xmlserialize 
+--------------
+ +
+(1 row)
+
+ xmlserialize 
+--------------
+ +
+(1 row)
+
+-- indent xml with empty element
+SELECT xmlserialize(DOCUMENT '' AS text INDENT);
+ xmlserialize 
+--------------
       +
+       +
+       +
+(1 row)
+
+SELECT xmlserialize(CONTENT  '' AS text INDENT);
+ xmlserialize 
+--------------
       +
+       +
+(1 row)
+
+-- 'no indent' = not using 'no indent'
+SELECT xmlserialize(DOCUMENT '42' AS text) = xmlserialize(DOCUMENT '42' AS text NO INDENT);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT xmlserialize(CONTENT  '42' AS text) = xmlserialize(CONTENT '42' AS text NO INDENT);
+ ?column? 
+----------
+ t
+(1 row)
+
 SELECT xml 'bar' IS DOCUMENT;
  ?column? 
 ----------
index 70fe34a04f4a1b0c9b78f5ab09ca6fead818ea5f..63b779470ff6dcd09f4f4ac3b0b68da0ee622784 100644 (file)
@@ -309,6 +309,140 @@ ERROR:  unsupported XML feature
 LINE 1: SELECT xmlserialize(document 'bad' as text);
                                      ^
 DETAIL:  This functionality requires the server to be built with libxml support.
+-- indent
+SELECT xmlserialize(DOCUMENT '42' AS text INDENT);
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmlserialize(DOCUMENT '42<...
+                                     ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+SELECT xmlserialize(CONTENT  '42' AS text INDENT);
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmlserialize(CONTENT  '42<...
+                                     ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+-- no indent
+SELECT xmlserialize(DOCUMENT '42' AS text NO INDENT);
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmlserialize(DOCUMENT '42<...
+                                     ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+SELECT xmlserialize(CONTENT  '42' AS text NO INDENT);
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmlserialize(CONTENT  '42<...
+                                     ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+-- indent non singly-rooted xml
+SELECT xmlserialize(DOCUMENT '7342' AS text INDENT);
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmlserialize(DOCUMENT '734...
+                                     ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+SELECT xmlserialize(CONTENT  '7342' AS text INDENT);
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmlserialize(CONTENT  '734...
+                                     ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+-- indent non singly-rooted xml with mixed contents
+SELECT xmlserialize(DOCUMENT 'text node73text node42' AS text INDENT);
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmlserialize(DOCUMENT 'text node73text nod...
+                                     ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+SELECT xmlserialize(CONTENT  'text node73text node42' AS text INDENT);
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmlserialize(CONTENT  'text node73text nod...
+                                     ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+-- indent singly-rooted xml with mixed contents
+SELECT xmlserialize(DOCUMENT '42text node73' AS text INDENT);
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmlserialize(DOCUMENT '42<...
+                                     ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+SELECT xmlserialize(CONTENT  '42text node73' AS text INDENT);
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmlserialize(CONTENT  '42<...
+                                     ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+-- indent empty string
+SELECT xmlserialize(DOCUMENT '' AS text INDENT);
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmlserialize(DOCUMENT '' AS text INDENT);
+                                     ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+SELECT xmlserialize(CONTENT  '' AS text INDENT);
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmlserialize(CONTENT  '' AS text INDENT);
+                                     ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+-- whitespaces
+SELECT xmlserialize(DOCUMENT '  ' AS text INDENT);
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmlserialize(DOCUMENT '  ' AS text INDENT);
+                                     ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+SELECT xmlserialize(CONTENT  '  ' AS text INDENT);
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmlserialize(CONTENT  '  ' AS text INDENT);
+                                     ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+-- indent null
+SELECT xmlserialize(DOCUMENT NULL AS text INDENT);
+ xmlserialize 
+--------------
+(1 row)
+
+SELECT xmlserialize(CONTENT  NULL AS text INDENT);
+ xmlserialize 
+--------------
+(1 row)
+
+-- indent with XML declaration
+SELECT xmlserialize(DOCUMENT '73' AS text INDENT);
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmlserialize(DOCUMENT '
+                                     ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+SELECT xmlserialize(CONTENT  '73' AS text INDENT);
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmlserialize(CONTENT  '
+                                     ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+-- indent containing DOCTYPE declaration
+SELECT xmlserialize(DOCUMENT '' AS text INDENT);
+ERROR:  unsupported XML feature
+                                     ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+ERROR:  unsupported XML feature
+                                     ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+-- indent xml with empty element
+SELECT xmlserialize(DOCUMENT '' AS text INDENT);
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmlserialize(DOCUMENT '' AS tex...
+                                     ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+SELECT xmlserialize(CONTENT  '' AS text INDENT);
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmlserialize(CONTENT  '' AS tex...
+                                     ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+-- 'no indent' = not using 'no indent'
+SELECT xmlserialize(DOCUMENT '42' AS text) = xmlserialize(DOCUMENT '42' AS text NO INDENT);
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmlserialize(DOCUMENT '42<...
+                                     ^
+DETAIL:  This functionality requires the server to be built with libxml support.
+SELECT xmlserialize(CONTENT  '42' AS text) = xmlserialize(CONTENT '42' AS text NO INDENT);
+ERROR:  unsupported XML feature
+LINE 1: SELECT xmlserialize(CONTENT  '42<...
+                                     ^
+DETAIL:  This functionality requires the server to be built with libxml support.
 SELECT xml 'bar' IS DOCUMENT;
 ERROR:  unsupported XML feature
 LINE 1: SELECT xml 'bar' IS DOCUMENT;
index 4f029d007258f056910b1f378ed0b6dcca1272e3..43c2558352a3a21bb7c0a32a4508291507fbd3ca 100644 (file)
@@ -466,6 +466,192 @@ SELECT xmlserialize(content 'good' as char(10));
 
 SELECT xmlserialize(document 'bad' as text);
 ERROR:  not an XML document
+-- indent
+SELECT xmlserialize(DOCUMENT '42' AS text INDENT);
+      xmlserialize       
+-------------------------
                  +
+                   +
+     42+
+                  +
+                  +
+(1 row)
+
+SELECT xmlserialize(CONTENT  '42' AS text INDENT);
+      xmlserialize       
+-------------------------
                  +
+                   +
+     42+
+                  +
+(1 row)
+
+-- no indent
+SELECT xmlserialize(DOCUMENT '42' AS text NO INDENT);
+               xmlserialize                
+-------------------------------------------
42
+(1 row)
+
+SELECT xmlserialize(CONTENT  '42' AS text NO INDENT);
+               xmlserialize                
+-------------------------------------------
42
+(1 row)
+
+-- indent non singly-rooted xml
+SELECT xmlserialize(DOCUMENT '7342' AS text INDENT);
+ERROR:  not an XML document
+SELECT xmlserialize(CONTENT  '7342' AS text INDENT);
+     xmlserialize      
+-----------------------
73        +
                +
+   42+
+(1 row)
+
+-- indent non singly-rooted xml with mixed contents
+SELECT xmlserialize(DOCUMENT 'text node73text node42' AS text INDENT);
+ERROR:  not an XML document
+SELECT xmlserialize(CONTENT  'text node73text node42' AS text INDENT);
+      xmlserialize      
+------------------------
+ text node             +
73text node+
                 +
+   42 +
+(1 row)
+
+-- indent singly-rooted xml with mixed contents
+SELECT xmlserialize(DOCUMENT '42text node73' AS text INDENT);
+                xmlserialize                 
+---------------------------------------------
                                      +
+                                       +
+     42                    +
+     text node73+
+                                      +
+                                      +
+(1 row)
+
+SELECT xmlserialize(CONTENT  '42text node73' AS text INDENT);
+                xmlserialize                 
+---------------------------------------------
                                      +
+                                       +
+     42                    +
+     text node73+
+                                      +
+(1 row)
+
+-- indent empty string
+SELECT xmlserialize(DOCUMENT '' AS text INDENT);
+ERROR:  not an XML document
+SELECT xmlserialize(CONTENT  '' AS text INDENT);
+ xmlserialize 
+--------------
+(1 row)
+
+-- whitespaces
+SELECT xmlserialize(DOCUMENT '  ' AS text INDENT);
+ERROR:  not an XML document
+SELECT xmlserialize(CONTENT  '  ' AS text INDENT);
+ xmlserialize 
+--------------
+   
+(1 row)
+
+-- indent null
+SELECT xmlserialize(DOCUMENT NULL AS text INDENT);
+ xmlserialize 
+--------------
+(1 row)
+
+SELECT xmlserialize(CONTENT  NULL AS text INDENT);
+ xmlserialize 
+--------------
+(1 row)
+
+-- indent with XML declaration
+SELECT xmlserialize(DOCUMENT '73' AS text INDENT);
+              xmlserialize              
+----------------------------------------
+
                                 +
+                                  +
+     73                     +
+                                 +
+                                 +
+(1 row)
+
+SELECT xmlserialize(CONTENT  '73' AS text INDENT);
+   xmlserialize    
+-------------------
            +
+             +
+     73+
+            +
+(1 row)
+
+-- indent containing DOCTYPE declaration
+SELECT xmlserialize(DOCUMENT '' AS text INDENT);
+ xmlserialize 
+--------------
+ +
+(1 row)
+
+ xmlserialize 
+--------------
+ +
+(1 row)
+
+-- indent xml with empty element
+SELECT xmlserialize(DOCUMENT '' AS text INDENT);
+ xmlserialize 
+--------------
       +
+       +
+       +
+(1 row)
+
+SELECT xmlserialize(CONTENT  '' AS text INDENT);
+ xmlserialize 
+--------------
       +
+       +
+(1 row)
+
+-- 'no indent' = not using 'no indent'
+SELECT xmlserialize(DOCUMENT '42' AS text) = xmlserialize(DOCUMENT '42' AS text NO INDENT);
+ ?column? 
+----------
+ t
+(1 row)
+
+SELECT xmlserialize(CONTENT  '42' AS text) = xmlserialize(CONTENT '42' AS text NO INDENT);
+ ?column? 
+----------
+ t
+(1 row)
+
 SELECT xml 'bar' IS DOCUMENT;
  ?column? 
 ----------
index 24e40d26539c88b56a599b32edb2941571f1c1e5..a591eea2e5d14dec4acf4501d59ddfd8d6d70b25 100644 (file)
@@ -132,6 +132,42 @@ SELECT xmlserialize(content data as character varying(20)) FROM xmltest;
 SELECT xmlserialize(content 'good' as char(10));
 SELECT xmlserialize(document 'bad' as text);
 
+-- indent
+SELECT xmlserialize(DOCUMENT '42' AS text INDENT);
+SELECT xmlserialize(CONTENT  '42' AS text INDENT);
+-- no indent
+SELECT xmlserialize(DOCUMENT '42' AS text NO INDENT);
+SELECT xmlserialize(CONTENT  '42' AS text NO INDENT);
+-- indent non singly-rooted xml
+SELECT xmlserialize(DOCUMENT '7342' AS text INDENT);
+SELECT xmlserialize(CONTENT  '7342' AS text INDENT);
+-- indent non singly-rooted xml with mixed contents
+SELECT xmlserialize(DOCUMENT 'text node73text node42' AS text INDENT);
+SELECT xmlserialize(CONTENT  'text node73text node42' AS text INDENT);
+-- indent singly-rooted xml with mixed contents
+SELECT xmlserialize(DOCUMENT '42text node73' AS text INDENT);
+SELECT xmlserialize(CONTENT  '42text node73' AS text INDENT);
+-- indent empty string
+SELECT xmlserialize(DOCUMENT '' AS text INDENT);
+SELECT xmlserialize(CONTENT  '' AS text INDENT);
+-- whitespaces
+SELECT xmlserialize(DOCUMENT '  ' AS text INDENT);
+SELECT xmlserialize(CONTENT  '  ' AS text INDENT);
+-- indent null
+SELECT xmlserialize(DOCUMENT NULL AS text INDENT);
+SELECT xmlserialize(CONTENT  NULL AS text INDENT);
+-- indent with XML declaration
+SELECT xmlserialize(DOCUMENT '73' AS text INDENT);
+SELECT xmlserialize(CONTENT  '73' AS text INDENT);
+-- indent containing DOCTYPE declaration
+SELECT xmlserialize(DOCUMENT '' AS text INDENT);
+-- indent xml with empty element
+SELECT xmlserialize(DOCUMENT '' AS text INDENT);
+SELECT xmlserialize(CONTENT  '' AS text INDENT);
+-- 'no indent' = not using 'no indent'
+SELECT xmlserialize(DOCUMENT '42' AS text) = xmlserialize(DOCUMENT '42' AS text NO INDENT);
+SELECT xmlserialize(CONTENT  '42' AS text) = xmlserialize(CONTENT '42' AS text NO INDENT);
 
 SELECT xml 'bar' IS DOCUMENT;
 SELECT xml 'barfoo' IS DOCUMENT;