Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1paMlm-0001o7-Eg for pgsql-hackers@arkaria.postgresql.org; Thu, 09 Mar 2023 20:21:46 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1paMll-0002B8-AB for pgsql-hackers@arkaria.postgresql.org; Thu, 09 Mar 2023 20:21:45 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1paMlk-0002Az-On for pgsql-hackers@lists.postgresql.org; Thu, 09 Mar 2023 20:21:45 +0000 Received: from sss.pgh.pa.us ([66.207.139.130]) by makus.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1paMlh-00013b-BB for pgsql-hackers@lists.postgresql.org; Thu, 09 Mar 2023 20:21:43 +0000 Received: from sss1.sss.pgh.pa.us (localhost [127.0.0.1]) by sss.pgh.pa.us (8.15.2/8.15.2) with ESMTP id 329KLa2g532260; Thu, 9 Mar 2023 15:21:36 -0500 From: Tom Lane To: Peter Smith cc: Jim Jones , Peter Eisentraut , Nikolay Samokhvalov , PostgreSQL Hackers , Andrey Borodin Subject: Re: [PATCH] Add pretty-printed XML output option In-reply-to: References: <2f5df461-dad8-6d7d-4568-08e10608a69b@uni-muenster.de> <62643598-b80d-90f4-8901-b85dccadf4c4@enterprisedb.com> <4c2efca5-8257-2424-6687-07c65e319e95@uni-muenster.de> <2eac4d78-2b20-e8ec-54cc-22a88653a1cc@uni-muenster.de> <6cb296fe-4b49-3691-633e-2e2366997b04@uni-muenster.de> <3baf8f25-3bd7-7db5-0b31-e4c92b6269ba@enterprisedb.com> <1cbe1087-6acb-2b0a-dc9b-0a2d5b99e466@uni-muenster.de> Comments: In-reply-to Peter Smith message dated "Fri, 24 Feb 2023 09:30:59 +1100" MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="----- =_aaaaaaaaaa0" Content-ID: <532143.1678393257.0@sss.pgh.pa.us> Date: Thu, 09 Mar 2023 15:21:36 -0500 Message-ID: <532259.1678393296@sss.pgh.pa.us> List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk ------- =_aaaaaaaaaa0 Content-Type: text/plain; charset="us-ascii" Content-ID: <532143.1678393257.1@sss.pgh.pa.us> Content-Transfer-Encoding: quoted-printable Peter Smith writes: > The patch v19 LGTM. I've looked through this now, and have some minor complaints and a major one. The major one is that it doesn't work for XML that doesn't satisfy IS DOCUMENT. For example, regression=3D# select '42'::xml i= s document; ?column? = ---------- f (1 row) regression=3D# select xmlserialize (content '42' as text); xmlserialize = ------------------------------------------- 42 (1 row) regression=3D# select xmlserialize (content '42' as text indent); ERROR: invalid XML document DETAIL: line 1: Extra content at the end of the document 42 ^ This is not what the documentation promises, and I don't think it's good enough --- the SQL spec has no restriction saying you can't use INDENT with CONTENT. I tried adjusting things so that we call xml_parse() with the appropriate DOCUMENT or CONTENT xmloption flag, but all that got me was empty output (except for a document header). It seems like xmlDocDumpFormatMemory is not the thing to use, at least not in the CONTENT case. But libxml2 has a few other "dump" functions, so maybe we can use a different one? I see we are using xmlNodeDump elsewhere, and that has a format option, so maybe there's a way forward there. A lesser issue is that INDENT tacks on a document header (XML declaration) whether there was one or not. I'm not sure whether that's an appropriate thing to do in the DOCUMENT case, but it sure seems weird in the CONTENT case. We have code that can strip off the header again, but we need to figure out exactly when to apply it. I also suspect that it's outright broken to attach a header claiming the data is now in UTF8 encoding. If the database encoding isn't UTF8, then either that's a lie or we now have an encoding violation. Another thing that's mildly irking me is that the current factorization of this code will result in xml_parse'ing the data twice, if you have both DOCUMENT and INDENT specified. We could consider avoiding that if we merged the indentation functionality into xmltotext_with_xmloption, but it's probably premature to do so when we haven't figured out how to get the output right --- we might end up needing two xml_parse calls anyway with different parameters, perhaps. I also had a bunch of cosmetic complaints (mostly around this having a bad case of add-at-the-end-itis), which I've cleaned up in the attached v20. This doesn't address any of the above, however. regards, tom lane ------- =_aaaaaaaaaa0 Content-Type: text/x-diff; name="v20-0001-Add-pretty-printed-XML-output-option.patch"; charset="us-ascii" Content-ID: <532143.1678393257.2@sss.pgh.pa.us> Content-Description: v20-0001-Add-pretty-printed-XML-output-option.patch Content-Transfer-Encoding: quoted-printable diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml index 467b49b199..53d59662b9 100644 --- a/doc/src/sgml/datatype.sgml +++ b/doc/src/sgml/datatype.sgml @@ -4460,14 +4460,18 @@ 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 character, character varying, or text (or an alias for one of those). Again, according to the SQL standard, this is the only way to convert between type xml and character types, but PostgreSQL also allows - you to simply cast the value. + you to simply cast the value. The option INDENT allows t= o + indent the serialized xml output - the default is NO INDENT. + It is designed to indent XML strings of type DOCUMENT, b= ut it can also + be used with CONTENT as long as value + contains a well-formed XML. = diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sq= l_features.txt index 0fb9ab7533..bb4c135a7f 100644 --- a/src/backend/catalog/sql_features.txt +++ b/src/backend/catalog/sql_features.txt @@ -621,7 +621,7 @@ X061 XMLParse: character string input and DOCUMENT opt= ion 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 YE= S = X071 XMLSerialize: character string serialization and DOCUMENT option Y= ES = X072 XMLSerialize: character string serialization YES = diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/= execExprInterp.c index 19351fe34b..3dcd15d5f0 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -3829,6 +3829,7 @@ ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op) { Datum *argvalue =3D op->d.xmlexpr.argvalue; bool *argnull =3D op->d.xmlexpr.argnull; + text *result; = /* argument type is known to be xml */ Assert(list_length(xexpr->args) =3D=3D 1); @@ -3837,8 +3838,12 @@ ExecEvalXmlExpr(ExprState *state, ExprEvalStep *op) return; value =3D argvalue[0]; = - *op->resvalue =3D PointerGetDatum(xmltotext_with_xmloption(DatumGetXm= lP(value), - xexpr->xmloption)); + result =3D xmltotext_with_xmloption(DatumGetXmlP(value), + xexpr->xmloption); + if (xexpr->indent) + result =3D xmlserialize_indent(result); + + *op->resvalue =3D PointerGetDatum(result); *op->resnull =3D false; } break; diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index a0138382a1..efe88ccf9d 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -613,7 +613,7 @@ static Node *makeRecursiveViewSelect(char *relname, Li= st *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, Li= st *aliases, Node *query); HANDLER HAVING HEADER_P HOLD HOUR_P = IDENTITY_P IF_P ILIKE IMMEDIATE IMMUTABLE IMPLICIT_P IMPORT_P IN_P INCLU= DE - INCLUDING INCREMENT INDEX INDEXES INHERIT INHERITS INITIALLY INLINE_P + INCLUDING INCREMENT INDENT INDEX INDEXES INHERIT INHERITS INITIALLY INLI= NE_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: $$ =3D 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_in= dent_option ')' { XmlSerialize *n =3D makeNode(XmlSerialize); = n->xmloption =3D $3; n->expr =3D $4; n->typeName =3D $6; + n->indent =3D $7; n->location =3D @1; $$ =3D (Node *) n; } @@ -15592,6 +15593,11 @@ document_or_content: DOCUMENT_P { $$ =3D XML= OPTION_DOCUMENT; } | CONTENT_P { $$ =3D XMLOPTION_CONTENT; } ; = +xml_indent_option: INDENT { $$ =3D true; } + | NO INDENT { $$ =3D false; } + | /*EMPTY*/ { $$ =3D false; } + ; + xml_whitespace_option: PRESERVE WHITESPACE_P { $$ =3D true; } | STRIP_P WHITESPACE_P { $$ =3D false; } | /*EMPTY*/ { $$ =3D 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 diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_ex= pr.c index 78221d2e0f..2331417552 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -2331,6 +2331,7 @@ transformXmlSerialize(ParseState *pstate, XmlSeriali= ze *xs) typenameTypeIdAndMod(pstate, xs->typeName, &targetType, &targetTypmod); = xexpr->xmloption =3D xs->xmloption; + xexpr->indent =3D xs->indent; xexpr->location =3D xs->location; /* We actually only need these to be able to parse back the expression. = */ xexpr->type =3D targetType; diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c index 079bcb1208..4d2549ed03 100644 --- a/src/backend/utils/adt/xml.c +++ b/src/backend/utils/adt/xml.c @@ -631,6 +631,39 @@ xmltotext_with_xmloption(xmltype *data, XmlOptionType= xmloption_arg) } = = +text * +xmlserialize_indent(text *data) +{ +#ifdef USE_LIBXML + text *result; + xmlDocPtr doc; + xmlChar *xmlbuf; + int nbytes; + + doc =3D xml_parse(data, XMLOPTION_DOCUMENT, false, + GetDatabaseEncoding(), NULL); + Assert(doc); + + /* Reformat with indenting requested */ + xmlDocDumpFormatMemory(doc, &xmlbuf, &nbytes, 1); + + xmlFreeDoc(doc); + + if (!nbytes) + elog(ERROR, "could not indent the given XML document"); + + result =3D cstring_to_text_with_len((const char *) xmlbuf, nbytes); + + xmlFree(xmlbuf); + + return result; +#else + NO_XML_SUPPORT(); + return NULL; +#endif +} + + xmltype * xmlelement(XmlExpr *xexpr, Datum *named_argvalue, bool *named_argnull, diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes= .h index 371aa0ffc5..028588fb33 100644 --- a/src/include/nodes/parsenodes.h +++ b/src/include/nodes/parsenodes.h @@ -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; = diff --git a/src/include/nodes/primnodes.h b/src/include/nodes/primnodes.h index 4220c63ab7..8fb5b4b919 100644 --- a/src/include/nodes/primnodes.h +++ b/src/include/nodes/primnodes.h @@ -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); diff --git a/src/include/parser/kwlist.h b/src/include/parser/kwlist.h index bb36213e6f..753e9ee174 100644 --- a/src/include/parser/kwlist.h +++ b/src/include/parser/kwlist.h @@ -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) diff --git a/src/include/utils/xml.h b/src/include/utils/xml.h index 311da06cd6..a1dfe4c631 100644 --- a/src/include/utils/xml.h +++ b/src/include/utils/xml.h @@ -78,6 +78,7 @@ extern xmltype *xmlpi(const char *target, text *arg, boo= l arg_is_null, bool *res extern xmltype *xmlroot(xmltype *data, text *version, int standalone); extern bool xml_is_document(xmltype *arg); extern text *xmltotext_with_xmloption(xmltype *data, XmlOptionType xmlopt= ion_arg); +extern text *xmlserialize_indent(text *data); extern char *escape_xml(const char *str); = extern char *map_sql_identifier_to_xml_name(const char *ident, bool fully= _escaped, bool escape_period); diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected= /xml.out index ad852dc2f7..ddbf0ca16b 100644 --- a/src/test/regress/expected/xml.out +++ b/src/test/regress/expected/xml.out @@ -486,6 +486,112 @@ 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) + +\set VERBOSITY terse +-- indent malformed xml +SELECT xmlserialize(DOCUMENT '42= ' AS text INDENT); +ERROR: not an XML document +SELECT xmlserialize(CONTENT '42= ' AS text INDENT); +ERROR: invalid XML document +-- indent empty string +SELECT xmlserialize(DOCUMENT '' AS text INDENT); +ERROR: not an XML document +SELECT xmlserialize(CONTENT '' AS text INDENT); +ERROR: invalid XML document +-- whitespaces +SELECT xmlserialize(DOCUMENT ' ' AS text INDENT); +ERROR: not an XML document +SELECT xmlserialize(CONTENT ' ' AS text INDENT); +ERROR: invalid XML document +\set VERBOSITY default +-- indent null +SELECT xmlserialize(DOCUMENT NULL AS text INDENT); + xmlserialize = +-------------- + = +(1 row) + +SELECT xmlserialize(CONTENT NULL AS text INDENT); + xmlserialize = +-------------- + = +(1 row) + +-- indent different encoding (returns UTF-8) +SELECT xmlserialize(DOCUMENT '42' AS text INDENT); + xmlserialize = +---------------------------------------- + + + + + + + 42 + + + + + + = +(1 row) + +SELECT xmlserialize(CONTENT '42' AS text INDENT); + xmlserialize = +---------------------------------------- + + + + + + + 42 + + + + + + = +(1 row) + +-- 'no indent' =3D not using 'no indent' +SELECT xmlserialize(DOCUMENT '42= ' AS text) =3D xmlserialize(DOCUMENT '42' AS text NO INDENT); + ?column? = +---------- + t +(1 row) + +SELECT xmlserialize(CONTENT '42'= AS text) =3D xmlserialize(CONTENT '42<= /foo>' AS text NO INDENT); + ?column? = +---------- + t +(1 row) + SELECT xml 'bar' IS DOCUMENT; ?column? = ---------- diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expect= ed/xml_1.out index 70fe34a04f..2944f84103 100644 --- a/src/test/regress/expected/xml_1.out +++ b/src/test/regress/expected/xml_1.out @@ -309,6 +309,80 @@ ERROR: unsupported XML feature LINE 1: SELECT xmlserialize(document 'bad' as text); ^ DETAIL: This functionality requires the server to be built with libxml s= upport. +-- 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 s= upport. +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 s= upport. +-- 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 s= upport. +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 s= upport. +\set VERBOSITY terse +-- indent malformed xml +SELECT xmlserialize(DOCUMENT '42= ' AS text INDENT); +ERROR: unsupported XML feature at character 30 +SELECT xmlserialize(CONTENT '42= ' AS text INDENT); +ERROR: unsupported XML feature at character 30 +-- indent empty string +SELECT xmlserialize(DOCUMENT '' AS text INDENT); +ERROR: unsupported XML feature at character 30 +SELECT xmlserialize(CONTENT '' AS text INDENT); +ERROR: unsupported XML feature at character 30 +-- whitespaces +SELECT xmlserialize(DOCUMENT ' ' AS text INDENT); +ERROR: unsupported XML feature at character 30 +SELECT xmlserialize(CONTENT ' ' AS text INDENT); +ERROR: unsupported XML feature at character 30 +\set VERBOSITY default +-- indent null +SELECT xmlserialize(DOCUMENT NULL AS text INDENT); + xmlserialize = +-------------- + = +(1 row) + +SELECT xmlserialize(CONTENT NULL AS text INDENT); + xmlserialize = +-------------- + = +(1 row) + +-- indent different encoding (returns UTF-8) +SELECT xmlserialize(DOCUMENT '42' AS text INDENT); +ERROR: unsupported XML feature +LINE 1: SELECT xmlserialize(DOCUMENT '42' AS text INDENT); +ERROR: unsupported XML feature +LINE 1: SELECT xmlserialize(CONTENT '42= ' AS text) =3D 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 s= upport. +SELECT xmlserialize(CONTENT '42'= AS text) =3D xmlserialize(CONTENT '42<= /foo>' AS text NO INDENT); +ERROR: unsupported XML feature +LINE 1: SELECT xmlserialize(CONTENT '42bar' IS DOCUMENT; ERROR: unsupported XML feature LINE 1: SELECT xml 'bar' IS DOCUMENT; diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expect= ed/xml_2.out index 4f029d0072..60dcb3d36a 100644 --- a/src/test/regress/expected/xml_2.out +++ b/src/test/regress/expected/xml_2.out @@ -466,6 +466,112 @@ 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) + +\set VERBOSITY terse +-- indent malformed xml +SELECT xmlserialize(DOCUMENT '42= ' AS text INDENT); +ERROR: not an XML document +SELECT xmlserialize(CONTENT '42= ' AS text INDENT); +ERROR: invalid XML document +-- indent empty string +SELECT xmlserialize(DOCUMENT '' AS text INDENT); +ERROR: not an XML document +SELECT xmlserialize(CONTENT '' AS text INDENT); +ERROR: invalid XML document +-- whitespaces +SELECT xmlserialize(DOCUMENT ' ' AS text INDENT); +ERROR: not an XML document +SELECT xmlserialize(CONTENT ' ' AS text INDENT); +ERROR: invalid XML document +\set VERBOSITY default +-- indent null +SELECT xmlserialize(DOCUMENT NULL AS text INDENT); + xmlserialize = +-------------- + = +(1 row) + +SELECT xmlserialize(CONTENT NULL AS text INDENT); + xmlserialize = +-------------- + = +(1 row) + +-- indent different encoding (returns UTF-8) +SELECT xmlserialize(DOCUMENT '42' AS text INDENT); + xmlserialize = +---------------------------------------- + + + + + + + 42 + + + + + + = +(1 row) + +SELECT xmlserialize(CONTENT '42' AS text INDENT); + xmlserialize = +---------------------------------------- + + + + + + + 42 + + + + + + = +(1 row) + +-- 'no indent' =3D not using 'no indent' +SELECT xmlserialize(DOCUMENT '42= ' AS text) =3D xmlserialize(DOCUMENT '42' AS text NO INDENT); + ?column? = +---------- + t +(1 row) + +SELECT xmlserialize(CONTENT '42'= AS text) =3D xmlserialize(CONTENT '42<= /foo>' AS text NO INDENT); + ?column? = +---------- + t +(1 row) + SELECT xml 'bar' IS DOCUMENT; ?column? = ---------- diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql index 24e40d2653..fea875adfd 100644 --- a/src/test/regress/sql/xml.sql +++ b/src/test/regress/sql/xml.sql @@ -132,6 +132,32 @@ 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); +\set VERBOSITY terse +-- indent malformed xml +SELECT xmlserialize(DOCUMENT '42= ' AS text INDENT); +SELECT xmlserialize(CONTENT '42= ' 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); +\set VERBOSITY default +-- indent null +SELECT xmlserialize(DOCUMENT NULL AS text INDENT); +SELECT xmlserialize(CONTENT NULL AS text INDENT); +-- indent different encoding (returns UTF-8) +SELECT xmlserialize(DOCUMENT '42' AS text INDENT); +SELECT xmlserialize(CONTENT '42' AS text INDENT); +-- 'no indent' =3D not using 'no indent' +SELECT xmlserialize(DOCUMENT '42= ' AS text) =3D xmlserialize(DOCUMENT '42' AS text NO INDENT); +SELECT xmlserialize(CONTENT '42'= AS text) =3D xmlserialize(CONTENT '42<= /foo>' AS text NO INDENT); = SELECT xml 'bar' IS DOCUMENT; SELECT xml 'barfoo' IS DOCUMENT; ------- =_aaaaaaaaaa0--