Skip to content

Commit e9ec72c

Browse files
author
Japleen Kaur
committed
Add WITH XMLNAMESPACES support
1 parent abbe07b commit e9ec72c

20 files changed

Lines changed: 4525 additions & 51 deletions

contrib/babelfishpg_tsql/runtime/functions.c

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6096,28 +6096,23 @@ openxml_simple(PG_FUNCTION_ARGS)
60966096

60976097

60986098
PG_FUNCTION_INFO_V1(bbf_xmlquery);
6099+
PG_FUNCTION_INFO_V1(bbf_xmlquery_ns);
60996100

61006101
/*
6101-
* bbf_xmlquery - C implementation of XML .query() method
6102+
* bbf_xmlquery_internal - shared implementation for the .query() XML method.
61026103
*
6103-
* Signature:
6104-
* sys.bbf_xmlquery(xpath_pattern TEXT, xml_element ANYELEMENT)
6105-
*
6106-
* Returns XML result of evaluating the XPath expression against the input.
6107-
* Returns empty XML if no nodes match.
6108-
*
6109-
* Validates:
6110-
* - Input must be XML type (or UDT based on XML)
6111-
* - QUOTED_IDENTIFIER must be ON
6104+
* Validates the input type and QUOTED_IDENTIFIER setting, then evaluates the
6105+
* XPath expression against the XML datum using the supplied namespace array
6106+
* (which may be empty). Returns the concatenated XML fragments, or the
6107+
* empty XML string if no nodes match.
61126108
*/
6113-
Datum
6114-
bbf_xmlquery(PG_FUNCTION_ARGS)
6109+
static Datum
6110+
bbf_xmlquery_internal(FunctionCallInfo fcinfo, ArrayType *namespaces)
61156111
{
61166112
text *xpath_expr;
61176113
Datum xml_datum;
61186114
Oid arg_type;
61196115
Oid immediate_base_type;
6120-
ArrayType *namespaces;
61216116
Datum xpath_result;
61226117
ArrayType *result_arr;
61236118
Datum *elems;
@@ -6161,13 +6156,11 @@ bbf_xmlquery(PG_FUNCTION_ARGS)
61616156
"SET options are correct for XML data type methods.")));
61626157

61636158
/*
6164-
* Call the built-in xpath(text, xml, text[][]) directly with an empty
6165-
* namespace array. Returns xml[] (array of XML fragments).
6166-
*
6167-
* TODO: when WITH XMLNAMESPACES is supported, populate this array with
6168-
* the declared (prefix, uri) pairs from the active namespace context.
6159+
* Call the built-in xpath(text, xml, text[][]). Returns xml[] (array of
6160+
* XML fragments). When invoked without WITH XMLNAMESPACES the caller
6161+
* passes an empty array so libxml2 only resolves prefixes already
6162+
* declared in the document.
61696163
*/
6170-
namespaces = construct_empty_array(TEXTOID);
61716164
xpath_result = DirectFunctionCall3(xpath,
61726165
PointerGetDatum(xpath_expr),
61736166
xml_datum,
@@ -6206,3 +6199,35 @@ bbf_xmlquery(PG_FUNCTION_ARGS)
62066199

62076200
PG_RETURN_XML_P((xmltype *) cstring_to_text_with_len(buf.data, buf.len));
62086201
}
6202+
6203+
/*
6204+
* bbf_xmlquery - C implementation of XML .query() method (no namespaces).
6205+
*
6206+
* Signature:
6207+
* sys.bbf_xmlquery(xpath_pattern TEXT, xml_element ANYELEMENT)
6208+
*/
6209+
Datum
6210+
bbf_xmlquery(PG_FUNCTION_ARGS)
6211+
{
6212+
ArrayType *namespaces = construct_empty_array(TEXTOID);
6213+
6214+
return bbf_xmlquery_internal(fcinfo, namespaces);
6215+
}
6216+
6217+
/*
6218+
* bbf_xmlquery_ns - C implementation of XML .query() with namespace support.
6219+
*
6220+
* Signature:
6221+
* sys.bbf_xmlquery(xpath_pattern TEXT, xml_element ANYELEMENT, nsarray TEXT[][])
6222+
*
6223+
* Used by the WITH XMLNAMESPACES rewrite path. The ANTLR rewrite layer
6224+
* appends the namespace array as a 3rd argument when a WITH XMLNAMESPACES
6225+
* clause is in scope.
6226+
*/
6227+
Datum
6228+
bbf_xmlquery_ns(PG_FUNCTION_ARGS)
6229+
{
6230+
ArrayType *namespaces = PG_GETARG_ARRAYTYPE_P(2);
6231+
6232+
return bbf_xmlquery_internal(fcinfo, namespaces);
6233+
}

