Skip to content
Open
63 changes: 44 additions & 19 deletions contrib/babelfishpg_tsql/runtime/functions.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
}
94 changes: 91 additions & 3 deletions contrib/babelfishpg_tsql/sql/sys_functions.sql
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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, '&lt;', '<');
result := pg_catalog.replace(result, '&gt;', '>');
result := pg_catalog.replace(result, '&apos;', '''');
result := pg_catalog.replace(result, '&quot;', '"');
result := pg_catalog.replace(result, '&amp;', '&');
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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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, '&lt;', '<');
result := pg_catalog.replace(result, '&gt;', '>');
result := pg_catalog.replace(result, '&apos;', '''');
result := pg_catalog.replace(result, '&quot;', '"');
result := pg_catalog.replace(result, '&amp;', '&');
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);
Expand Down
19 changes: 19 additions & 0 deletions contrib/babelfishpg_tsql/src/backend_parser/gram-tsql-epilogue.y.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

/*
Expand Down
Loading
Loading