diff --git a/doc/src/sgml/datatype.sgml b/doc/src/sgml/datatype.sgml index 467b49b199..4df8bd1b64 100644 --- a/doc/src/sgml/datatype.sgml +++ b/doc/src/sgml/datatype.sgml @@ -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 character, character varying, or @@ -4470,6 +4470,13 @@ XMLSERIALIZE ( { DOCUMENT | CONTENT } value AS + + 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 diff --git a/src/backend/catalog/sql_features.txt b/src/backend/catalog/sql_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 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 diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 19351fe34b..9cb9625ce9 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -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; 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, 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 diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.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, 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; diff --git a/src/backend/utils/adt/xml.c b/src/backend/utils/adt/xml.c index 079bcb1208..15adbd6a01 100644 --- a/src/backend/utils/adt/xml.c +++ b/src/backend/utils/adt/xml.c @@ -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"))); + } - /* It's actually binary compatible, save for the above check. */ - return (text *) data; + /* 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); + + /* + * 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); diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 309aed3703..b2eed22d46 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -57,6 +57,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 202303141 +#define CATALOG_VERSION_NO 202303151 #endif 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..224f6d75ff 100644 --- a/src/include/utils/xml.h +++ b/src/include/utils/xml.h @@ -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); diff --git a/src/test/regress/expected/xml.out b/src/test/regress/expected/xml.out index ad852dc2f7..398345ca67 100644 --- a/src/test/regress/expected/xml.out +++ b/src/test/regress/expected/xml.out @@ -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) + +SELECT xmlserialize(CONTENT '' AS text INDENT); + 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? ---------- diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out index 70fe34a04f..63b779470f 100644 --- a/src/test/regress/expected/xml_1.out +++ b/src/test/regress/expected/xml_1.out @@ -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 '73' AS text INDENT); +ERROR: unsupported XML feature +LINE 1: SELECT xmlserialize(CONTENT '' AS text INDENT); +ERROR: unsupported XML feature +LINE 1: SELECT xmlserialize(DOCUMENT '' AS text INDE... + ^ +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 INDE... + ^ +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; diff --git a/src/test/regress/expected/xml_2.out b/src/test/regress/expected/xml_2.out index 4f029d0072..43c2558352 100644 --- a/src/test/regress/expected/xml_2.out +++ b/src/test/regress/expected/xml_2.out @@ -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) + +SELECT xmlserialize(CONTENT '' AS text INDENT); + 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? ---------- diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql index 24e40d2653..a591eea2e5 100644 --- a/src/test/regress/sql/xml.sql +++ b/src/test/regress/sql/xml.sql @@ -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); +SELECT xmlserialize(CONTENT '' 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;