diff --git a/contrib/babelfishpg_tsql/runtime/functions.c b/contrib/babelfishpg_tsql/runtime/functions.c index 9cdd6093b9d..387e25da336 100644 --- a/contrib/babelfishpg_tsql/runtime/functions.c +++ b/contrib/babelfishpg_tsql/runtime/functions.c @@ -6376,28 +6376,23 @@ openxml_simple(PG_FUNCTION_ARGS) PG_FUNCTION_INFO_V1(bbf_xmlquery); +PG_FUNCTION_INFO_V1(bbf_xmlquery_ns); /* - * bbf_xmlquery - C implementation of XML .query() method + * bbf_xmlquery_internal - shared implementation for the .query() XML method. * - * Signature: - * sys.bbf_xmlquery(xpath_pattern TEXT, xml_element ANYELEMENT) - * - * Returns XML result of evaluating the XPath expression against the input. - * Returns empty XML if no nodes match. - * - * Validates: - * - Input must be XML type (or UDT based on XML) - * - QUOTED_IDENTIFIER must be ON + * Validates the input type and QUOTED_IDENTIFIER setting, then evaluates the + * XPath expression against the XML datum using the supplied namespace array + * (which may be empty). Returns the concatenated XML fragments, or the + * empty XML string if no nodes match. */ -Datum -bbf_xmlquery(PG_FUNCTION_ARGS) +static Datum +bbf_xmlquery_internal(FunctionCallInfo fcinfo, ArrayType *namespaces) { text *xpath_expr; Datum xml_datum; Oid arg_type; Oid immediate_base_type; - ArrayType *namespaces; Datum xpath_result; ArrayType *result_arr; Datum *elems; @@ -6441,13 +6436,11 @@ bbf_xmlquery(PG_FUNCTION_ARGS) "SET options are correct for XML data type methods."))); /* - * Call the built-in xpath(text, xml, text[][]) directly with an empty - * namespace array. Returns xml[] (array of XML fragments). - * - * TODO: when WITH XMLNAMESPACES is supported, populate this array with - * the declared (prefix, uri) pairs from the active namespace context. + * Call the built-in xpath(text, xml, text[][]). Returns xml[] (array of + * XML fragments). When invoked without WITH XMLNAMESPACES the caller + * passes an empty array so libxml2 only resolves prefixes already + * declared in the document. */ - namespaces = construct_empty_array(TEXTOID); xpath_result = DirectFunctionCall3(xpath, PointerGetDatum(xpath_expr), xml_datum, @@ -6486,3 +6479,35 @@ bbf_xmlquery(PG_FUNCTION_ARGS) PG_RETURN_XML_P((xmltype *) cstring_to_text_with_len(buf.data, buf.len)); } + +/* + * bbf_xmlquery - C implementation of XML .query() method (no namespaces). + * + * Signature: + * sys.bbf_xmlquery(xpath_pattern TEXT, xml_element ANYELEMENT) + */ +Datum +bbf_xmlquery(PG_FUNCTION_ARGS) +{ + ArrayType *namespaces = construct_empty_array(TEXTOID); + + return bbf_xmlquery_internal(fcinfo, namespaces); +} + +/* + * bbf_xmlquery_ns - C implementation of XML .query() with namespace support. + * + * Signature: + * sys.bbf_xmlquery(xpath_pattern TEXT, xml_element ANYELEMENT, nsarray TEXT[][]) + * + * Used by the WITH XMLNAMESPACES rewrite path. The ANTLR rewrite layer + * appends the namespace array as a 3rd argument when a WITH XMLNAMESPACES + * clause is in scope. + */ +Datum +bbf_xmlquery_ns(PG_FUNCTION_ARGS) +{ + ArrayType *namespaces = PG_GETARG_ARRAYTYPE_P(2); + + return bbf_xmlquery_internal(fcinfo, namespaces); +} diff --git a/contrib/babelfishpg_tsql/sql/sys_functions.sql b/contrib/babelfishpg_tsql/sql/sys_functions.sql index c250edbefbc..4571ff4db54 100644 --- a/contrib/babelfishpg_tsql/sql/sys_functions.sql +++ b/contrib/babelfishpg_tsql/sql/sys_functions.sql @@ -8,7 +8,8 @@ CREATE OR REPLACE FUNCTION sys.tsql_query_to_xml_sfunc( root_name text, elements boolean, xsinil boolean, - auto_metadata text + auto_metadata text, + ns_decls text ) RETURNS INTERNAL AS 'babelfishpg_tsql', 'tsql_query_to_xml_sfunc' LANGUAGE C STABLE; @@ -35,7 +36,8 @@ CREATE OR REPLACE AGGREGATE sys.tsql_select_for_xml_agg( root_name text, elements boolean, xsinil boolean, - auto_metadata text) + auto_metadata text, + ns_decls text) ( STYPE = INTERNAL, SFUNC = tsql_query_to_xml_sfunc, @@ -50,7 +52,8 @@ CREATE OR REPLACE AGGREGATE sys.tsql_select_for_xml_text_agg( root_name text, elements boolean, xsinil boolean, - auto_metadata text) + auto_metadata text, + ns_decls text) ( STYPE = INTERNAL, SFUNC = tsql_query_to_xml_sfunc, @@ -170,6 +173,91 @@ RETURNS XML AS 'babelfishpg_tsql', 'bbf_xmlquery' LANGUAGE C STABLE STRICT PARALLEL SAFE; +-- helper function for XML QUERY(xpath) with namespace support (used by WITH XMLNAMESPACES) +CREATE OR REPLACE FUNCTION sys.bbf_xmlquery(xpath_pattern TEXT, xml_element ANYELEMENT, nsarray TEXT[][]) +RETURNS XML +AS 'babelfishpg_tsql', 'bbf_xmlquery_ns' +LANGUAGE C STABLE STRICT PARALLEL SAFE; + +-- helper function for XML EXIST(xpath) with namespace support (used by WITH XMLNAMESPACES) +CREATE OR REPLACE FUNCTION sys.bbf_xmlexist(xpath_pattern TEXT, xml_element ANYELEMENT, nsarray TEXT[][]) +RETURNS sys.BIT +AS +$BODY$ +DECLARE + arg_datatype text; + arg_datatype_oid oid; + basetype oid; + pltsql_quoted_identifier text; +BEGIN + arg_datatype_oid := pg_typeof(xml_element)::oid; + arg_datatype := sys.translate_pg_type_to_tsql(arg_datatype_oid); + IF arg_datatype IS NULL THEN + basetype := sys.bbf_get_immediate_base_type_of_UDT(arg_datatype_oid); + arg_datatype := sys.translate_pg_type_to_tsql(basetype); + END IF; + + IF (arg_datatype != 'xml') THEN + RAISE EXCEPTION 'Cannot call methods on %.', arg_datatype; + END IF; + + pltsql_quoted_identifier := current_setting('babelfishpg_tsql.quoted_identifier'); + + IF (pltsql_quoted_identifier = 'off') THEN + RAISE EXCEPTION 'SELECT failed because the following SET options have incorrect settings: ''QUOTED_IDENTIFIER''. Verify that SET options are correct for XML data type methods.'; + END IF; + + RETURN (cardinality(xpath(xpath_pattern, xml_element, nsarray)) > 0)::int::sys.BIT; +END +$BODY$ +LANGUAGE plpgsql STABLE STRICT PARALLEL SAFE; + +-- helper function for XML VALUE(xpath) with namespace support (used by WITH XMLNAMESPACES) +CREATE OR REPLACE FUNCTION sys.bbf_xmlvalue(xpath_pattern TEXT, datatype TEXT, xml_element ANYELEMENT, nsarray TEXT[][]) +RETURNS sys.NVARCHAR +AS +$BODY$ +DECLARE + temp_datatype text; + temp_basetype oid; + result_set xml[]; + result sys.NVARCHAR; + pltsql_quoted_identifier text; +BEGIN + temp_datatype := sys.translate_pg_type_to_tsql(pg_typeof(xml_element)::oid); + IF temp_datatype IS NULL THEN + temp_basetype := sys.bbf_get_immediate_base_type_of_UDT(pg_typeof(xml_element)::oid); + temp_datatype := sys.translate_pg_type_to_tsql(temp_basetype); + END IF; + + IF (temp_datatype != 'xml') THEN + RAISE EXCEPTION 'Cannot call methods on %.', temp_datatype; + END IF; + + pltsql_quoted_identifier := current_setting('babelfishpg_tsql.quoted_identifier'); + + IF (pltsql_quoted_identifier = 'off') THEN + RAISE EXCEPTION 'SELECT failed because the following SET options have incorrect settings: ''QUOTED_IDENTIFIER''. Verify that SET options are correct for XML data type methods.'; + END IF; + + result_set := xpath(xpath_pattern, xml_element, nsarray); + IF (cardinality(result_set) > 1) THEN + RAISE EXCEPTION 'XML Value result is not a single value.'; + ELSIF (cardinality(result_set) = 0) THEN + RETURN NULL; + ELSE + result := (xpath('string(' || xpath_pattern || ')', xml_element, nsarray))[1]; + result := pg_catalog.replace(result, '<', '<'); + result := pg_catalog.replace(result, '>', '>'); + result := pg_catalog.replace(result, ''', ''''); + result := pg_catalog.replace(result, '"', '"'); + result := pg_catalog.replace(result, '&', '&'); + return result; + END IF; +END +$BODY$ +LANGUAGE plpgsql STABLE STRICT PARALLEL SAFE; + -- SELECT FOR JSON CREATE OR REPLACE FUNCTION sys.tsql_query_to_json_sfunc( state INTERNAL, diff --git a/contrib/babelfishpg_tsql/sql/upgrades/babelfishpg_tsql--5.6.0--5.7.0.sql b/contrib/babelfishpg_tsql/sql/upgrades/babelfishpg_tsql--5.6.0--5.7.0.sql index aa44425ae35..935e9104ab9 100644 --- a/contrib/babelfishpg_tsql/sql/upgrades/babelfishpg_tsql--5.6.0--5.7.0.sql +++ b/contrib/babelfishpg_tsql/sql/upgrades/babelfishpg_tsql--5.6.0--5.7.0.sql @@ -437,6 +437,149 @@ RETURNS SYS.SQL_VARIANT AS 'babelfishpg_tsql', 'objectpropertyex_internal' LANGUAGE C STABLE STRICT; +-- WITH XMLNAMESPACES support: extend FOR XML aggregate to carry namespace declarations +-- and add namespace-aware overloads for XML data type methods. +-- +-- The previous block above already migrated the aggregate from 7 args (legacy) to +-- 8 args (adding auto_metadata for FOR XML AUTO). We now drop those 8-arg +-- definitions and recreate at 9 args (adding ns_decls for WITH XMLNAMESPACES), +-- so the final shape matches sys_functions.sql. +CALL sys.babelfish_drop_deprecated_object('aggregate', 'sys', 'tsql_select_for_xml_agg', 'ANYELEMENT, int, text, boolean, text, boolean, boolean, text'); +CALL sys.babelfish_drop_deprecated_object('aggregate', 'sys', 'tsql_select_for_xml_text_agg', 'ANYELEMENT, int, text, boolean, text, boolean, boolean, text'); +CALL sys.babelfish_drop_deprecated_object('function', 'sys', 'tsql_query_to_xml_sfunc', 'INTERNAL, ANYELEMENT, int, text, boolean, text, boolean, boolean, text'); + +CREATE OR REPLACE FUNCTION sys.tsql_query_to_xml_sfunc( + state INTERNAL, + rec ANYELEMENT, + mode int, + element_name text, + binary_base64 boolean, + root_name text, + elements boolean, + xsinil boolean, + auto_metadata text, + ns_decls text +) RETURNS INTERNAL +AS 'babelfishpg_tsql', 'tsql_query_to_xml_sfunc' +LANGUAGE C STABLE; + +CREATE OR REPLACE AGGREGATE sys.tsql_select_for_xml_agg( + rec ANYELEMENT, + mode int, + element_name text, + binary_base64 boolean, + root_name text, + elements boolean, + xsinil boolean, + auto_metadata text, + ns_decls text) +( + STYPE = INTERNAL, + SFUNC = tsql_query_to_xml_sfunc, + FINALFUNC = tsql_query_to_xml_ffunc +); + +CREATE OR REPLACE AGGREGATE sys.tsql_select_for_xml_text_agg( + rec ANYELEMENT, + mode int, + element_name text, + binary_base64 boolean, + root_name text, + elements boolean, + xsinil boolean, + auto_metadata text, + ns_decls text) +( + STYPE = INTERNAL, + SFUNC = tsql_query_to_xml_sfunc, + FINALFUNC = tsql_query_to_xml_text_ffunc +); + +-- helper function for XML QUERY(xpath) with namespace support +CREATE OR REPLACE FUNCTION sys.bbf_xmlquery(xpath_pattern TEXT, xml_element ANYELEMENT, nsarray TEXT[][]) +RETURNS XML +AS 'babelfishpg_tsql', 'bbf_xmlquery_ns' +LANGUAGE C STABLE STRICT PARALLEL SAFE; + +-- helper function for XML EXIST(xpath) with namespace support +CREATE OR REPLACE FUNCTION sys.bbf_xmlexist(xpath_pattern TEXT, xml_element ANYELEMENT, nsarray TEXT[][]) +RETURNS sys.BIT +AS +$BODY$ +DECLARE + arg_datatype text; + arg_datatype_oid oid; + basetype oid; + pltsql_quoted_identifier text; +BEGIN + arg_datatype_oid := pg_typeof(xml_element)::oid; + arg_datatype := sys.translate_pg_type_to_tsql(arg_datatype_oid); + IF arg_datatype IS NULL THEN + basetype := sys.bbf_get_immediate_base_type_of_UDT(arg_datatype_oid); + arg_datatype := sys.translate_pg_type_to_tsql(basetype); + END IF; + + IF (arg_datatype != 'xml') THEN + RAISE EXCEPTION 'Cannot call methods on %.', arg_datatype; + END IF; + + pltsql_quoted_identifier := current_setting('babelfishpg_tsql.quoted_identifier'); + + IF (pltsql_quoted_identifier = 'off') THEN + RAISE EXCEPTION 'SELECT failed because the following SET options have incorrect settings: ''QUOTED_IDENTIFIER''. Verify that SET options are correct for XML data type methods.'; + END IF; + + RETURN (cardinality(xpath(xpath_pattern, xml_element, nsarray)) > 0)::int::sys.BIT; +END +$BODY$ +LANGUAGE plpgsql STABLE STRICT PARALLEL SAFE; + +-- helper function for XML VALUE(xpath) with namespace support +CREATE OR REPLACE FUNCTION sys.bbf_xmlvalue(xpath_pattern TEXT, datatype TEXT, xml_element ANYELEMENT, nsarray TEXT[][]) +RETURNS sys.NVARCHAR +AS +$BODY$ +DECLARE + temp_datatype text; + temp_basetype oid; + result_set xml[]; + result sys.NVARCHAR; + pltsql_quoted_identifier text; +BEGIN + temp_datatype := sys.translate_pg_type_to_tsql(pg_typeof(xml_element)::oid); + IF temp_datatype IS NULL THEN + temp_basetype := sys.bbf_get_immediate_base_type_of_UDT(pg_typeof(xml_element)::oid); + temp_datatype := sys.translate_pg_type_to_tsql(temp_basetype); + END IF; + + IF (temp_datatype != 'xml') THEN + RAISE EXCEPTION 'Cannot call methods on %.', temp_datatype; + END IF; + + pltsql_quoted_identifier := current_setting('babelfishpg_tsql.quoted_identifier'); + + IF (pltsql_quoted_identifier = 'off') THEN + RAISE EXCEPTION 'SELECT failed because the following SET options have incorrect settings: ''QUOTED_IDENTIFIER''. Verify that SET options are correct for XML data type methods.'; + END IF; + + result_set := xpath(xpath_pattern, xml_element, nsarray); + IF (cardinality(result_set) > 1) THEN + RAISE EXCEPTION 'XML Value result is not a single value.'; + ELSIF (cardinality(result_set) = 0) THEN + RETURN NULL; + ELSE + result := (xpath('string(' || xpath_pattern || ')', xml_element, nsarray))[1]; + result := pg_catalog.replace(result, '<', '<'); + result := pg_catalog.replace(result, '>', '>'); + result := pg_catalog.replace(result, ''', ''''); + result := pg_catalog.replace(result, '"', '"'); + result := pg_catalog.replace(result, '&', '&'); + return result; + END IF; +END +$BODY$ +LANGUAGE plpgsql STABLE STRICT PARALLEL SAFE; + -- Drops the temporary procedure used by the upgrade script. -- Please have this be one of the last statements executed in this upgrade script. DROP PROCEDURE sys.babelfish_drop_deprecated_object(varchar, varchar, varchar, varchar); diff --git a/contrib/babelfishpg_tsql/src/backend_parser/gram-tsql-epilogue.y.c b/contrib/babelfishpg_tsql/src/backend_parser/gram-tsql-epilogue.y.c index 410f99381d5..582886c8b7a 100644 --- a/contrib/babelfishpg_tsql/src/backend_parser/gram-tsql-epilogue.y.c +++ b/contrib/babelfishpg_tsql/src/backend_parser/gram-tsql-epilogue.y.c @@ -1930,6 +1930,25 @@ TsqlForXMLMakeFuncCall(TSQL_ForClause *forclause) func_args = lappend(func_args, makeBoolAConst(xsinil, -1)); /* 8th arg: auto_metadata placeholder (empty string, filled in by handleForXmlAuto) */ func_args = lappend(func_args, makeStringConst("", -1)); + /* + * 9th arg: namespace declarations from WITH XMLNAMESPACES, if any. + * The C++ ANTLR layer captures the WITH XMLNAMESPACES clause and stores + * a formatted decls string ('xmlns:p1="u1" xmlns:p2="u2"') on the + * enclosing PLtsql_stmt_execsql. We read it back from the currently + * executing stmt via get_current_tsql_estate(). + */ + { + PLtsql_execstate *estate = get_current_tsql_estate(); + char *ns_decls = NULL; + + if (estate && estate->err_stmt && estate->err_stmt->cmd_type == PLTSQL_STMT_EXECSQL) + ns_decls = ((PLtsql_stmt_execsql *) estate->err_stmt)->xml_namespace_decls; + + if (ns_decls && ns_decls[0] != '\0') + func_args = lappend(func_args, makeStringConst(ns_decls, -1)); + else + func_args = lappend(func_args, makeStringConst("", -1)); + } fc = makeFuncCall(func_name, func_args, COERCE_EXPLICIT_CALL, -1); /* diff --git a/contrib/babelfishpg_tsql/src/pltsql.h b/contrib/babelfishpg_tsql/src/pltsql.h index a91cc3a4c16..1b2801aa9f0 100644 --- a/contrib/babelfishpg_tsql/src/pltsql.h +++ b/contrib/babelfishpg_tsql/src/pltsql.h @@ -1299,6 +1299,11 @@ typedef struct PLtsql_stmt_execsql bool is_set_tran_isolation; /* SET TRANSACTION ISOLATION? */ char *original_query; /* Only for batch level statement. */ bool is_schemabinding; /* Is schema binding? */ + char *xml_namespace_decls; /* Namespace declarations from WITH + * XMLNAMESPACES, formatted as + * 'xmlns:p1="u1" xmlns:p2="u2"' for + * emission on FOR XML output elements. + * NULL when no XMLNAMESPACES clause. */ } PLtsql_stmt_execsql; /* diff --git a/contrib/babelfishpg_tsql/src/tsqlIface.cpp b/contrib/babelfishpg_tsql/src/tsqlIface.cpp index 904a27e914d..142686acfb2 100644 --- a/contrib/babelfishpg_tsql/src/tsqlIface.cpp +++ b/contrib/babelfishpg_tsql/src/tsqlIface.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -280,6 +281,22 @@ static std::map> rewritten_query_fragment // local_id can be rewritten in different ways in some cases (itvf), don't use rewritten_query_fragment. // TODO: incorporate local_id_positions with rewritten_query_fragment static std::map local_id_positions; + +// WITH XMLNAMESPACES context for the current statement. +// Populated by enterDeclare_xmlnamespaces_statement / enterWith_expression; +// cleared at statement boundaries via clear_rewritten_query_fragment. + +// For XML data type methods (.query()/.value()/.exist()): namespace array +// literal appended to the rewritten method call. +static std::string xmlnamespace_array_literal; + +// For FOR XML output (RAW/PATH/AUTO): 'xmlns:p="u"' string emitted on the +// row/root element. +static std::string xmlnamespace_decls_for_forxml; + +// For FOR XML column-alias validation: declared prefix names (excludes +// DEFAULT). +static std::set xmlnamespace_declared_prefixes; // For user-defined variables like @@var or @var# in the RETURN clause of an ITVF static std::map local_id_positions_atatuservar; @@ -743,6 +760,9 @@ clear_rewritten_query_fragment() { rewritten_query_fragment.clear(); local_id_positions.clear(); + xmlnamespace_array_literal.clear(); + xmlnamespace_decls_for_forxml.clear(); + xmlnamespace_declared_prefixes.clear(); } static void @@ -753,6 +773,338 @@ add_rewritten_query_fragment_to_mutator(PLtsql_expr_query_mutator *mutator) mutator->add(entry.first, entry.second.first, entry.second.second); } +/* + * Escape a string for embedding inside a double-quoted element of a PG array + * literal. Inside double quotes, PG array-literal parsing treats '"' as the + * element terminator and '\' as an escape character, so both must be + * backslash-escaped. Without this, a URI containing '"' produces a malformed + * array literal error and a URI containing '\' is silently corrupted (the + * backslash is dropped). + */ +static std::string +escape_for_pg_array_literal(const std::string &s) +{ + std::string out; + out.reserve(s.size()); + for (char c : s) + { + if (c == '"' || c == '\\') + out += '\\'; + out += c; + } + return out; +} + +/* + * Build a PG text[][] array literal from a list of xml_declaration parse nodes. + * Each xml_declaration is either: + * - 'uri' AS prefix -> {"prefix", "uri"} + * - DEFAULT 'uri' -> {"", "uri"} + * Returns a string like: '{{"ns","http://example.com"},{"","http://default.com"}}'::text[][] + * + * Uses the string-form array literal (not ARRAY[...]) because T-SQL dialect + * treats square brackets as identifier delimiters. + */ +static std::string +build_xmlnamespace_array_literal(const std::vector &decls) +{ + if (decls.empty()) + return ""; + + std::set seen_prefixes; + bool has_default = false; + static const std::string xml_ns_uri = "http://www.w3.org/XML/1998/namespace"; + + std::string result = "'{{"; + bool first = true; + for (auto *decl : decls) + { + if (!first) + result += "},{"; + first = false; + + if (decl->DEFAULT()) + { + /* DEFAULT 'uri' */ + std::string uri = ::getFullText(decl->char_string()); + if (uri.size() >= 2 && uri.front() == '\'' && uri.back() == '\'') + uri = uri.substr(1, uri.size() - 2); + + /* Rule 7: Empty URI */ + if (uri.empty()) + throw PGErrorWrapperException(ERROR, ERRCODE_SYNTAX_ERROR, + "Empty URI is not allowed in WITH XMLNAMESPACES clause.", 0, 0); + + /* Rule 2b: Duplicate DEFAULT */ + if (has_default) + throw PGErrorWrapperException(ERROR, ERRCODE_SYNTAX_ERROR, + "Attempt to redefine namespace prefix 'default'", 0, 0); + has_default = true; + + /* Rule 5b: xml namespace URI cannot be used with other prefixes */ + if (uri == xml_ns_uri) + throw PGErrorWrapperException(ERROR, ERRCODE_SYNTAX_ERROR, + "XML namespace prefix 'xml' can only be associated with the URI " + "http://www.w3.org/XML/1998/namespace. " + "This URI cannot be used with other prefixes.", 0, 0); + + result += "\"\"," "\"" + escape_for_pg_array_literal(uri) + "\""; + } + else + { + /* 'uri' AS prefix */ + std::string uri = ::getFullText(decl->xml_namespace_uri); + if (uri.size() >= 2 && uri.front() == '\'' && uri.back() == '\'') + uri = uri.substr(1, uri.size() - 2); + std::string prefix = stripQuoteFromId(decl->id()); + + /* Rule 7: Empty URI */ + if (uri.empty()) + throw PGErrorWrapperException(ERROR, ERRCODE_SYNTAX_ERROR, + "Empty URI is not allowed in WITH XMLNAMESPACES clause.", 0, 0); + + /* Rule 1: NCName - prefix must be a valid XML NCName. + * + * NameStartChar: '_' | letter | non-ASCII byte (permissive proxy + * for the W3C NameStartChar set). + * NameChar: NameStartChar | digit | '-' | '.'. + * + * Report the first invalid character, mirroring SQL Server's + * error message format. ':' is reported via its own message + * because T-SQL's check is colon-specific. + */ + for (size_t i = 0; i < prefix.size(); i++) + { + unsigned char c = (unsigned char) prefix[i]; + + if (c == ':') + throw PGErrorWrapperException(ERROR, ERRCODE_SYNTAX_ERROR, + format_errmsg("Prefix '%s' used in WITH XMLNAMESPACES clause " + "contains an invalid XML identifier. ':'(0x003A) is the " + "first character at fault.", prefix.c_str()), 0, 0); + + bool is_name_start = (c == '_') || + (c >= 'A' && c <= 'Z') || + (c >= 'a' && c <= 'z') || + c >= 0x80; + bool is_name_char = is_name_start || + (c >= '0' && c <= '9') || + c == '-' || c == '.'; + + bool ok = (i == 0) ? is_name_start : is_name_char; + if (!ok) + throw PGErrorWrapperException(ERROR, ERRCODE_SYNTAX_ERROR, + format_errmsg("Prefix '%s' used in WITH XMLNAMESPACES clause " + "contains an invalid XML identifier. '%c'(0x%04X) is the " + "first character at fault.", prefix.c_str(), c, c), 0, 0); + } + + /* Rule 4: xmlns prefix forbidden */ + if (prefix == "xmlns") + throw PGErrorWrapperException(ERROR, ERRCODE_SYNTAX_ERROR, + "Prefix 'xmlns' used in WITH XMLNAMESPACES is reserved and " + "cannot be used as a user-defined prefix.", 0, 0); + + /* Rule 5a: xml prefix can only use the xml namespace URI */ + if (prefix == "xml" && uri != xml_ns_uri) + throw PGErrorWrapperException(ERROR, ERRCODE_SYNTAX_ERROR, + "XML namespace prefix 'xml' can only be associated with the URI " + "http://www.w3.org/XML/1998/namespace. " + "This URI cannot be used with other prefixes.", 0, 0); + + /* Rule 5b: xml namespace URI can only be bound to xml prefix */ + if (uri == xml_ns_uri && prefix != "xml") + throw PGErrorWrapperException(ERROR, ERRCODE_SYNTAX_ERROR, + "XML namespace prefix 'xml' can only be associated with the URI " + "http://www.w3.org/XML/1998/namespace. " + "This URI cannot be used with other prefixes.", 0, 0); + + /* Rule 2: Duplicate prefix */ + if (seen_prefixes.count(prefix) > 0) + throw PGErrorWrapperException(ERROR, ERRCODE_SYNTAX_ERROR, + format_errmsg("Attempt to redefine namespace prefix '%s'", + prefix.c_str()), 0, 0); + seen_prefixes.insert(prefix); + + result += "\"" + prefix + "\",\"" + escape_for_pg_array_literal(uri) + "\""; + } + } + result += "}}'::_text"; + xmlnamespace_declared_prefixes = seen_prefixes; + return result; +} + +/* + * Escape characters that have XML attribute-value significance: &, <, >, ". + * The single quote is left alone because we always wrap the URI in double + * quotes. Returns a copy with replacements; the input is not modified. + */ +static std::string +xml_escape_attr_value(const std::string &in) +{ + std::string out; + out.reserve(in.size()); + for (char c : in) + { + switch (c) + { + case '&': out += "&"; break; + case '<': out += "<"; break; + case '>': out += ">"; break; + case '"': out += """; break; + default: out += c; break; + } + } + return out; +} + +/* + * Build a space-separated namespace declaration string from a list of + * xml_declaration parse tree nodes. Output format: + * xmlns:prefix="uri" xmlns:prefix2="uri2" + * For DEFAULT, emits xmlns="uri". + * + * Declarations are emitted in REVERSE declaration order to match SQL Server's + * FOR XML output for WITH XMLNAMESPACES. + * + * Note: a declared xsi prefix IS included in this output string. Deduplication + * with the xmlns:xsi declaration that ELEMENTS XSINIL emits is handled + * downstream in forxml.c by ns_decls_has_xsi(), which detects xsi already + * present in ns_decls and suppresses the redundant per-row XSINIL declaration. + * + * Caller is expected to have already validated declarations via + * build_xmlnamespace_array_literal, so this function performs no validation. + */ +static std::string +build_xmlnamespace_decls_string(const std::vector &decls) +{ + if (decls.empty()) + return ""; + + std::string result; + for (auto it = decls.rbegin(); it != decls.rend(); ++it) + { + auto *decl = *it; + std::string uri; + std::string prefix; + + if (decl->DEFAULT()) + { + uri = ::getFullText(decl->char_string()); + } + else + { + uri = ::getFullText(decl->xml_namespace_uri); + prefix = stripQuoteFromId(decl->id()); + } + + if (uri.size() >= 2 && uri.front() == '\'' && uri.back() == '\'') + uri = uri.substr(1, uri.size() - 2); + + if (!result.empty()) + result += " "; + + std::string escaped_uri = xml_escape_attr_value(uri); + + if (prefix.empty()) + result += "xmlns=\"" + escaped_uri + "\""; + else + result += "xmlns:" + prefix + "=\"" + escaped_uri + "\""; + } + return result; +} + +/* + * Validate prefixed FOR XML column aliases against declared namespace prefixes. + * + * For every SELECT list alias of the form 'prefix:local' (only the char_string + * form can carry a colon since 'id' aliases reject it), check that 'prefix' was + * declared via WITH XMLNAMESPACES. If not, raise SQL Server's error 6846 message. + * + * The check is skipped when the namespace context is empty (no WITH XMLNAMESPACES + * clause) — in that case the alias passes through unchanged and there is nothing + * to validate. + * + * Helper handles both alias positions in select_list_elem: + * - SELECT col AS 'ns:Name' -> expression_elem->as_column_alias + * - SELECT 'ns:Name' = col -> expression_elem->column_alias (left side) + */ +static void +validate_forxml_column_alias_prefixes(TSqlParser::Select_listContext *selectList, bool is_path) +{ + /* + * For PATH mode, T-SQL always validates prefixed aliases (and errors when + * the prefix isn't declared) regardless of whether WITH XMLNAMESPACES is + * present. For RAW/AUTO, validation only applies when declarations exist. + */ + if (!is_path && xmlnamespace_declared_prefixes.empty() && xmlnamespace_decls_for_forxml.empty()) + return; + if (!selectList) + return; + + auto check_alias = [](TSqlParser::Column_aliasContext *alias_ctx) { + if (!alias_ctx) + return; + + std::string raw; + if (alias_ctx->char_string()) + { + raw = ::getFullText(alias_ctx->char_string()); + /* strip surrounding quotes */ + if (raw.size() >= 2 && (raw.front() == '\'' || raw.front() == '"')) + raw = raw.substr(1, raw.size() - 2); + } + else if (alias_ctx->id()) + { + /* bracketed/quoted identifier: [ns:a], "ns:a" */ + raw = stripQuoteFromId(alias_ctx->id()); + } + else + { + return; + } + + size_t colon = raw.find(':'); + if (colon == std::string::npos) + return; /* unprefixed alias, nothing to check */ + + /* + * PATH-mode path-expression aliases (e.g. 'English/@xml:lang') use + * '/' to separate elements and '@' to mark attributes. Splitting on + * the first colon would misidentify the prefix, so leave them to the + * PATH alias parser. + */ + if (raw.find('/') != std::string::npos || raw.find('@') != std::string::npos) + return; + + std::string prefix = raw.substr(0, colon); + if (prefix.empty()) + return; /* malformed (':local'); leave for downstream to error on */ + + if (xmlnamespace_declared_prefixes.count(prefix) == 0) + throw PGErrorWrapperException(ERROR, ERRCODE_SYNTAX_ERROR, + format_errmsg("XML name space prefix '%s' declaration is missing " + "for FOR XML column name '%s'.", + prefix.c_str(), raw.c_str()), 0, 0); + }; + + for (auto *elem : selectList->select_list_elem()) + { + if (!elem->expression_elem()) + continue; + + auto *exp_elem = elem->expression_elem(); + + /* alias on the right: expr AS 'ns:Name' */ + if (exp_elem->as_column_alias()) + check_alias(exp_elem->as_column_alias()->column_alias()); + + /* alias on the left: 'ns:Name' = expr */ + if (exp_elem->column_alias()) + check_alias(exp_elem->column_alias()); + } +} + static void add_query_hints(PLtsql_expr_query_mutator *mutator, int contextOffset) { @@ -1062,6 +1414,55 @@ class tsqlCommonMutator : public TSqlParserBaseListener } } + void enterDeclare_xmlnamespaces_statement(TSqlParser::Declare_xmlnamespaces_statementContext *ctx) override + { + std::vector decls; + for (auto *d : ctx->xml_dec) + decls.push_back(d); + xmlnamespace_array_literal = build_xmlnamespace_array_literal(decls); + xmlnamespace_decls_for_forxml = build_xmlnamespace_decls_string(decls); + } + + void enterWith_expression(TSqlParser::With_expressionContext *ctx) override + { + if (ctx->XMLNAMESPACES()) + { + std::vector decls; + for (auto *d : ctx->xml_dec) + decls.push_back(d); + xmlnamespace_array_literal = build_xmlnamespace_array_literal(decls); + xmlnamespace_decls_for_forxml = build_xmlnamespace_decls_string(decls); + + /* + * Strip the XMLNAMESPACES clause from the SQL text sent to PG. + * Grammar: WITH (XMLNAMESPACES(...) COMMA?)? (CTEs)* + * + * If CTEs follow, strip "XMLNAMESPACES(...) ," leaving "WITH cte AS(...)". + * If no CTEs follow, strip "WITH XMLNAMESPACES(...)" entirely. + */ + size_t strip_start; + size_t strip_end; + + if (!ctx->ctes.empty()) + { + /* CTEs present: strip from XMLNAMESPACES token to just before first CTE */ + strip_start = ctx->XMLNAMESPACES()->getSymbol()->getStartIndex(); + strip_end = ctx->ctes[0]->start->getStartIndex(); + } + else + { + /* No CTEs: strip from WITH to end of RR_BRACKET */ + strip_start = ctx->WITH()->getSymbol()->getStartIndex(); + strip_end = ctx->RR_BRACKET()->getSymbol()->getStopIndex() + 1; + } + + std::string original_text = ctx->start->getInputStream()->getText( + antlr4::misc::Interval(strip_start, strip_end - 1)); + rewritten_query_fragment.emplace(std::make_pair(strip_start, + std::make_pair(original_text, ""))); + } + } + void exitDatatype_coloncolon_methods(TSqlParser::Datatype_coloncolon_methodsContext *ctx) override { std::string typeStr = ::getFullText(ctx->data_type()); @@ -2430,6 +2831,59 @@ class tsqlBuilder : public tsqlCommonMutator // // Please note that one "another_statement" may return a list of PLtsql_stmt // in case of DECLARE multiple variable with initializers at a time. + + if (ctx->declare_xmlnamespaces_statement()) + { + /* + * WITH XMLNAMESPACES (...) + * Handle like enterDml_statement: create the stmt from the inner DML, + * set up the mutator so the walker can collect rewrites as it descends. + */ + TSqlParser::Declare_xmlnamespaces_statementContext *xmlns_ctx = ctx->declare_xmlnamespaces_statement(); + + /* Capture namespace declarations */ + std::vector decls; + for (auto *d : xmlns_ctx->xml_dec) + decls.push_back(d); + xmlnamespace_array_literal = build_xmlnamespace_array_literal(decls); + xmlnamespace_decls_for_forxml = build_xmlnamespace_decls_string(decls); + + /* Find the inner DML and create a PLtsql_stmt for it */ + ParserRuleContext *inner_dml = nullptr; + if (xmlns_ctx->select_statement()) + inner_dml = xmlns_ctx->select_statement(); + else if (xmlns_ctx->insert_statement()) + inner_dml = xmlns_ctx->insert_statement(); + else if (xmlns_ctx->update_statement()) + inner_dml = xmlns_ctx->update_statement(); + else if (xmlns_ctx->delete_statement()) + inner_dml = xmlns_ctx->delete_statement(); + else if (xmlns_ctx->merge_statement()) + inner_dml = xmlns_ctx->merge_statement(); + + if (inner_dml) + { + /* + * clear_rewritten_query_fragment() wipes the namespace globals, + * so save and restore them instead of rebuilding. + */ + std::string saved_array_literal = xmlnamespace_array_literal; + std::string saved_decls_for_forxml = xmlnamespace_decls_for_forxml; + std::set saved_declared_prefixes = xmlnamespace_declared_prefixes; + + graft(makeSQL(inner_dml), peekContainer()); + clear_rewritten_query_fragment(); + /* Restore namespace context after clear */ + xmlnamespace_array_literal = saved_array_literal; + xmlnamespace_decls_for_forxml = saved_decls_for_forxml; + xmlnamespace_declared_prefixes = saved_declared_prefixes; + PLtsql_stmt_execsql *stmt = (PLtsql_stmt_execsql *) getPLtsql_fragment(inner_dml); + Assert(stmt); + statementMutator = std::make_unique(stmt->sqlstmt, inner_dml); + } + return; + } + std::vector result = makeAnother(ctx, *this); for (PLtsql_stmt *stmt : result) graft(stmt, peekContainer()); @@ -2528,6 +2982,19 @@ class tsqlBuilder : public tsqlCommonMutator add_rewritten_query_fragment_to_mutator(&mutator); mutator.run(); } + else if (ctx->declare_xmlnamespaces_statement()) + { + /* + * Apply the collected rewrites to the inner DML statement. + * The statementMutator was set up in enterAnother_statement. + */ + if (statementMutator) + { + add_rewritten_query_fragment_to_mutator(statementMutator.get()); + statementMutator->run(); + statementMutator = nullptr; + } + } // remove the offsets for processed fragments selectFragmentOffsets.clear(); @@ -3889,6 +4356,65 @@ static void process_select_statement( if (selectCtx->for_clause()->XML()) // FOR XML { Assert(selectCtx->for_clause()->RAW() || selectCtx->for_clause()->PATH() || selectCtx->for_clause()->AUTO()); + + /* + * Validate xsi prefix conflict with ELEMENTS XSINIL. + * If XSINIL is specified and the user declared 'xsi' prefix via + * WITH XMLNAMESPACES with a URI other than the schema-instance URI, + * reject the statement. + */ + if (!xmlnamespace_array_literal.empty() && !selectCtx->for_clause()->XSINIL().empty()) + { + /* Parse the namespace array literal to check for xsi prefix */ + std::string ns_str = xmlnamespace_array_literal; + std::string xsi_uri = "http://www.w3.org/2001/XMLSchema-instance"; + size_t pos = ns_str.find("\"xsi\""); + if (pos != std::string::npos) + { + /* Found xsi prefix — extract its URI and compare */ + size_t uri_start = ns_str.find("\"", pos + 5) + 1; + size_t uri_end = ns_str.find("\"", uri_start); + if (uri_start != std::string::npos && uri_end != std::string::npos) + { + std::string declared_uri = ns_str.substr(uri_start, uri_end - uri_start); + if (declared_uri != xsi_uri) + { + throw PGErrorWrapperException(ERROR, + ERRCODE_SYNTAX_ERROR, + "Redefinition of 'xsi' XML namespace prefix is not supported " + "with ELEMENTS XSINIL option of FOR XML.", + 0, 0); + } + } + } + } + + /* + * Validate prefixed FOR XML column aliases against the declared + * namespace prefixes. SQL Server raises error 6846 when an alias + * references a prefix not in the WITH XMLNAMESPACES declaration. + * PATH mode validates even without WITH XMLNAMESPACES; RAW/AUTO + * only validate when declarations are present. + */ + TSqlParser::Query_specificationContext *qctx = get_query_specification(selectCtx); + if (qctx && qctx->select_list()) + validate_forxml_column_alias_prefixes(qctx->select_list(), + selectCtx->for_clause()->PATH() != nullptr); + + /* + * Attach namespace declarations to the enclosing exec stmt so the + * backend parser can include them as the last (10th) aggregate + * argument when it builds the FOR XML aggregate call. + */ + if (!xmlnamespace_decls_for_forxml.empty() && mutator->ctx) + { + PLtsql_stmt *parentStmt = (PLtsql_stmt *) getPLtsql_fragment(mutator->ctx); + if (parentStmt && parentStmt->cmd_type == PLTSQL_STMT_EXECSQL) + { + PLtsql_stmt_execsql *execStmt = (PLtsql_stmt_execsql *) parentStmt; + execStmt->xml_namespace_decls = pstrdup(xmlnamespace_decls_for_forxml.c_str()); + } + } } else // for JSON { @@ -4978,6 +5504,7 @@ makeExecSql(ParserRuleContext *ctx) stmt->need_to_push_result = false; stmt->is_tsql_select_assign_stmt = false; stmt->insert_exec = false; + stmt->xml_namespace_decls = NULL; return (PLtsql_stmt *) stmt; } @@ -8852,8 +9379,7 @@ is_top_level_query_specification(TSqlParser::Query_specificationContext *ctx) { if (dynamic_cast(pctx) || dynamic_cast(pctx) || - dynamic_cast(pctx) || - dynamic_cast(pctx)) // not supported in BBF. excluding it from top-level select statement just in case. + dynamic_cast(pctx)) return false; pctx = pctx->parent; @@ -9693,6 +10219,13 @@ rewrite_dot_func_ref_args_query_helper(T ctx, TSqlParser::Method_callContext *me keysToRemove.clear(); std::string rewritten_exp = expr.substr((int)method->start->getStartIndex() - ctx->start->getStartIndex() + offset1, method_len + offset2) + "," + expr.substr(0, func_call_len + offset1 + 1) + ")"; + /* If XML namespace context is active, inject namespace array as extra argument */ + if (method->xml_methods() && !xmlnamespace_array_literal.empty()) + { + /* Insert namespace array before the closing ')' */ + rewritten_exp = rewritten_exp.substr(0, rewritten_exp.size() - 1) + "," + xmlnamespace_array_literal + ")"; + } + if (method->xml_methods() && method->xml_methods()->xml_func_arg()->VALUE()) { rewritten_exp = "cast(" + rewritten_exp + " as " + typename_arg + ")"; @@ -9968,6 +10501,12 @@ rewrite_function_call_dot_func_ref_args(T ctx) * Rewriting the query as: table.col.Func_name(arg) -> Func_name(arg, table.col) */ std::string rewritten_func = expr.substr((int)func_start_index - ctx->start->getStartIndex() + offset1, method_len + offset2) + "," + expr.substr(0, col_len + offset1 + 1) + ")"; + + /* If XML namespace context is active, inject namespace array as extra argument */ + if (ctx->xml_proc_name_table_column() && !xmlnamespace_array_literal.empty()) + { + rewritten_func = rewritten_func.substr(0, rewritten_func.size() - 1) + "," + xmlnamespace_array_literal + ")"; + } if (ctx->xml_proc_name_table_column() && ctx->xml_proc_name_table_column()->xml_func_arg()->VALUE()) { diff --git a/contrib/babelfishpg_tsql/src/tsqlUnsupportedFeatureHandler.cpp b/contrib/babelfishpg_tsql/src/tsqlUnsupportedFeatureHandler.cpp index a0fad074dfd..6eaeffa933b 100644 --- a/contrib/babelfishpg_tsql/src/tsqlUnsupportedFeatureHandler.cpp +++ b/contrib/babelfishpg_tsql/src/tsqlUnsupportedFeatureHandler.cpp @@ -152,7 +152,7 @@ class TsqlUnsupportedFeatureHandlerImpl : public TsqlUnsupportedFeatureHandler antlrcpp::Any visitSet_statement(TSqlParser::Set_statementContext *ctx) override; antlrcpp::Any visitCursor_statement(TSqlParser::Cursor_statementContext *ctx) override; antlrcpp::Any visitTransaction_statement(TSqlParser::Transaction_statementContext *ctx) override; - antlrcpp::Any visitDeclare_xmlnamespaces_statement(TSqlParser::Declare_xmlnamespaces_statementContext *ctx) override { handle(INSTR_UNSUPPORTED_TSQL_WITH_XMLNAMESPACES, "WITH XMLNAMESPACES", getLineAndPos(ctx)); return visitChildren(ctx); } + antlrcpp::Any visitDeclare_xmlnamespaces_statement(TSqlParser::Declare_xmlnamespaces_statementContext *ctx) override { return visitChildren(ctx); } antlrcpp::Any visitConversation_statement(TSqlParser::Conversation_statementContext *ctx) override { handle(INSTR_UNSUPPORTED_TSQL_CREATE_CONVERSATION_STMT, "conversation statements", getLineAndPos(ctx)); return visitChildren(ctx); } antlrcpp::Any visitCreate_contract(TSqlParser::Create_contractContext *ctx) override { handle(INSTR_UNSUPPORTED_TSQL_CREATE_CONTRACT, "CREATE CONTRACT", getLineAndPos(ctx)); return visitChildren(ctx); } antlrcpp::Any visitCreate_queue(TSqlParser::Create_queueContext *ctx) override { handle(INSTR_UNSUPPORTED_TSQL_CREATE_QUEUE, "CREATE QUEUE", getLineAndPos(ctx)); return visitChildren(ctx); } @@ -1449,9 +1449,6 @@ antlrcpp::Any TsqlUnsupportedFeatureHandlerImpl::visitGroup_by_item(TSqlParser:: antlrcpp::Any TsqlUnsupportedFeatureHandlerImpl::visitWith_expression(TSqlParser::With_expressionContext *ctx) { - if (ctx->XMLNAMESPACES()) - handle(INSTR_UNSUPPORTED_TSQL_WITH_XMLNAMESPACES, "WITH XMLNAMESPACES", getLineAndPos(ctx)); - return visitChildren(ctx); } diff --git a/contrib/babelfishpg_tsql/src/tsql_for/forxml.c b/contrib/babelfishpg_tsql/src/tsql_for/forxml.c index fe653048158..347a8136b3f 100644 --- a/contrib/babelfishpg_tsql/src/tsql_for/forxml.c +++ b/contrib/babelfishpg_tsql/src/tsql_for/forxml.c @@ -93,9 +93,9 @@ typedef struct forxml_state } forxml_state; static StringInfo for_xml_ffunc(PG_FUNCTION_ARGS); -static void tsql_row_to_xml_raw(StringInfo state, Datum record, const char *element_name, bool binary_base64, bool elements, bool xsinil, bool has_root); -static void tsql_row_to_xml_path(StringInfo state, Datum record, const char *element_name, bool binary_base64, bool xsinil, bool has_root); -static void tsql_row_to_xml_auto(StringInfo state, Datum record, bool elements, bool xsinil, forxml_auto_state *auto_state); +static void tsql_row_to_xml_raw(StringInfo state, Datum record, const char *element_name, bool binary_base64, bool elements, bool xsinil, const char *ns_decls, const char *all_ns_decls, bool has_root); +static void tsql_row_to_xml_path(StringInfo state, Datum record, const char *element_name, bool binary_base64, bool xsinil, const char *ns_decls, const char *all_ns_decls, bool has_root); +static void tsql_row_to_xml_auto(StringInfo state, Datum record, bool elements, bool xsinil, forxml_auto_state *auto_state, const char *ns_decls); static void update_tsql_datatype_and_val(HeapTuple tuple, TupleDesc tupdesc, Oid *datatype_oid, Datum *colval, bool binary_base64, int i); static char *tsql_escape_xml(const char *str); @@ -145,7 +145,7 @@ tsql_escape_xml(const char *str) static int find_first_changed_level(forxml_auto_state *auto_state, HeapTuple tuple, TupleDesc tupdesc); static void close_all_elements(StringInfo state, forxml_auto_state *auto_state); -static void output_row_xml(StringInfo state, forxml_auto_state *auto_state, HeapTuple tuple, TupleDesc tupdesc, bool elements, bool xsinil); +static void output_row_xml(StringInfo state, forxml_auto_state *auto_state, HeapTuple tuple, TupleDesc tupdesc, bool elements, bool xsinil, const char *ns_decls); static char *cached_value_to_xml_string(Datum value, int col_idx, forxml_auto_state *auto_state, Oid orig_type); static void init_output_func_cache(forxml_auto_state *auto_state, TupleDesc tupdesc); static void init_tsql_type_cache(forxml_auto_state *auto_state, TupleDesc tupdesc); @@ -155,6 +155,195 @@ static bool validate_attribute_centric_col_names_xml(const char *element_name, T PG_FUNCTION_INFO_V1(tsql_query_to_xml_sfunc); +/* + * Returns true if ns_decls declares the given namespace prefix. + * + * ns_decls (built by build_xmlnamespace_decls_string) has the form + * xmlns:p1="uri1" xmlns:p2="uri2" + * A URI value may itself contain spaces, '=', or even the literal substring + * "xmlns:xsi=", so a naive strstr() can false-match inside a URI. We instead + * walk declaration by declaration, comparing only the declared prefix name and + * skipping over the quoted URI value. + */ +static bool +ns_decls_has_prefix(const char *ns_decls, const char *prefix) +{ + const char *p; + size_t prefix_len; + + if (ns_decls == NULL || ns_decls[0] == '\0' || prefix == NULL) + return false; + prefix_len = strlen(prefix); + + for (p = ns_decls; *p != '\0';) + { + const char *open; + const char *close; + + /* Skip spaces between declarations */ + while (*p == ' ') + p++; + if (*p == '\0') + break; + + /* A prefixed declaration starts with "xmlns:=" */ + if (strncmp(p, "xmlns:", 6) == 0) + { + const char *name = p + 6; + const char *eq = strchr(name, '='); + + if (eq != NULL && + (size_t) (eq - name) == prefix_len && + strncmp(name, prefix, prefix_len) == 0) + return true; + } + + /* Advance past this declaration's quoted URI value */ + open = strchr(p, '"'); + if (open == NULL) + break; /* malformed; stop scanning */ + close = strchr(open + 1, '"'); + if (close == NULL) + break; /* unterminated; stop scanning */ + p = close + 1; + } + return false; +} + +/* + * Returns true when ns_decls (a string built by build_xmlnamespace_decls_string) + * already includes a binding for the xsi prefix to the XMLSchema-instance URI. + * + * Used by row-tag emission paths to skip the per-row "xmlns:xsi=..." attribute + * that XSINIL normally adds, when the same declaration was contributed by the + * caller's WITH XMLNAMESPACES. + */ +static bool +ns_decls_has_xsi(const char *ns_decls) +{ + return ns_decls_has_prefix(ns_decls, "xsi"); +} + +/* + * Restore colons in a column name produced from a 'prefix:local' alias. + * + * Aliases of the form 'prefix:local' get colon-encoded by + * map_sql_identifier_to_xml_name to 'prefix_x003A_local' because ':' is not a + * NCName character. T-SQL preserves the colon in the output regardless of + * whether the prefix is bound by WITH XMLNAMESPACES, so we always restore it + * (provided there is at least one character before the encoded colon). + * + * When the prefix matches one declared in ns_decls, additional _xNNNN_ + * escapes within the prefix are decoded too, so a declared 'xml' prefix + * (encoded by PG as '_x0078_ml') comes back as 'xml'. Undeclared prefixes + * keep their raw encoded form, matching T-SQL which preserves whatever the + * alias encoded to. + * + * Returns the input pointer when no rewrite is needed (no '_x003A_' or empty + * prefix portion); otherwise returns a freshly palloc'd string. + */ +static char * +restore_xml_prefix_colon(char *colname, const char *ns_decls) +{ + const char *enc = "_x003A_"; + const size_t enc_len = 7; + char *first; + StringInfoData decoded_prefix; + const char *p; + size_t raw_prefix_len; + bool prefix_declared = false; + char *result; + char *write; + const char *read; + + if (colname == NULL) + return colname; + + first = strstr(colname, enc); + if (first == NULL) + return colname; + + /* Raw prefix is the substring before the first encoded colon. */ + raw_prefix_len = (size_t) (first - colname); + if (raw_prefix_len == 0) + return colname; + + /* + * Decode any _xNNNN_ escapes in the prefix to get its logical form. We + * use this to check for a matching xmlns:= declaration; the + * declarations always store the unescaped prefix name. + */ + initStringInfo(&decoded_prefix); + p = colname; + while (p < first) + { + if (p + 7 <= first && + p[0] == '_' && p[1] == 'x' && + isxdigit((unsigned char) p[2]) && isxdigit((unsigned char) p[3]) && + isxdigit((unsigned char) p[4]) && isxdigit((unsigned char) p[5]) && + p[6] == '_') + { + char hex[5]; + long code; + + memcpy(hex, p + 2, 4); + hex[4] = '\0'; + code = strtol(hex, NULL, 16); + /* Only decode ASCII range; anything wider isn't a valid prefix character. */ + if (code > 0 && code < 128) + appendStringInfoChar(&decoded_prefix, (char) code); + else + appendBinaryStringInfo(&decoded_prefix, p, 7); + p += 7; + } + else + { + appendStringInfoChar(&decoded_prefix, *p); + p++; + } + } + + if (ns_decls != NULL && ns_decls[0] != '\0') + { + prefix_declared = ns_decls_has_prefix(ns_decls, decoded_prefix.data); + } + + /* + * Build the output: emit either the decoded prefix (when declared) or + * the raw encoded prefix (when not), then ':', then the rest of the name + * with subsequent _x003A_ sequences turned back into ':'. + */ + result = palloc(strlen(colname) + 1); + write = result; + if (prefix_declared) + { + memcpy(write, decoded_prefix.data, decoded_prefix.len); + write += decoded_prefix.len; + } + else + { + memcpy(write, colname, raw_prefix_len); + write += raw_prefix_len; + } + pfree(decoded_prefix.data); + + *write++ = ':'; + read = first + enc_len; + while (*read) + { + if (strncmp(read, enc, enc_len) == 0) + { + *write++ = ':'; + read += enc_len; + } + else + *write++ = *read++; + } + *write = '\0'; + + return result; +} + Datum tsql_query_to_xml_sfunc(PG_FUNCTION_ARGS) { @@ -167,16 +356,21 @@ tsql_query_to_xml_sfunc(PG_FUNCTION_ARGS) bool elements = false; bool xsinil = false; char *root_name; + char *ns_decls = NULL; + char *row_ns_decls; + bool has_root; MemoryContext agg_context; MemoryContext old_context; /* - * Backward compatibility: Check if ELEMENTS parameters are provided. - * Old 6-argument version (deprecated_in_5_6_0): state, rec, mode, element_name, binary_base64, root_name - * New 9-argument version (5.6.0+): adds elements, xsinil, auto_metadata parameters + * Backward compatibility: Check argument count. + * Old 6-argument version (deprecated_in_5_6_0): state, rec, mode, element_name, binary_base64, root_name + * 8-argument version (5.6.0+): adds elements, xsinil + * 9-argument version (FOR XML AUTO): adds auto_metadata + * 10-argument version (WITH XMLNAMESPACES): adds ns_decls */ - if (PG_NARGS() > 9) + if (PG_NARGS() > 10) ereport(ERROR, (errcode(ERRCODE_TOO_MANY_ARGUMENTS), errmsg("too many arguments"))); @@ -186,6 +380,15 @@ tsql_query_to_xml_sfunc(PG_FUNCTION_ARGS) elements = PG_GETARG_BOOL(6); xsinil = PG_GETARG_BOOL(7); } + + /* Read namespace declarations from arg 9 (set by ANTLR layer for WITH XMLNAMESPACES) */ + if (PG_NARGS() > 9 && !PG_ARGISNULL(9)) + { + char *raw = text_to_cstring(PG_GETARG_TEXT_PP(9)); + if (raw[0] != '\0') + ns_decls = raw; + } + if (!AggCheckCallContext(fcinfo, &agg_context)) elog(ERROR, "aggregate function called in non-aggregate context"); old_context = MemoryContextSwitchTo(agg_context); @@ -203,10 +406,17 @@ tsql_query_to_xml_sfunc(PG_FUNCTION_ARGS) { /* * We need to add an extra token to the beginning so that the - * finalfunc knows there is a root element. + * finalfunc knows there is a root element. When namespaces are + * declared, emit them on the ROOT element. */ - if (xsinil) + if (xsinil && ns_decls && !ns_decls_has_xsi(ns_decls)) + appendStringInfo(state, "{<%s " XML_XMLNS_XSI " %s>", root_name, ns_decls); + else if (xsinil && ns_decls) + appendStringInfo(state, "{<%s %s>", root_name, ns_decls); + else if (xsinil) appendStringInfo(state, "{<%s " XML_XMLNS_XSI ">", root_name); + else if (ns_decls) + appendStringInfo(state, "{<%s %s>", root_name, ns_decls); else appendStringInfo(state, "{<%s>", root_name); } @@ -239,6 +449,25 @@ tsql_query_to_xml_sfunc(PG_FUNCTION_ARGS) xml_auto_parse_metadata(auto_st, auto_metadata, tupdesc->natts); ReleaseTupleDesc(tupdesc); } + + /* + * Restore prefix colons in column and table aliases. Done once + * after metadata parsing so per-row emission stays cheap. The + * helper handles NULL ns_decls (no WITH XMLNAMESPACES) by + * restoring the colon for any prefixed alias, matching T-SQL. + */ + for (int i = 0; i < auto_st->num_columns; i++) + { + if (auto_st->column_names[i]) + auto_st->column_names[i] = restore_xml_prefix_colon(auto_st->column_names[i], ns_decls); + if (auto_st->table_aliases[i]) + auto_st->table_aliases[i] = restore_xml_prefix_colon(auto_st->table_aliases[i], ns_decls); + } + for (int level = 0; level <= auto_st->max_depth; level++) + { + if (auto_st->level_to_alias[level]) + auto_st->level_to_alias[level] = restore_xml_prefix_colon(auto_st->level_to_alias[level], ns_decls); + } } } } @@ -247,10 +476,20 @@ tsql_query_to_xml_sfunc(PG_FUNCTION_ARGS) fstate = (forxml_state *) PG_GETARG_POINTER(0); state = fstate->xml_output; } + + /* + * If ROOT is present, namespace declarations were already emitted on + * the root element. Don't pass them to row functions for re-emission. + */ + row_ns_decls = ns_decls; + has_root = (state->len > 0 && state->data[0] == '{'); + if (has_root) + row_ns_decls = NULL; + switch (mode) { case TSQL_FORXML_RAW: /* FOR XML RAW */ - tsql_row_to_xml_raw(state, record, element_name, binary_base64, elements, xsinil, fstate->has_root); + tsql_row_to_xml_raw(state, record, element_name, binary_base64, elements, xsinil, row_ns_decls, ns_decls, fstate->has_root); break; case TSQL_FORXML_AUTO: /* FOR XML AUTO */ { @@ -261,11 +500,11 @@ tsql_query_to_xml_sfunc(PG_FUNCTION_ARGS) (errcode(ERRCODE_INTERNAL_ERROR), errmsg("FOR XML AUTO state not initialized"))); - tsql_row_to_xml_auto(state, record, elements, xsinil, auto_state); + tsql_row_to_xml_auto(state, record, elements, xsinil, auto_state, row_ns_decls); } break; case TSQL_FORXML_PATH: /* FOR XML PATH */ - tsql_row_to_xml_path(state, record, element_name, binary_base64, xsinil, fstate->has_root); + tsql_row_to_xml_path(state, record, element_name, binary_base64, xsinil, row_ns_decls, ns_decls, fstate->has_root); break; case TSQL_FORXML_EXPLICIT: @@ -382,7 +621,7 @@ for_xml_ffunc(PG_FUNCTION_ARGS) * Map an SQL row to an XML element in RAW mode. */ static void -tsql_row_to_xml_raw(StringInfo state, Datum record, const char *element_name, bool binary_base64, bool elements, bool xsinil, bool has_root) +tsql_row_to_xml_raw(StringInfo state, Datum record, const char *element_name, bool binary_base64, bool elements, bool xsinil, const char *ns_decls, const char *all_ns_decls, bool has_root) { HeapTupleHeader td; Oid tupType; @@ -427,15 +666,26 @@ tsql_row_to_xml_raw(StringInfo state, Datum record, const char *element_name, bo * but when ROOT is present we already emitted it there, so * suppress the per-row declaration to match T-SQL behavior. */ - if (xsinil && !has_root) + if (xsinil && ns_decls && !ns_decls_has_xsi(ns_decls)) + appendStringInfo(state, "<%s " XML_XMLNS_XSI " %s>", element_name, ns_decls); + else if (xsinil && ns_decls) + appendStringInfo(state, "<%s %s>", element_name, ns_decls); + else if (xsinil && !has_root) appendStringInfo(state, "<%s " XML_XMLNS_XSI ">", element_name); + else if (ns_decls) + appendStringInfo(state, "<%s %s>", element_name, ns_decls); else appendStringInfo(state, "<%s>", element_name); } else { /* ATTRIBUTES mode: */ - appendStringInfo(state, "<%s", element_name); + if (xsinil && ns_decls && !ns_decls_has_xsi(ns_decls)) + appendStringInfo(state, "<%s " XML_XMLNS_XSI " %s", element_name, ns_decls); + else if (ns_decls) + appendStringInfo(state, "<%s %s", element_name, ns_decls); + else + appendStringInfo(state, "<%s", element_name); } } @@ -451,6 +701,14 @@ tsql_row_to_xml_raw(StringInfo state, Datum record, const char *element_name, bo continue; colname = map_sql_identifier_to_xml_name(NameStr(att->attname), true, false); + { + char *raw_colname = colname; + + colname = restore_xml_prefix_colon(raw_colname, all_ns_decls); + /* restore_xml_prefix_colon may return a new buffer; free the original */ + if (colname != raw_colname) + pfree(raw_colname); + } colval = heap_getattr(tuple, i + 1, tupdesc, &isnull); datatype_oid = att->atttypid; @@ -571,7 +829,7 @@ validate_attribute_centric_col_names_xml(const char *element_name, TupleDesc tup * Map an SQL row to an XML element in PATH mode. */ static void -tsql_row_to_xml_path(StringInfo state, Datum record, const char *element_name, bool binary_base64, bool xsinil, bool has_root) +tsql_row_to_xml_path(StringInfo state, Datum record, const char *element_name, bool binary_base64, bool xsinil, const char *ns_decls, const char *all_ns_decls, bool has_root) { HeapTupleHeader td; Oid tupType; @@ -611,15 +869,27 @@ tsql_row_to_xml_path(StringInfo state, Datum record, const char *element_name, b /* if "''" is the input path, ignore it per TSQL behavior */ if (has_att_centric) { - if (xsinil && !has_root) + if (xsinil && ns_decls && !ns_decls_has_xsi(ns_decls)) + appendStringInfo(state, "<%s " XML_XMLNS_XSI " %s", element_name, ns_decls); + else if (xsinil && ns_decls) + appendStringInfo(state, "<%s %s", element_name, ns_decls); + else if (xsinil && !has_root) appendStringInfo(state, "<%s " XML_XMLNS_XSI, element_name); + else if (ns_decls) + appendStringInfo(state, "<%s %s", element_name, ns_decls); else appendStringInfo(state, "<%s", element_name); } else { - if (xsinil && !has_root) + if (xsinil && ns_decls && !ns_decls_has_xsi(ns_decls)) + appendStringInfo(state, "<%s " XML_XMLNS_XSI " %s>", element_name, ns_decls); + else if (xsinil && ns_decls) + appendStringInfo(state, "<%s %s>", element_name, ns_decls); + else if (xsinil && !has_root) appendStringInfo(state, "<%s " XML_XMLNS_XSI ">", element_name); + else if (ns_decls) + appendStringInfo(state, "<%s %s>", element_name, ns_decls); else appendStringInfo(state, "<%s>", element_name); } @@ -638,6 +908,14 @@ tsql_row_to_xml_path(StringInfo state, Datum record, const char *element_name, b continue; colname = map_sql_identifier_to_xml_name(NameStr(att->attname), true, false); + { + char *raw_colname = colname; + + colname = restore_xml_prefix_colon(raw_colname, all_ns_decls); + /* restore_xml_prefix_colon may return a new buffer; free the original */ + if (colname != raw_colname) + pfree(raw_colname); + } colval = heap_getattr(tuple, i + 1, tupdesc, &isnull); datatype_oid = att->atttypid; @@ -1438,7 +1716,7 @@ auto_preformat_value(HeapTuple tuple, TupleDesc tupdesc, * 4. Store current values for next row comparison */ static void -output_row_xml(StringInfo state, forxml_auto_state *auto_state, HeapTuple tuple, TupleDesc tupdesc, bool elements, bool xsinil) +output_row_xml(StringInfo state, forxml_auto_state *auto_state, HeapTuple tuple, TupleDesc tupdesc, bool elements, bool xsinil, const char *ns_decls) { int first_changed_level; int deepest_level_in_row = auto_state->max_depth; @@ -1506,10 +1784,20 @@ output_row_xml(StringInfo state, forxml_auto_state *auto_state, HeapTuple tuple, continue; /* Open element tag */ - if (elements && xsinil && !auto_state->has_root && level == 1) + if (elements && xsinil && ns_decls && !ns_decls_has_xsi(ns_decls) && !auto_state->has_root && level == 1) + appendStringInfo(state, "<%s " XML_XMLNS_XSI " %s>", table_alias, ns_decls); + else if (elements && xsinil && ns_decls && !auto_state->has_root && level == 1) + appendStringInfo(state, "<%s %s>", table_alias, ns_decls); + else if (elements && xsinil && !auto_state->has_root && level == 1) appendStringInfo(state, "<%s " XML_XMLNS_XSI ">", table_alias); + else if (elements && ns_decls && !auto_state->has_root && level == 1) + appendStringInfo(state, "<%s %s>", table_alias, ns_decls); else if (elements) appendStringInfo(state, "<%s>", table_alias); + else if (xsinil && ns_decls && !ns_decls_has_xsi(ns_decls) && !auto_state->has_root && level == 1) + appendStringInfo(state, "<%s " XML_XMLNS_XSI " %s", table_alias, ns_decls); + else if (ns_decls && !auto_state->has_root && level == 1) + appendStringInfo(state, "<%s %s", table_alias, ns_decls); else appendStringInfo(state, "<%s", table_alias); @@ -1642,7 +1930,7 @@ output_row_xml(StringInfo state, forxml_auto_state *auto_state, HeapTuple tuple, * Map an SQL row to XML in AUTO mode. */ static void -tsql_row_to_xml_auto(StringInfo state, Datum record, bool elements, bool xsinil, forxml_auto_state *auto_state) +tsql_row_to_xml_auto(StringInfo state, Datum record, bool elements, bool xsinil, forxml_auto_state *auto_state, const char *ns_decls) { HeapTupleHeader td; Oid tupType; @@ -1671,7 +1959,7 @@ tsql_row_to_xml_auto(StringInfo state, Datum record, bool elements, bool xsinil, } /* Generate hierarchical XML output */ - output_row_xml(state, auto_state, tuple, tupdesc, elements, xsinil); + output_row_xml(state, auto_state, tuple, tupdesc, elements, xsinil, ns_decls); ReleaseTupleDesc(tupdesc); } diff --git a/test/JDBC/expected/BABEL-2514.out b/test/JDBC/expected/BABEL-2514.out index 3d638679f5f..bdf836513f8 100644 --- a/test/JDBC/expected/BABEL-2514.out +++ b/test/JDBC/expected/BABEL-2514.out @@ -97,9 +97,10 @@ varchar WITH XMLNAMESPACES ('uri' as ns1) SELECT 'value' = '1' go -~~ERROR (Code: 33557097)~~ - -~~ERROR (Message: 'WITH XMLNAMESPACES' is not currently supported in Babelfish)~~ +~~START~~ +varchar +1 +~~END~~ declare @string1 nvarchar(5); diff --git a/test/JDBC/expected/BABEL-UNSUPPORTED.out b/test/JDBC/expected/BABEL-UNSUPPORTED.out index 817caa5c052..5b4d317c3f9 100644 --- a/test/JDBC/expected/BABEL-UNSUPPORTED.out +++ b/test/JDBC/expected/BABEL-UNSUPPORTED.out @@ -2572,19 +2572,19 @@ WITH XMLNAMESPACES ('test.com' AS ns1, 'test2.com' AS ns1) SELECT 'test' AS 'Tes GO ~~ERROR (Code: 33557097)~~ -~~ERROR (Message: 'WITH XMLNAMESPACES' is not currently supported in Babelfish)~~ +~~ERROR (Message: Attempt to redefine namespace prefix 'ns1')~~ WITH XMLNAMESPACES ('test.com' AS n@s1) SELECT 'test' AS 'TestAttr' FOR XML RAW GO ~~ERROR (Code: 33557097)~~ -~~ERROR (Message: 'WITH XMLNAMESPACES' is not currently supported in Babelfish)~~ +~~ERROR (Message: Prefix 'n@s1' used in WITH XMLNAMESPACES clause contains an invalid XML identifier. '@'(0x0040) is the first character at fault.)~~ WITH XMLNAMESPACES ('test.com' AS xmlns) SELECT 'test' AS 'TestAttr' FOR XML RAW GO ~~ERROR (Code: 33557097)~~ -~~ERROR (Message: 'WITH XMLNAMESPACES' is not currently supported in Babelfish)~~ +~~ERROR (Message: Prefix 'xmlns' used in WITH XMLNAMESPACES is reserved and cannot be used as a user-defined prefix.)~~ -- BEGIN ATOMIC (10782) diff --git a/test/JDBC/expected/forxml-namespaces-before-17_11-or-18_5-vu-cleanup.out b/test/JDBC/expected/forxml-namespaces-before-17_11-or-18_5-vu-cleanup.out new file mode 100644 index 00000000000..089440fb567 --- /dev/null +++ b/test/JDBC/expected/forxml-namespaces-before-17_11-or-18_5-vu-cleanup.out @@ -0,0 +1,30 @@ + +-- ============================================ +-- SECTION: Cleanup +-- ============================================ +DROP VIEW forxml_ns_view1; +GO + +DROP VIEW forxml_ns_view2; +GO + +DROP TABLE forxml_ns_employees; +GO + +DROP TABLE forxml_ns_orders; +GO + +DROP TABLE forxml_ns_simple; +GO + +DROP TABLE forxml_ns_nullable; +GO + +DROP TABLE forxml_ns_types; +GO + +DROP TABLE forxml_ns_special; +GO + +DROP TABLE forxml_ns_unicode; +GO diff --git a/test/JDBC/expected/forxml-namespaces-before-17_11-or-18_5-vu-prepare.out b/test/JDBC/expected/forxml-namespaces-before-17_11-or-18_5-vu-prepare.out new file mode 100644 index 00000000000..ce7009e243c --- /dev/null +++ b/test/JDBC/expected/forxml-namespaces-before-17_11-or-18_5-vu-prepare.out @@ -0,0 +1,157 @@ + + +-- ============================================ +-- Tests for WITH XMLNAMESPACES + FOR XML (RAW, PATH, AUTO) +-- +-- WITH XMLNAMESPACES declares prefix-to-URI mappings whose scope is one +-- statement. T-SQL emits xmlns:prefix="uri" attributes on the appropriate +-- elements: +-- - FOR XML RAW: on each row element +-- - FOR XML PATH: on the outermost row element +-- - FOR XML AUTO: on the outermost (table-named) element +-- DEFAULT 'uri' emits xmlns="uri" (applies to unprefixed elements only). +-- ============================================ +-- ============================================ +-- SECTION: Base Tables +-- ============================================ +CREATE TABLE forxml_ns_employees ( + EmpID INT, + EmpName VARCHAR(50), + Dept VARCHAR(50) +); +GO + +INSERT INTO forxml_ns_employees VALUES (1, 'Alice', 'Sales'); +INSERT INTO forxml_ns_employees VALUES (2, 'Bob', 'IT'); +INSERT INTO forxml_ns_employees VALUES (3, NULL, 'HR'); +INSERT INTO forxml_ns_employees VALUES (4, 'Diana', NULL); +GO +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + + +CREATE TABLE forxml_ns_orders ( + OrderID INT, + EmpID INT, + Amount DECIMAL(10, 2) +); +GO + +INSERT INTO forxml_ns_orders VALUES (101, 1, 100.50); +INSERT INTO forxml_ns_orders VALUES (102, 2, 200.00); +INSERT INTO forxml_ns_orders VALUES (103, 1, NULL); +GO +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + + +CREATE TABLE forxml_ns_simple ( + a INT, + b VARCHAR(50) +); +GO + +INSERT INTO forxml_ns_simple VALUES (1, 'val1'); +INSERT INTO forxml_ns_simple VALUES (2, 'val2'); +GO +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + + +CREATE TABLE forxml_ns_nullable ( + a INT, + b VARCHAR(50) +); +GO + +INSERT INTO forxml_ns_nullable VALUES (NULL, NULL); +INSERT INTO forxml_ns_nullable VALUES (1, NULL); +INSERT INTO forxml_ns_nullable VALUES (NULL, 'x'); +GO +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + + +-- Table with multiple data types +CREATE TABLE forxml_ns_types ( + IntCol INT, + VarcharCol VARCHAR(50), + BitCol BIT, + DecimalCol DECIMAL(10,2), + DateCol DATE +); +GO + +INSERT INTO forxml_ns_types VALUES (100, 'Text1', 1, 99.99, '2024-01-01'); +INSERT INTO forxml_ns_types VALUES (NULL, NULL, NULL, NULL, NULL); +INSERT INTO forxml_ns_types VALUES (200, NULL, 0, NULL, '2024-06-15'); +GO +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + + +-- Special characters table +CREATE TABLE forxml_ns_special ( + ID INT, + Value VARCHAR(100) +); +GO + +INSERT INTO forxml_ns_special VALUES (1, 'a & b'); +INSERT INTO forxml_ns_special VALUES (2, 'a < b'); +INSERT INTO forxml_ns_special VALUES (3, 'say "hello"'); +INSERT INTO forxml_ns_special VALUES (4, 'x'); +GO +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + + +-- Unicode/multibyte values table +CREATE TABLE forxml_ns_unicode ( + ID INT, + Name NVARCHAR(50) +); +GO + +INSERT INTO forxml_ns_unicode VALUES (1, N'日本語'); +INSERT INTO forxml_ns_unicode VALUES (2, N'中文'); +INSERT INTO forxml_ns_unicode VALUES (3, N'한국어'); +GO +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + + + +-- ============================================ +-- SECTION: Dependent Views +-- ============================================ +CREATE VIEW forxml_ns_view1 AS +SELECT EmpID, EmpName FROM forxml_ns_employees; +GO + +CREATE VIEW forxml_ns_view2 AS +SELECT a, b FROM forxml_ns_simple; +GO diff --git a/test/JDBC/expected/forxml-namespaces-before-17_11-or-18_5-vu-verify.out b/test/JDBC/expected/forxml-namespaces-before-17_11-or-18_5-vu-verify.out new file mode 100644 index 00000000000..80eddca078b --- /dev/null +++ b/test/JDBC/expected/forxml-namespaces-before-17_11-or-18_5-vu-verify.out @@ -0,0 +1,1210 @@ + + +-- ============================================ +-- WITH XMLNAMESPACES + FOR XML (RAW, PATH, AUTO) +-- +-- Scope: WITH XMLNAMESPACES declares prefix-to-URI mappings for one +-- statement. T-SQL emits xmlns:prefix="uri" attributes on the appropriate +-- elements: +-- - FOR XML RAW: on each row element +-- - FOR XML PATH: on the outermost row element +-- - FOR XML AUTO: on the outermost (table-named) element +-- DEFAULT 'uri' emits xmlns="uri" +-- ============================================ +-- ============================================ +-- SECTION 1: FOR XML RAW +-- ============================================ +-- 1.1 Basic single-prefix RAW with no prefix used in row/cols +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW; +GO +~~START~~ +ntext + +~~END~~ + + +-- 1.2 RAW with named row, no prefix in row/cols +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 1.3 RAW with prefixed row name, no prefix in cols +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 1.4 RAW with prefixed column aliases +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 1.5 Multiple prefixes, each used in different columns +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1, 'http://example.com/ns2' AS ns2) +SELECT EmpID AS [ns1:ID], EmpName AS [ns2:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 1.6 DEFAULT namespace +WITH XMLNAMESPACES(DEFAULT 'http://example.com/default') +SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW; +GO +~~START~~ +ntext + +~~END~~ + + +-- 1.7 DEFAULT + prefix combined +WITH XMLNAMESPACES(DEFAULT 'http://example.com/default', 'http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 1.8 RAW + ELEMENTS +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'), ELEMENTS; +GO +~~START~~ +ntext +1Alice +~~END~~ + + +-- 1.9 RAW + ELEMENTS XSINIL with NULL value +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name], Dept AS [ns1:Dept] +FROM forxml_ns_employees WHERE EmpID = 3 FOR XML RAW('ns1:Emp'), ELEMENTS XSINIL; +GO +~~START~~ +ntext +3HR +~~END~~ + + +-- 1.10 RAW + ELEMENTS ABSENT (NULLs dropped) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name], Dept AS [ns1:Dept] +FROM forxml_ns_employees WHERE EmpID = 3 FOR XML RAW('ns1:Emp'), ELEMENTS ABSENT; +GO +~~START~~ +ntext +3HR +~~END~~ + + +-- 1.11 RAW + ROOT (unprefixed root) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'), ROOT('Doc'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 1.12 RAW + ROOT (prefixed root) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'), ROOT('ns1:Doc'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 1.13 RAW + TYPE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'), TYPE; +GO +~~START~~ +xml + +~~END~~ + + +-- 1.14 RAW + ROOT + TYPE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'), ROOT('ns1:Doc'), TYPE; +GO +~~START~~ +xml + +~~END~~ + + +-- 1.15 RAW multi-row with namespaces +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 1.16 RAW with view as the source +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_view1 WHERE EmpID = 1 FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 1.17 RAW with computed expression and prefixed alias +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID + 100 AS [ns1:OffsetID], UPPER(EmpName) AS [ns1:UpperName] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + + +-- ============================================ +-- SECTION 2: FOR XML PATH +-- ============================================ +-- 2.1 PATH with prefix on row and column elements +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML PATH('ns1:Emp'); +GO +~~START~~ +ntext +1Alice +~~END~~ + + +-- 2.2 PATH default row name with prefix on cols only +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML PATH; +GO +~~START~~ +ntext +1Alice +~~END~~ + + +-- 2.3 PATH with attribute-centric prefix (@) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [@ns1:id], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML PATH('ns1:Emp'); +GO +~~START~~ +ntext +Alice +~~END~~ + + +-- 2.4 PATH + ROOT prefixed +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML PATH('ns1:Emp'), ROOT('ns1:Doc'); +GO +~~START~~ +ntext +1Alice +~~END~~ + + +-- 2.5 PATH + ELEMENTS XSINIL with NULL +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name], Dept AS [ns1:Dept] +FROM forxml_ns_employees WHERE EmpID = 3 FOR XML PATH('ns1:Emp'), ELEMENTS XSINIL; +GO +~~START~~ +ntext +3HR +~~END~~ + + +-- 2.6 PATH + DEFAULT namespace +WITH XMLNAMESPACES(DEFAULT 'http://example.com/default') +SELECT EmpID AS ID, EmpName AS Name +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML PATH('Emp'); +GO +~~START~~ +ntext +1Alice +~~END~~ + + +-- 2.7 PATH + DEFAULT + prefix +WITH XMLNAMESPACES(DEFAULT 'http://example.com/default', 'http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML PATH('ns1:Emp'); +GO +~~START~~ +ntext +1Alice +~~END~~ + + +-- 2.8 PATH + multiple prefixes +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1, 'http://example.com/ns2' AS ns2) +SELECT EmpID AS [ns1:ID], EmpName AS [ns2:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML PATH('ns1:Emp'); +GO +~~START~~ +ntext +1Alice +~~END~~ + + +-- 2.9 PATH + ROOT + TYPE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML PATH('ns1:Emp'), ROOT('ns1:Doc'), TYPE; +GO +~~START~~ +xml +1Alice +~~END~~ + + +-- 2.10 PATH multi-row +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees FOR XML PATH('ns1:Emp'); +GO +~~START~~ +ntext +1Alice2Bob34Diana +~~END~~ + + + +-- ============================================ +-- SECTION 3: FOR XML AUTO +-- ============================================ +-- 3.1 AUTO with prefixed table name +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT forxml_ns_employees.EmpID, forxml_ns_employees.EmpName +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML AUTO; +GO +~~START~~ +ntext + +~~END~~ + + +-- 3.2 AUTO + ELEMENTS +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT forxml_ns_employees.EmpID, forxml_ns_employees.EmpName +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML AUTO, ELEMENTS; +GO +~~START~~ +ntext +1Alice +~~END~~ + + +-- 3.3 AUTO + ROOT +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT forxml_ns_employees.EmpID, forxml_ns_employees.EmpName +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML AUTO, ROOT('Doc'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 3.4 AUTO + DEFAULT + prefix +WITH XMLNAMESPACES(DEFAULT 'http://example.com/default', 'http://example.com/ns1' AS ns1) +SELECT forxml_ns_employees.EmpID, forxml_ns_employees.EmpName +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML AUTO; +GO +~~START~~ +ntext + +~~END~~ + + +-- 3.5 AUTO + TYPE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT forxml_ns_employees.EmpID, forxml_ns_employees.EmpName +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML AUTO, TYPE; +GO +~~START~~ +xml + +~~END~~ + + +-- 3.6 AUTO + ELEMENTS XSINIL + NULL row +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT forxml_ns_employees.EmpID, forxml_ns_employees.EmpName, forxml_ns_employees.Dept +FROM forxml_ns_employees WHERE EmpID = 3 FOR XML AUTO, ELEMENTS XSINIL; +GO +~~START~~ +ntext +3HR +~~END~~ + + +-- 3.7 AUTO multi-row +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT forxml_ns_employees.EmpID, forxml_ns_employees.EmpName +FROM forxml_ns_employees FOR XML AUTO; +GO +~~START~~ +ntext + +~~END~~ + + +-- 3.8 AUTO with JOIN (parent-child nesting) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID, e.EmpName, o.OrderID, o.Amount +FROM forxml_ns_employees e +JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID, o.OrderID +FOR XML AUTO; +GO +~~START~~ +ntext + +~~END~~ + + +-- 3.9 AUTO with JOIN + ELEMENTS +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID, e.EmpName, o.OrderID, o.Amount +FROM forxml_ns_employees e +JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID, o.OrderID +FOR XML AUTO, ELEMENTS; +GO +~~START~~ +ntext +1Alice101100.501032Bob102200.00 +~~END~~ + + +-- 3.10 AUTO with JOIN + ROOT +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID, e.EmpName, o.OrderID, o.Amount +FROM forxml_ns_employees e +JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID, o.OrderID +FOR XML AUTO, ROOT('Doc'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 3.11 AUTO with LEFT JOIN, NULL children +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID, e.EmpName, o.OrderID, o.Amount +FROM forxml_ns_employees e +LEFT JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID +FOR XML AUTO; +GO +~~START~~ +ntext + +~~END~~ + + +-- 3.12 AUTO with CTE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1), +ActiveEmps AS (SELECT EmpID, EmpName FROM forxml_ns_employees WHERE Dept IS NOT NULL) +SELECT a.EmpID, a.EmpName +FROM ActiveEmps a +FOR XML AUTO; +GO +~~START~~ +ntext + +~~END~~ + + +-- 3.13 AUTO with subquery in FROM +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT sub.EmpID, sub.EmpName +FROM (SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID <= 2) sub +FOR XML AUTO; +GO +~~START~~ +ntext + +~~END~~ + + +-- 3.14 AUTO multi-table JOIN with ELEMENTS XSINIL +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID, e.EmpName, e.Dept, o.OrderID, o.Amount +FROM forxml_ns_employees e +LEFT JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID, o.OrderID +FOR XML AUTO, ELEMENTS XSINIL; +GO +~~START~~ +ntext +1AliceSales101100.501032BobIT102200.003HR4Diana +~~END~~ + + +-- 3.15 AUTO + DEFAULT ns + JOIN +WITH XMLNAMESPACES(DEFAULT 'http://default.com', 'http://example.com/ns1' AS ns1) +SELECT e.EmpID, e.EmpName, o.OrderID +FROM forxml_ns_employees e +JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID +FOR XML AUTO; +GO +~~START~~ +ntext + +~~END~~ + + +-- 3.16 AUTO with JOIN + TYPE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID, e.EmpName, o.OrderID +FROM forxml_ns_employees e +JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID +FOR XML AUTO, TYPE; +GO +~~START~~ +xml + +~~END~~ + + + +-- ============================================ +-- SECTION 4: Validation / Error cases +-- ============================================ +-- 4.1 Duplicate prefix declaration +WITH XMLNAMESPACES('http://example.com/u1' AS ns1, 'http://example.com/u2' AS ns1) +SELECT 1 AS [ns1:a] FOR XML RAW('ns1:Row'); +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Attempt to redefine namespace prefix 'ns1')~~ + + +-- 4.2 Multiple DEFAULT declarations +WITH XMLNAMESPACES(DEFAULT 'http://a', DEFAULT 'http://b') +SELECT 1 AS a FOR XML RAW; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Attempt to redefine namespace prefix 'default')~~ + + +-- 4.3 Reserved 'xmlns' prefix +WITH XMLNAMESPACES('http://example.com' AS xmlns) +SELECT 1 AS [xmlns:a] FOR XML RAW; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Prefix 'xmlns' used in WITH XMLNAMESPACES is reserved and cannot be used as a user-defined prefix.)~~ + + +-- 4.4 Empty URI +WITH XMLNAMESPACES('' AS ns1) +SELECT 1 AS [ns1:a] FOR XML RAW; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Empty URI is not allowed in WITH XMLNAMESPACES clause.)~~ + + +-- 4.5 Reserved 'xml' prefix re-bound to wrong URI +WITH XMLNAMESPACES('http://wrong.uri' AS xml) +SELECT 1 AS [xml:a] FOR XML RAW; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: XML namespace prefix 'xml' can only be associated with the URI http://www.w3.org/XML/1998/namespace. This URI cannot be used with other prefixes.)~~ + + +-- 4.6 The XML namespace URI bound to a non-xml prefix +WITH XMLNAMESPACES('http://www.w3.org/XML/1998/namespace' AS notxml) +SELECT 1 AS a FOR XML RAW; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: XML namespace prefix 'xml' can only be associated with the URI http://www.w3.org/XML/1998/namespace. This URI cannot be used with other prefixes.)~~ + + +-- 4.7 xsi prefix bound to xsi URI without XSINIL — alias should resolve +WITH XMLNAMESPACES('http://www.w3.org/2001/XMLSchema-instance' AS xsi) +SELECT 1 AS [xsi:a] FOR XML RAW; +GO +~~START~~ +ntext + +~~END~~ + + +-- 4.8 xsi prefix bound to xsi URI with XSINIL — must not duplicate xmlns:xsi +WITH XMLNAMESPACES('http://www.w3.org/2001/XMLSchema-instance' AS xsi) +SELECT 1 AS a, NULL AS b FOR XML RAW, ELEMENTS XSINIL; +GO +~~START~~ +ntext +1 +~~END~~ + + + +-- ============================================ +-- SECTION 5: CTE / subquery combinations +-- ============================================ +-- 5.1 WITH XMLNAMESPACES followed by CTE (must come before CTE) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1), +ns_cte AS (SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] FROM ns_cte FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 5.2 Outer query with subquery that doesn't use namespaces +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM (SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 1) sub +FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + + +-- ============================================ +-- SECTION 6: Mixed scalar types +-- ============================================ +-- 6.1 numeric columns +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT OrderID AS [ns1:ID], Amount AS [ns1:Amt] +FROM forxml_ns_orders WHERE OrderID = 101 FOR XML RAW('ns1:Order'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 6.2 NULL numeric with XSINIL +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT OrderID AS [ns1:ID], Amount AS [ns1:Amt] +FROM forxml_ns_orders WHERE OrderID = 103 FOR XML RAW('ns1:Order'), ELEMENTS XSINIL; +GO +~~START~~ +ntext +103 +~~END~~ + + + +-- ============================================ +-- SECTION 7: All-NULL rows with XSINIL + namespaces +-- ============================================ +-- 7.1 RAW +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT a AS [ns1:a], b AS [ns1:b] FROM forxml_ns_nullable WHERE a IS NULL AND b IS NULL +FOR XML RAW('ns1:Row'), ELEMENTS XSINIL; +GO +~~START~~ +ntext + +~~END~~ + + +-- 7.2 PATH +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT a AS [ns1:a], b AS [ns1:b] FROM forxml_ns_nullable WHERE a IS NULL AND b IS NULL +FOR XML PATH('ns1:Row'), ELEMENTS XSINIL; +GO +~~START~~ +ntext + +~~END~~ + + + +-- ============================================ +-- SECTION 8: View-based queries +-- ============================================ +-- 8.1 SELECT FROM view with RAW +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT a AS [ns1:a], b AS [ns1:b] FROM forxml_ns_view2 FOR XML RAW('ns1:Row'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 8.2 SELECT FROM view with PATH +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT a AS [ns1:a], b AS [ns1:b] FROM forxml_ns_view2 FOR XML PATH('ns1:Row'); +GO +~~START~~ +ntext +1val12val2 +~~END~~ + + + +-- ============================================ +-- SECTION 9: Special characters / unicode in URIs and values +-- ============================================ +-- 9.1 URI with hash and percent characters +WITH XMLNAMESPACES('http://example.com/path#section%20path' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 9.1b URI with ampersand (must be XML-escaped to & in attribute value) +WITH XMLNAMESPACES('http://example.com/path?q=v&more' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 9.1c URI with less-than (must be XML-escaped) +WITH XMLNAMESPACES('http://example.com/' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 9.2 Long URI +WITH XMLNAMESPACES('http://example.com/very/long/path/with/many/segments/that/goes/on/and/on/for/testing/purposes/only' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + + +-- ============================================ +-- SECTION 10: Different data types +-- ============================================ +-- 10.1 RAW with all data types prefixed +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT IntCol AS [ns1:Int], VarcharCol AS [ns1:Str], BitCol AS [ns1:Bit], + DecimalCol AS [ns1:Dec], DateCol AS [ns1:Dt] +FROM forxml_ns_types WHERE IntCol = 100 FOR XML RAW('ns1:Row'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 10.2 PATH with all data types and ELEMENTS XSINIL on NULL row +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT IntCol AS [ns1:Int], VarcharCol AS [ns1:Str], BitCol AS [ns1:Bit] +FROM forxml_ns_types WHERE IntCol IS NULL AND VarcharCol IS NULL +FOR XML PATH('ns1:Row'), ELEMENTS XSINIL; +GO +~~START~~ +ntext + +~~END~~ + + + +-- ============================================ +-- SECTION 11: Special characters in values +-- ============================================ +-- 11.1 Ampersand, less-than, quotes — RAW +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT ID AS [ns1:ID], Value AS [ns1:Value] +FROM forxml_ns_special FOR XML RAW('ns1:Row'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 11.2 Embedded XML markup — PATH +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT ID AS [ns1:ID], Value AS [ns1:Value] +FROM forxml_ns_special WHERE ID = 4 FOR XML PATH('ns1:Row'); +GO +~~START~~ +ntext +4<tag>x</tag> +~~END~~ + + + +-- ============================================ +-- SECTION 12: Unicode/multibyte values +-- ============================================ +-- 12.1 Multibyte values in column with prefixed alias +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT ID AS [ns1:ID], Name AS [ns1:Name] +FROM forxml_ns_unicode FOR XML RAW('ns1:Row'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 12.2 Multibyte values with PATH ELEMENTS +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT ID AS [ns1:ID], Name AS [ns1:Name] +FROM forxml_ns_unicode FOR XML PATH('ns1:Row'), ELEMENTS; +GO +~~START~~ +ntext +1日本語2中文3한국어 +~~END~~ + + + +-- ============================================ +-- SECTION 13: ORDER BY / TOP / GROUP BY +-- ============================================ +-- 13.1 ORDER BY prefixed alias +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees ORDER BY EmpID DESC FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 13.2 TOP with namespaces +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT TOP 2 EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees ORDER BY EmpID FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 13.3 GROUP BY with COUNT and namespace +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT Dept AS [ns1:Dept], COUNT(*) AS [ns1:N] +FROM forxml_ns_employees GROUP BY Dept ORDER BY Dept FOR XML RAW('ns1:Group'); +GO +~~START~~ +ntext + +~~END~~ + + + +-- ============================================ +-- SECTION 14: WHERE clause variations +-- ============================================ +-- 14.1 WHERE with IN +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID IN (1, 3) FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 14.2 WHERE with LIKE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpName LIKE 'A%' FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 14.3 WHERE with IS NULL +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE Dept IS NULL FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + + +-- ============================================ +-- SECTION 15: UNION queries +-- ============================================ +-- 15.1 UNION ALL with namespaces +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] FROM forxml_ns_employees WHERE EmpID = 1 +UNION ALL +SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 2 +FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 15.2 UNION (distinct) with namespaces +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] FROM forxml_ns_employees WHERE EmpID = 1 +UNION +SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + + +-- ============================================ +-- SECTION 16: Subqueries (correlated, in SELECT list) +-- ============================================ +-- 16.1 Scalar subquery in SELECT list +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID AS [ns1:ID], + (SELECT COUNT(*) FROM forxml_ns_orders o WHERE o.EmpID = e.EmpID) AS [ns1:OrderCount] +FROM forxml_ns_employees e WHERE e.EmpID <= 2 FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 16.2 Subquery FOR XML returning XML inline (no TYPE — string concatenation) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID AS [ns1:ID], e.EmpName AS [ns1:Name], + (SELECT TOP 1 o.OrderID FROM forxml_ns_orders o WHERE o.EmpID = e.EmpID ORDER BY o.OrderID) AS [ns1:FirstOrderID] +FROM forxml_ns_employees e WHERE e.EmpID = 1 +FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + + +-- ============================================ +-- SECTION 17: SELECT INTO variable / SET assignment +-- ============================================ +-- 17.1 SELECT INTO @var with namespaces +DECLARE @x XML; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x = (SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] + FROM forxml_ns_employees WHERE EmpID = 1 + FOR XML RAW('ns1:Emp'), TYPE); +SELECT @x; +GO +~~START~~ +xml + +~~END~~ + + +-- 17.2 NVARCHAR variable with namespaces +DECLARE @s NVARCHAR(MAX); +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @s = (SELECT EmpID AS [ns1:ID] + FROM forxml_ns_employees WHERE EmpID = 1 + FOR XML RAW('ns1:Emp')); +SELECT @s; +GO +~~START~~ +nvarchar + +~~END~~ + + + +-- ============================================ +-- SECTION 18: XML methods on FOR XML result +-- ============================================ +-- 18.1 .query() on FOR XML TYPE result +DECLARE @x XML; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x = (SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] + FROM forxml_ns_employees WHERE EmpID = 1 + FOR XML RAW('ns1:Emp'), TYPE); +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/ns1:Emp'); +GO +~~START~~ +xml + +~~END~~ + + +-- 18.2 .value() on FOR XML TYPE result +DECLARE @x XML; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x = (SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] + FROM forxml_ns_employees WHERE EmpID = 1 + FOR XML PATH('ns1:Emp'), TYPE); +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.value('(/ns1:Emp/ns1:Name)[1]', 'varchar(50)'); +GO +~~START~~ +varchar +Alice +~~END~~ + + + +-- ============================================ +-- SECTION 19: JOIN queries with namespaces (extended) +-- ============================================ +-- 19.1 INNER JOIN with prefixed columns from both tables — RAW +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID AS [ns1:EmpID], e.EmpName AS [ns1:EmpName], + o.OrderID AS [ns1:OrderID], o.Amount AS [ns1:Amount] +FROM forxml_ns_employees e +JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID, o.OrderID FOR XML RAW('ns1:Row'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 19.2 LEFT JOIN PATH with ELEMENTS XSINIL +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID AS [ns1:EmpID], o.OrderID AS [ns1:OrderID] +FROM forxml_ns_employees e +LEFT JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID FOR XML PATH('ns1:Row'), ELEMENTS XSINIL; +GO +~~START~~ +ntext +11011103210234 +~~END~~ + + + +-- ============================================ +-- SECTION 20: Custom row element name variations +-- ============================================ +-- 20.1 RAW name with hyphens (NCName allows '-') +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML RAW('ns1:Emp-Record'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 20.2 RAW name with underscores +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML RAW('ns1:Emp_Record'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 20.3 PATH long element name +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML PATH('ns1:VeryLongElementNameUsedToTestBoundaryConditions'); +GO +~~START~~ +ntext +1 +~~END~~ + + + +-- ============================================ +-- SECTION 21: Empty result set +-- ============================================ +-- 21.1 RAW with no rows returned +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 999 +FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext +~~END~~ + + +-- 21.2 PATH with no rows + ROOT +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 999 +FOR XML PATH('ns1:Emp'), ROOT('ns1:Doc'); +GO +~~START~~ +ntext +~~END~~ + + + +-- ============================================ +-- SECTION 22: Undeclared prefix in column alias (colon restoration) +-- ============================================ +-- 22.1 RAW attribute mode - undeclared prefix +SELECT 1 AS [ns:a] FOR XML RAW; +GO +~~START~~ +ntext + +~~END~~ + + +-- 22.2 RAW ELEMENTS mode - undeclared prefix +SELECT 1 AS [ns:a] FOR XML RAW, ELEMENTS; +GO +~~START~~ +ntext +1 +~~END~~ + + +-- ============================================ +-- SECTION 23: Invalid prefix character validation in WITH XMLNAMESPACES +-- ============================================ +-- 23.1 Special character in prefix - errors +WITH XMLNAMESPACES('http://test.com' AS [n@s1]) SELECT 'val' AS x FOR XML RAW; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Prefix 'n@s1' used in WITH XMLNAMESPACES clause contains an invalid XML identifier. '@'(0x0040) is the first character at fault.)~~ + + +-- 23.2 Numeric first character - errors +WITH XMLNAMESPACES('http://test.com' AS [1ns]) SELECT 'val' AS x FOR XML RAW; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Prefix '1ns' used in WITH XMLNAMESPACES clause contains an invalid XML identifier. '1'(0x0031) is the first character at fault.)~~ + + +-- 23.3 Hyphen first character - errors +WITH XMLNAMESPACES('http://test.com' AS [-ns]) SELECT 'val' AS x FOR XML RAW; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Prefix '-ns' used in WITH XMLNAMESPACES clause contains an invalid XML identifier. '-'(0x002D) is the first character at fault.)~~ + + +-- 23.4 Hyphen mid-prefix - valid, passes +WITH XMLNAMESPACES('http://test.com' AS [n-s1]) SELECT 'val' AS x FOR XML RAW; +GO +~~START~~ +ntext + +~~END~~ + + +-- 23.5 Digit mid-prefix - valid, passes +WITH XMLNAMESPACES('http://test.com' AS [ns123]) SELECT 'val' AS x FOR XML RAW; +GO +~~START~~ +ntext + +~~END~~ + + + +-- ============================================ +-- SECTION 24: ns_decls_has_xsi must not false-match a URI substring +-- ============================================ +-- 24.1 Declared URI contains "xmlns:xsi=" substring, with ELEMENTS XSINIL. +WITH XMLNAMESPACES('http://x/?xmlns:xsi=fake' AS n) +SELECT 1 AS a, CAST(NULL AS VARCHAR(10)) AS b +FOR XML RAW, ELEMENTS XSINIL; +GO +~~START~~ +ntext +1 +~~END~~ + + +-- 24.2 Same false-match guard in PATH mode +WITH XMLNAMESPACES('http://x/?xmlns:xsi=fake' AS n) +SELECT 1 AS a, CAST(NULL AS VARCHAR(10)) AS b +FOR XML PATH('Row'), ELEMENTS XSINIL; +GO +~~START~~ +ntext +1 +~~END~~ + diff --git a/test/JDBC/expected/forxml-namespaces-vu-cleanup.out b/test/JDBC/expected/forxml-namespaces-vu-cleanup.out new file mode 100644 index 00000000000..089440fb567 --- /dev/null +++ b/test/JDBC/expected/forxml-namespaces-vu-cleanup.out @@ -0,0 +1,30 @@ + +-- ============================================ +-- SECTION: Cleanup +-- ============================================ +DROP VIEW forxml_ns_view1; +GO + +DROP VIEW forxml_ns_view2; +GO + +DROP TABLE forxml_ns_employees; +GO + +DROP TABLE forxml_ns_orders; +GO + +DROP TABLE forxml_ns_simple; +GO + +DROP TABLE forxml_ns_nullable; +GO + +DROP TABLE forxml_ns_types; +GO + +DROP TABLE forxml_ns_special; +GO + +DROP TABLE forxml_ns_unicode; +GO diff --git a/test/JDBC/expected/forxml-namespaces-vu-prepare.out b/test/JDBC/expected/forxml-namespaces-vu-prepare.out new file mode 100644 index 00000000000..ce7009e243c --- /dev/null +++ b/test/JDBC/expected/forxml-namespaces-vu-prepare.out @@ -0,0 +1,157 @@ + + +-- ============================================ +-- Tests for WITH XMLNAMESPACES + FOR XML (RAW, PATH, AUTO) +-- +-- WITH XMLNAMESPACES declares prefix-to-URI mappings whose scope is one +-- statement. T-SQL emits xmlns:prefix="uri" attributes on the appropriate +-- elements: +-- - FOR XML RAW: on each row element +-- - FOR XML PATH: on the outermost row element +-- - FOR XML AUTO: on the outermost (table-named) element +-- DEFAULT 'uri' emits xmlns="uri" (applies to unprefixed elements only). +-- ============================================ +-- ============================================ +-- SECTION: Base Tables +-- ============================================ +CREATE TABLE forxml_ns_employees ( + EmpID INT, + EmpName VARCHAR(50), + Dept VARCHAR(50) +); +GO + +INSERT INTO forxml_ns_employees VALUES (1, 'Alice', 'Sales'); +INSERT INTO forxml_ns_employees VALUES (2, 'Bob', 'IT'); +INSERT INTO forxml_ns_employees VALUES (3, NULL, 'HR'); +INSERT INTO forxml_ns_employees VALUES (4, 'Diana', NULL); +GO +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + + +CREATE TABLE forxml_ns_orders ( + OrderID INT, + EmpID INT, + Amount DECIMAL(10, 2) +); +GO + +INSERT INTO forxml_ns_orders VALUES (101, 1, 100.50); +INSERT INTO forxml_ns_orders VALUES (102, 2, 200.00); +INSERT INTO forxml_ns_orders VALUES (103, 1, NULL); +GO +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + + +CREATE TABLE forxml_ns_simple ( + a INT, + b VARCHAR(50) +); +GO + +INSERT INTO forxml_ns_simple VALUES (1, 'val1'); +INSERT INTO forxml_ns_simple VALUES (2, 'val2'); +GO +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + + +CREATE TABLE forxml_ns_nullable ( + a INT, + b VARCHAR(50) +); +GO + +INSERT INTO forxml_ns_nullable VALUES (NULL, NULL); +INSERT INTO forxml_ns_nullable VALUES (1, NULL); +INSERT INTO forxml_ns_nullable VALUES (NULL, 'x'); +GO +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + + +-- Table with multiple data types +CREATE TABLE forxml_ns_types ( + IntCol INT, + VarcharCol VARCHAR(50), + BitCol BIT, + DecimalCol DECIMAL(10,2), + DateCol DATE +); +GO + +INSERT INTO forxml_ns_types VALUES (100, 'Text1', 1, 99.99, '2024-01-01'); +INSERT INTO forxml_ns_types VALUES (NULL, NULL, NULL, NULL, NULL); +INSERT INTO forxml_ns_types VALUES (200, NULL, 0, NULL, '2024-06-15'); +GO +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + + +-- Special characters table +CREATE TABLE forxml_ns_special ( + ID INT, + Value VARCHAR(100) +); +GO + +INSERT INTO forxml_ns_special VALUES (1, 'a & b'); +INSERT INTO forxml_ns_special VALUES (2, 'a < b'); +INSERT INTO forxml_ns_special VALUES (3, 'say "hello"'); +INSERT INTO forxml_ns_special VALUES (4, 'x'); +GO +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + + +-- Unicode/multibyte values table +CREATE TABLE forxml_ns_unicode ( + ID INT, + Name NVARCHAR(50) +); +GO + +INSERT INTO forxml_ns_unicode VALUES (1, N'日本語'); +INSERT INTO forxml_ns_unicode VALUES (2, N'中文'); +INSERT INTO forxml_ns_unicode VALUES (3, N'한국어'); +GO +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + + + +-- ============================================ +-- SECTION: Dependent Views +-- ============================================ +CREATE VIEW forxml_ns_view1 AS +SELECT EmpID, EmpName FROM forxml_ns_employees; +GO + +CREATE VIEW forxml_ns_view2 AS +SELECT a, b FROM forxml_ns_simple; +GO diff --git a/test/JDBC/expected/forxml-namespaces-vu-verify.out b/test/JDBC/expected/forxml-namespaces-vu-verify.out new file mode 100644 index 00000000000..80eddca078b --- /dev/null +++ b/test/JDBC/expected/forxml-namespaces-vu-verify.out @@ -0,0 +1,1210 @@ + + +-- ============================================ +-- WITH XMLNAMESPACES + FOR XML (RAW, PATH, AUTO) +-- +-- Scope: WITH XMLNAMESPACES declares prefix-to-URI mappings for one +-- statement. T-SQL emits xmlns:prefix="uri" attributes on the appropriate +-- elements: +-- - FOR XML RAW: on each row element +-- - FOR XML PATH: on the outermost row element +-- - FOR XML AUTO: on the outermost (table-named) element +-- DEFAULT 'uri' emits xmlns="uri" +-- ============================================ +-- ============================================ +-- SECTION 1: FOR XML RAW +-- ============================================ +-- 1.1 Basic single-prefix RAW with no prefix used in row/cols +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW; +GO +~~START~~ +ntext + +~~END~~ + + +-- 1.2 RAW with named row, no prefix in row/cols +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 1.3 RAW with prefixed row name, no prefix in cols +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 1.4 RAW with prefixed column aliases +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 1.5 Multiple prefixes, each used in different columns +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1, 'http://example.com/ns2' AS ns2) +SELECT EmpID AS [ns1:ID], EmpName AS [ns2:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 1.6 DEFAULT namespace +WITH XMLNAMESPACES(DEFAULT 'http://example.com/default') +SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW; +GO +~~START~~ +ntext + +~~END~~ + + +-- 1.7 DEFAULT + prefix combined +WITH XMLNAMESPACES(DEFAULT 'http://example.com/default', 'http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 1.8 RAW + ELEMENTS +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'), ELEMENTS; +GO +~~START~~ +ntext +1Alice +~~END~~ + + +-- 1.9 RAW + ELEMENTS XSINIL with NULL value +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name], Dept AS [ns1:Dept] +FROM forxml_ns_employees WHERE EmpID = 3 FOR XML RAW('ns1:Emp'), ELEMENTS XSINIL; +GO +~~START~~ +ntext +3HR +~~END~~ + + +-- 1.10 RAW + ELEMENTS ABSENT (NULLs dropped) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name], Dept AS [ns1:Dept] +FROM forxml_ns_employees WHERE EmpID = 3 FOR XML RAW('ns1:Emp'), ELEMENTS ABSENT; +GO +~~START~~ +ntext +3HR +~~END~~ + + +-- 1.11 RAW + ROOT (unprefixed root) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'), ROOT('Doc'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 1.12 RAW + ROOT (prefixed root) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'), ROOT('ns1:Doc'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 1.13 RAW + TYPE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'), TYPE; +GO +~~START~~ +xml + +~~END~~ + + +-- 1.14 RAW + ROOT + TYPE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'), ROOT('ns1:Doc'), TYPE; +GO +~~START~~ +xml + +~~END~~ + + +-- 1.15 RAW multi-row with namespaces +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 1.16 RAW with view as the source +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_view1 WHERE EmpID = 1 FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 1.17 RAW with computed expression and prefixed alias +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID + 100 AS [ns1:OffsetID], UPPER(EmpName) AS [ns1:UpperName] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + + +-- ============================================ +-- SECTION 2: FOR XML PATH +-- ============================================ +-- 2.1 PATH with prefix on row and column elements +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML PATH('ns1:Emp'); +GO +~~START~~ +ntext +1Alice +~~END~~ + + +-- 2.2 PATH default row name with prefix on cols only +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML PATH; +GO +~~START~~ +ntext +1Alice +~~END~~ + + +-- 2.3 PATH with attribute-centric prefix (@) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [@ns1:id], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML PATH('ns1:Emp'); +GO +~~START~~ +ntext +Alice +~~END~~ + + +-- 2.4 PATH + ROOT prefixed +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML PATH('ns1:Emp'), ROOT('ns1:Doc'); +GO +~~START~~ +ntext +1Alice +~~END~~ + + +-- 2.5 PATH + ELEMENTS XSINIL with NULL +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name], Dept AS [ns1:Dept] +FROM forxml_ns_employees WHERE EmpID = 3 FOR XML PATH('ns1:Emp'), ELEMENTS XSINIL; +GO +~~START~~ +ntext +3HR +~~END~~ + + +-- 2.6 PATH + DEFAULT namespace +WITH XMLNAMESPACES(DEFAULT 'http://example.com/default') +SELECT EmpID AS ID, EmpName AS Name +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML PATH('Emp'); +GO +~~START~~ +ntext +1Alice +~~END~~ + + +-- 2.7 PATH + DEFAULT + prefix +WITH XMLNAMESPACES(DEFAULT 'http://example.com/default', 'http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML PATH('ns1:Emp'); +GO +~~START~~ +ntext +1Alice +~~END~~ + + +-- 2.8 PATH + multiple prefixes +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1, 'http://example.com/ns2' AS ns2) +SELECT EmpID AS [ns1:ID], EmpName AS [ns2:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML PATH('ns1:Emp'); +GO +~~START~~ +ntext +1Alice +~~END~~ + + +-- 2.9 PATH + ROOT + TYPE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML PATH('ns1:Emp'), ROOT('ns1:Doc'), TYPE; +GO +~~START~~ +xml +1Alice +~~END~~ + + +-- 2.10 PATH multi-row +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees FOR XML PATH('ns1:Emp'); +GO +~~START~~ +ntext +1Alice2Bob34Diana +~~END~~ + + + +-- ============================================ +-- SECTION 3: FOR XML AUTO +-- ============================================ +-- 3.1 AUTO with prefixed table name +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT forxml_ns_employees.EmpID, forxml_ns_employees.EmpName +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML AUTO; +GO +~~START~~ +ntext + +~~END~~ + + +-- 3.2 AUTO + ELEMENTS +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT forxml_ns_employees.EmpID, forxml_ns_employees.EmpName +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML AUTO, ELEMENTS; +GO +~~START~~ +ntext +1Alice +~~END~~ + + +-- 3.3 AUTO + ROOT +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT forxml_ns_employees.EmpID, forxml_ns_employees.EmpName +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML AUTO, ROOT('Doc'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 3.4 AUTO + DEFAULT + prefix +WITH XMLNAMESPACES(DEFAULT 'http://example.com/default', 'http://example.com/ns1' AS ns1) +SELECT forxml_ns_employees.EmpID, forxml_ns_employees.EmpName +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML AUTO; +GO +~~START~~ +ntext + +~~END~~ + + +-- 3.5 AUTO + TYPE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT forxml_ns_employees.EmpID, forxml_ns_employees.EmpName +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML AUTO, TYPE; +GO +~~START~~ +xml + +~~END~~ + + +-- 3.6 AUTO + ELEMENTS XSINIL + NULL row +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT forxml_ns_employees.EmpID, forxml_ns_employees.EmpName, forxml_ns_employees.Dept +FROM forxml_ns_employees WHERE EmpID = 3 FOR XML AUTO, ELEMENTS XSINIL; +GO +~~START~~ +ntext +3HR +~~END~~ + + +-- 3.7 AUTO multi-row +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT forxml_ns_employees.EmpID, forxml_ns_employees.EmpName +FROM forxml_ns_employees FOR XML AUTO; +GO +~~START~~ +ntext + +~~END~~ + + +-- 3.8 AUTO with JOIN (parent-child nesting) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID, e.EmpName, o.OrderID, o.Amount +FROM forxml_ns_employees e +JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID, o.OrderID +FOR XML AUTO; +GO +~~START~~ +ntext + +~~END~~ + + +-- 3.9 AUTO with JOIN + ELEMENTS +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID, e.EmpName, o.OrderID, o.Amount +FROM forxml_ns_employees e +JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID, o.OrderID +FOR XML AUTO, ELEMENTS; +GO +~~START~~ +ntext +1Alice101100.501032Bob102200.00 +~~END~~ + + +-- 3.10 AUTO with JOIN + ROOT +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID, e.EmpName, o.OrderID, o.Amount +FROM forxml_ns_employees e +JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID, o.OrderID +FOR XML AUTO, ROOT('Doc'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 3.11 AUTO with LEFT JOIN, NULL children +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID, e.EmpName, o.OrderID, o.Amount +FROM forxml_ns_employees e +LEFT JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID +FOR XML AUTO; +GO +~~START~~ +ntext + +~~END~~ + + +-- 3.12 AUTO with CTE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1), +ActiveEmps AS (SELECT EmpID, EmpName FROM forxml_ns_employees WHERE Dept IS NOT NULL) +SELECT a.EmpID, a.EmpName +FROM ActiveEmps a +FOR XML AUTO; +GO +~~START~~ +ntext + +~~END~~ + + +-- 3.13 AUTO with subquery in FROM +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT sub.EmpID, sub.EmpName +FROM (SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID <= 2) sub +FOR XML AUTO; +GO +~~START~~ +ntext + +~~END~~ + + +-- 3.14 AUTO multi-table JOIN with ELEMENTS XSINIL +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID, e.EmpName, e.Dept, o.OrderID, o.Amount +FROM forxml_ns_employees e +LEFT JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID, o.OrderID +FOR XML AUTO, ELEMENTS XSINIL; +GO +~~START~~ +ntext +1AliceSales101100.501032BobIT102200.003HR4Diana +~~END~~ + + +-- 3.15 AUTO + DEFAULT ns + JOIN +WITH XMLNAMESPACES(DEFAULT 'http://default.com', 'http://example.com/ns1' AS ns1) +SELECT e.EmpID, e.EmpName, o.OrderID +FROM forxml_ns_employees e +JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID +FOR XML AUTO; +GO +~~START~~ +ntext + +~~END~~ + + +-- 3.16 AUTO with JOIN + TYPE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID, e.EmpName, o.OrderID +FROM forxml_ns_employees e +JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID +FOR XML AUTO, TYPE; +GO +~~START~~ +xml + +~~END~~ + + + +-- ============================================ +-- SECTION 4: Validation / Error cases +-- ============================================ +-- 4.1 Duplicate prefix declaration +WITH XMLNAMESPACES('http://example.com/u1' AS ns1, 'http://example.com/u2' AS ns1) +SELECT 1 AS [ns1:a] FOR XML RAW('ns1:Row'); +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Attempt to redefine namespace prefix 'ns1')~~ + + +-- 4.2 Multiple DEFAULT declarations +WITH XMLNAMESPACES(DEFAULT 'http://a', DEFAULT 'http://b') +SELECT 1 AS a FOR XML RAW; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Attempt to redefine namespace prefix 'default')~~ + + +-- 4.3 Reserved 'xmlns' prefix +WITH XMLNAMESPACES('http://example.com' AS xmlns) +SELECT 1 AS [xmlns:a] FOR XML RAW; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Prefix 'xmlns' used in WITH XMLNAMESPACES is reserved and cannot be used as a user-defined prefix.)~~ + + +-- 4.4 Empty URI +WITH XMLNAMESPACES('' AS ns1) +SELECT 1 AS [ns1:a] FOR XML RAW; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Empty URI is not allowed in WITH XMLNAMESPACES clause.)~~ + + +-- 4.5 Reserved 'xml' prefix re-bound to wrong URI +WITH XMLNAMESPACES('http://wrong.uri' AS xml) +SELECT 1 AS [xml:a] FOR XML RAW; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: XML namespace prefix 'xml' can only be associated with the URI http://www.w3.org/XML/1998/namespace. This URI cannot be used with other prefixes.)~~ + + +-- 4.6 The XML namespace URI bound to a non-xml prefix +WITH XMLNAMESPACES('http://www.w3.org/XML/1998/namespace' AS notxml) +SELECT 1 AS a FOR XML RAW; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: XML namespace prefix 'xml' can only be associated with the URI http://www.w3.org/XML/1998/namespace. This URI cannot be used with other prefixes.)~~ + + +-- 4.7 xsi prefix bound to xsi URI without XSINIL — alias should resolve +WITH XMLNAMESPACES('http://www.w3.org/2001/XMLSchema-instance' AS xsi) +SELECT 1 AS [xsi:a] FOR XML RAW; +GO +~~START~~ +ntext + +~~END~~ + + +-- 4.8 xsi prefix bound to xsi URI with XSINIL — must not duplicate xmlns:xsi +WITH XMLNAMESPACES('http://www.w3.org/2001/XMLSchema-instance' AS xsi) +SELECT 1 AS a, NULL AS b FOR XML RAW, ELEMENTS XSINIL; +GO +~~START~~ +ntext +1 +~~END~~ + + + +-- ============================================ +-- SECTION 5: CTE / subquery combinations +-- ============================================ +-- 5.1 WITH XMLNAMESPACES followed by CTE (must come before CTE) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1), +ns_cte AS (SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] FROM ns_cte FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 5.2 Outer query with subquery that doesn't use namespaces +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM (SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 1) sub +FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + + +-- ============================================ +-- SECTION 6: Mixed scalar types +-- ============================================ +-- 6.1 numeric columns +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT OrderID AS [ns1:ID], Amount AS [ns1:Amt] +FROM forxml_ns_orders WHERE OrderID = 101 FOR XML RAW('ns1:Order'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 6.2 NULL numeric with XSINIL +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT OrderID AS [ns1:ID], Amount AS [ns1:Amt] +FROM forxml_ns_orders WHERE OrderID = 103 FOR XML RAW('ns1:Order'), ELEMENTS XSINIL; +GO +~~START~~ +ntext +103 +~~END~~ + + + +-- ============================================ +-- SECTION 7: All-NULL rows with XSINIL + namespaces +-- ============================================ +-- 7.1 RAW +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT a AS [ns1:a], b AS [ns1:b] FROM forxml_ns_nullable WHERE a IS NULL AND b IS NULL +FOR XML RAW('ns1:Row'), ELEMENTS XSINIL; +GO +~~START~~ +ntext + +~~END~~ + + +-- 7.2 PATH +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT a AS [ns1:a], b AS [ns1:b] FROM forxml_ns_nullable WHERE a IS NULL AND b IS NULL +FOR XML PATH('ns1:Row'), ELEMENTS XSINIL; +GO +~~START~~ +ntext + +~~END~~ + + + +-- ============================================ +-- SECTION 8: View-based queries +-- ============================================ +-- 8.1 SELECT FROM view with RAW +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT a AS [ns1:a], b AS [ns1:b] FROM forxml_ns_view2 FOR XML RAW('ns1:Row'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 8.2 SELECT FROM view with PATH +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT a AS [ns1:a], b AS [ns1:b] FROM forxml_ns_view2 FOR XML PATH('ns1:Row'); +GO +~~START~~ +ntext +1val12val2 +~~END~~ + + + +-- ============================================ +-- SECTION 9: Special characters / unicode in URIs and values +-- ============================================ +-- 9.1 URI with hash and percent characters +WITH XMLNAMESPACES('http://example.com/path#section%20path' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 9.1b URI with ampersand (must be XML-escaped to & in attribute value) +WITH XMLNAMESPACES('http://example.com/path?q=v&more' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 9.1c URI with less-than (must be XML-escaped) +WITH XMLNAMESPACES('http://example.com/' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 9.2 Long URI +WITH XMLNAMESPACES('http://example.com/very/long/path/with/many/segments/that/goes/on/and/on/for/testing/purposes/only' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + + +-- ============================================ +-- SECTION 10: Different data types +-- ============================================ +-- 10.1 RAW with all data types prefixed +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT IntCol AS [ns1:Int], VarcharCol AS [ns1:Str], BitCol AS [ns1:Bit], + DecimalCol AS [ns1:Dec], DateCol AS [ns1:Dt] +FROM forxml_ns_types WHERE IntCol = 100 FOR XML RAW('ns1:Row'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 10.2 PATH with all data types and ELEMENTS XSINIL on NULL row +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT IntCol AS [ns1:Int], VarcharCol AS [ns1:Str], BitCol AS [ns1:Bit] +FROM forxml_ns_types WHERE IntCol IS NULL AND VarcharCol IS NULL +FOR XML PATH('ns1:Row'), ELEMENTS XSINIL; +GO +~~START~~ +ntext + +~~END~~ + + + +-- ============================================ +-- SECTION 11: Special characters in values +-- ============================================ +-- 11.1 Ampersand, less-than, quotes — RAW +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT ID AS [ns1:ID], Value AS [ns1:Value] +FROM forxml_ns_special FOR XML RAW('ns1:Row'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 11.2 Embedded XML markup — PATH +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT ID AS [ns1:ID], Value AS [ns1:Value] +FROM forxml_ns_special WHERE ID = 4 FOR XML PATH('ns1:Row'); +GO +~~START~~ +ntext +4<tag>x</tag> +~~END~~ + + + +-- ============================================ +-- SECTION 12: Unicode/multibyte values +-- ============================================ +-- 12.1 Multibyte values in column with prefixed alias +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT ID AS [ns1:ID], Name AS [ns1:Name] +FROM forxml_ns_unicode FOR XML RAW('ns1:Row'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 12.2 Multibyte values with PATH ELEMENTS +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT ID AS [ns1:ID], Name AS [ns1:Name] +FROM forxml_ns_unicode FOR XML PATH('ns1:Row'), ELEMENTS; +GO +~~START~~ +ntext +1日本語2中文3한국어 +~~END~~ + + + +-- ============================================ +-- SECTION 13: ORDER BY / TOP / GROUP BY +-- ============================================ +-- 13.1 ORDER BY prefixed alias +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees ORDER BY EmpID DESC FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 13.2 TOP with namespaces +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT TOP 2 EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees ORDER BY EmpID FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 13.3 GROUP BY with COUNT and namespace +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT Dept AS [ns1:Dept], COUNT(*) AS [ns1:N] +FROM forxml_ns_employees GROUP BY Dept ORDER BY Dept FOR XML RAW('ns1:Group'); +GO +~~START~~ +ntext + +~~END~~ + + + +-- ============================================ +-- SECTION 14: WHERE clause variations +-- ============================================ +-- 14.1 WHERE with IN +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID IN (1, 3) FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 14.2 WHERE with LIKE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpName LIKE 'A%' FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 14.3 WHERE with IS NULL +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE Dept IS NULL FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + + +-- ============================================ +-- SECTION 15: UNION queries +-- ============================================ +-- 15.1 UNION ALL with namespaces +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] FROM forxml_ns_employees WHERE EmpID = 1 +UNION ALL +SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 2 +FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 15.2 UNION (distinct) with namespaces +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] FROM forxml_ns_employees WHERE EmpID = 1 +UNION +SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + + +-- ============================================ +-- SECTION 16: Subqueries (correlated, in SELECT list) +-- ============================================ +-- 16.1 Scalar subquery in SELECT list +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID AS [ns1:ID], + (SELECT COUNT(*) FROM forxml_ns_orders o WHERE o.EmpID = e.EmpID) AS [ns1:OrderCount] +FROM forxml_ns_employees e WHERE e.EmpID <= 2 FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 16.2 Subquery FOR XML returning XML inline (no TYPE — string concatenation) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID AS [ns1:ID], e.EmpName AS [ns1:Name], + (SELECT TOP 1 o.OrderID FROM forxml_ns_orders o WHERE o.EmpID = e.EmpID ORDER BY o.OrderID) AS [ns1:FirstOrderID] +FROM forxml_ns_employees e WHERE e.EmpID = 1 +FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext + +~~END~~ + + + +-- ============================================ +-- SECTION 17: SELECT INTO variable / SET assignment +-- ============================================ +-- 17.1 SELECT INTO @var with namespaces +DECLARE @x XML; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x = (SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] + FROM forxml_ns_employees WHERE EmpID = 1 + FOR XML RAW('ns1:Emp'), TYPE); +SELECT @x; +GO +~~START~~ +xml + +~~END~~ + + +-- 17.2 NVARCHAR variable with namespaces +DECLARE @s NVARCHAR(MAX); +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @s = (SELECT EmpID AS [ns1:ID] + FROM forxml_ns_employees WHERE EmpID = 1 + FOR XML RAW('ns1:Emp')); +SELECT @s; +GO +~~START~~ +nvarchar + +~~END~~ + + + +-- ============================================ +-- SECTION 18: XML methods on FOR XML result +-- ============================================ +-- 18.1 .query() on FOR XML TYPE result +DECLARE @x XML; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x = (SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] + FROM forxml_ns_employees WHERE EmpID = 1 + FOR XML RAW('ns1:Emp'), TYPE); +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/ns1:Emp'); +GO +~~START~~ +xml + +~~END~~ + + +-- 18.2 .value() on FOR XML TYPE result +DECLARE @x XML; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x = (SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] + FROM forxml_ns_employees WHERE EmpID = 1 + FOR XML PATH('ns1:Emp'), TYPE); +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.value('(/ns1:Emp/ns1:Name)[1]', 'varchar(50)'); +GO +~~START~~ +varchar +Alice +~~END~~ + + + +-- ============================================ +-- SECTION 19: JOIN queries with namespaces (extended) +-- ============================================ +-- 19.1 INNER JOIN with prefixed columns from both tables — RAW +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID AS [ns1:EmpID], e.EmpName AS [ns1:EmpName], + o.OrderID AS [ns1:OrderID], o.Amount AS [ns1:Amount] +FROM forxml_ns_employees e +JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID, o.OrderID FOR XML RAW('ns1:Row'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 19.2 LEFT JOIN PATH with ELEMENTS XSINIL +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID AS [ns1:EmpID], o.OrderID AS [ns1:OrderID] +FROM forxml_ns_employees e +LEFT JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID FOR XML PATH('ns1:Row'), ELEMENTS XSINIL; +GO +~~START~~ +ntext +11011103210234 +~~END~~ + + + +-- ============================================ +-- SECTION 20: Custom row element name variations +-- ============================================ +-- 20.1 RAW name with hyphens (NCName allows '-') +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML RAW('ns1:Emp-Record'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 20.2 RAW name with underscores +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML RAW('ns1:Emp_Record'); +GO +~~START~~ +ntext + +~~END~~ + + +-- 20.3 PATH long element name +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML PATH('ns1:VeryLongElementNameUsedToTestBoundaryConditions'); +GO +~~START~~ +ntext +1 +~~END~~ + + + +-- ============================================ +-- SECTION 21: Empty result set +-- ============================================ +-- 21.1 RAW with no rows returned +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 999 +FOR XML RAW('ns1:Emp'); +GO +~~START~~ +ntext +~~END~~ + + +-- 21.2 PATH with no rows + ROOT +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 999 +FOR XML PATH('ns1:Emp'), ROOT('ns1:Doc'); +GO +~~START~~ +ntext +~~END~~ + + + +-- ============================================ +-- SECTION 22: Undeclared prefix in column alias (colon restoration) +-- ============================================ +-- 22.1 RAW attribute mode - undeclared prefix +SELECT 1 AS [ns:a] FOR XML RAW; +GO +~~START~~ +ntext + +~~END~~ + + +-- 22.2 RAW ELEMENTS mode - undeclared prefix +SELECT 1 AS [ns:a] FOR XML RAW, ELEMENTS; +GO +~~START~~ +ntext +1 +~~END~~ + + +-- ============================================ +-- SECTION 23: Invalid prefix character validation in WITH XMLNAMESPACES +-- ============================================ +-- 23.1 Special character in prefix - errors +WITH XMLNAMESPACES('http://test.com' AS [n@s1]) SELECT 'val' AS x FOR XML RAW; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Prefix 'n@s1' used in WITH XMLNAMESPACES clause contains an invalid XML identifier. '@'(0x0040) is the first character at fault.)~~ + + +-- 23.2 Numeric first character - errors +WITH XMLNAMESPACES('http://test.com' AS [1ns]) SELECT 'val' AS x FOR XML RAW; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Prefix '1ns' used in WITH XMLNAMESPACES clause contains an invalid XML identifier. '1'(0x0031) is the first character at fault.)~~ + + +-- 23.3 Hyphen first character - errors +WITH XMLNAMESPACES('http://test.com' AS [-ns]) SELECT 'val' AS x FOR XML RAW; +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Prefix '-ns' used in WITH XMLNAMESPACES clause contains an invalid XML identifier. '-'(0x002D) is the first character at fault.)~~ + + +-- 23.4 Hyphen mid-prefix - valid, passes +WITH XMLNAMESPACES('http://test.com' AS [n-s1]) SELECT 'val' AS x FOR XML RAW; +GO +~~START~~ +ntext + +~~END~~ + + +-- 23.5 Digit mid-prefix - valid, passes +WITH XMLNAMESPACES('http://test.com' AS [ns123]) SELECT 'val' AS x FOR XML RAW; +GO +~~START~~ +ntext + +~~END~~ + + + +-- ============================================ +-- SECTION 24: ns_decls_has_xsi must not false-match a URI substring +-- ============================================ +-- 24.1 Declared URI contains "xmlns:xsi=" substring, with ELEMENTS XSINIL. +WITH XMLNAMESPACES('http://x/?xmlns:xsi=fake' AS n) +SELECT 1 AS a, CAST(NULL AS VARCHAR(10)) AS b +FOR XML RAW, ELEMENTS XSINIL; +GO +~~START~~ +ntext +1 +~~END~~ + + +-- 24.2 Same false-match guard in PATH mode +WITH XMLNAMESPACES('http://x/?xmlns:xsi=fake' AS n) +SELECT 1 AS a, CAST(NULL AS VARCHAR(10)) AS b +FOR XML PATH('Row'), ELEMENTS XSINIL; +GO +~~START~~ +ntext +1 +~~END~~ + diff --git a/test/JDBC/expected/non_default_server_collation/chinese_prc_ci_as/BABEL-UNSUPPORTED.out b/test/JDBC/expected/non_default_server_collation/chinese_prc_ci_as/BABEL-UNSUPPORTED.out index 817caa5c052..5b4d317c3f9 100644 --- a/test/JDBC/expected/non_default_server_collation/chinese_prc_ci_as/BABEL-UNSUPPORTED.out +++ b/test/JDBC/expected/non_default_server_collation/chinese_prc_ci_as/BABEL-UNSUPPORTED.out @@ -2572,19 +2572,19 @@ WITH XMLNAMESPACES ('test.com' AS ns1, 'test2.com' AS ns1) SELECT 'test' AS 'Tes GO ~~ERROR (Code: 33557097)~~ -~~ERROR (Message: 'WITH XMLNAMESPACES' is not currently supported in Babelfish)~~ +~~ERROR (Message: Attempt to redefine namespace prefix 'ns1')~~ WITH XMLNAMESPACES ('test.com' AS n@s1) SELECT 'test' AS 'TestAttr' FOR XML RAW GO ~~ERROR (Code: 33557097)~~ -~~ERROR (Message: 'WITH XMLNAMESPACES' is not currently supported in Babelfish)~~ +~~ERROR (Message: Prefix 'n@s1' used in WITH XMLNAMESPACES clause contains an invalid XML identifier. '@'(0x0040) is the first character at fault.)~~ WITH XMLNAMESPACES ('test.com' AS xmlns) SELECT 'test' AS 'TestAttr' FOR XML RAW GO ~~ERROR (Code: 33557097)~~ -~~ERROR (Message: 'WITH XMLNAMESPACES' is not currently supported in Babelfish)~~ +~~ERROR (Message: Prefix 'xmlns' used in WITH XMLNAMESPACES is reserved and cannot be used as a user-defined prefix.)~~ -- BEGIN ATOMIC (10782) diff --git a/test/JDBC/expected/xml-methods-namespaces-before-17_11-or-18_5-vu-cleanup.out b/test/JDBC/expected/xml-methods-namespaces-before-17_11-or-18_5-vu-cleanup.out new file mode 100644 index 00000000000..d2cb4462509 --- /dev/null +++ b/test/JDBC/expected/xml-methods-namespaces-before-17_11-or-18_5-vu-cleanup.out @@ -0,0 +1,18 @@ + +-- ============================================ +-- SECTION: Cleanup +-- ============================================ +DROP VIEW xmlns_methods_view1; +GO + +DROP TABLE xmlns_methods_t1; +GO + +DROP TABLE xmlns_methods_t2; +GO + +DROP TABLE xmlns_methods_orders; +GO + +DROP TABLE xmlns_methods_comments; +GO diff --git a/test/JDBC/expected/xml-methods-namespaces-before-17_11-or-18_5-vu-prepare.out b/test/JDBC/expected/xml-methods-namespaces-before-17_11-or-18_5-vu-prepare.out new file mode 100644 index 00000000000..f03fbc12952 --- /dev/null +++ b/test/JDBC/expected/xml-methods-namespaces-before-17_11-or-18_5-vu-prepare.out @@ -0,0 +1,81 @@ + + +-- ============================================ +-- Tests for XML data type methods (.query, .value, .exist) +-- combined with WITH XMLNAMESPACES. +-- +-- WITH XMLNAMESPACES provides namespace bindings for XPath evaluation. +-- The bindings are passed to PG's xpath() function as a text[][] array. +-- This lets prefixed XPath like /ns1:item resolve correctly when the +-- target XML uses xmlns:ns1. +-- ============================================ +-- ============================================ +-- SECTION: Base Tables +-- ============================================ +CREATE TABLE xmlns_methods_t1 (id INT, data XML); +GO + +INSERT INTO xmlns_methods_t1 VALUES (1, 'val1val2'); +INSERT INTO xmlns_methods_t1 VALUES (2, '1020'); +INSERT INTO xmlns_methods_t1 VALUES (3, ''); +INSERT INTO xmlns_methods_t1 VALUES (4, NULL); +INSERT INTO xmlns_methods_t1 VALUES (5, '42'); +GO +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + + +CREATE TABLE xmlns_methods_t2 (id INT, data XML); +GO + +INSERT INTO xmlns_methods_t2 VALUES (1, 'SQL29.99XML39.99'); +INSERT INTO xmlns_methods_t2 VALUES (2, ''); +GO +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + + + +-- ============================================ +-- SECTION: Dependent Views +-- ============================================ +CREATE VIEW xmlns_methods_view1 AS +SELECT id, data FROM xmlns_methods_t1; +GO + + +-- ============================================ +-- SECTION: Tables for advanced method tests +-- ============================================ +CREATE TABLE xmlns_methods_orders (id INT, data XML); +GO + +INSERT INTO xmlns_methods_orders VALUES (1, 'WidgetGadget'); +INSERT INTO xmlns_methods_orders VALUES (2, 'Gizmo'); +INSERT INTO xmlns_methods_orders VALUES (3, ''); +GO +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + + +CREATE TABLE xmlns_methods_comments (id INT, data XML); +GO + +INSERT INTO xmlns_methods_comments VALUES (1, 'val'); +INSERT INTO xmlns_methods_comments VALUES (2, 'val'); +GO +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + diff --git a/test/JDBC/expected/xml-methods-namespaces-before-17_11-or-18_5-vu-verify.out b/test/JDBC/expected/xml-methods-namespaces-before-17_11-or-18_5-vu-verify.out new file mode 100644 index 00000000000..581f200e2c7 --- /dev/null +++ b/test/JDBC/expected/xml-methods-namespaces-before-17_11-or-18_5-vu-verify.out @@ -0,0 +1,835 @@ + + +-- ============================================ +-- WITH XMLNAMESPACES + .query() / .value() / .exist() +-- ============================================ +-- ============================================ +-- SECTION 1: .query() with prefixed XPath +-- ============================================ +-- 1.1 Query a prefixed element using a declared prefix +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/ns1:item'); +GO +~~START~~ +xml +val +~~END~~ + + +-- 1.2 Query unprefixed name where XML element is prefixed -> empty +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/item'); +GO +~~START~~ +xml + +~~END~~ + + +-- 1.3 Query with multiple prefixes used in different paths +DECLARE @x XML = '12'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1, 'http://example.com/ns2' AS ns2) +SELECT @x.query('/root/ns1:a'), @x.query('/root/ns2:b'); +GO +~~START~~ +xml#!#xml +1#!#2 +~~END~~ + + +-- 1.4 Query repeated prefixed elements +DECLARE @x XML = 'ab'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/ns1:item'); +GO +~~START~~ +xml +ab +~~END~~ + + +-- 1.5 Query positional access with prefix +DECLARE @x XML = 'ab'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/ns1:item[1]'), @x.query('/root/ns1:item[2]'); +GO +~~START~~ +xml#!#xml +a#!#b +~~END~~ + + +-- 1.6 Query with attribute predicate using prefix +DECLARE @x XML = 'SQLXML'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/ns1:book[@ns1:id="2"]/ns1:title'); +GO +~~START~~ +xml +XML +~~END~~ + + +-- 1.7 Query NULL XML +DECLARE @x XML = NULL; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/ns1:item'); +GO +~~START~~ +xml + +~~END~~ + + +-- 1.8 Query against table column +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, data.query('/root/ns1:item') +FROM xmlns_methods_t1 WHERE id IN (1, 2) +ORDER BY id; +GO +~~START~~ +int#!#xml +1#!#val1val2 +2#!# +~~END~~ + + +-- 1.9 Query with no match returns empty +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/ns1:nonexistent'); +GO +~~START~~ +xml + +~~END~~ + + +-- 1.10 Wildcard with prefix +DECLARE @x XML = '12'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/ns1:*'); +GO +~~START~~ +xml +12 +~~END~~ + + + +-- ============================================ +-- SECTION 2: .exist() with prefixed XPath +-- ============================================ +-- 2.1 Exist returns 1 when prefixed match found +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.exist('/root/ns1:item'); +GO +~~START~~ +bit +1 +~~END~~ + + +-- 2.2 Exist returns 0 when prefixed name doesn't match +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.exist('/root/ns1:other'); +GO +~~START~~ +bit +0 +~~END~~ + + +-- 2.3 Exist with attribute predicate +DECLARE @x XML = ''; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.exist('/root/item/@ns1:attr'); +GO +~~START~~ +bit +1 +~~END~~ + + +-- 2.4 Exist with attribute predicate (negative) +DECLARE @x XML = ''; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.exist('/root/item/@ns1:attr'); +GO +~~START~~ +bit +0 +~~END~~ + + +-- 2.5 Exist on NULL +DECLARE @x XML = NULL; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.exist('/root/ns1:item'); +GO +~~START~~ +bit + +~~END~~ + + +-- 2.6 Exist with multiple namespaces +DECLARE @x XML = '1'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1, 'http://example.com/ns2' AS ns2) +SELECT @x.exist('/root/ns1:a'), @x.exist('/root/ns2:b'); +GO +~~START~~ +bit#!#bit +1#!#0 +~~END~~ + + + +-- ============================================ +-- SECTION 3: .value() with prefixed XPath +-- ============================================ +-- 3.1 Value of prefixed element +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.value('(/root/ns1:item)[1]', 'varchar(50)'); +GO +~~START~~ +varchar +val +~~END~~ + + +-- 3.2 Value cast to int +DECLARE @x XML = '42'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.value('(/root/ns1:item)[1]', 'int'); +GO +~~START~~ +int +42 +~~END~~ + + +-- 3.3 Value of attribute with prefix +DECLARE @x XML = ''; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.value('(/root/item/@ns1:attr)[1]', 'varchar(50)'); +GO +~~START~~ +varchar +value +~~END~~ + + +-- 3.4 Value when no match - returns NULL +DECLARE @x XML = ''; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.value('(/root/ns1:item)[1]', 'varchar(50)'); +GO +~~START~~ +varchar + +~~END~~ + + +-- 3.5 Value of multiple matches selects positional +DECLARE @x XML = 'ab'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.value('(/root/ns1:item)[1]', 'varchar(10)'), + @x.value('(/root/ns1:item)[2]', 'varchar(10)'); +GO +~~START~~ +varchar#!#varchar +a#!#b +~~END~~ + + +-- 3.6 Value on NULL XML +DECLARE @x XML = NULL; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.value('(/root/ns1:item)[1]', 'varchar(50)'); +GO +~~START~~ +varchar + +~~END~~ + + +-- 3.7 Value with multiple namespaces +DECLARE @x XML = '1020'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1, 'http://example.com/ns2' AS ns2) +SELECT @x.value('(/root/ns1:a)[1]', 'int'), + @x.value('(/root/ns2:b)[1]', 'int'); +GO +~~START~~ +int#!#int +10#!#20 +~~END~~ + + + +-- ============================================ +-- SECTION 4: Methods on table columns with namespaces +-- ============================================ +-- 4.1 .query on column +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, data.query('/root/ns1:item') +FROM xmlns_methods_t1 WHERE id = 1; +GO +~~START~~ +int#!#xml +1#!#val1val2 +~~END~~ + + +-- 4.2 .value on column +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, data.value('(/root/ns1:item)[1]', 'varchar(50)') +FROM xmlns_methods_t1 WHERE id IN (1, 5); +GO +~~START~~ +int#!#varchar +1#!#val1 +5#!#42 +~~END~~ + + +-- 4.3 .exist on column +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, data.exist('/root/ns1:item') +FROM xmlns_methods_t1 WHERE id <= 5 +ORDER BY id; +GO +~~START~~ +int#!#bit +1#!#1 +2#!#0 +3#!#0 +4#!# +5#!#1 +~~END~~ + + +-- 4.4 .query on view +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, data.query('/root/ns1:item') +FROM xmlns_methods_view1 WHERE id = 1; +GO +~~START~~ +int#!#xml +1#!#val1val2 +~~END~~ + + + +-- ============================================ +-- SECTION 5: Complex namespace cases +-- ============================================ +-- 5.1 Same prefix used in multiple statements (no carry-over) +DECLARE @x XML = '1'; +WITH XMLNAMESPACES('http://uri1' AS p) +SELECT @x.value('(/root/p:a)[1]', 'int'); +GO +~~START~~ +int +1 +~~END~~ + + +-- 5.2 Prefix in XML differs from prefix in WITH XMLNAMESPACES (matching by URI) +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/ns1' AS bar) +SELECT @x.value('(/root/bar:item)[1]', 'varchar(50)'); +GO +~~START~~ +varchar +val +~~END~~ + + +-- 5.3 Predicate using prefixed attribute +WITH XMLNAMESPACES('http://example.com/products' AS p) +SELECT data.query('/catalog/p:book[@p:id="2"]/p:title') +FROM xmlns_methods_t2 WHERE id = 1; +GO +~~START~~ +xml +XML +~~END~~ + + +-- 5.4 Numeric predicate on prefixed element +WITH XMLNAMESPACES('http://example.com/products' AS p) +SELECT data.query('/catalog/p:book[p:price>30]/p:title') +FROM xmlns_methods_t2 WHERE id = 1; +GO +~~START~~ +xml +XML +~~END~~ + + +-- 5.5 Wildcard with prefix on table column +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT data.query('/root/ns1:*') +FROM xmlns_methods_t1 WHERE id = 1; +GO +~~START~~ +xml +val1val2 +~~END~~ + + +-- 5.6 .query then .value chained on result +DECLARE @x XML = 'hello'; +DECLARE @r XML; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @r = @x.query('/root/ns1:item'); +SELECT @r; +GO +~~START~~ +xml +hello +~~END~~ + + + +-- ============================================ +-- SECTION 6: NULL XML behaviors +-- ============================================ +-- 6.1 Query NULL row (with namespaces) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT data.query('/root/ns1:item') FROM xmlns_methods_t1 WHERE id = 4; +GO +~~START~~ +xml + +~~END~~ + + +-- 6.2 Value on NULL row (with namespaces) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT data.value('(/root/ns1:item)[1]', 'varchar(50)') FROM xmlns_methods_t1 WHERE id = 4; +GO +~~START~~ +varchar + +~~END~~ + + +-- 6.3 Exist on NULL row (with namespaces) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT data.exist('/root/ns1:item') FROM xmlns_methods_t1 WHERE id = 4; +GO +~~START~~ +bit + +~~END~~ + + + +-- ============================================ +-- SECTION 7: Validation with same rules as FOR XML +-- ============================================ +-- 7.1 Duplicate prefix +DECLARE @x XML = ''; +WITH XMLNAMESPACES('http://a' AS p, 'http://b' AS p) +SELECT @x.query('/root'); +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Attempt to redefine namespace prefix 'p')~~ + + +-- 7.2 Empty URI +DECLARE @x XML = ''; +WITH XMLNAMESPACES('' AS p) +SELECT @x.query('/root'); +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Empty URI is not allowed in WITH XMLNAMESPACES clause.)~~ + + +-- 7.3 Reserved xmlns prefix +DECLARE @x XML = ''; +WITH XMLNAMESPACES('http://a' AS xmlns) +SELECT @x.query('/root'); +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Prefix 'xmlns' used in WITH XMLNAMESPACES is reserved and cannot be used as a user-defined prefix.)~~ + + +-- 7.4 xml prefix with wrong URI +DECLARE @x XML = ''; +WITH XMLNAMESPACES('http://wrong' AS xml) +SELECT @x.query('/root'); +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: XML namespace prefix 'xml' can only be associated with the URI http://www.w3.org/XML/1998/namespace. This URI cannot be used with other prefixes.)~~ + + + +-- ============================================ +-- SECTION 8: Variable assignment with namespaces +-- ============================================ +-- 8.1 .query() result assigned to XML variable +DECLARE @x XML = 'val'; +DECLARE @r XML; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @r = @x.query('/root/ns1:item'); +SELECT @r; +GO +~~START~~ +xml +val +~~END~~ + + +-- 8.2 .value() result into varchar +DECLARE @x XML = 'hello'; +DECLARE @s VARCHAR(50); +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @s = @x.value('(/root/ns1:item)[1]', 'varchar(50)'); +SELECT @s; +GO +~~START~~ +varchar +hello +~~END~~ + + +-- 8.3 .exist() into bit +DECLARE @x XML = ''; +DECLARE @b BIT; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @b = @x.exist('/root/ns1:item'); +SELECT @b; +GO +~~START~~ +bit +1 +~~END~~ + + + +-- ============================================ +-- SECTION 9: Methods in WHERE clause +-- ============================================ +-- 9.1 .exist() in WHERE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id FROM xmlns_methods_t1 WHERE data.exist('/root/ns1:item') = 1 +ORDER BY id; +GO +~~START~~ +int +1 +5 +~~END~~ + + +-- 9.2 .value() in WHERE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id FROM xmlns_methods_t1 WHERE data.value('(/root/ns1:item)[1]', 'varchar(50)') = 'val1' +ORDER BY id; +GO +~~START~~ +int +1 +~~END~~ + + + +-- ============================================ +-- SECTION 10: Methods in CASE expression +-- ============================================ +-- 10.1 CASE with .exist() +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, + CASE WHEN data.exist('/root/ns1:item') = 1 THEN 'has-item' ELSE 'no-item' END AS Tag +FROM xmlns_methods_t1 WHERE id <= 3 +ORDER BY id; +GO +~~START~~ +int#!#varchar +1#!#has-item +2#!#no-item +3#!#no-item +~~END~~ + + + +-- ============================================ +-- SECTION 11: Methods with JOIN +-- ============================================ +-- 11.1 JOIN on .value() result +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT t1.id, t1.data.value('(/root/ns1:item)[1]', 'varchar(50)') AS V +FROM xmlns_methods_t1 t1 JOIN xmlns_methods_view1 v ON t1.id = v.id +WHERE t1.id IN (1, 5) +ORDER BY t1.id; +GO +~~START~~ +int#!#varchar +1#!#val1 +5#!#42 +~~END~~ + + + +-- ============================================ +-- SECTION 12: Methods with ORDER BY / TOP +-- ============================================ +-- 12.1 ORDER BY .value() +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, data.value('(/root/ns1:item)[1]', 'varchar(50)') AS V +FROM xmlns_methods_t1 WHERE id IN (1, 5) +ORDER BY data.value('(/root/ns1:item)[1]', 'varchar(50)'); +GO +~~START~~ +int#!#varchar +5#!#42 +1#!#val1 +~~END~~ + + +-- 12.2 TOP with .query() +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT TOP 1 id, data.query('/root/ns1:item') +FROM xmlns_methods_t1 WHERE id <= 5 +ORDER BY id; +GO +~~START~~ +int#!#xml +1#!#val1val2 +~~END~~ + + + +-- ============================================ +-- SECTION 13: Methods on views +-- ============================================ +-- 13.1 .query() on view +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, data.query('/root/ns1:item') +FROM xmlns_methods_view1 WHERE id <= 2 ORDER BY id; +GO +~~START~~ +int#!#xml +1#!#val1val2 +2#!# +~~END~~ + + +-- 13.2 .exist() in WHERE on view +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id FROM xmlns_methods_view1 +WHERE data.exist('/root/ns1:item') = 1 ORDER BY id; +GO +~~START~~ +int +1 +5 +~~END~~ + + + +-- ============================================ +-- SECTION 14: XML with comments +-- ============================================ +-- 14.1 .query() ignores comments outside target +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT data.query('/root/ns1:item') +FROM xmlns_methods_comments WHERE id = 1; +GO +~~START~~ +xml +val +~~END~~ + + +-- 14.2 .value() with inline comment in value +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT data.value('(/root/ns1:item)[1]', 'varchar(50)') +FROM xmlns_methods_comments WHERE id = 2; +GO +~~START~~ +varchar +val +~~END~~ + + + +-- ============================================ +-- SECTION 15: Spaces and case in method calls +-- ============================================ +-- 15.1 Spaces around .query() +DECLARE @x XML = 'v'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x . query('/root/ns1:item'); +GO +~~START~~ +xml +v +~~END~~ + + +-- 15.2 Spaces around .value() +DECLARE @x XML = 'v'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x . value('(/root/ns1:item)[1]', 'varchar(50)'); +GO +~~START~~ +varchar +v +~~END~~ + + + +-- ============================================ +-- SECTION 16: Predicates with prefixed attributes (richer) +-- ============================================ +-- 16.1 Attribute predicate with numeric comparison +WITH XMLNAMESPACES('http://example.com/products' AS p) +SELECT data.query('/order/p:item[@p:price>20]') FROM xmlns_methods_orders WHERE id = 1; +GO +~~START~~ +xml +WidgetGadget +~~END~~ + + +-- 16.2 Multiple prefixed attribute predicates +WITH XMLNAMESPACES('http://example.com/products' AS p) +SELECT data.query('/order/p:item[@p:price>20 and @p:price<40]') FROM xmlns_methods_orders WHERE id = 1; +GO +~~START~~ +xml +Widget +~~END~~ + + +-- 16.3 Empty XML root with .query() prefix path +WITH XMLNAMESPACES('http://example.com/products' AS p) +SELECT data.query('/order/p:item') FROM xmlns_methods_orders WHERE id = 3; +GO +~~START~~ +xml + +~~END~~ + + + +-- ============================================ +-- SECTION 17: Multiple chained method calls +-- ============================================ +-- 17.1 .query() result then .value() +DECLARE @x XML = 'val1val2'; +DECLARE @r XML; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @r = @x.query('/root/ns1:item[1]'); +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @r.value('(/ns1:item)[1]', 'varchar(50)'); +GO +~~START~~ +varchar +val1 +~~END~~ + + + +-- ============================================ +-- SECTION 18: Result from FOR XML chained +-- ============================================ +-- 18.1 FOR XML result fed to .query() under namespace +DECLARE @x XML; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x = (SELECT 1 AS [ns1:a], 'v' AS [ns1:b] FOR XML PATH('ns1:Row'), TYPE); +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/ns1:Row/ns1:a'); +GO +~~START~~ +xml +1 +~~END~~ + + + +-- ============================================ +-- SECTION 19: Special characters in WITH XMLNAMESPACES URIs +-- ============================================ +-- 19.1 Double-quote in URI (array-literal escaping) -> empty +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/"q"' AS p) +SELECT @x.query('/root/p:item'); +GO +~~START~~ +xml + +~~END~~ + + +-- 19.2 Backslash in URI (array-literal escaping) -> empty +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/a\b' AS p) +SELECT @x.query('/root/p:item'); +GO +~~START~~ +xml + +~~END~~ + + +-- 19.3 Single-quote in URI (escaped as '' in T-SQL), resolves end-to-end -> val +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/it''s' AS ns1) +SELECT @x.value('(/root/ns1:item)[1]', 'varchar(50)'); +GO +~~START~~ +varchar +val +~~END~~ + + +-- 19.4 Combined double-quote and backslash in URI (array-literal escaping) -> 0 +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/"a"\b' AS p) +SELECT @x.exist('/root/p:item'); +GO +~~START~~ +bit +0 +~~END~~ + + +-- 19.5 Limitation: a document whose own namespace URI contains a character that +-- is illegal in an RFC 3986 URI ('\' or '"') is rejected by PostgreSQL's xpath() +-- (libxml2 URI validation). SQL Server accepts it and returns the element. +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/a\b' AS ns1) +SELECT @x.query('/root/ns1:item'); +GO +~~START~~ +xml +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: could not parse XML document)~~ + + +-- 19.6 Same limitation with a double-quote in the document namespace URI +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/"q"' AS ns1) +SELECT @x.query('/root/ns1:item'); +GO +~~START~~ +xml +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: could not parse XML document)~~ + diff --git a/test/JDBC/expected/xml-methods-namespaces-vu-cleanup.out b/test/JDBC/expected/xml-methods-namespaces-vu-cleanup.out new file mode 100644 index 00000000000..d2cb4462509 --- /dev/null +++ b/test/JDBC/expected/xml-methods-namespaces-vu-cleanup.out @@ -0,0 +1,18 @@ + +-- ============================================ +-- SECTION: Cleanup +-- ============================================ +DROP VIEW xmlns_methods_view1; +GO + +DROP TABLE xmlns_methods_t1; +GO + +DROP TABLE xmlns_methods_t2; +GO + +DROP TABLE xmlns_methods_orders; +GO + +DROP TABLE xmlns_methods_comments; +GO diff --git a/test/JDBC/expected/xml-methods-namespaces-vu-prepare.out b/test/JDBC/expected/xml-methods-namespaces-vu-prepare.out new file mode 100644 index 00000000000..f03fbc12952 --- /dev/null +++ b/test/JDBC/expected/xml-methods-namespaces-vu-prepare.out @@ -0,0 +1,81 @@ + + +-- ============================================ +-- Tests for XML data type methods (.query, .value, .exist) +-- combined with WITH XMLNAMESPACES. +-- +-- WITH XMLNAMESPACES provides namespace bindings for XPath evaluation. +-- The bindings are passed to PG's xpath() function as a text[][] array. +-- This lets prefixed XPath like /ns1:item resolve correctly when the +-- target XML uses xmlns:ns1. +-- ============================================ +-- ============================================ +-- SECTION: Base Tables +-- ============================================ +CREATE TABLE xmlns_methods_t1 (id INT, data XML); +GO + +INSERT INTO xmlns_methods_t1 VALUES (1, 'val1val2'); +INSERT INTO xmlns_methods_t1 VALUES (2, '1020'); +INSERT INTO xmlns_methods_t1 VALUES (3, ''); +INSERT INTO xmlns_methods_t1 VALUES (4, NULL); +INSERT INTO xmlns_methods_t1 VALUES (5, '42'); +GO +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + + +CREATE TABLE xmlns_methods_t2 (id INT, data XML); +GO + +INSERT INTO xmlns_methods_t2 VALUES (1, 'SQL29.99XML39.99'); +INSERT INTO xmlns_methods_t2 VALUES (2, ''); +GO +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + + + +-- ============================================ +-- SECTION: Dependent Views +-- ============================================ +CREATE VIEW xmlns_methods_view1 AS +SELECT id, data FROM xmlns_methods_t1; +GO + + +-- ============================================ +-- SECTION: Tables for advanced method tests +-- ============================================ +CREATE TABLE xmlns_methods_orders (id INT, data XML); +GO + +INSERT INTO xmlns_methods_orders VALUES (1, 'WidgetGadget'); +INSERT INTO xmlns_methods_orders VALUES (2, 'Gizmo'); +INSERT INTO xmlns_methods_orders VALUES (3, ''); +GO +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + + +CREATE TABLE xmlns_methods_comments (id INT, data XML); +GO + +INSERT INTO xmlns_methods_comments VALUES (1, 'val'); +INSERT INTO xmlns_methods_comments VALUES (2, 'val'); +GO +~~ROW COUNT: 1~~ + +~~ROW COUNT: 1~~ + diff --git a/test/JDBC/expected/xml-methods-namespaces-vu-verify.out b/test/JDBC/expected/xml-methods-namespaces-vu-verify.out new file mode 100644 index 00000000000..581f200e2c7 --- /dev/null +++ b/test/JDBC/expected/xml-methods-namespaces-vu-verify.out @@ -0,0 +1,835 @@ + + +-- ============================================ +-- WITH XMLNAMESPACES + .query() / .value() / .exist() +-- ============================================ +-- ============================================ +-- SECTION 1: .query() with prefixed XPath +-- ============================================ +-- 1.1 Query a prefixed element using a declared prefix +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/ns1:item'); +GO +~~START~~ +xml +val +~~END~~ + + +-- 1.2 Query unprefixed name where XML element is prefixed -> empty +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/item'); +GO +~~START~~ +xml + +~~END~~ + + +-- 1.3 Query with multiple prefixes used in different paths +DECLARE @x XML = '12'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1, 'http://example.com/ns2' AS ns2) +SELECT @x.query('/root/ns1:a'), @x.query('/root/ns2:b'); +GO +~~START~~ +xml#!#xml +1#!#2 +~~END~~ + + +-- 1.4 Query repeated prefixed elements +DECLARE @x XML = 'ab'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/ns1:item'); +GO +~~START~~ +xml +ab +~~END~~ + + +-- 1.5 Query positional access with prefix +DECLARE @x XML = 'ab'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/ns1:item[1]'), @x.query('/root/ns1:item[2]'); +GO +~~START~~ +xml#!#xml +a#!#b +~~END~~ + + +-- 1.6 Query with attribute predicate using prefix +DECLARE @x XML = 'SQLXML'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/ns1:book[@ns1:id="2"]/ns1:title'); +GO +~~START~~ +xml +XML +~~END~~ + + +-- 1.7 Query NULL XML +DECLARE @x XML = NULL; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/ns1:item'); +GO +~~START~~ +xml + +~~END~~ + + +-- 1.8 Query against table column +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, data.query('/root/ns1:item') +FROM xmlns_methods_t1 WHERE id IN (1, 2) +ORDER BY id; +GO +~~START~~ +int#!#xml +1#!#val1val2 +2#!# +~~END~~ + + +-- 1.9 Query with no match returns empty +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/ns1:nonexistent'); +GO +~~START~~ +xml + +~~END~~ + + +-- 1.10 Wildcard with prefix +DECLARE @x XML = '12'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/ns1:*'); +GO +~~START~~ +xml +12 +~~END~~ + + + +-- ============================================ +-- SECTION 2: .exist() with prefixed XPath +-- ============================================ +-- 2.1 Exist returns 1 when prefixed match found +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.exist('/root/ns1:item'); +GO +~~START~~ +bit +1 +~~END~~ + + +-- 2.2 Exist returns 0 when prefixed name doesn't match +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.exist('/root/ns1:other'); +GO +~~START~~ +bit +0 +~~END~~ + + +-- 2.3 Exist with attribute predicate +DECLARE @x XML = ''; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.exist('/root/item/@ns1:attr'); +GO +~~START~~ +bit +1 +~~END~~ + + +-- 2.4 Exist with attribute predicate (negative) +DECLARE @x XML = ''; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.exist('/root/item/@ns1:attr'); +GO +~~START~~ +bit +0 +~~END~~ + + +-- 2.5 Exist on NULL +DECLARE @x XML = NULL; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.exist('/root/ns1:item'); +GO +~~START~~ +bit + +~~END~~ + + +-- 2.6 Exist with multiple namespaces +DECLARE @x XML = '1'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1, 'http://example.com/ns2' AS ns2) +SELECT @x.exist('/root/ns1:a'), @x.exist('/root/ns2:b'); +GO +~~START~~ +bit#!#bit +1#!#0 +~~END~~ + + + +-- ============================================ +-- SECTION 3: .value() with prefixed XPath +-- ============================================ +-- 3.1 Value of prefixed element +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.value('(/root/ns1:item)[1]', 'varchar(50)'); +GO +~~START~~ +varchar +val +~~END~~ + + +-- 3.2 Value cast to int +DECLARE @x XML = '42'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.value('(/root/ns1:item)[1]', 'int'); +GO +~~START~~ +int +42 +~~END~~ + + +-- 3.3 Value of attribute with prefix +DECLARE @x XML = ''; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.value('(/root/item/@ns1:attr)[1]', 'varchar(50)'); +GO +~~START~~ +varchar +value +~~END~~ + + +-- 3.4 Value when no match - returns NULL +DECLARE @x XML = ''; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.value('(/root/ns1:item)[1]', 'varchar(50)'); +GO +~~START~~ +varchar + +~~END~~ + + +-- 3.5 Value of multiple matches selects positional +DECLARE @x XML = 'ab'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.value('(/root/ns1:item)[1]', 'varchar(10)'), + @x.value('(/root/ns1:item)[2]', 'varchar(10)'); +GO +~~START~~ +varchar#!#varchar +a#!#b +~~END~~ + + +-- 3.6 Value on NULL XML +DECLARE @x XML = NULL; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.value('(/root/ns1:item)[1]', 'varchar(50)'); +GO +~~START~~ +varchar + +~~END~~ + + +-- 3.7 Value with multiple namespaces +DECLARE @x XML = '1020'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1, 'http://example.com/ns2' AS ns2) +SELECT @x.value('(/root/ns1:a)[1]', 'int'), + @x.value('(/root/ns2:b)[1]', 'int'); +GO +~~START~~ +int#!#int +10#!#20 +~~END~~ + + + +-- ============================================ +-- SECTION 4: Methods on table columns with namespaces +-- ============================================ +-- 4.1 .query on column +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, data.query('/root/ns1:item') +FROM xmlns_methods_t1 WHERE id = 1; +GO +~~START~~ +int#!#xml +1#!#val1val2 +~~END~~ + + +-- 4.2 .value on column +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, data.value('(/root/ns1:item)[1]', 'varchar(50)') +FROM xmlns_methods_t1 WHERE id IN (1, 5); +GO +~~START~~ +int#!#varchar +1#!#val1 +5#!#42 +~~END~~ + + +-- 4.3 .exist on column +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, data.exist('/root/ns1:item') +FROM xmlns_methods_t1 WHERE id <= 5 +ORDER BY id; +GO +~~START~~ +int#!#bit +1#!#1 +2#!#0 +3#!#0 +4#!# +5#!#1 +~~END~~ + + +-- 4.4 .query on view +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, data.query('/root/ns1:item') +FROM xmlns_methods_view1 WHERE id = 1; +GO +~~START~~ +int#!#xml +1#!#val1val2 +~~END~~ + + + +-- ============================================ +-- SECTION 5: Complex namespace cases +-- ============================================ +-- 5.1 Same prefix used in multiple statements (no carry-over) +DECLARE @x XML = '1'; +WITH XMLNAMESPACES('http://uri1' AS p) +SELECT @x.value('(/root/p:a)[1]', 'int'); +GO +~~START~~ +int +1 +~~END~~ + + +-- 5.2 Prefix in XML differs from prefix in WITH XMLNAMESPACES (matching by URI) +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/ns1' AS bar) +SELECT @x.value('(/root/bar:item)[1]', 'varchar(50)'); +GO +~~START~~ +varchar +val +~~END~~ + + +-- 5.3 Predicate using prefixed attribute +WITH XMLNAMESPACES('http://example.com/products' AS p) +SELECT data.query('/catalog/p:book[@p:id="2"]/p:title') +FROM xmlns_methods_t2 WHERE id = 1; +GO +~~START~~ +xml +XML +~~END~~ + + +-- 5.4 Numeric predicate on prefixed element +WITH XMLNAMESPACES('http://example.com/products' AS p) +SELECT data.query('/catalog/p:book[p:price>30]/p:title') +FROM xmlns_methods_t2 WHERE id = 1; +GO +~~START~~ +xml +XML +~~END~~ + + +-- 5.5 Wildcard with prefix on table column +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT data.query('/root/ns1:*') +FROM xmlns_methods_t1 WHERE id = 1; +GO +~~START~~ +xml +val1val2 +~~END~~ + + +-- 5.6 .query then .value chained on result +DECLARE @x XML = 'hello'; +DECLARE @r XML; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @r = @x.query('/root/ns1:item'); +SELECT @r; +GO +~~START~~ +xml +hello +~~END~~ + + + +-- ============================================ +-- SECTION 6: NULL XML behaviors +-- ============================================ +-- 6.1 Query NULL row (with namespaces) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT data.query('/root/ns1:item') FROM xmlns_methods_t1 WHERE id = 4; +GO +~~START~~ +xml + +~~END~~ + + +-- 6.2 Value on NULL row (with namespaces) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT data.value('(/root/ns1:item)[1]', 'varchar(50)') FROM xmlns_methods_t1 WHERE id = 4; +GO +~~START~~ +varchar + +~~END~~ + + +-- 6.3 Exist on NULL row (with namespaces) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT data.exist('/root/ns1:item') FROM xmlns_methods_t1 WHERE id = 4; +GO +~~START~~ +bit + +~~END~~ + + + +-- ============================================ +-- SECTION 7: Validation with same rules as FOR XML +-- ============================================ +-- 7.1 Duplicate prefix +DECLARE @x XML = ''; +WITH XMLNAMESPACES('http://a' AS p, 'http://b' AS p) +SELECT @x.query('/root'); +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Attempt to redefine namespace prefix 'p')~~ + + +-- 7.2 Empty URI +DECLARE @x XML = ''; +WITH XMLNAMESPACES('' AS p) +SELECT @x.query('/root'); +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Empty URI is not allowed in WITH XMLNAMESPACES clause.)~~ + + +-- 7.3 Reserved xmlns prefix +DECLARE @x XML = ''; +WITH XMLNAMESPACES('http://a' AS xmlns) +SELECT @x.query('/root'); +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: Prefix 'xmlns' used in WITH XMLNAMESPACES is reserved and cannot be used as a user-defined prefix.)~~ + + +-- 7.4 xml prefix with wrong URI +DECLARE @x XML = ''; +WITH XMLNAMESPACES('http://wrong' AS xml) +SELECT @x.query('/root'); +GO +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: XML namespace prefix 'xml' can only be associated with the URI http://www.w3.org/XML/1998/namespace. This URI cannot be used with other prefixes.)~~ + + + +-- ============================================ +-- SECTION 8: Variable assignment with namespaces +-- ============================================ +-- 8.1 .query() result assigned to XML variable +DECLARE @x XML = 'val'; +DECLARE @r XML; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @r = @x.query('/root/ns1:item'); +SELECT @r; +GO +~~START~~ +xml +val +~~END~~ + + +-- 8.2 .value() result into varchar +DECLARE @x XML = 'hello'; +DECLARE @s VARCHAR(50); +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @s = @x.value('(/root/ns1:item)[1]', 'varchar(50)'); +SELECT @s; +GO +~~START~~ +varchar +hello +~~END~~ + + +-- 8.3 .exist() into bit +DECLARE @x XML = ''; +DECLARE @b BIT; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @b = @x.exist('/root/ns1:item'); +SELECT @b; +GO +~~START~~ +bit +1 +~~END~~ + + + +-- ============================================ +-- SECTION 9: Methods in WHERE clause +-- ============================================ +-- 9.1 .exist() in WHERE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id FROM xmlns_methods_t1 WHERE data.exist('/root/ns1:item') = 1 +ORDER BY id; +GO +~~START~~ +int +1 +5 +~~END~~ + + +-- 9.2 .value() in WHERE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id FROM xmlns_methods_t1 WHERE data.value('(/root/ns1:item)[1]', 'varchar(50)') = 'val1' +ORDER BY id; +GO +~~START~~ +int +1 +~~END~~ + + + +-- ============================================ +-- SECTION 10: Methods in CASE expression +-- ============================================ +-- 10.1 CASE with .exist() +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, + CASE WHEN data.exist('/root/ns1:item') = 1 THEN 'has-item' ELSE 'no-item' END AS Tag +FROM xmlns_methods_t1 WHERE id <= 3 +ORDER BY id; +GO +~~START~~ +int#!#varchar +1#!#has-item +2#!#no-item +3#!#no-item +~~END~~ + + + +-- ============================================ +-- SECTION 11: Methods with JOIN +-- ============================================ +-- 11.1 JOIN on .value() result +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT t1.id, t1.data.value('(/root/ns1:item)[1]', 'varchar(50)') AS V +FROM xmlns_methods_t1 t1 JOIN xmlns_methods_view1 v ON t1.id = v.id +WHERE t1.id IN (1, 5) +ORDER BY t1.id; +GO +~~START~~ +int#!#varchar +1#!#val1 +5#!#42 +~~END~~ + + + +-- ============================================ +-- SECTION 12: Methods with ORDER BY / TOP +-- ============================================ +-- 12.1 ORDER BY .value() +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, data.value('(/root/ns1:item)[1]', 'varchar(50)') AS V +FROM xmlns_methods_t1 WHERE id IN (1, 5) +ORDER BY data.value('(/root/ns1:item)[1]', 'varchar(50)'); +GO +~~START~~ +int#!#varchar +5#!#42 +1#!#val1 +~~END~~ + + +-- 12.2 TOP with .query() +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT TOP 1 id, data.query('/root/ns1:item') +FROM xmlns_methods_t1 WHERE id <= 5 +ORDER BY id; +GO +~~START~~ +int#!#xml +1#!#val1val2 +~~END~~ + + + +-- ============================================ +-- SECTION 13: Methods on views +-- ============================================ +-- 13.1 .query() on view +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, data.query('/root/ns1:item') +FROM xmlns_methods_view1 WHERE id <= 2 ORDER BY id; +GO +~~START~~ +int#!#xml +1#!#val1val2 +2#!# +~~END~~ + + +-- 13.2 .exist() in WHERE on view +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id FROM xmlns_methods_view1 +WHERE data.exist('/root/ns1:item') = 1 ORDER BY id; +GO +~~START~~ +int +1 +5 +~~END~~ + + + +-- ============================================ +-- SECTION 14: XML with comments +-- ============================================ +-- 14.1 .query() ignores comments outside target +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT data.query('/root/ns1:item') +FROM xmlns_methods_comments WHERE id = 1; +GO +~~START~~ +xml +val +~~END~~ + + +-- 14.2 .value() with inline comment in value +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT data.value('(/root/ns1:item)[1]', 'varchar(50)') +FROM xmlns_methods_comments WHERE id = 2; +GO +~~START~~ +varchar +val +~~END~~ + + + +-- ============================================ +-- SECTION 15: Spaces and case in method calls +-- ============================================ +-- 15.1 Spaces around .query() +DECLARE @x XML = 'v'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x . query('/root/ns1:item'); +GO +~~START~~ +xml +v +~~END~~ + + +-- 15.2 Spaces around .value() +DECLARE @x XML = 'v'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x . value('(/root/ns1:item)[1]', 'varchar(50)'); +GO +~~START~~ +varchar +v +~~END~~ + + + +-- ============================================ +-- SECTION 16: Predicates with prefixed attributes (richer) +-- ============================================ +-- 16.1 Attribute predicate with numeric comparison +WITH XMLNAMESPACES('http://example.com/products' AS p) +SELECT data.query('/order/p:item[@p:price>20]') FROM xmlns_methods_orders WHERE id = 1; +GO +~~START~~ +xml +WidgetGadget +~~END~~ + + +-- 16.2 Multiple prefixed attribute predicates +WITH XMLNAMESPACES('http://example.com/products' AS p) +SELECT data.query('/order/p:item[@p:price>20 and @p:price<40]') FROM xmlns_methods_orders WHERE id = 1; +GO +~~START~~ +xml +Widget +~~END~~ + + +-- 16.3 Empty XML root with .query() prefix path +WITH XMLNAMESPACES('http://example.com/products' AS p) +SELECT data.query('/order/p:item') FROM xmlns_methods_orders WHERE id = 3; +GO +~~START~~ +xml + +~~END~~ + + + +-- ============================================ +-- SECTION 17: Multiple chained method calls +-- ============================================ +-- 17.1 .query() result then .value() +DECLARE @x XML = 'val1val2'; +DECLARE @r XML; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @r = @x.query('/root/ns1:item[1]'); +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @r.value('(/ns1:item)[1]', 'varchar(50)'); +GO +~~START~~ +varchar +val1 +~~END~~ + + + +-- ============================================ +-- SECTION 18: Result from FOR XML chained +-- ============================================ +-- 18.1 FOR XML result fed to .query() under namespace +DECLARE @x XML; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x = (SELECT 1 AS [ns1:a], 'v' AS [ns1:b] FOR XML PATH('ns1:Row'), TYPE); +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/ns1:Row/ns1:a'); +GO +~~START~~ +xml +1 +~~END~~ + + + +-- ============================================ +-- SECTION 19: Special characters in WITH XMLNAMESPACES URIs +-- ============================================ +-- 19.1 Double-quote in URI (array-literal escaping) -> empty +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/"q"' AS p) +SELECT @x.query('/root/p:item'); +GO +~~START~~ +xml + +~~END~~ + + +-- 19.2 Backslash in URI (array-literal escaping) -> empty +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/a\b' AS p) +SELECT @x.query('/root/p:item'); +GO +~~START~~ +xml + +~~END~~ + + +-- 19.3 Single-quote in URI (escaped as '' in T-SQL), resolves end-to-end -> val +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/it''s' AS ns1) +SELECT @x.value('(/root/ns1:item)[1]', 'varchar(50)'); +GO +~~START~~ +varchar +val +~~END~~ + + +-- 19.4 Combined double-quote and backslash in URI (array-literal escaping) -> 0 +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/"a"\b' AS p) +SELECT @x.exist('/root/p:item'); +GO +~~START~~ +bit +0 +~~END~~ + + +-- 19.5 Limitation: a document whose own namespace URI contains a character that +-- is illegal in an RFC 3986 URI ('\' or '"') is rejected by PostgreSQL's xpath() +-- (libxml2 URI validation). SQL Server accepts it and returns the element. +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/a\b' AS ns1) +SELECT @x.query('/root/ns1:item'); +GO +~~START~~ +xml +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: could not parse XML document)~~ + + +-- 19.6 Same limitation with a double-quote in the document namespace URI +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/"q"' AS ns1) +SELECT @x.query('/root/ns1:item'); +GO +~~START~~ +xml +~~ERROR (Code: 33557097)~~ + +~~ERROR (Message: could not parse XML document)~~ + diff --git a/test/JDBC/expected/xml_exist-before-16_5-vu-verify.out b/test/JDBC/expected/xml_exist-before-16_5-vu-verify.out index 03346939d77..93c382c245e 100644 --- a/test/JDBC/expected/xml_exist-before-16_5-vu-verify.out +++ b/test/JDBC/expected/xml_exist-before-16_5-vu-verify.out @@ -310,9 +310,10 @@ DECLARE @xml XML = 'HelloWorld#!# +~~END~~ DECLARE @xml XML = 'HelloWorld]]>'; diff --git a/test/JDBC/input/forxml/forxml-namespaces-before-17_11-or-18_5-vu-cleanup.sql b/test/JDBC/input/forxml/forxml-namespaces-before-17_11-or-18_5-vu-cleanup.sql new file mode 100644 index 00000000000..092252634c5 --- /dev/null +++ b/test/JDBC/input/forxml/forxml-namespaces-before-17_11-or-18_5-vu-cleanup.sql @@ -0,0 +1,30 @@ +-- ============================================ +-- SECTION: Cleanup +-- ============================================ + +DROP VIEW forxml_ns_view1; +GO + +DROP VIEW forxml_ns_view2; +GO + +DROP TABLE forxml_ns_employees; +GO + +DROP TABLE forxml_ns_orders; +GO + +DROP TABLE forxml_ns_simple; +GO + +DROP TABLE forxml_ns_nullable; +GO + +DROP TABLE forxml_ns_types; +GO + +DROP TABLE forxml_ns_special; +GO + +DROP TABLE forxml_ns_unicode; +GO diff --git a/test/JDBC/input/forxml/forxml-namespaces-before-17_11-or-18_5-vu-prepare.sql b/test/JDBC/input/forxml/forxml-namespaces-before-17_11-or-18_5-vu-prepare.sql new file mode 100644 index 00000000000..1f557321ff8 --- /dev/null +++ b/test/JDBC/input/forxml/forxml-namespaces-before-17_11-or-18_5-vu-prepare.sql @@ -0,0 +1,113 @@ +-- ============================================ +-- Tests for WITH XMLNAMESPACES + FOR XML (RAW, PATH, AUTO) +-- +-- WITH XMLNAMESPACES declares prefix-to-URI mappings whose scope is one +-- statement. T-SQL emits xmlns:prefix="uri" attributes on the appropriate +-- elements: +-- - FOR XML RAW: on each row element +-- - FOR XML PATH: on the outermost row element +-- - FOR XML AUTO: on the outermost (table-named) element +-- DEFAULT 'uri' emits xmlns="uri" (applies to unprefixed elements only). +-- ============================================ + +-- ============================================ +-- SECTION: Base Tables +-- ============================================ + +CREATE TABLE forxml_ns_employees ( + EmpID INT, + EmpName VARCHAR(50), + Dept VARCHAR(50) +); +GO + +INSERT INTO forxml_ns_employees VALUES (1, 'Alice', 'Sales'); +INSERT INTO forxml_ns_employees VALUES (2, 'Bob', 'IT'); +INSERT INTO forxml_ns_employees VALUES (3, NULL, 'HR'); +INSERT INTO forxml_ns_employees VALUES (4, 'Diana', NULL); +GO + +CREATE TABLE forxml_ns_orders ( + OrderID INT, + EmpID INT, + Amount DECIMAL(10, 2) +); +GO + +INSERT INTO forxml_ns_orders VALUES (101, 1, 100.50); +INSERT INTO forxml_ns_orders VALUES (102, 2, 200.00); +INSERT INTO forxml_ns_orders VALUES (103, 1, NULL); +GO + +CREATE TABLE forxml_ns_simple ( + a INT, + b VARCHAR(50) +); +GO + +INSERT INTO forxml_ns_simple VALUES (1, 'val1'); +INSERT INTO forxml_ns_simple VALUES (2, 'val2'); +GO + +CREATE TABLE forxml_ns_nullable ( + a INT, + b VARCHAR(50) +); +GO + +INSERT INTO forxml_ns_nullable VALUES (NULL, NULL); +INSERT INTO forxml_ns_nullable VALUES (1, NULL); +INSERT INTO forxml_ns_nullable VALUES (NULL, 'x'); +GO + +-- Table with multiple data types +CREATE TABLE forxml_ns_types ( + IntCol INT, + VarcharCol VARCHAR(50), + BitCol BIT, + DecimalCol DECIMAL(10,2), + DateCol DATE +); +GO + +INSERT INTO forxml_ns_types VALUES (100, 'Text1', 1, 99.99, '2024-01-01'); +INSERT INTO forxml_ns_types VALUES (NULL, NULL, NULL, NULL, NULL); +INSERT INTO forxml_ns_types VALUES (200, NULL, 0, NULL, '2024-06-15'); +GO + +-- Special characters table +CREATE TABLE forxml_ns_special ( + ID INT, + Value VARCHAR(100) +); +GO + +INSERT INTO forxml_ns_special VALUES (1, 'a & b'); +INSERT INTO forxml_ns_special VALUES (2, 'a < b'); +INSERT INTO forxml_ns_special VALUES (3, 'say "hello"'); +INSERT INTO forxml_ns_special VALUES (4, 'x'); +GO + +-- Unicode/multibyte values table +CREATE TABLE forxml_ns_unicode ( + ID INT, + Name NVARCHAR(50) +); +GO + +INSERT INTO forxml_ns_unicode VALUES (1, N'日本語'); +INSERT INTO forxml_ns_unicode VALUES (2, N'中文'); +INSERT INTO forxml_ns_unicode VALUES (3, N'한국어'); +GO + +-- ============================================ +-- SECTION: Dependent Views +-- ============================================ + +CREATE VIEW forxml_ns_view1 AS +SELECT EmpID, EmpName FROM forxml_ns_employees; +GO + +CREATE VIEW forxml_ns_view2 AS +SELECT a, b FROM forxml_ns_simple; +GO diff --git a/test/JDBC/input/forxml/forxml-namespaces-before-17_11-or-18_5-vu-verify.sql b/test/JDBC/input/forxml/forxml-namespaces-before-17_11-or-18_5-vu-verify.sql new file mode 100644 index 00000000000..e076fdec10f --- /dev/null +++ b/test/JDBC/input/forxml/forxml-namespaces-before-17_11-or-18_5-vu-verify.sql @@ -0,0 +1,726 @@ +-- ============================================ +-- WITH XMLNAMESPACES + FOR XML (RAW, PATH, AUTO) +-- +-- Scope: WITH XMLNAMESPACES declares prefix-to-URI mappings for one +-- statement. T-SQL emits xmlns:prefix="uri" attributes on the appropriate +-- elements: +-- - FOR XML RAW: on each row element +-- - FOR XML PATH: on the outermost row element +-- - FOR XML AUTO: on the outermost (table-named) element +-- DEFAULT 'uri' emits xmlns="uri" +-- ============================================ + +-- ============================================ +-- SECTION 1: FOR XML RAW +-- ============================================ + +-- 1.1 Basic single-prefix RAW with no prefix used in row/cols +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW; +GO + +-- 1.2 RAW with named row, no prefix in row/cols +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('Emp'); +GO + +-- 1.3 RAW with prefixed row name, no prefix in cols +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'); +GO + +-- 1.4 RAW with prefixed column aliases +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'); +GO + +-- 1.5 Multiple prefixes, each used in different columns +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1, 'http://example.com/ns2' AS ns2) +SELECT EmpID AS [ns1:ID], EmpName AS [ns2:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'); +GO + +-- 1.6 DEFAULT namespace +WITH XMLNAMESPACES(DEFAULT 'http://example.com/default') +SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW; +GO + +-- 1.7 DEFAULT + prefix combined +WITH XMLNAMESPACES(DEFAULT 'http://example.com/default', 'http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'); +GO + +-- 1.8 RAW + ELEMENTS +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'), ELEMENTS; +GO + +-- 1.9 RAW + ELEMENTS XSINIL with NULL value +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name], Dept AS [ns1:Dept] +FROM forxml_ns_employees WHERE EmpID = 3 FOR XML RAW('ns1:Emp'), ELEMENTS XSINIL; +GO + +-- 1.10 RAW + ELEMENTS ABSENT (NULLs dropped) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name], Dept AS [ns1:Dept] +FROM forxml_ns_employees WHERE EmpID = 3 FOR XML RAW('ns1:Emp'), ELEMENTS ABSENT; +GO + +-- 1.11 RAW + ROOT (unprefixed root) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'), ROOT('Doc'); +GO + +-- 1.12 RAW + ROOT (prefixed root) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'), ROOT('ns1:Doc'); +GO + +-- 1.13 RAW + TYPE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'), TYPE; +GO + +-- 1.14 RAW + ROOT + TYPE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'), ROOT('ns1:Doc'), TYPE; +GO + +-- 1.15 RAW multi-row with namespaces +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees FOR XML RAW('ns1:Emp'); +GO + +-- 1.16 RAW with view as the source +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_view1 WHERE EmpID = 1 FOR XML RAW('ns1:Emp'); +GO + +-- 1.17 RAW with computed expression and prefixed alias +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID + 100 AS [ns1:OffsetID], UPPER(EmpName) AS [ns1:UpperName] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'); +GO + +-- ============================================ +-- SECTION 2: FOR XML PATH +-- ============================================ + +-- 2.1 PATH with prefix on row and column elements +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML PATH('ns1:Emp'); +GO + +-- 2.2 PATH default row name with prefix on cols only +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML PATH; +GO + +-- 2.3 PATH with attribute-centric prefix (@) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [@ns1:id], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML PATH('ns1:Emp'); +GO + +-- 2.4 PATH + ROOT prefixed +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML PATH('ns1:Emp'), ROOT('ns1:Doc'); +GO + +-- 2.5 PATH + ELEMENTS XSINIL with NULL +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name], Dept AS [ns1:Dept] +FROM forxml_ns_employees WHERE EmpID = 3 FOR XML PATH('ns1:Emp'), ELEMENTS XSINIL; +GO + +-- 2.6 PATH + DEFAULT namespace +WITH XMLNAMESPACES(DEFAULT 'http://example.com/default') +SELECT EmpID AS ID, EmpName AS Name +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML PATH('Emp'); +GO + +-- 2.7 PATH + DEFAULT + prefix +WITH XMLNAMESPACES(DEFAULT 'http://example.com/default', 'http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML PATH('ns1:Emp'); +GO + +-- 2.8 PATH + multiple prefixes +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1, 'http://example.com/ns2' AS ns2) +SELECT EmpID AS [ns1:ID], EmpName AS [ns2:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML PATH('ns1:Emp'); +GO + +-- 2.9 PATH + ROOT + TYPE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML PATH('ns1:Emp'), ROOT('ns1:Doc'), TYPE; +GO + +-- 2.10 PATH multi-row +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees FOR XML PATH('ns1:Emp'); +GO + +-- ============================================ +-- SECTION 3: FOR XML AUTO +-- ============================================ + +-- 3.1 AUTO with prefixed table name +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT forxml_ns_employees.EmpID, forxml_ns_employees.EmpName +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML AUTO; +GO + +-- 3.2 AUTO + ELEMENTS +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT forxml_ns_employees.EmpID, forxml_ns_employees.EmpName +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML AUTO, ELEMENTS; +GO + +-- 3.3 AUTO + ROOT +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT forxml_ns_employees.EmpID, forxml_ns_employees.EmpName +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML AUTO, ROOT('Doc'); +GO + +-- 3.4 AUTO + DEFAULT + prefix +WITH XMLNAMESPACES(DEFAULT 'http://example.com/default', 'http://example.com/ns1' AS ns1) +SELECT forxml_ns_employees.EmpID, forxml_ns_employees.EmpName +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML AUTO; +GO + +-- 3.5 AUTO + TYPE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT forxml_ns_employees.EmpID, forxml_ns_employees.EmpName +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML AUTO, TYPE; +GO + +-- 3.6 AUTO + ELEMENTS XSINIL + NULL row +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT forxml_ns_employees.EmpID, forxml_ns_employees.EmpName, forxml_ns_employees.Dept +FROM forxml_ns_employees WHERE EmpID = 3 FOR XML AUTO, ELEMENTS XSINIL; +GO + +-- 3.7 AUTO multi-row +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT forxml_ns_employees.EmpID, forxml_ns_employees.EmpName +FROM forxml_ns_employees FOR XML AUTO; +GO + +-- 3.8 AUTO with JOIN (parent-child nesting) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID, e.EmpName, o.OrderID, o.Amount +FROM forxml_ns_employees e +JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID, o.OrderID +FOR XML AUTO; +GO + +-- 3.9 AUTO with JOIN + ELEMENTS +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID, e.EmpName, o.OrderID, o.Amount +FROM forxml_ns_employees e +JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID, o.OrderID +FOR XML AUTO, ELEMENTS; +GO + +-- 3.10 AUTO with JOIN + ROOT +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID, e.EmpName, o.OrderID, o.Amount +FROM forxml_ns_employees e +JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID, o.OrderID +FOR XML AUTO, ROOT('Doc'); +GO + +-- 3.11 AUTO with LEFT JOIN, NULL children +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID, e.EmpName, o.OrderID, o.Amount +FROM forxml_ns_employees e +LEFT JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID +FOR XML AUTO; +GO + +-- 3.12 AUTO with CTE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1), +ActiveEmps AS (SELECT EmpID, EmpName FROM forxml_ns_employees WHERE Dept IS NOT NULL) +SELECT a.EmpID, a.EmpName +FROM ActiveEmps a +FOR XML AUTO; +GO + +-- 3.13 AUTO with subquery in FROM +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT sub.EmpID, sub.EmpName +FROM (SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID <= 2) sub +FOR XML AUTO; +GO + +-- 3.14 AUTO multi-table JOIN with ELEMENTS XSINIL +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID, e.EmpName, e.Dept, o.OrderID, o.Amount +FROM forxml_ns_employees e +LEFT JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID, o.OrderID +FOR XML AUTO, ELEMENTS XSINIL; +GO + +-- 3.15 AUTO + DEFAULT ns + JOIN +WITH XMLNAMESPACES(DEFAULT 'http://default.com', 'http://example.com/ns1' AS ns1) +SELECT e.EmpID, e.EmpName, o.OrderID +FROM forxml_ns_employees e +JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID +FOR XML AUTO; +GO + +-- 3.16 AUTO with JOIN + TYPE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID, e.EmpName, o.OrderID +FROM forxml_ns_employees e +JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID +FOR XML AUTO, TYPE; +GO + +-- ============================================ +-- SECTION 4: Validation / Error cases +-- ============================================ + +-- 4.1 Duplicate prefix declaration +WITH XMLNAMESPACES('http://example.com/u1' AS ns1, 'http://example.com/u2' AS ns1) +SELECT 1 AS [ns1:a] FOR XML RAW('ns1:Row'); +GO + +-- 4.2 Multiple DEFAULT declarations +WITH XMLNAMESPACES(DEFAULT 'http://a', DEFAULT 'http://b') +SELECT 1 AS a FOR XML RAW; +GO + +-- 4.3 Reserved 'xmlns' prefix +WITH XMLNAMESPACES('http://example.com' AS xmlns) +SELECT 1 AS [xmlns:a] FOR XML RAW; +GO + +-- 4.4 Empty URI +WITH XMLNAMESPACES('' AS ns1) +SELECT 1 AS [ns1:a] FOR XML RAW; +GO + +-- 4.5 Reserved 'xml' prefix re-bound to wrong URI +WITH XMLNAMESPACES('http://wrong.uri' AS xml) +SELECT 1 AS [xml:a] FOR XML RAW; +GO + +-- 4.6 The XML namespace URI bound to a non-xml prefix +WITH XMLNAMESPACES('http://www.w3.org/XML/1998/namespace' AS notxml) +SELECT 1 AS a FOR XML RAW; +GO + +-- 4.7 xsi prefix bound to xsi URI without XSINIL — alias should resolve +WITH XMLNAMESPACES('http://www.w3.org/2001/XMLSchema-instance' AS xsi) +SELECT 1 AS [xsi:a] FOR XML RAW; +GO + +-- 4.8 xsi prefix bound to xsi URI with XSINIL — must not duplicate xmlns:xsi +WITH XMLNAMESPACES('http://www.w3.org/2001/XMLSchema-instance' AS xsi) +SELECT 1 AS a, NULL AS b FOR XML RAW, ELEMENTS XSINIL; +GO + +-- ============================================ +-- SECTION 5: CTE / subquery combinations +-- ============================================ + +-- 5.1 WITH XMLNAMESPACES followed by CTE (must come before CTE) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1), +ns_cte AS (SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] FROM ns_cte FOR XML RAW('ns1:Emp'); +GO + +-- 5.2 Outer query with subquery that doesn't use namespaces +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM (SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 1) sub +FOR XML RAW('ns1:Emp'); +GO + +-- ============================================ +-- SECTION 6: Mixed scalar types +-- ============================================ + +-- 6.1 numeric columns +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT OrderID AS [ns1:ID], Amount AS [ns1:Amt] +FROM forxml_ns_orders WHERE OrderID = 101 FOR XML RAW('ns1:Order'); +GO + +-- 6.2 NULL numeric with XSINIL +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT OrderID AS [ns1:ID], Amount AS [ns1:Amt] +FROM forxml_ns_orders WHERE OrderID = 103 FOR XML RAW('ns1:Order'), ELEMENTS XSINIL; +GO + +-- ============================================ +-- SECTION 7: All-NULL rows with XSINIL + namespaces +-- ============================================ + +-- 7.1 RAW +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT a AS [ns1:a], b AS [ns1:b] FROM forxml_ns_nullable WHERE a IS NULL AND b IS NULL +FOR XML RAW('ns1:Row'), ELEMENTS XSINIL; +GO + +-- 7.2 PATH +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT a AS [ns1:a], b AS [ns1:b] FROM forxml_ns_nullable WHERE a IS NULL AND b IS NULL +FOR XML PATH('ns1:Row'), ELEMENTS XSINIL; +GO + +-- ============================================ +-- SECTION 8: View-based queries +-- ============================================ + +-- 8.1 SELECT FROM view with RAW +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT a AS [ns1:a], b AS [ns1:b] FROM forxml_ns_view2 FOR XML RAW('ns1:Row'); +GO + +-- 8.2 SELECT FROM view with PATH +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT a AS [ns1:a], b AS [ns1:b] FROM forxml_ns_view2 FOR XML PATH('ns1:Row'); +GO + +-- ============================================ +-- SECTION 9: Special characters / unicode in URIs and values +-- ============================================ + +-- 9.1 URI with hash and percent characters +WITH XMLNAMESPACES('http://example.com/path#section%20path' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML RAW('ns1:Emp'); +GO + +-- 9.1b URI with ampersand (must be XML-escaped to & in attribute value) +WITH XMLNAMESPACES('http://example.com/path?q=v&more' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML RAW('ns1:Emp'); +GO + +-- 9.1c URI with less-than (must be XML-escaped) +WITH XMLNAMESPACES('http://example.com/' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML RAW('ns1:Emp'); +GO + +-- 9.2 Long URI +WITH XMLNAMESPACES('http://example.com/very/long/path/with/many/segments/that/goes/on/and/on/for/testing/purposes/only' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML RAW('ns1:Emp'); +GO + +-- ============================================ +-- SECTION 10: Different data types +-- ============================================ + +-- 10.1 RAW with all data types prefixed +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT IntCol AS [ns1:Int], VarcharCol AS [ns1:Str], BitCol AS [ns1:Bit], + DecimalCol AS [ns1:Dec], DateCol AS [ns1:Dt] +FROM forxml_ns_types WHERE IntCol = 100 FOR XML RAW('ns1:Row'); +GO + +-- 10.2 PATH with all data types and ELEMENTS XSINIL on NULL row +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT IntCol AS [ns1:Int], VarcharCol AS [ns1:Str], BitCol AS [ns1:Bit] +FROM forxml_ns_types WHERE IntCol IS NULL AND VarcharCol IS NULL +FOR XML PATH('ns1:Row'), ELEMENTS XSINIL; +GO + +-- ============================================ +-- SECTION 11: Special characters in values +-- ============================================ + +-- 11.1 Ampersand, less-than, quotes — RAW +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT ID AS [ns1:ID], Value AS [ns1:Value] +FROM forxml_ns_special FOR XML RAW('ns1:Row'); +GO + +-- 11.2 Embedded XML markup — PATH +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT ID AS [ns1:ID], Value AS [ns1:Value] +FROM forxml_ns_special WHERE ID = 4 FOR XML PATH('ns1:Row'); +GO + +-- ============================================ +-- SECTION 12: Unicode/multibyte values +-- ============================================ + +-- 12.1 Multibyte values in column with prefixed alias +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT ID AS [ns1:ID], Name AS [ns1:Name] +FROM forxml_ns_unicode FOR XML RAW('ns1:Row'); +GO + +-- 12.2 Multibyte values with PATH ELEMENTS +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT ID AS [ns1:ID], Name AS [ns1:Name] +FROM forxml_ns_unicode FOR XML PATH('ns1:Row'), ELEMENTS; +GO + +-- ============================================ +-- SECTION 13: ORDER BY / TOP / GROUP BY +-- ============================================ + +-- 13.1 ORDER BY prefixed alias +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees ORDER BY EmpID DESC FOR XML RAW('ns1:Emp'); +GO + +-- 13.2 TOP with namespaces +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT TOP 2 EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees ORDER BY EmpID FOR XML RAW('ns1:Emp'); +GO + +-- 13.3 GROUP BY with COUNT and namespace +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT Dept AS [ns1:Dept], COUNT(*) AS [ns1:N] +FROM forxml_ns_employees GROUP BY Dept ORDER BY Dept FOR XML RAW('ns1:Group'); +GO + +-- ============================================ +-- SECTION 14: WHERE clause variations +-- ============================================ + +-- 14.1 WHERE with IN +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID IN (1, 3) FOR XML RAW('ns1:Emp'); +GO + +-- 14.2 WHERE with LIKE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpName LIKE 'A%' FOR XML RAW('ns1:Emp'); +GO + +-- 14.3 WHERE with IS NULL +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE Dept IS NULL FOR XML RAW('ns1:Emp'); +GO + +-- ============================================ +-- SECTION 15: UNION queries +-- ============================================ + +-- 15.1 UNION ALL with namespaces +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] FROM forxml_ns_employees WHERE EmpID = 1 +UNION ALL +SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 2 +FOR XML RAW('ns1:Emp'); +GO + +-- 15.2 UNION (distinct) with namespaces +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] FROM forxml_ns_employees WHERE EmpID = 1 +UNION +SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML RAW('ns1:Emp'); +GO + +-- ============================================ +-- SECTION 16: Subqueries (correlated, in SELECT list) +-- ============================================ + +-- 16.1 Scalar subquery in SELECT list +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID AS [ns1:ID], + (SELECT COUNT(*) FROM forxml_ns_orders o WHERE o.EmpID = e.EmpID) AS [ns1:OrderCount] +FROM forxml_ns_employees e WHERE e.EmpID <= 2 FOR XML RAW('ns1:Emp'); +GO + +-- 16.2 Subquery FOR XML returning XML inline (no TYPE — string concatenation) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID AS [ns1:ID], e.EmpName AS [ns1:Name], + (SELECT TOP 1 o.OrderID FROM forxml_ns_orders o WHERE o.EmpID = e.EmpID ORDER BY o.OrderID) AS [ns1:FirstOrderID] +FROM forxml_ns_employees e WHERE e.EmpID = 1 +FOR XML RAW('ns1:Emp'); +GO + +-- ============================================ +-- SECTION 17: SELECT INTO variable / SET assignment +-- ============================================ + +-- 17.1 SELECT INTO @var with namespaces +DECLARE @x XML; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x = (SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] + FROM forxml_ns_employees WHERE EmpID = 1 + FOR XML RAW('ns1:Emp'), TYPE); +SELECT @x; +GO + +-- 17.2 NVARCHAR variable with namespaces +DECLARE @s NVARCHAR(MAX); +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @s = (SELECT EmpID AS [ns1:ID] + FROM forxml_ns_employees WHERE EmpID = 1 + FOR XML RAW('ns1:Emp')); +SELECT @s; +GO + +-- ============================================ +-- SECTION 18: XML methods on FOR XML result +-- ============================================ + +-- 18.1 .query() on FOR XML TYPE result +DECLARE @x XML; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x = (SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] + FROM forxml_ns_employees WHERE EmpID = 1 + FOR XML RAW('ns1:Emp'), TYPE); +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/ns1:Emp'); +GO + +-- 18.2 .value() on FOR XML TYPE result +DECLARE @x XML; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x = (SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] + FROM forxml_ns_employees WHERE EmpID = 1 + FOR XML PATH('ns1:Emp'), TYPE); +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.value('(/ns1:Emp/ns1:Name)[1]', 'varchar(50)'); +GO + +-- ============================================ +-- SECTION 19: JOIN queries with namespaces (extended) +-- ============================================ + +-- 19.1 INNER JOIN with prefixed columns from both tables — RAW +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID AS [ns1:EmpID], e.EmpName AS [ns1:EmpName], + o.OrderID AS [ns1:OrderID], o.Amount AS [ns1:Amount] +FROM forxml_ns_employees e +JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID, o.OrderID FOR XML RAW('ns1:Row'); +GO + +-- 19.2 LEFT JOIN PATH with ELEMENTS XSINIL +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID AS [ns1:EmpID], o.OrderID AS [ns1:OrderID] +FROM forxml_ns_employees e +LEFT JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID FOR XML PATH('ns1:Row'), ELEMENTS XSINIL; +GO + +-- ============================================ +-- SECTION 20: Custom row element name variations +-- ============================================ + +-- 20.1 RAW name with hyphens (NCName allows '-') +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML RAW('ns1:Emp-Record'); +GO + +-- 20.2 RAW name with underscores +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML RAW('ns1:Emp_Record'); +GO + +-- 20.3 PATH long element name +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML PATH('ns1:VeryLongElementNameUsedToTestBoundaryConditions'); +GO + +-- ============================================ +-- SECTION 21: Empty result set +-- ============================================ + +-- 21.1 RAW with no rows returned +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 999 +FOR XML RAW('ns1:Emp'); +GO + +-- 21.2 PATH with no rows + ROOT +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 999 +FOR XML PATH('ns1:Emp'), ROOT('ns1:Doc'); +GO + +-- ============================================ +-- SECTION 22: Undeclared prefix in column alias (colon restoration) +-- ============================================ + +-- 22.1 RAW attribute mode - undeclared prefix +SELECT 1 AS [ns:a] FOR XML RAW; +GO + +-- 22.2 RAW ELEMENTS mode - undeclared prefix +SELECT 1 AS [ns:a] FOR XML RAW, ELEMENTS; +GO +-- ============================================ +-- SECTION 23: Invalid prefix character validation in WITH XMLNAMESPACES +-- ============================================ + +-- 23.1 Special character in prefix - errors +WITH XMLNAMESPACES('http://test.com' AS [n@s1]) SELECT 'val' AS x FOR XML RAW; +GO + +-- 23.2 Numeric first character - errors +WITH XMLNAMESPACES('http://test.com' AS [1ns]) SELECT 'val' AS x FOR XML RAW; +GO + +-- 23.3 Hyphen first character - errors +WITH XMLNAMESPACES('http://test.com' AS [-ns]) SELECT 'val' AS x FOR XML RAW; +GO + +-- 23.4 Hyphen mid-prefix - valid, passes +WITH XMLNAMESPACES('http://test.com' AS [n-s1]) SELECT 'val' AS x FOR XML RAW; +GO + +-- 23.5 Digit mid-prefix - valid, passes +WITH XMLNAMESPACES('http://test.com' AS [ns123]) SELECT 'val' AS x FOR XML RAW; +GO + +-- ============================================ +-- SECTION 24: ns_decls_has_xsi must not false-match a URI substring +-- ============================================ + +-- 24.1 Declared URI contains "xmlns:xsi=" substring, with ELEMENTS XSINIL. +WITH XMLNAMESPACES('http://x/?xmlns:xsi=fake' AS n) +SELECT 1 AS a, CAST(NULL AS VARCHAR(10)) AS b +FOR XML RAW, ELEMENTS XSINIL; +GO + +-- 24.2 Same false-match guard in PATH mode +WITH XMLNAMESPACES('http://x/?xmlns:xsi=fake' AS n) +SELECT 1 AS a, CAST(NULL AS VARCHAR(10)) AS b +FOR XML PATH('Row'), ELEMENTS XSINIL; +GO diff --git a/test/JDBC/input/forxml/forxml-namespaces-vu-cleanup.sql b/test/JDBC/input/forxml/forxml-namespaces-vu-cleanup.sql new file mode 100644 index 00000000000..092252634c5 --- /dev/null +++ b/test/JDBC/input/forxml/forxml-namespaces-vu-cleanup.sql @@ -0,0 +1,30 @@ +-- ============================================ +-- SECTION: Cleanup +-- ============================================ + +DROP VIEW forxml_ns_view1; +GO + +DROP VIEW forxml_ns_view2; +GO + +DROP TABLE forxml_ns_employees; +GO + +DROP TABLE forxml_ns_orders; +GO + +DROP TABLE forxml_ns_simple; +GO + +DROP TABLE forxml_ns_nullable; +GO + +DROP TABLE forxml_ns_types; +GO + +DROP TABLE forxml_ns_special; +GO + +DROP TABLE forxml_ns_unicode; +GO diff --git a/test/JDBC/input/forxml/forxml-namespaces-vu-prepare.sql b/test/JDBC/input/forxml/forxml-namespaces-vu-prepare.sql new file mode 100644 index 00000000000..1f557321ff8 --- /dev/null +++ b/test/JDBC/input/forxml/forxml-namespaces-vu-prepare.sql @@ -0,0 +1,113 @@ +-- ============================================ +-- Tests for WITH XMLNAMESPACES + FOR XML (RAW, PATH, AUTO) +-- +-- WITH XMLNAMESPACES declares prefix-to-URI mappings whose scope is one +-- statement. T-SQL emits xmlns:prefix="uri" attributes on the appropriate +-- elements: +-- - FOR XML RAW: on each row element +-- - FOR XML PATH: on the outermost row element +-- - FOR XML AUTO: on the outermost (table-named) element +-- DEFAULT 'uri' emits xmlns="uri" (applies to unprefixed elements only). +-- ============================================ + +-- ============================================ +-- SECTION: Base Tables +-- ============================================ + +CREATE TABLE forxml_ns_employees ( + EmpID INT, + EmpName VARCHAR(50), + Dept VARCHAR(50) +); +GO + +INSERT INTO forxml_ns_employees VALUES (1, 'Alice', 'Sales'); +INSERT INTO forxml_ns_employees VALUES (2, 'Bob', 'IT'); +INSERT INTO forxml_ns_employees VALUES (3, NULL, 'HR'); +INSERT INTO forxml_ns_employees VALUES (4, 'Diana', NULL); +GO + +CREATE TABLE forxml_ns_orders ( + OrderID INT, + EmpID INT, + Amount DECIMAL(10, 2) +); +GO + +INSERT INTO forxml_ns_orders VALUES (101, 1, 100.50); +INSERT INTO forxml_ns_orders VALUES (102, 2, 200.00); +INSERT INTO forxml_ns_orders VALUES (103, 1, NULL); +GO + +CREATE TABLE forxml_ns_simple ( + a INT, + b VARCHAR(50) +); +GO + +INSERT INTO forxml_ns_simple VALUES (1, 'val1'); +INSERT INTO forxml_ns_simple VALUES (2, 'val2'); +GO + +CREATE TABLE forxml_ns_nullable ( + a INT, + b VARCHAR(50) +); +GO + +INSERT INTO forxml_ns_nullable VALUES (NULL, NULL); +INSERT INTO forxml_ns_nullable VALUES (1, NULL); +INSERT INTO forxml_ns_nullable VALUES (NULL, 'x'); +GO + +-- Table with multiple data types +CREATE TABLE forxml_ns_types ( + IntCol INT, + VarcharCol VARCHAR(50), + BitCol BIT, + DecimalCol DECIMAL(10,2), + DateCol DATE +); +GO + +INSERT INTO forxml_ns_types VALUES (100, 'Text1', 1, 99.99, '2024-01-01'); +INSERT INTO forxml_ns_types VALUES (NULL, NULL, NULL, NULL, NULL); +INSERT INTO forxml_ns_types VALUES (200, NULL, 0, NULL, '2024-06-15'); +GO + +-- Special characters table +CREATE TABLE forxml_ns_special ( + ID INT, + Value VARCHAR(100) +); +GO + +INSERT INTO forxml_ns_special VALUES (1, 'a & b'); +INSERT INTO forxml_ns_special VALUES (2, 'a < b'); +INSERT INTO forxml_ns_special VALUES (3, 'say "hello"'); +INSERT INTO forxml_ns_special VALUES (4, 'x'); +GO + +-- Unicode/multibyte values table +CREATE TABLE forxml_ns_unicode ( + ID INT, + Name NVARCHAR(50) +); +GO + +INSERT INTO forxml_ns_unicode VALUES (1, N'日本語'); +INSERT INTO forxml_ns_unicode VALUES (2, N'中文'); +INSERT INTO forxml_ns_unicode VALUES (3, N'한국어'); +GO + +-- ============================================ +-- SECTION: Dependent Views +-- ============================================ + +CREATE VIEW forxml_ns_view1 AS +SELECT EmpID, EmpName FROM forxml_ns_employees; +GO + +CREATE VIEW forxml_ns_view2 AS +SELECT a, b FROM forxml_ns_simple; +GO diff --git a/test/JDBC/input/forxml/forxml-namespaces-vu-verify.sql b/test/JDBC/input/forxml/forxml-namespaces-vu-verify.sql new file mode 100644 index 00000000000..e076fdec10f --- /dev/null +++ b/test/JDBC/input/forxml/forxml-namespaces-vu-verify.sql @@ -0,0 +1,726 @@ +-- ============================================ +-- WITH XMLNAMESPACES + FOR XML (RAW, PATH, AUTO) +-- +-- Scope: WITH XMLNAMESPACES declares prefix-to-URI mappings for one +-- statement. T-SQL emits xmlns:prefix="uri" attributes on the appropriate +-- elements: +-- - FOR XML RAW: on each row element +-- - FOR XML PATH: on the outermost row element +-- - FOR XML AUTO: on the outermost (table-named) element +-- DEFAULT 'uri' emits xmlns="uri" +-- ============================================ + +-- ============================================ +-- SECTION 1: FOR XML RAW +-- ============================================ + +-- 1.1 Basic single-prefix RAW with no prefix used in row/cols +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW; +GO + +-- 1.2 RAW with named row, no prefix in row/cols +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('Emp'); +GO + +-- 1.3 RAW with prefixed row name, no prefix in cols +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'); +GO + +-- 1.4 RAW with prefixed column aliases +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'); +GO + +-- 1.5 Multiple prefixes, each used in different columns +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1, 'http://example.com/ns2' AS ns2) +SELECT EmpID AS [ns1:ID], EmpName AS [ns2:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'); +GO + +-- 1.6 DEFAULT namespace +WITH XMLNAMESPACES(DEFAULT 'http://example.com/default') +SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW; +GO + +-- 1.7 DEFAULT + prefix combined +WITH XMLNAMESPACES(DEFAULT 'http://example.com/default', 'http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'); +GO + +-- 1.8 RAW + ELEMENTS +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'), ELEMENTS; +GO + +-- 1.9 RAW + ELEMENTS XSINIL with NULL value +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name], Dept AS [ns1:Dept] +FROM forxml_ns_employees WHERE EmpID = 3 FOR XML RAW('ns1:Emp'), ELEMENTS XSINIL; +GO + +-- 1.10 RAW + ELEMENTS ABSENT (NULLs dropped) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name], Dept AS [ns1:Dept] +FROM forxml_ns_employees WHERE EmpID = 3 FOR XML RAW('ns1:Emp'), ELEMENTS ABSENT; +GO + +-- 1.11 RAW + ROOT (unprefixed root) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'), ROOT('Doc'); +GO + +-- 1.12 RAW + ROOT (prefixed root) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'), ROOT('ns1:Doc'); +GO + +-- 1.13 RAW + TYPE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'), TYPE; +GO + +-- 1.14 RAW + ROOT + TYPE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'), ROOT('ns1:Doc'), TYPE; +GO + +-- 1.15 RAW multi-row with namespaces +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees FOR XML RAW('ns1:Emp'); +GO + +-- 1.16 RAW with view as the source +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_view1 WHERE EmpID = 1 FOR XML RAW('ns1:Emp'); +GO + +-- 1.17 RAW with computed expression and prefixed alias +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID + 100 AS [ns1:OffsetID], UPPER(EmpName) AS [ns1:UpperName] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML RAW('ns1:Emp'); +GO + +-- ============================================ +-- SECTION 2: FOR XML PATH +-- ============================================ + +-- 2.1 PATH with prefix on row and column elements +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML PATH('ns1:Emp'); +GO + +-- 2.2 PATH default row name with prefix on cols only +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML PATH; +GO + +-- 2.3 PATH with attribute-centric prefix (@) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [@ns1:id], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML PATH('ns1:Emp'); +GO + +-- 2.4 PATH + ROOT prefixed +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML PATH('ns1:Emp'), ROOT('ns1:Doc'); +GO + +-- 2.5 PATH + ELEMENTS XSINIL with NULL +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name], Dept AS [ns1:Dept] +FROM forxml_ns_employees WHERE EmpID = 3 FOR XML PATH('ns1:Emp'), ELEMENTS XSINIL; +GO + +-- 2.6 PATH + DEFAULT namespace +WITH XMLNAMESPACES(DEFAULT 'http://example.com/default') +SELECT EmpID AS ID, EmpName AS Name +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML PATH('Emp'); +GO + +-- 2.7 PATH + DEFAULT + prefix +WITH XMLNAMESPACES(DEFAULT 'http://example.com/default', 'http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML PATH('ns1:Emp'); +GO + +-- 2.8 PATH + multiple prefixes +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1, 'http://example.com/ns2' AS ns2) +SELECT EmpID AS [ns1:ID], EmpName AS [ns2:Name] +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML PATH('ns1:Emp'); +GO + +-- 2.9 PATH + ROOT + TYPE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML PATH('ns1:Emp'), ROOT('ns1:Doc'), TYPE; +GO + +-- 2.10 PATH multi-row +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees FOR XML PATH('ns1:Emp'); +GO + +-- ============================================ +-- SECTION 3: FOR XML AUTO +-- ============================================ + +-- 3.1 AUTO with prefixed table name +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT forxml_ns_employees.EmpID, forxml_ns_employees.EmpName +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML AUTO; +GO + +-- 3.2 AUTO + ELEMENTS +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT forxml_ns_employees.EmpID, forxml_ns_employees.EmpName +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML AUTO, ELEMENTS; +GO + +-- 3.3 AUTO + ROOT +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT forxml_ns_employees.EmpID, forxml_ns_employees.EmpName +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML AUTO, ROOT('Doc'); +GO + +-- 3.4 AUTO + DEFAULT + prefix +WITH XMLNAMESPACES(DEFAULT 'http://example.com/default', 'http://example.com/ns1' AS ns1) +SELECT forxml_ns_employees.EmpID, forxml_ns_employees.EmpName +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML AUTO; +GO + +-- 3.5 AUTO + TYPE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT forxml_ns_employees.EmpID, forxml_ns_employees.EmpName +FROM forxml_ns_employees WHERE EmpID = 1 FOR XML AUTO, TYPE; +GO + +-- 3.6 AUTO + ELEMENTS XSINIL + NULL row +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT forxml_ns_employees.EmpID, forxml_ns_employees.EmpName, forxml_ns_employees.Dept +FROM forxml_ns_employees WHERE EmpID = 3 FOR XML AUTO, ELEMENTS XSINIL; +GO + +-- 3.7 AUTO multi-row +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT forxml_ns_employees.EmpID, forxml_ns_employees.EmpName +FROM forxml_ns_employees FOR XML AUTO; +GO + +-- 3.8 AUTO with JOIN (parent-child nesting) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID, e.EmpName, o.OrderID, o.Amount +FROM forxml_ns_employees e +JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID, o.OrderID +FOR XML AUTO; +GO + +-- 3.9 AUTO with JOIN + ELEMENTS +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID, e.EmpName, o.OrderID, o.Amount +FROM forxml_ns_employees e +JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID, o.OrderID +FOR XML AUTO, ELEMENTS; +GO + +-- 3.10 AUTO with JOIN + ROOT +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID, e.EmpName, o.OrderID, o.Amount +FROM forxml_ns_employees e +JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID, o.OrderID +FOR XML AUTO, ROOT('Doc'); +GO + +-- 3.11 AUTO with LEFT JOIN, NULL children +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID, e.EmpName, o.OrderID, o.Amount +FROM forxml_ns_employees e +LEFT JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID +FOR XML AUTO; +GO + +-- 3.12 AUTO with CTE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1), +ActiveEmps AS (SELECT EmpID, EmpName FROM forxml_ns_employees WHERE Dept IS NOT NULL) +SELECT a.EmpID, a.EmpName +FROM ActiveEmps a +FOR XML AUTO; +GO + +-- 3.13 AUTO with subquery in FROM +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT sub.EmpID, sub.EmpName +FROM (SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID <= 2) sub +FOR XML AUTO; +GO + +-- 3.14 AUTO multi-table JOIN with ELEMENTS XSINIL +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID, e.EmpName, e.Dept, o.OrderID, o.Amount +FROM forxml_ns_employees e +LEFT JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID, o.OrderID +FOR XML AUTO, ELEMENTS XSINIL; +GO + +-- 3.15 AUTO + DEFAULT ns + JOIN +WITH XMLNAMESPACES(DEFAULT 'http://default.com', 'http://example.com/ns1' AS ns1) +SELECT e.EmpID, e.EmpName, o.OrderID +FROM forxml_ns_employees e +JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID +FOR XML AUTO; +GO + +-- 3.16 AUTO with JOIN + TYPE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID, e.EmpName, o.OrderID +FROM forxml_ns_employees e +JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID +FOR XML AUTO, TYPE; +GO + +-- ============================================ +-- SECTION 4: Validation / Error cases +-- ============================================ + +-- 4.1 Duplicate prefix declaration +WITH XMLNAMESPACES('http://example.com/u1' AS ns1, 'http://example.com/u2' AS ns1) +SELECT 1 AS [ns1:a] FOR XML RAW('ns1:Row'); +GO + +-- 4.2 Multiple DEFAULT declarations +WITH XMLNAMESPACES(DEFAULT 'http://a', DEFAULT 'http://b') +SELECT 1 AS a FOR XML RAW; +GO + +-- 4.3 Reserved 'xmlns' prefix +WITH XMLNAMESPACES('http://example.com' AS xmlns) +SELECT 1 AS [xmlns:a] FOR XML RAW; +GO + +-- 4.4 Empty URI +WITH XMLNAMESPACES('' AS ns1) +SELECT 1 AS [ns1:a] FOR XML RAW; +GO + +-- 4.5 Reserved 'xml' prefix re-bound to wrong URI +WITH XMLNAMESPACES('http://wrong.uri' AS xml) +SELECT 1 AS [xml:a] FOR XML RAW; +GO + +-- 4.6 The XML namespace URI bound to a non-xml prefix +WITH XMLNAMESPACES('http://www.w3.org/XML/1998/namespace' AS notxml) +SELECT 1 AS a FOR XML RAW; +GO + +-- 4.7 xsi prefix bound to xsi URI without XSINIL — alias should resolve +WITH XMLNAMESPACES('http://www.w3.org/2001/XMLSchema-instance' AS xsi) +SELECT 1 AS [xsi:a] FOR XML RAW; +GO + +-- 4.8 xsi prefix bound to xsi URI with XSINIL — must not duplicate xmlns:xsi +WITH XMLNAMESPACES('http://www.w3.org/2001/XMLSchema-instance' AS xsi) +SELECT 1 AS a, NULL AS b FOR XML RAW, ELEMENTS XSINIL; +GO + +-- ============================================ +-- SECTION 5: CTE / subquery combinations +-- ============================================ + +-- 5.1 WITH XMLNAMESPACES followed by CTE (must come before CTE) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1), +ns_cte AS (SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] FROM ns_cte FOR XML RAW('ns1:Emp'); +GO + +-- 5.2 Outer query with subquery that doesn't use namespaces +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM (SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 1) sub +FOR XML RAW('ns1:Emp'); +GO + +-- ============================================ +-- SECTION 6: Mixed scalar types +-- ============================================ + +-- 6.1 numeric columns +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT OrderID AS [ns1:ID], Amount AS [ns1:Amt] +FROM forxml_ns_orders WHERE OrderID = 101 FOR XML RAW('ns1:Order'); +GO + +-- 6.2 NULL numeric with XSINIL +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT OrderID AS [ns1:ID], Amount AS [ns1:Amt] +FROM forxml_ns_orders WHERE OrderID = 103 FOR XML RAW('ns1:Order'), ELEMENTS XSINIL; +GO + +-- ============================================ +-- SECTION 7: All-NULL rows with XSINIL + namespaces +-- ============================================ + +-- 7.1 RAW +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT a AS [ns1:a], b AS [ns1:b] FROM forxml_ns_nullable WHERE a IS NULL AND b IS NULL +FOR XML RAW('ns1:Row'), ELEMENTS XSINIL; +GO + +-- 7.2 PATH +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT a AS [ns1:a], b AS [ns1:b] FROM forxml_ns_nullable WHERE a IS NULL AND b IS NULL +FOR XML PATH('ns1:Row'), ELEMENTS XSINIL; +GO + +-- ============================================ +-- SECTION 8: View-based queries +-- ============================================ + +-- 8.1 SELECT FROM view with RAW +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT a AS [ns1:a], b AS [ns1:b] FROM forxml_ns_view2 FOR XML RAW('ns1:Row'); +GO + +-- 8.2 SELECT FROM view with PATH +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT a AS [ns1:a], b AS [ns1:b] FROM forxml_ns_view2 FOR XML PATH('ns1:Row'); +GO + +-- ============================================ +-- SECTION 9: Special characters / unicode in URIs and values +-- ============================================ + +-- 9.1 URI with hash and percent characters +WITH XMLNAMESPACES('http://example.com/path#section%20path' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML RAW('ns1:Emp'); +GO + +-- 9.1b URI with ampersand (must be XML-escaped to & in attribute value) +WITH XMLNAMESPACES('http://example.com/path?q=v&more' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML RAW('ns1:Emp'); +GO + +-- 9.1c URI with less-than (must be XML-escaped) +WITH XMLNAMESPACES('http://example.com/' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML RAW('ns1:Emp'); +GO + +-- 9.2 Long URI +WITH XMLNAMESPACES('http://example.com/very/long/path/with/many/segments/that/goes/on/and/on/for/testing/purposes/only' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML RAW('ns1:Emp'); +GO + +-- ============================================ +-- SECTION 10: Different data types +-- ============================================ + +-- 10.1 RAW with all data types prefixed +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT IntCol AS [ns1:Int], VarcharCol AS [ns1:Str], BitCol AS [ns1:Bit], + DecimalCol AS [ns1:Dec], DateCol AS [ns1:Dt] +FROM forxml_ns_types WHERE IntCol = 100 FOR XML RAW('ns1:Row'); +GO + +-- 10.2 PATH with all data types and ELEMENTS XSINIL on NULL row +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT IntCol AS [ns1:Int], VarcharCol AS [ns1:Str], BitCol AS [ns1:Bit] +FROM forxml_ns_types WHERE IntCol IS NULL AND VarcharCol IS NULL +FOR XML PATH('ns1:Row'), ELEMENTS XSINIL; +GO + +-- ============================================ +-- SECTION 11: Special characters in values +-- ============================================ + +-- 11.1 Ampersand, less-than, quotes — RAW +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT ID AS [ns1:ID], Value AS [ns1:Value] +FROM forxml_ns_special FOR XML RAW('ns1:Row'); +GO + +-- 11.2 Embedded XML markup — PATH +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT ID AS [ns1:ID], Value AS [ns1:Value] +FROM forxml_ns_special WHERE ID = 4 FOR XML PATH('ns1:Row'); +GO + +-- ============================================ +-- SECTION 12: Unicode/multibyte values +-- ============================================ + +-- 12.1 Multibyte values in column with prefixed alias +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT ID AS [ns1:ID], Name AS [ns1:Name] +FROM forxml_ns_unicode FOR XML RAW('ns1:Row'); +GO + +-- 12.2 Multibyte values with PATH ELEMENTS +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT ID AS [ns1:ID], Name AS [ns1:Name] +FROM forxml_ns_unicode FOR XML PATH('ns1:Row'), ELEMENTS; +GO + +-- ============================================ +-- SECTION 13: ORDER BY / TOP / GROUP BY +-- ============================================ + +-- 13.1 ORDER BY prefixed alias +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees ORDER BY EmpID DESC FOR XML RAW('ns1:Emp'); +GO + +-- 13.2 TOP with namespaces +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT TOP 2 EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees ORDER BY EmpID FOR XML RAW('ns1:Emp'); +GO + +-- 13.3 GROUP BY with COUNT and namespace +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT Dept AS [ns1:Dept], COUNT(*) AS [ns1:N] +FROM forxml_ns_employees GROUP BY Dept ORDER BY Dept FOR XML RAW('ns1:Group'); +GO + +-- ============================================ +-- SECTION 14: WHERE clause variations +-- ============================================ + +-- 14.1 WHERE with IN +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpID IN (1, 3) FOR XML RAW('ns1:Emp'); +GO + +-- 14.2 WHERE with LIKE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE EmpName LIKE 'A%' FOR XML RAW('ns1:Emp'); +GO + +-- 14.3 WHERE with IS NULL +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] +FROM forxml_ns_employees WHERE Dept IS NULL FOR XML RAW('ns1:Emp'); +GO + +-- ============================================ +-- SECTION 15: UNION queries +-- ============================================ + +-- 15.1 UNION ALL with namespaces +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] FROM forxml_ns_employees WHERE EmpID = 1 +UNION ALL +SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 2 +FOR XML RAW('ns1:Emp'); +GO + +-- 15.2 UNION (distinct) with namespaces +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] FROM forxml_ns_employees WHERE EmpID = 1 +UNION +SELECT EmpID, EmpName FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML RAW('ns1:Emp'); +GO + +-- ============================================ +-- SECTION 16: Subqueries (correlated, in SELECT list) +-- ============================================ + +-- 16.1 Scalar subquery in SELECT list +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID AS [ns1:ID], + (SELECT COUNT(*) FROM forxml_ns_orders o WHERE o.EmpID = e.EmpID) AS [ns1:OrderCount] +FROM forxml_ns_employees e WHERE e.EmpID <= 2 FOR XML RAW('ns1:Emp'); +GO + +-- 16.2 Subquery FOR XML returning XML inline (no TYPE — string concatenation) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID AS [ns1:ID], e.EmpName AS [ns1:Name], + (SELECT TOP 1 o.OrderID FROM forxml_ns_orders o WHERE o.EmpID = e.EmpID ORDER BY o.OrderID) AS [ns1:FirstOrderID] +FROM forxml_ns_employees e WHERE e.EmpID = 1 +FOR XML RAW('ns1:Emp'); +GO + +-- ============================================ +-- SECTION 17: SELECT INTO variable / SET assignment +-- ============================================ + +-- 17.1 SELECT INTO @var with namespaces +DECLARE @x XML; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x = (SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] + FROM forxml_ns_employees WHERE EmpID = 1 + FOR XML RAW('ns1:Emp'), TYPE); +SELECT @x; +GO + +-- 17.2 NVARCHAR variable with namespaces +DECLARE @s NVARCHAR(MAX); +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @s = (SELECT EmpID AS [ns1:ID] + FROM forxml_ns_employees WHERE EmpID = 1 + FOR XML RAW('ns1:Emp')); +SELECT @s; +GO + +-- ============================================ +-- SECTION 18: XML methods on FOR XML result +-- ============================================ + +-- 18.1 .query() on FOR XML TYPE result +DECLARE @x XML; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x = (SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] + FROM forxml_ns_employees WHERE EmpID = 1 + FOR XML RAW('ns1:Emp'), TYPE); +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/ns1:Emp'); +GO + +-- 18.2 .value() on FOR XML TYPE result +DECLARE @x XML; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x = (SELECT EmpID AS [ns1:ID], EmpName AS [ns1:Name] + FROM forxml_ns_employees WHERE EmpID = 1 + FOR XML PATH('ns1:Emp'), TYPE); +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.value('(/ns1:Emp/ns1:Name)[1]', 'varchar(50)'); +GO + +-- ============================================ +-- SECTION 19: JOIN queries with namespaces (extended) +-- ============================================ + +-- 19.1 INNER JOIN with prefixed columns from both tables — RAW +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID AS [ns1:EmpID], e.EmpName AS [ns1:EmpName], + o.OrderID AS [ns1:OrderID], o.Amount AS [ns1:Amount] +FROM forxml_ns_employees e +JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID, o.OrderID FOR XML RAW('ns1:Row'); +GO + +-- 19.2 LEFT JOIN PATH with ELEMENTS XSINIL +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT e.EmpID AS [ns1:EmpID], o.OrderID AS [ns1:OrderID] +FROM forxml_ns_employees e +LEFT JOIN forxml_ns_orders o ON e.EmpID = o.EmpID +ORDER BY e.EmpID FOR XML PATH('ns1:Row'), ELEMENTS XSINIL; +GO + +-- ============================================ +-- SECTION 20: Custom row element name variations +-- ============================================ + +-- 20.1 RAW name with hyphens (NCName allows '-') +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML RAW('ns1:Emp-Record'); +GO + +-- 20.2 RAW name with underscores +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML RAW('ns1:Emp_Record'); +GO + +-- 20.3 PATH long element name +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 1 +FOR XML PATH('ns1:VeryLongElementNameUsedToTestBoundaryConditions'); +GO + +-- ============================================ +-- SECTION 21: Empty result set +-- ============================================ + +-- 21.1 RAW with no rows returned +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 999 +FOR XML RAW('ns1:Emp'); +GO + +-- 21.2 PATH with no rows + ROOT +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT EmpID AS [ns1:ID] FROM forxml_ns_employees WHERE EmpID = 999 +FOR XML PATH('ns1:Emp'), ROOT('ns1:Doc'); +GO + +-- ============================================ +-- SECTION 22: Undeclared prefix in column alias (colon restoration) +-- ============================================ + +-- 22.1 RAW attribute mode - undeclared prefix +SELECT 1 AS [ns:a] FOR XML RAW; +GO + +-- 22.2 RAW ELEMENTS mode - undeclared prefix +SELECT 1 AS [ns:a] FOR XML RAW, ELEMENTS; +GO +-- ============================================ +-- SECTION 23: Invalid prefix character validation in WITH XMLNAMESPACES +-- ============================================ + +-- 23.1 Special character in prefix - errors +WITH XMLNAMESPACES('http://test.com' AS [n@s1]) SELECT 'val' AS x FOR XML RAW; +GO + +-- 23.2 Numeric first character - errors +WITH XMLNAMESPACES('http://test.com' AS [1ns]) SELECT 'val' AS x FOR XML RAW; +GO + +-- 23.3 Hyphen first character - errors +WITH XMLNAMESPACES('http://test.com' AS [-ns]) SELECT 'val' AS x FOR XML RAW; +GO + +-- 23.4 Hyphen mid-prefix - valid, passes +WITH XMLNAMESPACES('http://test.com' AS [n-s1]) SELECT 'val' AS x FOR XML RAW; +GO + +-- 23.5 Digit mid-prefix - valid, passes +WITH XMLNAMESPACES('http://test.com' AS [ns123]) SELECT 'val' AS x FOR XML RAW; +GO + +-- ============================================ +-- SECTION 24: ns_decls_has_xsi must not false-match a URI substring +-- ============================================ + +-- 24.1 Declared URI contains "xmlns:xsi=" substring, with ELEMENTS XSINIL. +WITH XMLNAMESPACES('http://x/?xmlns:xsi=fake' AS n) +SELECT 1 AS a, CAST(NULL AS VARCHAR(10)) AS b +FOR XML RAW, ELEMENTS XSINIL; +GO + +-- 24.2 Same false-match guard in PATH mode +WITH XMLNAMESPACES('http://x/?xmlns:xsi=fake' AS n) +SELECT 1 AS a, CAST(NULL AS VARCHAR(10)) AS b +FOR XML PATH('Row'), ELEMENTS XSINIL; +GO diff --git a/test/JDBC/input/xml/xml-methods-namespaces-before-17_11-or-18_5-vu-cleanup.sql b/test/JDBC/input/xml/xml-methods-namespaces-before-17_11-or-18_5-vu-cleanup.sql new file mode 100644 index 00000000000..889e6b9a23c --- /dev/null +++ b/test/JDBC/input/xml/xml-methods-namespaces-before-17_11-or-18_5-vu-cleanup.sql @@ -0,0 +1,18 @@ +-- ============================================ +-- SECTION: Cleanup +-- ============================================ + +DROP VIEW xmlns_methods_view1; +GO + +DROP TABLE xmlns_methods_t1; +GO + +DROP TABLE xmlns_methods_t2; +GO + +DROP TABLE xmlns_methods_orders; +GO + +DROP TABLE xmlns_methods_comments; +GO diff --git a/test/JDBC/input/xml/xml-methods-namespaces-before-17_11-or-18_5-vu-prepare.sql b/test/JDBC/input/xml/xml-methods-namespaces-before-17_11-or-18_5-vu-prepare.sql new file mode 100644 index 00000000000..3f16fb3ec8f --- /dev/null +++ b/test/JDBC/input/xml/xml-methods-namespaces-before-17_11-or-18_5-vu-prepare.sql @@ -0,0 +1,57 @@ +-- ============================================ +-- Tests for XML data type methods (.query, .value, .exist) +-- combined with WITH XMLNAMESPACES. +-- +-- WITH XMLNAMESPACES provides namespace bindings for XPath evaluation. +-- The bindings are passed to PG's xpath() function as a text[][] array. +-- This lets prefixed XPath like /ns1:item resolve correctly when the +-- target XML uses xmlns:ns1. +-- ============================================ + +-- ============================================ +-- SECTION: Base Tables +-- ============================================ + +CREATE TABLE xmlns_methods_t1 (id INT, data XML); +GO + +INSERT INTO xmlns_methods_t1 VALUES (1, 'val1val2'); +INSERT INTO xmlns_methods_t1 VALUES (2, '1020'); +INSERT INTO xmlns_methods_t1 VALUES (3, ''); +INSERT INTO xmlns_methods_t1 VALUES (4, NULL); +INSERT INTO xmlns_methods_t1 VALUES (5, '42'); +GO + +CREATE TABLE xmlns_methods_t2 (id INT, data XML); +GO + +INSERT INTO xmlns_methods_t2 VALUES (1, 'SQL29.99XML39.99'); +INSERT INTO xmlns_methods_t2 VALUES (2, ''); +GO + +-- ============================================ +-- SECTION: Dependent Views +-- ============================================ + +CREATE VIEW xmlns_methods_view1 AS +SELECT id, data FROM xmlns_methods_t1; +GO + +-- ============================================ +-- SECTION: Tables for advanced method tests +-- ============================================ + +CREATE TABLE xmlns_methods_orders (id INT, data XML); +GO + +INSERT INTO xmlns_methods_orders VALUES (1, 'WidgetGadget'); +INSERT INTO xmlns_methods_orders VALUES (2, 'Gizmo'); +INSERT INTO xmlns_methods_orders VALUES (3, ''); +GO + +CREATE TABLE xmlns_methods_comments (id INT, data XML); +GO + +INSERT INTO xmlns_methods_comments VALUES (1, 'val'); +INSERT INTO xmlns_methods_comments VALUES (2, 'val'); +GO diff --git a/test/JDBC/input/xml/xml-methods-namespaces-before-17_11-or-18_5-vu-verify.sql b/test/JDBC/input/xml/xml-methods-namespaces-before-17_11-or-18_5-vu-verify.sql new file mode 100644 index 00000000000..722400213f1 --- /dev/null +++ b/test/JDBC/input/xml/xml-methods-namespaces-before-17_11-or-18_5-vu-verify.sql @@ -0,0 +1,494 @@ +-- ============================================ +-- WITH XMLNAMESPACES + .query() / .value() / .exist() +-- ============================================ + +-- ============================================ +-- SECTION 1: .query() with prefixed XPath +-- ============================================ + +-- 1.1 Query a prefixed element using a declared prefix +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/ns1:item'); +GO + +-- 1.2 Query unprefixed name where XML element is prefixed -> empty +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/item'); +GO + +-- 1.3 Query with multiple prefixes used in different paths +DECLARE @x XML = '12'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1, 'http://example.com/ns2' AS ns2) +SELECT @x.query('/root/ns1:a'), @x.query('/root/ns2:b'); +GO + +-- 1.4 Query repeated prefixed elements +DECLARE @x XML = 'ab'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/ns1:item'); +GO + +-- 1.5 Query positional access with prefix +DECLARE @x XML = 'ab'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/ns1:item[1]'), @x.query('/root/ns1:item[2]'); +GO + +-- 1.6 Query with attribute predicate using prefix +DECLARE @x XML = 'SQLXML'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/ns1:book[@ns1:id="2"]/ns1:title'); +GO + +-- 1.7 Query NULL XML +DECLARE @x XML = NULL; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/ns1:item'); +GO + +-- 1.8 Query against table column +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, data.query('/root/ns1:item') +FROM xmlns_methods_t1 WHERE id IN (1, 2) +ORDER BY id; +GO + +-- 1.9 Query with no match returns empty +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/ns1:nonexistent'); +GO + +-- 1.10 Wildcard with prefix +DECLARE @x XML = '12'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/ns1:*'); +GO + +-- ============================================ +-- SECTION 2: .exist() with prefixed XPath +-- ============================================ + +-- 2.1 Exist returns 1 when prefixed match found +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.exist('/root/ns1:item'); +GO + +-- 2.2 Exist returns 0 when prefixed name doesn't match +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.exist('/root/ns1:other'); +GO + +-- 2.3 Exist with attribute predicate +DECLARE @x XML = ''; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.exist('/root/item/@ns1:attr'); +GO + +-- 2.4 Exist with attribute predicate (negative) +DECLARE @x XML = ''; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.exist('/root/item/@ns1:attr'); +GO + +-- 2.5 Exist on NULL +DECLARE @x XML = NULL; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.exist('/root/ns1:item'); +GO + +-- 2.6 Exist with multiple namespaces +DECLARE @x XML = '1'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1, 'http://example.com/ns2' AS ns2) +SELECT @x.exist('/root/ns1:a'), @x.exist('/root/ns2:b'); +GO + +-- ============================================ +-- SECTION 3: .value() with prefixed XPath +-- ============================================ + +-- 3.1 Value of prefixed element +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.value('(/root/ns1:item)[1]', 'varchar(50)'); +GO + +-- 3.2 Value cast to int +DECLARE @x XML = '42'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.value('(/root/ns1:item)[1]', 'int'); +GO + +-- 3.3 Value of attribute with prefix +DECLARE @x XML = ''; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.value('(/root/item/@ns1:attr)[1]', 'varchar(50)'); +GO + +-- 3.4 Value when no match - returns NULL +DECLARE @x XML = ''; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.value('(/root/ns1:item)[1]', 'varchar(50)'); +GO + +-- 3.5 Value of multiple matches selects positional +DECLARE @x XML = 'ab'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.value('(/root/ns1:item)[1]', 'varchar(10)'), + @x.value('(/root/ns1:item)[2]', 'varchar(10)'); +GO + +-- 3.6 Value on NULL XML +DECLARE @x XML = NULL; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.value('(/root/ns1:item)[1]', 'varchar(50)'); +GO + +-- 3.7 Value with multiple namespaces +DECLARE @x XML = '1020'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1, 'http://example.com/ns2' AS ns2) +SELECT @x.value('(/root/ns1:a)[1]', 'int'), + @x.value('(/root/ns2:b)[1]', 'int'); +GO + +-- ============================================ +-- SECTION 4: Methods on table columns with namespaces +-- ============================================ + +-- 4.1 .query on column +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, data.query('/root/ns1:item') +FROM xmlns_methods_t1 WHERE id = 1; +GO + +-- 4.2 .value on column +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, data.value('(/root/ns1:item)[1]', 'varchar(50)') +FROM xmlns_methods_t1 WHERE id IN (1, 5); +GO + +-- 4.3 .exist on column +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, data.exist('/root/ns1:item') +FROM xmlns_methods_t1 WHERE id <= 5 +ORDER BY id; +GO + +-- 4.4 .query on view +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, data.query('/root/ns1:item') +FROM xmlns_methods_view1 WHERE id = 1; +GO + +-- ============================================ +-- SECTION 5: Complex namespace cases +-- ============================================ + +-- 5.1 Same prefix used in multiple statements (no carry-over) +DECLARE @x XML = '1'; +WITH XMLNAMESPACES('http://uri1' AS p) +SELECT @x.value('(/root/p:a)[1]', 'int'); +GO + +-- 5.2 Prefix in XML differs from prefix in WITH XMLNAMESPACES (matching by URI) +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/ns1' AS bar) +SELECT @x.value('(/root/bar:item)[1]', 'varchar(50)'); +GO + +-- 5.3 Predicate using prefixed attribute +WITH XMLNAMESPACES('http://example.com/products' AS p) +SELECT data.query('/catalog/p:book[@p:id="2"]/p:title') +FROM xmlns_methods_t2 WHERE id = 1; +GO + +-- 5.4 Numeric predicate on prefixed element +WITH XMLNAMESPACES('http://example.com/products' AS p) +SELECT data.query('/catalog/p:book[p:price>30]/p:title') +FROM xmlns_methods_t2 WHERE id = 1; +GO + +-- 5.5 Wildcard with prefix on table column +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT data.query('/root/ns1:*') +FROM xmlns_methods_t1 WHERE id = 1; +GO + +-- 5.6 .query then .value chained on result +DECLARE @x XML = 'hello'; +DECLARE @r XML; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @r = @x.query('/root/ns1:item'); +SELECT @r; +GO + +-- ============================================ +-- SECTION 6: NULL XML behaviors +-- ============================================ + +-- 6.1 Query NULL row (with namespaces) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT data.query('/root/ns1:item') FROM xmlns_methods_t1 WHERE id = 4; +GO + +-- 6.2 Value on NULL row (with namespaces) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT data.value('(/root/ns1:item)[1]', 'varchar(50)') FROM xmlns_methods_t1 WHERE id = 4; +GO + +-- 6.3 Exist on NULL row (with namespaces) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT data.exist('/root/ns1:item') FROM xmlns_methods_t1 WHERE id = 4; +GO + +-- ============================================ +-- SECTION 7: Validation with same rules as FOR XML +-- ============================================ + +-- 7.1 Duplicate prefix +DECLARE @x XML = ''; +WITH XMLNAMESPACES('http://a' AS p, 'http://b' AS p) +SELECT @x.query('/root'); +GO + +-- 7.2 Empty URI +DECLARE @x XML = ''; +WITH XMLNAMESPACES('' AS p) +SELECT @x.query('/root'); +GO + +-- 7.3 Reserved xmlns prefix +DECLARE @x XML = ''; +WITH XMLNAMESPACES('http://a' AS xmlns) +SELECT @x.query('/root'); +GO + +-- 7.4 xml prefix with wrong URI +DECLARE @x XML = ''; +WITH XMLNAMESPACES('http://wrong' AS xml) +SELECT @x.query('/root'); +GO + +-- ============================================ +-- SECTION 8: Variable assignment with namespaces +-- ============================================ + +-- 8.1 .query() result assigned to XML variable +DECLARE @x XML = 'val'; +DECLARE @r XML; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @r = @x.query('/root/ns1:item'); +SELECT @r; +GO + +-- 8.2 .value() result into varchar +DECLARE @x XML = 'hello'; +DECLARE @s VARCHAR(50); +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @s = @x.value('(/root/ns1:item)[1]', 'varchar(50)'); +SELECT @s; +GO + +-- 8.3 .exist() into bit +DECLARE @x XML = ''; +DECLARE @b BIT; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @b = @x.exist('/root/ns1:item'); +SELECT @b; +GO + +-- ============================================ +-- SECTION 9: Methods in WHERE clause +-- ============================================ + +-- 9.1 .exist() in WHERE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id FROM xmlns_methods_t1 WHERE data.exist('/root/ns1:item') = 1 +ORDER BY id; +GO + +-- 9.2 .value() in WHERE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id FROM xmlns_methods_t1 WHERE data.value('(/root/ns1:item)[1]', 'varchar(50)') = 'val1' +ORDER BY id; +GO + +-- ============================================ +-- SECTION 10: Methods in CASE expression +-- ============================================ + +-- 10.1 CASE with .exist() +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, + CASE WHEN data.exist('/root/ns1:item') = 1 THEN 'has-item' ELSE 'no-item' END AS Tag +FROM xmlns_methods_t1 WHERE id <= 3 +ORDER BY id; +GO + +-- ============================================ +-- SECTION 11: Methods with JOIN +-- ============================================ + +-- 11.1 JOIN on .value() result +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT t1.id, t1.data.value('(/root/ns1:item)[1]', 'varchar(50)') AS V +FROM xmlns_methods_t1 t1 JOIN xmlns_methods_view1 v ON t1.id = v.id +WHERE t1.id IN (1, 5) +ORDER BY t1.id; +GO + +-- ============================================ +-- SECTION 12: Methods with ORDER BY / TOP +-- ============================================ + +-- 12.1 ORDER BY .value() +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, data.value('(/root/ns1:item)[1]', 'varchar(50)') AS V +FROM xmlns_methods_t1 WHERE id IN (1, 5) +ORDER BY data.value('(/root/ns1:item)[1]', 'varchar(50)'); +GO + +-- 12.2 TOP with .query() +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT TOP 1 id, data.query('/root/ns1:item') +FROM xmlns_methods_t1 WHERE id <= 5 +ORDER BY id; +GO + +-- ============================================ +-- SECTION 13: Methods on views +-- ============================================ + +-- 13.1 .query() on view +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, data.query('/root/ns1:item') +FROM xmlns_methods_view1 WHERE id <= 2 ORDER BY id; +GO + +-- 13.2 .exist() in WHERE on view +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id FROM xmlns_methods_view1 +WHERE data.exist('/root/ns1:item') = 1 ORDER BY id; +GO + +-- ============================================ +-- SECTION 14: XML with comments +-- ============================================ + +-- 14.1 .query() ignores comments outside target +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT data.query('/root/ns1:item') +FROM xmlns_methods_comments WHERE id = 1; +GO + +-- 14.2 .value() with inline comment in value +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT data.value('(/root/ns1:item)[1]', 'varchar(50)') +FROM xmlns_methods_comments WHERE id = 2; +GO + +-- ============================================ +-- SECTION 15: Spaces and case in method calls +-- ============================================ + +-- 15.1 Spaces around .query() +DECLARE @x XML = 'v'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x . query('/root/ns1:item'); +GO + +-- 15.2 Spaces around .value() +DECLARE @x XML = 'v'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x . value('(/root/ns1:item)[1]', 'varchar(50)'); +GO + +-- ============================================ +-- SECTION 16: Predicates with prefixed attributes (richer) +-- ============================================ + +-- 16.1 Attribute predicate with numeric comparison +WITH XMLNAMESPACES('http://example.com/products' AS p) +SELECT data.query('/order/p:item[@p:price>20]') FROM xmlns_methods_orders WHERE id = 1; +GO + +-- 16.2 Multiple prefixed attribute predicates +WITH XMLNAMESPACES('http://example.com/products' AS p) +SELECT data.query('/order/p:item[@p:price>20 and @p:price<40]') FROM xmlns_methods_orders WHERE id = 1; +GO + +-- 16.3 Empty XML root with .query() prefix path +WITH XMLNAMESPACES('http://example.com/products' AS p) +SELECT data.query('/order/p:item') FROM xmlns_methods_orders WHERE id = 3; +GO + +-- ============================================ +-- SECTION 17: Multiple chained method calls +-- ============================================ + +-- 17.1 .query() result then .value() +DECLARE @x XML = 'val1val2'; +DECLARE @r XML; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @r = @x.query('/root/ns1:item[1]'); +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @r.value('(/ns1:item)[1]', 'varchar(50)'); +GO + +-- ============================================ +-- SECTION 18: Result from FOR XML chained +-- ============================================ + +-- 18.1 FOR XML result fed to .query() under namespace +DECLARE @x XML; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x = (SELECT 1 AS [ns1:a], 'v' AS [ns1:b] FOR XML PATH('ns1:Row'), TYPE); +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/ns1:Row/ns1:a'); +GO + +-- ============================================ +-- SECTION 19: Special characters in WITH XMLNAMESPACES URIs +-- ============================================ + +-- 19.1 Double-quote in URI (array-literal escaping) -> empty +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/"q"' AS p) +SELECT @x.query('/root/p:item'); +GO + +-- 19.2 Backslash in URI (array-literal escaping) -> empty +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/a\b' AS p) +SELECT @x.query('/root/p:item'); +GO + +-- 19.3 Single-quote in URI (escaped as '' in T-SQL), resolves end-to-end -> val +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/it''s' AS ns1) +SELECT @x.value('(/root/ns1:item)[1]', 'varchar(50)'); +GO + +-- 19.4 Combined double-quote and backslash in URI (array-literal escaping) -> 0 +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/"a"\b' AS p) +SELECT @x.exist('/root/p:item'); +GO + +-- 19.5 Limitation: a document whose own namespace URI contains a character that +-- is illegal in an RFC 3986 URI ('\' or '"') is rejected by PostgreSQL's xpath() +-- (libxml2 URI validation). SQL Server accepts it and returns the element. +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/a\b' AS ns1) +SELECT @x.query('/root/ns1:item'); +GO + +-- 19.6 Same limitation with a double-quote in the document namespace URI +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/"q"' AS ns1) +SELECT @x.query('/root/ns1:item'); +GO diff --git a/test/JDBC/input/xml/xml-methods-namespaces-vu-cleanup.sql b/test/JDBC/input/xml/xml-methods-namespaces-vu-cleanup.sql new file mode 100644 index 00000000000..889e6b9a23c --- /dev/null +++ b/test/JDBC/input/xml/xml-methods-namespaces-vu-cleanup.sql @@ -0,0 +1,18 @@ +-- ============================================ +-- SECTION: Cleanup +-- ============================================ + +DROP VIEW xmlns_methods_view1; +GO + +DROP TABLE xmlns_methods_t1; +GO + +DROP TABLE xmlns_methods_t2; +GO + +DROP TABLE xmlns_methods_orders; +GO + +DROP TABLE xmlns_methods_comments; +GO diff --git a/test/JDBC/input/xml/xml-methods-namespaces-vu-prepare.sql b/test/JDBC/input/xml/xml-methods-namespaces-vu-prepare.sql new file mode 100644 index 00000000000..3f16fb3ec8f --- /dev/null +++ b/test/JDBC/input/xml/xml-methods-namespaces-vu-prepare.sql @@ -0,0 +1,57 @@ +-- ============================================ +-- Tests for XML data type methods (.query, .value, .exist) +-- combined with WITH XMLNAMESPACES. +-- +-- WITH XMLNAMESPACES provides namespace bindings for XPath evaluation. +-- The bindings are passed to PG's xpath() function as a text[][] array. +-- This lets prefixed XPath like /ns1:item resolve correctly when the +-- target XML uses xmlns:ns1. +-- ============================================ + +-- ============================================ +-- SECTION: Base Tables +-- ============================================ + +CREATE TABLE xmlns_methods_t1 (id INT, data XML); +GO + +INSERT INTO xmlns_methods_t1 VALUES (1, 'val1val2'); +INSERT INTO xmlns_methods_t1 VALUES (2, '1020'); +INSERT INTO xmlns_methods_t1 VALUES (3, ''); +INSERT INTO xmlns_methods_t1 VALUES (4, NULL); +INSERT INTO xmlns_methods_t1 VALUES (5, '42'); +GO + +CREATE TABLE xmlns_methods_t2 (id INT, data XML); +GO + +INSERT INTO xmlns_methods_t2 VALUES (1, 'SQL29.99XML39.99'); +INSERT INTO xmlns_methods_t2 VALUES (2, ''); +GO + +-- ============================================ +-- SECTION: Dependent Views +-- ============================================ + +CREATE VIEW xmlns_methods_view1 AS +SELECT id, data FROM xmlns_methods_t1; +GO + +-- ============================================ +-- SECTION: Tables for advanced method tests +-- ============================================ + +CREATE TABLE xmlns_methods_orders (id INT, data XML); +GO + +INSERT INTO xmlns_methods_orders VALUES (1, 'WidgetGadget'); +INSERT INTO xmlns_methods_orders VALUES (2, 'Gizmo'); +INSERT INTO xmlns_methods_orders VALUES (3, ''); +GO + +CREATE TABLE xmlns_methods_comments (id INT, data XML); +GO + +INSERT INTO xmlns_methods_comments VALUES (1, 'val'); +INSERT INTO xmlns_methods_comments VALUES (2, 'val'); +GO diff --git a/test/JDBC/input/xml/xml-methods-namespaces-vu-verify.sql b/test/JDBC/input/xml/xml-methods-namespaces-vu-verify.sql new file mode 100644 index 00000000000..722400213f1 --- /dev/null +++ b/test/JDBC/input/xml/xml-methods-namespaces-vu-verify.sql @@ -0,0 +1,494 @@ +-- ============================================ +-- WITH XMLNAMESPACES + .query() / .value() / .exist() +-- ============================================ + +-- ============================================ +-- SECTION 1: .query() with prefixed XPath +-- ============================================ + +-- 1.1 Query a prefixed element using a declared prefix +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/ns1:item'); +GO + +-- 1.2 Query unprefixed name where XML element is prefixed -> empty +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/item'); +GO + +-- 1.3 Query with multiple prefixes used in different paths +DECLARE @x XML = '12'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1, 'http://example.com/ns2' AS ns2) +SELECT @x.query('/root/ns1:a'), @x.query('/root/ns2:b'); +GO + +-- 1.4 Query repeated prefixed elements +DECLARE @x XML = 'ab'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/ns1:item'); +GO + +-- 1.5 Query positional access with prefix +DECLARE @x XML = 'ab'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/ns1:item[1]'), @x.query('/root/ns1:item[2]'); +GO + +-- 1.6 Query with attribute predicate using prefix +DECLARE @x XML = 'SQLXML'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/ns1:book[@ns1:id="2"]/ns1:title'); +GO + +-- 1.7 Query NULL XML +DECLARE @x XML = NULL; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/ns1:item'); +GO + +-- 1.8 Query against table column +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, data.query('/root/ns1:item') +FROM xmlns_methods_t1 WHERE id IN (1, 2) +ORDER BY id; +GO + +-- 1.9 Query with no match returns empty +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/ns1:nonexistent'); +GO + +-- 1.10 Wildcard with prefix +DECLARE @x XML = '12'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/root/ns1:*'); +GO + +-- ============================================ +-- SECTION 2: .exist() with prefixed XPath +-- ============================================ + +-- 2.1 Exist returns 1 when prefixed match found +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.exist('/root/ns1:item'); +GO + +-- 2.2 Exist returns 0 when prefixed name doesn't match +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.exist('/root/ns1:other'); +GO + +-- 2.3 Exist with attribute predicate +DECLARE @x XML = ''; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.exist('/root/item/@ns1:attr'); +GO + +-- 2.4 Exist with attribute predicate (negative) +DECLARE @x XML = ''; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.exist('/root/item/@ns1:attr'); +GO + +-- 2.5 Exist on NULL +DECLARE @x XML = NULL; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.exist('/root/ns1:item'); +GO + +-- 2.6 Exist with multiple namespaces +DECLARE @x XML = '1'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1, 'http://example.com/ns2' AS ns2) +SELECT @x.exist('/root/ns1:a'), @x.exist('/root/ns2:b'); +GO + +-- ============================================ +-- SECTION 3: .value() with prefixed XPath +-- ============================================ + +-- 3.1 Value of prefixed element +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.value('(/root/ns1:item)[1]', 'varchar(50)'); +GO + +-- 3.2 Value cast to int +DECLARE @x XML = '42'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.value('(/root/ns1:item)[1]', 'int'); +GO + +-- 3.3 Value of attribute with prefix +DECLARE @x XML = ''; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.value('(/root/item/@ns1:attr)[1]', 'varchar(50)'); +GO + +-- 3.4 Value when no match - returns NULL +DECLARE @x XML = ''; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.value('(/root/ns1:item)[1]', 'varchar(50)'); +GO + +-- 3.5 Value of multiple matches selects positional +DECLARE @x XML = 'ab'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.value('(/root/ns1:item)[1]', 'varchar(10)'), + @x.value('(/root/ns1:item)[2]', 'varchar(10)'); +GO + +-- 3.6 Value on NULL XML +DECLARE @x XML = NULL; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.value('(/root/ns1:item)[1]', 'varchar(50)'); +GO + +-- 3.7 Value with multiple namespaces +DECLARE @x XML = '1020'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1, 'http://example.com/ns2' AS ns2) +SELECT @x.value('(/root/ns1:a)[1]', 'int'), + @x.value('(/root/ns2:b)[1]', 'int'); +GO + +-- ============================================ +-- SECTION 4: Methods on table columns with namespaces +-- ============================================ + +-- 4.1 .query on column +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, data.query('/root/ns1:item') +FROM xmlns_methods_t1 WHERE id = 1; +GO + +-- 4.2 .value on column +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, data.value('(/root/ns1:item)[1]', 'varchar(50)') +FROM xmlns_methods_t1 WHERE id IN (1, 5); +GO + +-- 4.3 .exist on column +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, data.exist('/root/ns1:item') +FROM xmlns_methods_t1 WHERE id <= 5 +ORDER BY id; +GO + +-- 4.4 .query on view +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, data.query('/root/ns1:item') +FROM xmlns_methods_view1 WHERE id = 1; +GO + +-- ============================================ +-- SECTION 5: Complex namespace cases +-- ============================================ + +-- 5.1 Same prefix used in multiple statements (no carry-over) +DECLARE @x XML = '1'; +WITH XMLNAMESPACES('http://uri1' AS p) +SELECT @x.value('(/root/p:a)[1]', 'int'); +GO + +-- 5.2 Prefix in XML differs from prefix in WITH XMLNAMESPACES (matching by URI) +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/ns1' AS bar) +SELECT @x.value('(/root/bar:item)[1]', 'varchar(50)'); +GO + +-- 5.3 Predicate using prefixed attribute +WITH XMLNAMESPACES('http://example.com/products' AS p) +SELECT data.query('/catalog/p:book[@p:id="2"]/p:title') +FROM xmlns_methods_t2 WHERE id = 1; +GO + +-- 5.4 Numeric predicate on prefixed element +WITH XMLNAMESPACES('http://example.com/products' AS p) +SELECT data.query('/catalog/p:book[p:price>30]/p:title') +FROM xmlns_methods_t2 WHERE id = 1; +GO + +-- 5.5 Wildcard with prefix on table column +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT data.query('/root/ns1:*') +FROM xmlns_methods_t1 WHERE id = 1; +GO + +-- 5.6 .query then .value chained on result +DECLARE @x XML = 'hello'; +DECLARE @r XML; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @r = @x.query('/root/ns1:item'); +SELECT @r; +GO + +-- ============================================ +-- SECTION 6: NULL XML behaviors +-- ============================================ + +-- 6.1 Query NULL row (with namespaces) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT data.query('/root/ns1:item') FROM xmlns_methods_t1 WHERE id = 4; +GO + +-- 6.2 Value on NULL row (with namespaces) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT data.value('(/root/ns1:item)[1]', 'varchar(50)') FROM xmlns_methods_t1 WHERE id = 4; +GO + +-- 6.3 Exist on NULL row (with namespaces) +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT data.exist('/root/ns1:item') FROM xmlns_methods_t1 WHERE id = 4; +GO + +-- ============================================ +-- SECTION 7: Validation with same rules as FOR XML +-- ============================================ + +-- 7.1 Duplicate prefix +DECLARE @x XML = ''; +WITH XMLNAMESPACES('http://a' AS p, 'http://b' AS p) +SELECT @x.query('/root'); +GO + +-- 7.2 Empty URI +DECLARE @x XML = ''; +WITH XMLNAMESPACES('' AS p) +SELECT @x.query('/root'); +GO + +-- 7.3 Reserved xmlns prefix +DECLARE @x XML = ''; +WITH XMLNAMESPACES('http://a' AS xmlns) +SELECT @x.query('/root'); +GO + +-- 7.4 xml prefix with wrong URI +DECLARE @x XML = ''; +WITH XMLNAMESPACES('http://wrong' AS xml) +SELECT @x.query('/root'); +GO + +-- ============================================ +-- SECTION 8: Variable assignment with namespaces +-- ============================================ + +-- 8.1 .query() result assigned to XML variable +DECLARE @x XML = 'val'; +DECLARE @r XML; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @r = @x.query('/root/ns1:item'); +SELECT @r; +GO + +-- 8.2 .value() result into varchar +DECLARE @x XML = 'hello'; +DECLARE @s VARCHAR(50); +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @s = @x.value('(/root/ns1:item)[1]', 'varchar(50)'); +SELECT @s; +GO + +-- 8.3 .exist() into bit +DECLARE @x XML = ''; +DECLARE @b BIT; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @b = @x.exist('/root/ns1:item'); +SELECT @b; +GO + +-- ============================================ +-- SECTION 9: Methods in WHERE clause +-- ============================================ + +-- 9.1 .exist() in WHERE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id FROM xmlns_methods_t1 WHERE data.exist('/root/ns1:item') = 1 +ORDER BY id; +GO + +-- 9.2 .value() in WHERE +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id FROM xmlns_methods_t1 WHERE data.value('(/root/ns1:item)[1]', 'varchar(50)') = 'val1' +ORDER BY id; +GO + +-- ============================================ +-- SECTION 10: Methods in CASE expression +-- ============================================ + +-- 10.1 CASE with .exist() +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, + CASE WHEN data.exist('/root/ns1:item') = 1 THEN 'has-item' ELSE 'no-item' END AS Tag +FROM xmlns_methods_t1 WHERE id <= 3 +ORDER BY id; +GO + +-- ============================================ +-- SECTION 11: Methods with JOIN +-- ============================================ + +-- 11.1 JOIN on .value() result +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT t1.id, t1.data.value('(/root/ns1:item)[1]', 'varchar(50)') AS V +FROM xmlns_methods_t1 t1 JOIN xmlns_methods_view1 v ON t1.id = v.id +WHERE t1.id IN (1, 5) +ORDER BY t1.id; +GO + +-- ============================================ +-- SECTION 12: Methods with ORDER BY / TOP +-- ============================================ + +-- 12.1 ORDER BY .value() +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, data.value('(/root/ns1:item)[1]', 'varchar(50)') AS V +FROM xmlns_methods_t1 WHERE id IN (1, 5) +ORDER BY data.value('(/root/ns1:item)[1]', 'varchar(50)'); +GO + +-- 12.2 TOP with .query() +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT TOP 1 id, data.query('/root/ns1:item') +FROM xmlns_methods_t1 WHERE id <= 5 +ORDER BY id; +GO + +-- ============================================ +-- SECTION 13: Methods on views +-- ============================================ + +-- 13.1 .query() on view +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id, data.query('/root/ns1:item') +FROM xmlns_methods_view1 WHERE id <= 2 ORDER BY id; +GO + +-- 13.2 .exist() in WHERE on view +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT id FROM xmlns_methods_view1 +WHERE data.exist('/root/ns1:item') = 1 ORDER BY id; +GO + +-- ============================================ +-- SECTION 14: XML with comments +-- ============================================ + +-- 14.1 .query() ignores comments outside target +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT data.query('/root/ns1:item') +FROM xmlns_methods_comments WHERE id = 1; +GO + +-- 14.2 .value() with inline comment in value +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT data.value('(/root/ns1:item)[1]', 'varchar(50)') +FROM xmlns_methods_comments WHERE id = 2; +GO + +-- ============================================ +-- SECTION 15: Spaces and case in method calls +-- ============================================ + +-- 15.1 Spaces around .query() +DECLARE @x XML = 'v'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x . query('/root/ns1:item'); +GO + +-- 15.2 Spaces around .value() +DECLARE @x XML = 'v'; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x . value('(/root/ns1:item)[1]', 'varchar(50)'); +GO + +-- ============================================ +-- SECTION 16: Predicates with prefixed attributes (richer) +-- ============================================ + +-- 16.1 Attribute predicate with numeric comparison +WITH XMLNAMESPACES('http://example.com/products' AS p) +SELECT data.query('/order/p:item[@p:price>20]') FROM xmlns_methods_orders WHERE id = 1; +GO + +-- 16.2 Multiple prefixed attribute predicates +WITH XMLNAMESPACES('http://example.com/products' AS p) +SELECT data.query('/order/p:item[@p:price>20 and @p:price<40]') FROM xmlns_methods_orders WHERE id = 1; +GO + +-- 16.3 Empty XML root with .query() prefix path +WITH XMLNAMESPACES('http://example.com/products' AS p) +SELECT data.query('/order/p:item') FROM xmlns_methods_orders WHERE id = 3; +GO + +-- ============================================ +-- SECTION 17: Multiple chained method calls +-- ============================================ + +-- 17.1 .query() result then .value() +DECLARE @x XML = 'val1val2'; +DECLARE @r XML; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @r = @x.query('/root/ns1:item[1]'); +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @r.value('(/ns1:item)[1]', 'varchar(50)'); +GO + +-- ============================================ +-- SECTION 18: Result from FOR XML chained +-- ============================================ + +-- 18.1 FOR XML result fed to .query() under namespace +DECLARE @x XML; +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x = (SELECT 1 AS [ns1:a], 'v' AS [ns1:b] FOR XML PATH('ns1:Row'), TYPE); +WITH XMLNAMESPACES('http://example.com/ns1' AS ns1) +SELECT @x.query('/ns1:Row/ns1:a'); +GO + +-- ============================================ +-- SECTION 19: Special characters in WITH XMLNAMESPACES URIs +-- ============================================ + +-- 19.1 Double-quote in URI (array-literal escaping) -> empty +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/"q"' AS p) +SELECT @x.query('/root/p:item'); +GO + +-- 19.2 Backslash in URI (array-literal escaping) -> empty +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/a\b' AS p) +SELECT @x.query('/root/p:item'); +GO + +-- 19.3 Single-quote in URI (escaped as '' in T-SQL), resolves end-to-end -> val +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/it''s' AS ns1) +SELECT @x.value('(/root/ns1:item)[1]', 'varchar(50)'); +GO + +-- 19.4 Combined double-quote and backslash in URI (array-literal escaping) -> 0 +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/"a"\b' AS p) +SELECT @x.exist('/root/p:item'); +GO + +-- 19.5 Limitation: a document whose own namespace URI contains a character that +-- is illegal in an RFC 3986 URI ('\' or '"') is rejected by PostgreSQL's xpath() +-- (libxml2 URI validation). SQL Server accepts it and returns the element. +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/a\b' AS ns1) +SELECT @x.query('/root/ns1:item'); +GO + +-- 19.6 Same limitation with a double-quote in the document namespace URI +DECLARE @x XML = 'val'; +WITH XMLNAMESPACES('http://example.com/"q"' AS ns1) +SELECT @x.query('/root/ns1:item'); +GO diff --git a/test/JDBC/jdbc_schedule b/test/JDBC/jdbc_schedule index 985f0d2b586..d6a3815d55f 100644 --- a/test/JDBC/jdbc_schedule +++ b/test/JDBC/jdbc_schedule @@ -773,3 +773,9 @@ ignore#!#babel_sqlvariant_coerce_type_before_16_14-or-17_10-or-18_4-vu-cleanup ignore#!#xml_query-before-17_11-or-18_5-vu-prepare ignore#!#xml_query-before-17_11-or-18_5-vu-verify ignore#!#xml_query-before-17_11-or-18_5-vu-cleanup +ignore#!#forxml-namespaces-before-17_11-or-18_5-vu-prepare +ignore#!#forxml-namespaces-before-17_11-or-18_5-vu-verify +ignore#!#forxml-namespaces-before-17_11-or-18_5-vu-cleanup +ignore#!#xml-methods-namespaces-before-17_11-or-18_5-vu-prepare +ignore#!#xml-methods-namespaces-before-17_11-or-18_5-vu-verify +ignore#!#xml-methods-namespaces-before-17_11-or-18_5-vu-cleanup diff --git a/test/JDBC/upgrade/13_4/schedule b/test/JDBC/upgrade/13_4/schedule index dccc7f17bc5..e3ae3e3fea0 100644 --- a/test/JDBC/upgrade/13_4/schedule +++ b/test/JDBC/upgrade/13_4/schedule @@ -282,3 +282,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/13_5/schedule b/test/JDBC/upgrade/13_5/schedule index ef61628aee8..3a3ebd63ec6 100644 --- a/test/JDBC/upgrade/13_5/schedule +++ b/test/JDBC/upgrade/13_5/schedule @@ -335,3 +335,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/13_6/schedule b/test/JDBC/upgrade/13_6/schedule index 7dc9a3682ae..b7d9f8241b3 100644 --- a/test/JDBC/upgrade/13_6/schedule +++ b/test/JDBC/upgrade/13_6/schedule @@ -393,3 +393,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/13_7/schedule b/test/JDBC/upgrade/13_7/schedule index 5005e59297e..3f043771094 100644 --- a/test/JDBC/upgrade/13_7/schedule +++ b/test/JDBC/upgrade/13_7/schedule @@ -386,3 +386,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/13_8/schedule b/test/JDBC/upgrade/13_8/schedule index 5005e59297e..3f043771094 100644 --- a/test/JDBC/upgrade/13_8/schedule +++ b/test/JDBC/upgrade/13_8/schedule @@ -386,3 +386,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/13_9/schedule b/test/JDBC/upgrade/13_9/schedule index 64160ea035c..21279b9f358 100644 --- a/test/JDBC/upgrade/13_9/schedule +++ b/test/JDBC/upgrade/13_9/schedule @@ -391,3 +391,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/14_10/schedule b/test/JDBC/upgrade/14_10/schedule index 9d0f0c1fbca..365c746cadc 100644 --- a/test/JDBC/upgrade/14_10/schedule +++ b/test/JDBC/upgrade/14_10/schedule @@ -510,3 +510,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/14_11/schedule b/test/JDBC/upgrade/14_11/schedule index d2159ccb6ab..06111a3e74a 100644 --- a/test/JDBC/upgrade/14_11/schedule +++ b/test/JDBC/upgrade/14_11/schedule @@ -509,3 +509,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/14_12/schedule b/test/JDBC/upgrade/14_12/schedule index 2e8cad14f1a..d56959527ee 100644 --- a/test/JDBC/upgrade/14_12/schedule +++ b/test/JDBC/upgrade/14_12/schedule @@ -510,3 +510,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/14_13/schedule b/test/JDBC/upgrade/14_13/schedule index 98024b50b58..c6fb580502a 100644 --- a/test/JDBC/upgrade/14_13/schedule +++ b/test/JDBC/upgrade/14_13/schedule @@ -510,3 +510,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/14_15/schedule b/test/JDBC/upgrade/14_15/schedule index b43ed74458f..2db6611d93f 100644 --- a/test/JDBC/upgrade/14_15/schedule +++ b/test/JDBC/upgrade/14_15/schedule @@ -507,3 +507,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/14_17/schedule b/test/JDBC/upgrade/14_17/schedule index a42d06c3f9e..13a6899dc57 100644 --- a/test/JDBC/upgrade/14_17/schedule +++ b/test/JDBC/upgrade/14_17/schedule @@ -504,3 +504,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/14_18/schedule b/test/JDBC/upgrade/14_18/schedule index 13135abd3dc..2be58dfbf31 100644 --- a/test/JDBC/upgrade/14_18/schedule +++ b/test/JDBC/upgrade/14_18/schedule @@ -507,3 +507,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/14_19/schedule b/test/JDBC/upgrade/14_19/schedule index 4af159762b4..8586d7440dd 100644 --- a/test/JDBC/upgrade/14_19/schedule +++ b/test/JDBC/upgrade/14_19/schedule @@ -505,3 +505,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/14_20/schedule b/test/JDBC/upgrade/14_20/schedule index 4af159762b4..8586d7440dd 100644 --- a/test/JDBC/upgrade/14_20/schedule +++ b/test/JDBC/upgrade/14_20/schedule @@ -505,3 +505,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/14_22/schedule b/test/JDBC/upgrade/14_22/schedule index 1b1a17875eb..9df2c4b075b 100644 --- a/test/JDBC/upgrade/14_22/schedule +++ b/test/JDBC/upgrade/14_22/schedule @@ -504,4 +504,6 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 babel_sqlvariant_coerce_type_before_16_14-or-17_10-or-18_4 diff --git a/test/JDBC/upgrade/14_23/schedule b/test/JDBC/upgrade/14_23/schedule index 1b1a17875eb..9df2c4b075b 100644 --- a/test/JDBC/upgrade/14_23/schedule +++ b/test/JDBC/upgrade/14_23/schedule @@ -504,4 +504,6 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 babel_sqlvariant_coerce_type_before_16_14-or-17_10-or-18_4 diff --git a/test/JDBC/upgrade/14_24/schedule b/test/JDBC/upgrade/14_24/schedule index 95eb8683c5f..8db53624cd2 100644 --- a/test/JDBC/upgrade/14_24/schedule +++ b/test/JDBC/upgrade/14_24/schedule @@ -148,6 +148,8 @@ forjson-subquery forjson-datatypes forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 forxml-path forxml-subquery BABEL-PROCID diff --git a/test/JDBC/upgrade/14_3/schedule b/test/JDBC/upgrade/14_3/schedule index ea39c2a215a..c714f274269 100644 --- a/test/JDBC/upgrade/14_3/schedule +++ b/test/JDBC/upgrade/14_3/schedule @@ -423,3 +423,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/14_5/schedule b/test/JDBC/upgrade/14_5/schedule index 5ab72311496..71fb8046376 100644 --- a/test/JDBC/upgrade/14_5/schedule +++ b/test/JDBC/upgrade/14_5/schedule @@ -435,3 +435,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/14_6/schedule b/test/JDBC/upgrade/14_6/schedule index 099adc688cf..d79a697b943 100644 --- a/test/JDBC/upgrade/14_6/schedule +++ b/test/JDBC/upgrade/14_6/schedule @@ -477,3 +477,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/14_7/schedule b/test/JDBC/upgrade/14_7/schedule index 4ebf5adc58c..ec2ade11feb 100644 --- a/test/JDBC/upgrade/14_7/schedule +++ b/test/JDBC/upgrade/14_7/schedule @@ -500,3 +500,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/14_8/schedule b/test/JDBC/upgrade/14_8/schedule index a2f8cd5c907..178b304a7dc 100644 --- a/test/JDBC/upgrade/14_8/schedule +++ b/test/JDBC/upgrade/14_8/schedule @@ -502,3 +502,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/14_9/schedule b/test/JDBC/upgrade/14_9/schedule index 57eb7eeb563..e76ffc16316 100644 --- a/test/JDBC/upgrade/14_9/schedule +++ b/test/JDBC/upgrade/14_9/schedule @@ -505,3 +505,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/15_1/schedule b/test/JDBC/upgrade/15_1/schedule index af514d88e88..8ad7dcd3b79 100644 --- a/test/JDBC/upgrade/15_1/schedule +++ b/test/JDBC/upgrade/15_1/schedule @@ -484,3 +484,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/15_10/schedule b/test/JDBC/upgrade/15_10/schedule index 6614a4ffc51..7c5469111ec 100644 --- a/test/JDBC/upgrade/15_10/schedule +++ b/test/JDBC/upgrade/15_10/schedule @@ -608,3 +608,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/15_12/schedule b/test/JDBC/upgrade/15_12/schedule index 1d92d39357d..12c8ffb6e6b 100644 --- a/test/JDBC/upgrade/15_12/schedule +++ b/test/JDBC/upgrade/15_12/schedule @@ -605,3 +605,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/15_13/schedule b/test/JDBC/upgrade/15_13/schedule index 90c52de049c..07f481aa025 100644 --- a/test/JDBC/upgrade/15_13/schedule +++ b/test/JDBC/upgrade/15_13/schedule @@ -613,3 +613,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/15_14/schedule b/test/JDBC/upgrade/15_14/schedule index 7bcce188130..8af5e0801e6 100644 --- a/test/JDBC/upgrade/15_14/schedule +++ b/test/JDBC/upgrade/15_14/schedule @@ -615,3 +615,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/15_15/schedule b/test/JDBC/upgrade/15_15/schedule index 1d3b569a51b..557118f98f8 100644 --- a/test/JDBC/upgrade/15_15/schedule +++ b/test/JDBC/upgrade/15_15/schedule @@ -611,3 +611,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/15_17/schedule b/test/JDBC/upgrade/15_17/schedule index e1fa21eb54a..aeac799144c 100644 --- a/test/JDBC/upgrade/15_17/schedule +++ b/test/JDBC/upgrade/15_17/schedule @@ -610,4 +610,6 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 babel_sqlvariant_coerce_type_before_16_14-or-17_10-or-18_4 diff --git a/test/JDBC/upgrade/15_18/schedule b/test/JDBC/upgrade/15_18/schedule index e1fa21eb54a..aeac799144c 100644 --- a/test/JDBC/upgrade/15_18/schedule +++ b/test/JDBC/upgrade/15_18/schedule @@ -610,4 +610,6 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 babel_sqlvariant_coerce_type_before_16_14-or-17_10-or-18_4 diff --git a/test/JDBC/upgrade/15_19/schedule b/test/JDBC/upgrade/15_19/schedule index 1fd7e52c242..74daa1d0b7b 100644 --- a/test/JDBC/upgrade/15_19/schedule +++ b/test/JDBC/upgrade/15_19/schedule @@ -222,6 +222,8 @@ format format-dep forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 forxml-path forxml-subquery fulltextserviceproperty diff --git a/test/JDBC/upgrade/15_2/schedule b/test/JDBC/upgrade/15_2/schedule index d56732e1251..77400d461a8 100644 --- a/test/JDBC/upgrade/15_2/schedule +++ b/test/JDBC/upgrade/15_2/schedule @@ -520,3 +520,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/15_3/schedule b/test/JDBC/upgrade/15_3/schedule index 1379b2f6fe1..8a38f064b0c 100644 --- a/test/JDBC/upgrade/15_3/schedule +++ b/test/JDBC/upgrade/15_3/schedule @@ -540,3 +540,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/15_4/schedule b/test/JDBC/upgrade/15_4/schedule index b58b8249c41..d36053974cf 100644 --- a/test/JDBC/upgrade/15_4/schedule +++ b/test/JDBC/upgrade/15_4/schedule @@ -553,3 +553,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/15_5/schedule b/test/JDBC/upgrade/15_5/schedule index 82039d5a781..a0d3a522c59 100644 --- a/test/JDBC/upgrade/15_5/schedule +++ b/test/JDBC/upgrade/15_5/schedule @@ -589,3 +589,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/15_6/schedule b/test/JDBC/upgrade/15_6/schedule index 2cfa4835c8e..c7fa499feb4 100644 --- a/test/JDBC/upgrade/15_6/schedule +++ b/test/JDBC/upgrade/15_6/schedule @@ -605,3 +605,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/15_7/schedule b/test/JDBC/upgrade/15_7/schedule index 74a23fe4c3f..60d39542c88 100644 --- a/test/JDBC/upgrade/15_7/schedule +++ b/test/JDBC/upgrade/15_7/schedule @@ -614,3 +614,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/15_8/schedule b/test/JDBC/upgrade/15_8/schedule index c6ef0379fbe..21ddce6a0b4 100644 --- a/test/JDBC/upgrade/15_8/schedule +++ b/test/JDBC/upgrade/15_8/schedule @@ -606,3 +606,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/16_1/schedule b/test/JDBC/upgrade/16_1/schedule index 46003a292dd..de091bdca60 100644 --- a/test/JDBC/upgrade/16_1/schedule +++ b/test/JDBC/upgrade/16_1/schedule @@ -600,3 +600,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/16_10/schedule b/test/JDBC/upgrade/16_10/schedule index 0a662130a6a..7d7995dd1f8 100644 --- a/test/JDBC/upgrade/16_10/schedule +++ b/test/JDBC/upgrade/16_10/schedule @@ -653,3 +653,5 @@ sys-fn-varbintohexsubstring forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/16_11/schedule b/test/JDBC/upgrade/16_11/schedule index 3ee27211e89..36b4e3121f6 100644 --- a/test/JDBC/upgrade/16_11/schedule +++ b/test/JDBC/upgrade/16_11/schedule @@ -650,3 +650,5 @@ sys-fn-varbintohexsubstring forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/16_13/schedule b/test/JDBC/upgrade/16_13/schedule index 4fbe053df69..1ecbb5465be 100644 --- a/test/JDBC/upgrade/16_13/schedule +++ b/test/JDBC/upgrade/16_13/schedule @@ -653,4 +653,6 @@ sys-fn-varbintohexsubstring forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 babel_sqlvariant_coerce_type_before_16_14-or-17_10-or-18_4 diff --git a/test/JDBC/upgrade/16_14/schedule b/test/JDBC/upgrade/16_14/schedule index add6e764a36..a5352f38c70 100644 --- a/test/JDBC/upgrade/16_14/schedule +++ b/test/JDBC/upgrade/16_14/schedule @@ -653,4 +653,6 @@ sys-fn-varbintohexsubstring forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 babel_sqlvariant_coerce_type diff --git a/test/JDBC/upgrade/16_15/schedule b/test/JDBC/upgrade/16_15/schedule index 9abc313dbc6..f36053c712e 100644 --- a/test/JDBC/upgrade/16_15/schedule +++ b/test/JDBC/upgrade/16_15/schedule @@ -224,6 +224,8 @@ format format-dep forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 forxml-path forxml-subquery fulltextserviceproperty diff --git a/test/JDBC/upgrade/16_2/schedule b/test/JDBC/upgrade/16_2/schedule index e0431135f93..880468c3252 100644 --- a/test/JDBC/upgrade/16_2/schedule +++ b/test/JDBC/upgrade/16_2/schedule @@ -616,3 +616,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/16_3/schedule b/test/JDBC/upgrade/16_3/schedule index 315e8279fcc..2ba0e7c699a 100644 --- a/test/JDBC/upgrade/16_3/schedule +++ b/test/JDBC/upgrade/16_3/schedule @@ -622,3 +622,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/16_4/schedule b/test/JDBC/upgrade/16_4/schedule index 07828c61dd5..a451df5ae5e 100644 --- a/test/JDBC/upgrade/16_4/schedule +++ b/test/JDBC/upgrade/16_4/schedule @@ -637,3 +637,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/16_6/schedule b/test/JDBC/upgrade/16_6/schedule index 30a44ae203d..853395b05fd 100644 --- a/test/JDBC/upgrade/16_6/schedule +++ b/test/JDBC/upgrade/16_6/schedule @@ -643,3 +643,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/16_8/schedule b/test/JDBC/upgrade/16_8/schedule index d8fbffc30e3..1543fc18092 100644 --- a/test/JDBC/upgrade/16_8/schedule +++ b/test/JDBC/upgrade/16_8/schedule @@ -650,3 +650,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/16_9/schedule b/test/JDBC/upgrade/16_9/schedule index 7ee30ea4d85..1203837cd6d 100644 --- a/test/JDBC/upgrade/16_9/schedule +++ b/test/JDBC/upgrade/16_9/schedule @@ -653,3 +653,5 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/17_10/schedule b/test/JDBC/upgrade/17_10/schedule index c9b52a84176..53a979767aa 100644 --- a/test/JDBC/upgrade/17_10/schedule +++ b/test/JDBC/upgrade/17_10/schedule @@ -684,5 +684,7 @@ forxml-raw-elements forxml-raw-elements-1gb-limit forxml-path-elements forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 babel_sqlvariant_coerce_type xml_query-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/17_4/schedule b/test/JDBC/upgrade/17_4/schedule index ba33168dac1..9b37b693d62 100644 --- a/test/JDBC/upgrade/17_4/schedule +++ b/test/JDBC/upgrade/17_4/schedule @@ -647,4 +647,6 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 xml_query-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/17_5/schedule b/test/JDBC/upgrade/17_5/schedule index e250f334856..dcafe431e1e 100644 --- a/test/JDBC/upgrade/17_5/schedule +++ b/test/JDBC/upgrade/17_5/schedule @@ -661,4 +661,6 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 xml_query-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/17_6/schedule b/test/JDBC/upgrade/17_6/schedule index 98ba20cc8ca..0135cd647f2 100644 --- a/test/JDBC/upgrade/17_6/schedule +++ b/test/JDBC/upgrade/17_6/schedule @@ -672,4 +672,6 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 xml_query-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/17_7/schedule b/test/JDBC/upgrade/17_7/schedule index 6d3bc42fcf6..95d6369e2f9 100644 --- a/test/JDBC/upgrade/17_7/schedule +++ b/test/JDBC/upgrade/17_7/schedule @@ -673,4 +673,6 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 xml_query-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/17_9/schedule b/test/JDBC/upgrade/17_9/schedule index d4abd0f275f..45e6fe9e2c1 100644 --- a/test/JDBC/upgrade/17_9/schedule +++ b/test/JDBC/upgrade/17_9/schedule @@ -680,5 +680,7 @@ forxml-raw-elements-before-17_9-or-18_3 forxml-path-elements-before-17_10-or-18_4 forxml-before-17_10-or-18_4 forxml-auto-before-17_11-or-18_5 +forxml-namespaces-before-17_11-or-18_5 +xml-methods-namespaces-before-17_11-or-18_5 babel_sqlvariant_coerce_type_before_16_14-or-17_10-or-18_4 xml_query-before-17_11-or-18_5 diff --git a/test/JDBC/upgrade/latest/schedule b/test/JDBC/upgrade/latest/schedule index 90c356fc536..1dde4208c8d 100644 --- a/test/JDBC/upgrade/latest/schedule +++ b/test/JDBC/upgrade/latest/schedule @@ -690,3 +690,5 @@ babel_sqlvariant_coerce_type BABEL-5264 BABEL-6037 xml_query +forxml-namespaces +xml-methods-namespaces diff --git a/test/python/expected/upgrade_validation/expected_dependency.out b/test/python/expected/upgrade_validation/expected_dependency.out index c8ce8d43e01..6f6425f1cb3 100644 --- a/test/python/expected/upgrade_validation/expected_dependency.out +++ b/test/python/expected/upgrade_validation/expected_dependency.out @@ -226,6 +226,9 @@ Function sys.bbf_varbinary(sys.geography,integer,boolean) Function sys.bbf_varbinary(sys.geometry,integer,boolean) Function sys.bbf_varbinary_binary_cmp(sys.bbf_varbinary,sys.bbf_binary) Function sys.bbf_varbinary_cmp(sys.bbf_varbinary,sys.bbf_varbinary) +Function sys.bbf_xmlexist(text,anyelement,text[]) +Function sys.bbf_xmlquery(text,anyelement,text[]) +Function sys.bbf_xmlvalue(text,text,anyelement,text[]) Function sys.bbfbinary_sqlvariant(sys.bbf_binary) Function sys.bbfvarbinary_sqlvariant(sys.bbf_varbinary) Function sys.bigint_avg(internal) @@ -760,7 +763,7 @@ Function sys.tsql_get_returntypmodvalue(oid) Function sys.tsql_query_to_json_ffunc(internal) Function sys.tsql_query_to_json_sfunc(internal,anyelement,integer,boolean,boolean,text) Function sys.tsql_query_to_xml_ffunc(internal) -Function sys.tsql_query_to_xml_sfunc(internal,anyelement,integer,text,boolean,text,boolean,boolean,text) +Function sys.tsql_query_to_xml_sfunc(internal,anyelement,integer,text,boolean,text,boolean,boolean,text,text) Function sys.tsql_query_to_xml_text_ffunc(internal) Function sys.tsql_stat_get_activity(text) Function sys.tsql_type_length_for_sp_columns_helper(text,integer,integer)