Skip to content

Commit abbe07b

Browse files
authored
Add support for the method .query() for XML data type (#4845)
Currently, Babelfish supports .exist() and .value() XML methods but .query() was unsupported and threw an error. With this change, .query(xpath) is now supported and returns XML results (for XPath 1.0 compatible queries). The implementation follows the same 3-stage architecture like .exist() and .value(): ANTLR rewriting: @x.query('/path') is rewritten to sys.bbf_xmlquery('/path', @x) Grammar: BBF_XMLQUERY is mapped in gram-tsql-rule.y PL/pgSQL function sys.bbf_xmlquery(): validates the input type, checks QUOTED_IDENTIFIER, calls PostgreSQL's xpath(), and returns the result as XML Task: BABEL-5224 Signed-off-by: Japleen Kaur <amjj@amazon.com>
1 parent e6a8a8a commit abbe07b

33 files changed

Lines changed: 6051 additions & 30 deletions

contrib/babelfishpg_tsql/runtime/functions.c

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6094,3 +6094,115 @@ openxml_simple(PG_FUNCTION_ARGS)
60946094
#endif /* USE_LIBXML */
60956095
}
60966096

6097+
6098+
PG_FUNCTION_INFO_V1(bbf_xmlquery);
6099+
6100+
/*
6101+
* bbf_xmlquery - C implementation of XML .query() method
6102+
*
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
6112+
*/
6113+
Datum
6114+
bbf_xmlquery(PG_FUNCTION_ARGS)
6115+
{
6116+
text *xpath_expr;
6117+
Datum xml_datum;
6118+
Oid arg_type;
6119+
Oid immediate_base_type;
6120+
ArrayType *namespaces;
6121+
Datum xpath_result;
6122+
ArrayType *result_arr;
6123+
Datum *elems;
6124+
bool *nulls;
6125+
int nitems;
6126+
StringInfoData buf;
6127+
int i;
6128+
6129+
xpath_expr = PG_GETARG_TEXT_PP(0);
6130+
xml_datum = PG_GETARG_DATUM(1);
6131+
6132+
/* Lookup the datatype of the supplied argument */
6133+
arg_type = get_fn_expr_argtype(fcinfo->flinfo, 1);
6134+
6135+
/* UDT handling: resolve to immediate base type if it's a UDT */
6136+
immediate_base_type = get_immediate_base_type_of_UDT_internal(arg_type);
6137+
if (OidIsValid(immediate_base_type))
6138+
arg_type = immediate_base_type;
6139+
6140+
if (arg_type != XMLOID)
6141+
{
6142+
const char *typname = NULL;
6143+
6144+
/* Get T-SQL type name for error message */
6145+
if (common_utility_plugin_ptr)
6146+
typname = (*common_utility_plugin_ptr->resolve_pg_type_to_tsql)(arg_type);
6147+
if (typname == NULL)
6148+
typname = format_type_be(arg_type);
6149+
6150+
ereport(ERROR,
6151+
(errcode(ERRCODE_DATATYPE_MISMATCH),
6152+
errmsg("Cannot call methods on %s.", typname)));
6153+
}
6154+
6155+
/* Check QUOTED_IDENTIFIER setting (required for XML methods in T-SQL) */
6156+
if (!pltsql_quoted_identifier)
6157+
ereport(ERROR,
6158+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
6159+
errmsg("SELECT failed because the following SET options have "
6160+
"incorrect settings: 'QUOTED_IDENTIFIER'. Verify that "
6161+
"SET options are correct for XML data type methods.")));
6162+
6163+
/*
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.
6169+
*/
6170+
namespaces = construct_empty_array(TEXTOID);
6171+
xpath_result = DirectFunctionCall3(xpath,
6172+
PointerGetDatum(xpath_expr),
6173+
xml_datum,
6174+
PointerGetDatum(namespaces));
6175+
6176+
result_arr = DatumGetArrayTypeP(xpath_result);
6177+
6178+
/* Deconstruct the result array */
6179+
deconstruct_array(result_arr, XMLOID, -1, false, TYPALIGN_INT,
6180+
&elems, &nulls, &nitems);
6181+
6182+
/* Empty result → return empty string as XML (matches T-SQL behavior) */
6183+
if (nitems == 0)
6184+
PG_RETURN_XML_P((xmltype *) cstring_to_text(""));
6185+
6186+
/* Single result → return directly (common fast path) */
6187+
if (nitems == 1 && !nulls[0])
6188+
PG_RETURN_DATUM(elems[0]);
6189+
6190+
/*
6191+
* Multiple results - concatenate all XML fragments.
6192+
* Equivalent to: SELECT xmlagg(x) FROM unnest(result_set) AS x
6193+
*/
6194+
initStringInfo(&buf);
6195+
for (i = 0; i < nitems; i++)
6196+
{
6197+
if (!nulls[i])
6198+
{
6199+
text *fragment = DatumGetTextPP(elems[i]);
6200+
6201+
appendBinaryStringInfo(&buf,
6202+
VARDATA_ANY(fragment),
6203+
VARSIZE_ANY_EXHDR(fragment));
6204+
}
6205+
}
6206+
6207+
PG_RETURN_XML_P((xmltype *) cstring_to_text_with_len(buf.data, buf.len));
6208+
}