contrib/babelfishpg_tsql/sql/sys_functions.sql

Lines changed: 91 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ CREATE OR REPLACE FUNCTION sys.tsql_query_to_xml_sfunc(
88
root_name text,
99
elements boolean,
1010
xsinil boolean,
11-
auto_metadata text
11+
auto_metadata text,
12+
ns_decls text
1213
) RETURNS INTERNAL
1314
AS 'babelfishpg_tsql', 'tsql_query_to_xml_sfunc'
1415
LANGUAGE C STABLE;
@@ -35,7 +36,8 @@ CREATE OR REPLACE AGGREGATE sys.tsql_select_for_xml_agg(
3536
root_name text,
3637
elements boolean,
3738
xsinil boolean,
38-
auto_metadata text)
39+
auto_metadata text,
40+
ns_decls text)
3941
(
4042
STYPE = INTERNAL,
4143
SFUNC = tsql_query_to_xml_sfunc,
@@ -50,7 +52,8 @@ CREATE OR REPLACE AGGREGATE sys.tsql_select_for_xml_text_agg(
5052
root_name text,
5153
elements boolean,
5254
xsinil boolean,
53-
auto_metadata text)
55+
auto_metadata text,
56+
ns_decls text)
5457
(
5558
STYPE = INTERNAL,
5659
SFUNC = tsql_query_to_xml_sfunc,
@@ -170,6 +173,91 @@ RETURNS XML
170173
AS 'babelfishpg_tsql', 'bbf_xmlquery'
171174
LANGUAGE C STABLE STRICT PARALLEL SAFE;
172175

176+
-- helper function for XML QUERY(xpath) with namespace support (used by WITH XMLNAMESPACES)
177+
CREATE OR REPLACE FUNCTION sys.bbf_xmlquery(xpath_pattern TEXT, xml_element ANYELEMENT, nsarray TEXT[][])
178+
RETURNS XML
179+
AS 'babelfishpg_tsql', 'bbf_xmlquery_ns'
180+
LANGUAGE C STABLE STRICT PARALLEL SAFE;
181+
182+
-- helper function for XML EXIST(xpath) with namespace support (used by WITH XMLNAMESPACES)
183+
CREATE OR REPLACE FUNCTION sys.bbf_xmlexist(xpath_pattern TEXT, xml_element ANYELEMENT, nsarray TEXT[][])
184+
RETURNS sys.BIT
185+
AS
186+
$BODY$
187+
DECLARE
188+
arg_datatype text;
189+
arg_datatype_oid oid;
190+
basetype oid;
191+
pltsql_quoted_identifier text;
192+
BEGIN
193+
arg_datatype_oid := pg_typeof(xml_element)::oid;
194+
arg_datatype := sys.translate_pg_type_to_tsql(arg_datatype_oid);
195+
IF arg_datatype IS NULL THEN
196+
basetype := sys.bbf_get_immediate_base_type_of_UDT(arg_datatype_oid);
197+
arg_datatype := sys.translate_pg_type_to_tsql(basetype);
198+
END IF;
199+
200+
IF (arg_datatype != 'xml') THEN
201+
RAISE EXCEPTION 'Cannot call methods on %.', arg_datatype;
202+
END IF;
203+
204+
pltsql_quoted_identifier := current_setting('babelfishpg_tsql.quoted_identifier');
205+
206+
IF (pltsql_quoted_identifier = 'off') THEN
207+
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.';
208+
END IF;
209+
210+
RETURN (cardinality(xpath(xpath_pattern, xml_element, nsarray)) > 0)::int::sys.BIT;
211+
END
212+
$BODY$
213+
LANGUAGE plpgsql STABLE STRICT PARALLEL SAFE;
214+
215+
-- helper function for XML VALUE(xpath) with namespace support (used by WITH XMLNAMESPACES)
216+
CREATE OR REPLACE FUNCTION sys.bbf_xmlvalue(xpath_pattern TEXT, datatype TEXT, xml_element ANYELEMENT, nsarray TEXT[][])
217+
RETURNS sys.NVARCHAR
218+
AS
219+
$BODY$
220+
DECLARE
221+
temp_datatype text;
222+
temp_basetype oid;
223+
result_set xml[];
224+
result sys.NVARCHAR;
225+
pltsql_quoted_identifier text;
226+
BEGIN
227+
temp_datatype := sys.translate_pg_type_to_tsql(pg_typeof(xml_element)::oid);
228+
IF temp_datatype IS NULL THEN
229+
temp_basetype := sys.bbf_get_immediate_base_type_of_UDT(pg_typeof(xml_element)::oid);
230+
temp_datatype := sys.translate_pg_type_to_tsql(temp_basetype);
231+
END IF;
232+
233+
IF (temp_datatype != 'xml') THEN
234+
RAISE EXCEPTION 'Cannot call methods on %.', temp_datatype;
235+
END IF;
236+
237+
pltsql_quoted_identifier := current_setting('babelfishpg_tsql.quoted_identifier');
238+
239+
IF (pltsql_quoted_identifier = 'off') THEN
240+
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.';
241+
END IF;
242+
243+
result_set := xpath(xpath_pattern, xml_element, nsarray);
244+
IF (cardinality(result_set) > 1) THEN
245+
RAISE EXCEPTION 'XML Value result is not a single value.';
246+
ELSIF (cardinality(result_set) = 0) THEN
247+
RETURN NULL;
248+
ELSE
249+
result := (xpath('string(' || xpath_pattern || ')', xml_element, nsarray))[1];
250+
result := pg_catalog.replace(result, '&lt;', '<');
251+
result := pg_catalog.replace(result, '&gt;', '>');
252+
result := pg_catalog.replace(result, '&apos;', '''');
253+
result := pg_catalog.replace(result, '&quot;', '"');
254+
result := pg_catalog.replace(result, '&amp;', '&');
255+
return result;
256+
END IF;
257+
END
258+
$BODY$
259+
LANGUAGE plpgsql STABLE STRICT PARALLEL SAFE;
260+
173261
-- SELECT FOR JSON
174262
CREATE OR REPLACE FUNCTION sys.tsql_query_to_json_sfunc(
175263
state INTERNAL,

contrib/babelfishpg_tsql/sql/upgrades/babelfishpg_tsql--5.6.0--5.7.0.sql

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,149 @@ CREATE OR REPLACE AGGREGATE sys.tsql_select_for_xml_text_agg(
201201
FINALFUNC = tsql_query_to_xml_text_ffunc
202202
);
203203

204+
-- WITH XMLNAMESPACES support: extend FOR XML aggregate to carry namespace declarations
205+
-- and add namespace-aware overloads for XML data type methods.
206+
--
207+
-- The previous block above already migrated the aggregate from 7 args (legacy) to
208+
-- 8 args (adding auto_metadata for FOR XML AUTO). We now drop those 8-arg
209+
-- definitions and recreate at 9 args (adding ns_decls for WITH XMLNAMESPACES),
210+
-- so the final shape matches sys_functions.sql.
211+
CALL sys.babelfish_drop_deprecated_object('aggregate', 'sys', 'tsql_select_for_xml_agg', 'ANYELEMENT, int, text, boolean, text, boolean, boolean, text');
212+
CALL sys.babelfish_drop_deprecated_object('aggregate', 'sys', 'tsql_select_for_xml_text_agg', 'ANYELEMENT, int, text, boolean, text, boolean, boolean, text');
213+
CALL sys.babelfish_drop_deprecated_object('function', 'sys', 'tsql_query_to_xml_sfunc', 'INTERNAL, ANYELEMENT, int, text, boolean, text, boolean, boolean, text');
214+
215+
CREATE OR REPLACE FUNCTION sys.tsql_query_to_xml_sfunc(
216+
state INTERNAL,
217+
rec ANYELEMENT,
218+
mode int,
219+
element_name text,
220+
binary_base64 boolean,
221+
root_name text,
222+
elements boolean,
223+
xsinil boolean,
224+
auto_metadata text,
225+
ns_decls text
226+
) RETURNS INTERNAL
227+
AS 'babelfishpg_tsql', 'tsql_query_to_xml_sfunc'
228+
LANGUAGE C STABLE;
229+
230+
CREATE OR REPLACE AGGREGATE sys.tsql_select_for_xml_agg(
231+
rec ANYELEMENT,
232+
mode int,
233+
element_name text,
234+
binary_base64 boolean,
235+
root_name text,
236+
elements boolean,
237+
xsinil boolean,
238+
auto_metadata text,
239+
ns_decls text)
240+
(
241+
STYPE = INTERNAL,
242+
SFUNC = tsql_query_to_xml_sfunc,
243+
FINALFUNC = tsql_query_to_xml_ffunc
244+
);
245+
246+
CREATE OR REPLACE AGGREGATE sys.tsql_select_for_xml_text_agg(
247+
rec ANYELEMENT,
248+
mode int,
249+
element_name text,
250+
binary_base64 boolean,
251+
root_name text,
252+
elements boolean,
253+
xsinil boolean,
254+
auto_metadata text,
255+
ns_decls text)
256+
(
257+
STYPE = INTERNAL,
258+
SFUNC = tsql_query_to_xml_sfunc,
259+
FINALFUNC = tsql_query_to_xml_text_ffunc
260+
);
261+
262+
-- helper function for XML QUERY(xpath) with namespace support
263+
CREATE OR REPLACE FUNCTION sys.bbf_xmlquery(xpath_pattern TEXT, xml_element ANYELEMENT, nsarray TEXT[][])
264+
RETURNS XML
265+
AS 'babelfishpg_tsql', 'bbf_xmlquery_ns'
266+
LANGUAGE C STABLE STRICT PARALLEL SAFE;
267+
268+
-- helper function for XML EXIST(xpath) with namespace support
269+
CREATE OR REPLACE FUNCTION sys.bbf_xmlexist(xpath_pattern TEXT, xml_element ANYELEMENT, nsarray TEXT[][])
270+
RETURNS sys.BIT
271+
AS
272+
$BODY$
273+
DECLARE
274+
arg_datatype text;
275+
arg_datatype_oid oid;
276+
basetype oid;
277+
pltsql_quoted_identifier text;
278+
BEGIN
279+
arg_datatype_oid := pg_typeof(xml_element)::oid;
280+
arg_datatype := sys.translate_pg_type_to_tsql(arg_datatype_oid);
281+
IF arg_datatype IS NULL THEN
282+
basetype := sys.bbf_get_immediate_base_type_of_UDT(arg_datatype_oid);
283+
arg_datatype := sys.translate_pg_type_to_tsql(basetype);
284+
END IF;
285+
286+
IF (arg_datatype != 'xml') THEN
287+
RAISE EXCEPTION 'Cannot call methods on %.', arg_datatype;
288+
END IF;
289+
290+
pltsql_quoted_identifier := current_setting('babelfishpg_tsql.quoted_identifier');
291+
292+
IF (pltsql_quoted_identifier = 'off') THEN
293+
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.';
294+
END IF;
295+
296+
RETURN (cardinality(xpath(xpath_pattern, xml_element, nsarray)) > 0)::int::sys.BIT;
297+
END
298+
$BODY$
299+
LANGUAGE plpgsql STABLE STRICT PARALLEL SAFE;
300+
301+
-- helper function for XML VALUE(xpath) with namespace support
302+
CREATE OR REPLACE FUNCTION sys.bbf_xmlvalue(xpath_pattern TEXT, datatype TEXT, xml_element ANYELEMENT, nsarray TEXT[][])
303+
RETURNS sys.NVARCHAR
304+
AS
305+
$BODY$
306+
DECLARE
307+
temp_datatype text;
308+
temp_basetype oid;
309+
result_set xml[];
310+
result sys.NVARCHAR;
311+
pltsql_quoted_identifier text;
312+
BEGIN
313+
temp_datatype := sys.translate_pg_type_to_tsql(pg_typeof(xml_element)::oid);
314+
IF temp_datatype IS NULL THEN
315+
temp_basetype := sys.bbf_get_immediate_base_type_of_UDT(pg_typeof(xml_element)::oid);
316+
temp_datatype := sys.translate_pg_type_to_tsql(temp_basetype);
317+
END IF;
318+
319+
IF (temp_datatype != 'xml') THEN
320+
RAISE EXCEPTION 'Cannot call methods on %.', temp_datatype;
321+
END IF;
322+
323+
pltsql_quoted_identifier := current_setting('babelfishpg_tsql.quoted_identifier');
324+
325+
IF (pltsql_quoted_identifier = 'off') THEN
326+
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.';
327+
END IF;
328+
329+
result_set := xpath(xpath_pattern, xml_element, nsarray);
330+
IF (cardinality(result_set) > 1) THEN
331+
RAISE EXCEPTION 'XML Value result is not a single value.';
332+
ELSIF (cardinality(result_set) = 0) THEN
333+
RETURN NULL;
334+
ELSE
335+
result := (xpath('string(' || xpath_pattern || ')', xml_element, nsarray))[1];
336+
result := pg_catalog.replace(result, '&lt;', '<');
337+
result := pg_catalog.replace(result, '&gt;', '>');
338+
result := pg_catalog.replace(result, '&apos;', '''');
339+
result := pg_catalog.replace(result, '&quot;', '"');
340+
result := pg_catalog.replace(result, '&amp;', '&');
341+
return result;
342+
END IF;
343+
END
344+
$BODY$
345+
LANGUAGE plpgsql STABLE STRICT PARALLEL SAFE;
346+
204347
-- Drops the temporary procedure used by the upgrade script.
205348
-- Please have this be one of the last statements executed in this upgrade script.
206349
DROP PROCEDURE sys.babelfish_drop_deprecated_object(varchar, varchar, varchar, varchar);

contrib/babelfishpg_tsql/src/backend_parser/gram-tsql-epilogue.y.c

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1930,6 +1930,25 @@ TsqlForXMLMakeFuncCall(TSQL_ForClause *forclause)
19301930
func_args = lappend(func_args, makeBoolAConst(xsinil, -1));
19311931
/* 8th arg: auto_metadata placeholder (empty string, filled in by handleForXmlAuto) */
19321932
func_args = lappend(func_args, makeStringConst("", -1));
1933+
/*
1934+
* 9th arg: namespace declarations from WITH XMLNAMESPACES, if any.
1935+
* The C++ ANTLR layer captures the WITH XMLNAMESPACES clause and stores
1936+
* a formatted decls string ('xmlns:p1="u1" xmlns:p2="u2"') on the
1937+
* enclosing PLtsql_stmt_execsql. We read it back from the currently
1938+
* executing stmt via get_current_tsql_estate().
1939+
*/
1940+
{
1941+
PLtsql_execstate *estate = get_current_tsql_estate();
1942+
char *ns_decls = NULL;
1943+
1944+
if (estate && estate->err_stmt && estate->err_stmt->cmd_type == PLTSQL_STMT_EXECSQL)
1945+
ns_decls = ((PLtsql_stmt_execsql *) estate->err_stmt)->xml_namespace_decls;
1946+
1947+
if (ns_decls && ns_decls[0] != '\0')
1948+
func_args = lappend(func_args, makeStringConst(ns_decls, -1));
1949+
else
1950+
func_args = lappend(func_args, makeStringConst("", -1));
1951+
}
19331952
fc = makeFuncCall(func_name, func_args, COERCE_EXPLICIT_CALL, -1);
19341953

19351954
/*

0 commit comments

Comments
 (0)