contrib/babelfishpg_tsql/sql/sys_functions.sql

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,11 +159,17 @@ BEGIN
159159
result := pg_catalog.replace(result, '&quot;', '"');
160160
result := pg_catalog.replace(result, '&amp;', '&');
161161
return result;
162-
END IF;
162+
END IF;
163163
END
164164
$BODY$
165165
LANGUAGE plpgsql STABLE STRICT PARALLEL SAFE;
166166

167+
-- helper function for XML QUERY(xpath)
168+
CREATE OR REPLACE FUNCTION sys.bbf_xmlquery(xpath_pattern TEXT, xml_element ANYELEMENT)
169+
RETURNS XML
170+
AS 'babelfishpg_tsql', 'bbf_xmlquery'
171+
LANGUAGE C STABLE STRICT PARALLEL SAFE;
172+
167173
-- SELECT FOR JSON
168174
CREATE OR REPLACE FUNCTION sys.tsql_query_to_json_sfunc(
169175
state INTERNAL,

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ $$
6666
end;
6767
$$;
6868

69+
-- helper function for XML QUERY(xpath)
70+
CREATE OR REPLACE FUNCTION sys.bbf_xmlquery(xpath_pattern TEXT, xml_element ANYELEMENT)
71+
RETURNS XML
72+
AS 'babelfishpg_tsql', 'bbf_xmlquery'
73+
LANGUAGE C STABLE STRICT PARALLEL SAFE;
74+
6975
-- BABELFISH_FUNCTION_EXT (antlr_parse_cache)
7076
SET allow_system_table_mods = on;
7177
ALTER TABLE sys.babelfish_function_ext ADD COLUMN IF NOT EXISTS antlr_parse_cache_tree TEXT DEFAULT NULL;

contrib/babelfishpg_tsql/src/pltsql_instr.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ typedef enum PgTsqlInstrMetricType
4545
INSTR_UNSUPPORTED_TSQL_FREETEXT,
4646
INSTR_UNSUPPORTED_TSQL_NEXT_VALUE_FOR,
4747
INSTR_UNSUPPORTED_TSQL_XML_NODES,
48-
INSTR_UNSUPPORTED_TSQL_XML_QUERY,
4948
INSTR_UNSUPPORTED_TSQL_XML_MODIFY,
5049
INSTR_UNSUPPORTED_TSQL_ALTER_FUNCTION,
5150
INSTR_UNSUPPORTED_TSQL_ALTER_FUNCTION_ENCRYPTION_OPTION,

contrib/babelfishpg_tsql/src/tsqlIface.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1050,7 +1050,7 @@ class tsqlCommonMutator : public TSqlParserBaseListener
10501050

10511051
void exitXml_func_arg(TSqlParser::Xml_func_argContext *ctx) override
10521052
{
1053-
if (ctx->EXIST() || ctx->VALUE())
1053+
if (ctx->EXIST() || ctx->VALUE() || ctx->QUERY())
10541054
{
10551055
size_t startPosition = ctx->start->getStartIndex();
10561056
rewritten_query_fragment.emplace(std::make_pair(startPosition, std::make_pair("", "bbf_xml")));
@@ -9477,6 +9477,10 @@ validateXMLFunctionArgs(TSqlParser::Xml_func_argContext *xml_func, TSqlParser::E
94779477
if (xml_func->VALUE() && (expr_list == NULL || expr_list->expression().size() != 2))
94789478
throw PGErrorWrapperException(ERROR, ERRCODE_UNDEFINED_FUNCTION, "The value function requires 2 argument(s).", getLineAndPos(xml_func));
94799479

9480+
/* XML QUERY function requires only 1 argument */
9481+
if (xml_func->QUERY() && (expr_list == NULL || expr_list->expression().size() != 1))
9482+
throw PGErrorWrapperException(ERROR, ERRCODE_UNDEFINED_FUNCTION, "The query function requires 1 argument(s).", getLineAndPos(xml_func));
9483+
94809484
/* Only String Literal is allowed as agument for XML Functions */
94819485
if (expr_list)
94829486
{

contrib/babelfishpg_tsql/src/tsqlUnsupportedFeatureHandler.cpp

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1518,10 +1518,8 @@ antlrcpp::Any TsqlUnsupportedFeatureHandlerImpl::visitId(TSqlParser::IdContext *
15181518

15191519
antlrcpp::Any TsqlUnsupportedFeatureHandlerImpl::visitXml_func_arg(TSqlParser::Xml_func_argContext *ctx)
15201520
{
1521-
if (ctx->QUERY())
1522-
handle(INSTR_UNSUPPORTED_TSQL_XML_QUERY, "XML QUERY", getLineAndPos(ctx));
1523-
else if (ctx->MODIFY())
1524-
handle(INSTR_UNSUPPORTED_TSQL_XML_QUERY, "XML MODIFY", getLineAndPos(ctx));
1521+
if (ctx->MODIFY())
1522+
handle(INSTR_UNSUPPORTED_TSQL_XML_MODIFY, "XML MODIFY", getLineAndPos(ctx));
15251523
return visitChildren(ctx);
15261524
}
15271525

test/JDBC/expected/forxml-path-elements-before-17_10-or-18_4-vu-verify.out

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1116,9 +1116,10 @@ varchar
11161116
-- Using .query() method on XSINIL result
11171117
SELECT (SELECT ID, Name, Department FROM forxml_path_elements_t1 FOR XML PATH('Employee'), ROOT('Data'), ELEMENTS XSINIL, TYPE).query('/Data/Employee[1]') AS query_result;
11181118
GO
1119-
~~ERROR (Code: 33557097)~~
1120-
1121-
~~ERROR (Message: 'XML QUERY' is not currently supported in Babelfish)~~
1119+
~~START~~
1120+
xml
1121+
<Employee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><ID>1</ID><Name>John</Name><Department>Sales</Department></Employee>
1122+
~~END~~
11221123

11231124

11241125
-- Using .exist() method on XSINIL result

test/JDBC/expected/forxml-path-elements-vu-verify.out

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1202,9 +1202,10 @@ varchar
12021202
-- Using .query() method on XSINIL result
12031203
SELECT (SELECT ID, Name, Department FROM forxml_path_elements_t1 FOR XML PATH('Employee'), ROOT('Data'), ELEMENTS XSINIL, TYPE).query('/Data/Employee[1]') AS query_result;
12041204
GO
1205-
~~ERROR (Code: 33557097)~~
1206-
1207-
~~ERROR (Message: 'XML QUERY' is not currently supported in Babelfish)~~
1205+
~~START~~
1206+
xml
1207+
<Employee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><ID>1</ID><Name>John</Name><Department>Sales</Department></Employee>
1208+
~~END~~
12081209

12091210

12101211
-- Using .exist() method on XSINIL result

test/JDBC/expected/xml_exist-before-16_5-vu-verify.out

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -823,11 +823,12 @@ GO
823823

824824
-- Exist function called on XML Query
825825
DECLARE @xml XML = '<artists> <artist name="John Doe"/> <artist name="Edward Poe"/> <artist name="Mark The Great"/> </artists>'
826-
SELECT @xml.query('/artists/artist').exist('/artist/@name')
826+
SELECT @xml.query('/artists').exist('/artists/artist/@name')
827827
GO
828-
~~ERROR (Code: 33557097)~~
829-
830-
~~ERROR (Message: 'XML QUERY' is not currently supported in Babelfish)~~
828+
~~START~~
829+
bit
830+
1
831+
~~END~~
831832

832833

833834
-- Dependent objects
@@ -1086,9 +1087,10 @@ John Doe
10861087
DECLARE @xml XML = '<artists> <artist name="John Doe"/> <artist name="Edward Poe"/> <artist name="Mark The Great"/> </artists>'
10871088
SELECT @xml.query('/artists/artist')
10881089
GO
1089-
~~ERROR (Code: 33557097)~~
1090-
1091-
~~ERROR (Message: 'XML QUERY' is not currently supported in Babelfish)~~
1090+
~~START~~
1091+
xml
1092+
<artist name="John Doe"/><artist name="Edward Poe"/><artist name="Mark The Great"/>
1093+
~~END~~
10921094

10931095

10941096
DECLARE @xml XML = '<Root><Child1>Value1</Child1><Child2>Value2</Child2></Root>';

test/JDBC/expected/xml_exist-vu-verify.out

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -823,11 +823,12 @@ GO
823823

824824
-- Exist function called on XML Query
825825
DECLARE @xml XML = '<artists> <artist name="John Doe"/> <artist name="Edward Poe"/> <artist name="Mark The Great"/> </artists>'
826-
SELECT @xml.query('/artists/artist').exist('/artist/@name')
826+
SELECT @xml.query('/artists').exist('/artists/artist/@name')
827827
GO
828-
~~ERROR (Code: 33557097)~~
829-
830-
~~ERROR (Message: 'XML QUERY' is not currently supported in Babelfish)~~
828+
~~START~~
829+
bit
830+
1
831+
~~END~~
831832

832833

833834
-- Dependent objects
@@ -1274,9 +1275,10 @@ John Doe
12741275
DECLARE @xml XML = '<artists> <artist name="John Doe"/> <artist name="Edward Poe"/> <artist name="Mark The Great"/> </artists>'
12751276
SELECT @xml.query('/artists/artist')
12761277
GO
1277-
~~ERROR (Code: 33557097)~~
1278-
1279-
~~ERROR (Message: 'XML QUERY' is not currently supported in Babelfish)~~
1278+
~~START~~
1279+
xml
1280+
<artist name="John Doe"/><artist name="Edward Poe"/><artist name="Mark The Great"/>
1281+
~~END~~
12801282

12811283

12821284
DECLARE @xml XML = '<Root><Child1>Value1</Child1><Child2>Value2</Child2></Root>';

0 commit comments

Comments
 (0)