Skip to content

Added support for XML VALUE() datatype method #3771

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 14 commits into
base: BABEL_5_X_DEV
Choose a base branch
from
Open
47 changes: 47 additions & 0 deletions contrib/babelfishpg_tsql/sql/sys_functions.sql
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,53 @@ END
$BODY$
LANGUAGE plpgsql STABLE STRICT PARALLEL SAFE;

-- helper functions for XML VALUE(xpath)
CREATE OR REPLACE FUNCTION sys.bbf_xmlvalue(TEXT, ANYELEMENT)
RETURNS sys.NVARCHAR
AS
$BODY$
DECLARE
arg_datatype text;
arg_basetype oid;
result_set xml[];
result sys.NVARCHAR;
pltsql_quoted_identifier text;
BEGIN
arg_datatype := sys.translate_pg_type_to_tsql(pg_typeof($2)::oid);
IF arg_datatype IS NULL THEN
-- for User Defined Datatype, use immediate base type to check for argument datatype validation
arg_basetype := sys.bbf_get_immediate_base_type_of_UDT(pg_typeof($2)::oid);
arg_datatype := sys.translate_pg_type_to_tsql(arg_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;

result_set := xpath($1, $2);
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(' + $1 + ')', $2))[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 @@ -208,6 +208,52 @@ RETURNS INTEGER AS
'babelfishpg_tsql', 'isnumeric'
LANGUAGE C IMMUTABLE PARALLEL SAFE;

CREATE OR REPLACE FUNCTION sys.bbf_xmlvalue(TEXT, ANYELEMENT)
RETURNS sys.NVARCHAR
AS
$BODY$
DECLARE
arg_datatype text;
arg_basetype oid;
result_set xml[];
result sys.NVARCHAR;
pltsql_quoted_identifier text;
BEGIN
arg_datatype := sys.translate_pg_type_to_tsql(pg_typeof($2)::oid);
IF arg_datatype IS NULL THEN
-- for User Defined Datatype, use immediate base type to check for argument datatype validation
arg_basetype := sys.bbf_get_immediate_base_type_of_UDT(pg_typeof($2)::oid);
arg_datatype := sys.translate_pg_type_to_tsql(arg_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;

result_set := xpath($1, $2);
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(' + $1 + ')', $2))[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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@
TSQL_CONTAINS TSQL_FREETEXT
TSQL_RECOMPILE
TSQL_UNPIVOT
TSQL_XML_VALUE

/*
* WITH_paren is added to support table hints syntax WITH (<table_hint> [[,]...n]),
Expand Down
11 changes: 11 additions & 0 deletions contrib/babelfishpg_tsql/src/backend_parser/gram-tsql-rule.y
Original file line number Diff line number Diff line change
Expand Up @@ -2119,6 +2119,16 @@ func_expr_common_subexpr:
COERCE_EXPLICIT_CALL,
@1);
}
| TSQL_XML_VALUE '(' c_expr ',' Typename ',' c_expr ')'
{
Node *helperFuncCall;
helperFuncCall = (Node *) makeFuncCall(TsqlSystemFuncName("bbf_xmlvalue"),
list_make2($3, $7),
COERCE_EXPLICIT_CALL,
@1);

$$ = makeTypeCast(helperFuncCall, $5, @1);
}
;

opt_var_name_list:
Expand Down Expand Up @@ -4855,6 +4865,7 @@ reserved_keyword:
| TSQL_TRY_CONVERT
| TSQL_TRY_PARSE
| TSQL_EXEC
| TSQL_XML_VALUE
;

bare_label_keyword:
Expand Down
1 change: 1 addition & 0 deletions contrib/babelfishpg_tsql/src/backend_parser/kwlist.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ PG_KEYWORD("authorization", AUTHORIZATION, TYPE_FUNC_NAME_KEYWORD)
PG_KEYWORD("auto", TSQL_AUTO, UNRESERVED_KEYWORD)
PG_KEYWORD("backward", BACKWARD, UNRESERVED_KEYWORD)
PG_KEYWORD("base64", TSQL_BASE64, UNRESERVED_KEYWORD)
PG_KEYWORD("bbf_xmlvalue", TSQL_XML_VALUE, UNRESERVED_KEYWORD)
PG_KEYWORD("before", BEFORE, UNRESERVED_KEYWORD)
PG_KEYWORD("begin", BEGIN_P, UNRESERVED_KEYWORD)
PG_KEYWORD("between", BETWEEN, COL_NAME_KEYWORD)
Expand Down
26 changes: 25 additions & 1 deletion contrib/babelfishpg_tsql/src/tsqlIface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1048,7 +1048,7 @@ class tsqlCommonMutator : public TSqlParserBaseListener

void exitXml_func_arg(TSqlParser::Xml_func_argContext *ctx) override
{
if (ctx->EXIST())
if (ctx->EXIST() || ctx->VALUE())
{
size_t startPosition = ctx->start->getStartIndex();
rewritten_query_fragment.emplace(std::make_pair(startPosition, std::make_pair("", "bbf_xml")));
Expand Down Expand Up @@ -9288,6 +9288,10 @@ validateXMLFunctionArgs(TSqlParser::Xml_func_argContext *xml_func, TSqlParser::E
if (xml_func->EXIST() && (expr_list == NULL || expr_list->expression().size() != 1))
throw PGErrorWrapperException(ERROR, ERRCODE_UNDEFINED_FUNCTION, "The exist function requires 1 argument(s).", getLineAndPos(xml_func));

/* XML VALUE function requires only 2 argument */
if (xml_func->VALUE() && (expr_list == NULL || expr_list->expression().size() != 2))
throw PGErrorWrapperException(ERROR, ERRCODE_UNDEFINED_FUNCTION, "The value function requires 2 argument(s).", getLineAndPos(xml_func));

/* Only String Literal is allowed as agument for XML Functions */
if (expr_list)
{
Expand All @@ -9314,6 +9318,16 @@ handleXMLFunctionsInFunctionCall(TSqlParser::Function_callContext *ctx)
/* validate the xml method arguments before rewriting */
validateXMLFunctionArgs(ctx->xml_proc_name_table_column()->xml_func_arg(), ctx->expression_list());

if (ctx->xml_proc_name_table_column()->xml_func_arg()->VALUE())
{
TSqlParser::ExpressionContext *expr = ctx->expression_list()->expression()[1];
std::string arg_str = ::getFullText(expr);
size_t startPosition = expr->start->getStartIndex();
size_t stopPosition = expr->stop->getStopIndex();

std::string rewritten_arg_str = arg_str.substr(1, stopPosition - startPosition - 1);
rewritten_query_fragment.emplace(std::make_pair(startPosition, std::make_pair(arg_str, rewritten_arg_str)));
}
rewrite_function_call_dot_func_ref_args(ctx);
}
}
Expand Down Expand Up @@ -9350,6 +9364,16 @@ handleClrUdtFuncCall(TSqlParser::Clr_udt_func_callContext *ctx)
/* validate the xml method arguments before rewriting */
validateXMLFunctionArgs(method->xml_methods()->xml_func_arg(), method->xml_methods()->expression_list());

if (method->xml_methods()->xml_func_arg()->VALUE())
{
TSqlParser::ExpressionContext *expr = method->xml_methods()->expression_list()->expression()[1];
std::string arg_str = ::getFullText(expr);
size_t startPosition = expr->start->getStartIndex();
size_t stopPosition = expr->stop->getStopIndex();

std::string rewritten_arg_str = arg_str.substr(1, stopPosition - startPosition - 1);
rewritten_query_fragment.emplace(std::make_pair(startPosition, std::make_pair(arg_str, rewritten_arg_str)));
}
size_t expr_list_start_index = method->xml_methods()->expression_list()->start->getStartIndex();
size_t expr_list_stop_index = method->xml_methods()->expression_list()->stop->getStopIndex();
rewrite_dot_func_ref_args_query_helper(ctx, method, ind, expr_list_start_index, expr_list_stop_index);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1527,9 +1527,7 @@ antlrcpp::Any TsqlUnsupportedFeatureHandlerImpl::visitId(TSqlParser::IdContext *

antlrcpp::Any TsqlUnsupportedFeatureHandlerImpl::visitXml_func_arg(TSqlParser::Xml_func_argContext *ctx)
{
if (ctx->VALUE())
handle(INSTR_UNSUPPORTED_TSQL_XML_VALUE, "XML VALUE", getLineAndPos(ctx));
else if (ctx->QUERY())
if (ctx->QUERY())
handle(INSTR_UNSUPPORTED_TSQL_XML_QUERY, "XML QUERY", getLineAndPos(ctx));
else if (ctx->MODIFY())
handle(INSTR_UNSUPPORTED_TSQL_XML_QUERY, "XML MODIFY", getLineAndPos(ctx));
Expand Down
80 changes: 56 additions & 24 deletions test/JDBC/expected/TestCompileTimeErrorWithDefaultBehave.out
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@ GO


DECLARE @xml XML
SET @xml = CONVERT(XML, '<root></root>')
SELECT @xml.value('()[1]', 'VARCHAR')
SET @xml = CONVERT(XML, '<Root>
<row id="1"><name>Rohit</name><oflw>some text</oflw></row>
<row id="2"><name>Bhagat</name></row>
<row id="3" />
</Root>')
SELECT T.c.value('.', 'varchar(10)') AS result FROM @x.nodes('/Root/row/name') T(c);
GO
~~ERROR (Code: 33557097)~~

~~ERROR (Message: 'XML VALUE' is not currently supported in Babelfish)~~
~~ERROR (Message: 'XML NODES' is not currently supported in Babelfish)~~

GO

Expand All @@ -28,12 +32,16 @@ GO


DECLARE @xml XML
SET @xml = CONVERT(XML, '<root></root>')
SELECT @xml.value('()[1]', 'VARCHAR')
SET @xml = CONVERT(XML, '<Root>
<row id="1"><name>Rohit</name><oflw>some text</oflw></row>
<row id="2"><name>Bhagat</name></row>
<row id="3" />
</Root>')
SELECT T.c.value('.', 'varchar(10)') AS result FROM @x.nodes('/Root/row/name') T(c);
GO
~~ERROR (Code: 33557097)~~

~~ERROR (Message: 'XML VALUE' is not currently supported in Babelfish)~~
~~ERROR (Message: 'XML NODES' is not currently supported in Babelfish)~~



Expand All @@ -59,14 +67,18 @@ GO
create procedure error_mapping.ErrorHandling1 as
begin
DECLARE @xml XML
SET @xml = CONVERT(XML, '<root></root>')
SELECT @xml.value('()[1]', 'VARCHAR')
SET @xml = CONVERT(XML, '<Root>
<row id="1"><name>Rohit</name><oflw>some text</oflw></row>
<row id="2"><name>Bhagat</name></row>
<row id="3" />
</Root>')
SELECT T.c.value('.', 'varchar(10)') AS result FROM @x.nodes('/Root/row/name') T(c);
if @@error > 0 select cast('STATEMENT TERMINATING ERROR' as text);
end
GO
~~ERROR (Code: 33557097)~~

~~ERROR (Message: 'XML VALUE' is not currently supported in Babelfish)~~
~~ERROR (Message: 'XML NODES' is not currently supported in Babelfish)~~


declare @err int = @@error; if (@err > 0 and @@trancount > 0) select cast('BATCH ONLY TERMINATING' as text) else if @err > 0 select cast('BATCH TERMINATING\ txn rolledback' as text);
Expand Down Expand Up @@ -103,14 +115,18 @@ GO
create procedure error_mapping.ErrorHandling1 as
begin
DECLARE @xml XML
SET @xml = CONVERT(XML, '<root></root>')
SELECT @xml.value('()[1]', 'VARCHAR')
SET @xml = CONVERT(XML, '<Root>
<row id="1"><name>Rohit</name><oflw>some text</oflw></row>
<row id="2"><name>Bhagat</name></row>
<row id="3" />
</Root>')
SELECT T.c.value('.', 'varchar(10)') AS result FROM @x.nodes('/Root/row/name') T(c);
if @@error > 0 select cast('STATEMENT TERMINATING ERROR' as text);
end
GO
~~ERROR (Code: 33557097)~~

~~ERROR (Message: 'XML VALUE' is not currently supported in Babelfish)~~
~~ERROR (Message: 'XML NODES' is not currently supported in Babelfish)~~


declare @err int = @@error; if (@err > 0 and @@trancount > 0) select cast('BATCH ONLY TERMINATING' as text) else if @err > 0 select cast('BATCH TERMINATING\ txn rolledback' as text);
Expand Down Expand Up @@ -140,12 +156,16 @@ GO


DECLARE @xml XML
SET @xml = CONVERT(XML, '<root></root>')
SELECT @xml.value('(*:)[1]', 'VARCHAR')
SET @xml = CONVERT(XML, '<Root>
<row id="1"><name>Rohit</name><oflw>some text</oflw></row>
<row id="2"><name>Bhagat</name></row>
<row id="3" />
</Root>')
SELECT T.c.value('.', 'varchar(10)') AS result FROM @x.nodes('/Root/row/name') T(c);
GO
~~ERROR (Code: 33557097)~~

~~ERROR (Message: 'XML VALUE' is not currently supported in Babelfish)~~
~~ERROR (Message: 'XML NODES' is not currently supported in Babelfish)~~

GO

Expand All @@ -156,12 +176,16 @@ GO


DECLARE @xml XML
SET @xml = CONVERT(XML, '<root></root>')
SELECT @xml.value('(*:)[1]', 'VARCHAR')
SET @xml = CONVERT(XML, '<Root>
<row id="1"><name>Rohit</name><oflw>some text</oflw></row>
<row id="2"><name>Bhagat</name></row>
<row id="3" />
</Root>')
SELECT T.c.value('.', 'varchar(10)') AS result FROM @x.nodes('/Root/row/name') T(c);
GO
~~ERROR (Code: 33557097)~~

~~ERROR (Message: 'XML VALUE' is not currently supported in Babelfish)~~
~~ERROR (Message: 'XML NODES' is not currently supported in Babelfish)~~



Expand All @@ -186,14 +210,18 @@ GO
create procedure error_mapping.ErrorHandling1 as
begin
DECLARE @xml XML
SET @xml = CONVERT(XML, '<root></root>')
SELECT @xml.value('(*:)[1]', 'VARCHAR')
SET @xml = CONVERT(XML, '<Root>
<row id="1"><name>Rohit</name><oflw>some text</oflw></row>
<row id="2"><name>Bhagat</name></row>
<row id="3" />
</Root>')
SELECT T.c.value('.', 'varchar(10)') AS result FROM @x.nodes('/Root/row/name') T(c);
if @@error > 0 select cast('STATEMENT TERMINATING ERROR' as text);
end
GO
~~ERROR (Code: 33557097)~~

~~ERROR (Message: 'XML VALUE' is not currently supported in Babelfish)~~
~~ERROR (Message: 'XML NODES' is not currently supported in Babelfish)~~


declare @err int = @@error; if (@err > 0 and @@trancount > 0) select cast('BATCH ONLY TERMINATING' as text) else if @err > 0 select cast('BATCH TERMINATING\ txn rolledback' as text);
Expand Down Expand Up @@ -230,14 +258,18 @@ GO
create procedure error_mapping.ErrorHandling1 as
begin
DECLARE @xml XML
SET @xml = CONVERT(XML, '<root></root>')
SELECT @xml.value('(*:)[1]', 'VARCHAR')
SET @xml = CONVERT(XML, '<Root>
<row id="1"><name>Rohit</name><oflw>some text</oflw></row>
<row id="2"><name>Bhagat</name></row>
<row id="3" />
</Root>')
SELECT T.c.value('.', 'varchar(10)') AS result FROM @x.nodes('/Root/row/name') T(c);
if @@error > 0 select cast('STATEMENT TERMINATING ERROR' as text);
end
GO
~~ERROR (Code: 33557097)~~

~~ERROR (Message: 'XML VALUE' is not currently supported in Babelfish)~~
~~ERROR (Message: 'XML NODES' is not currently supported in Babelfish)~~


declare @err int = @@error; if (@err > 0 and @@trancount > 0) select cast('BATCH ONLY TERMINATING' as text) else if @err > 0 select cast('BATCH TERMINATING\ txn rolledback' as text);
Expand Down
7 changes: 4 additions & 3 deletions test/JDBC/expected/xml_exist-before-16_5-vu-verify.out
Original file line number Diff line number Diff line change
Expand Up @@ -1077,9 +1077,10 @@ bit
DECLARE @xml XML = '<artists> <artist name="John Doe"/> <artist name="Edward Poe"/> <artist name="Mark The Great"/> </artists>'
SELECT @xml.value('(/artists/artist/@name)[1]','varchar(20)')
GO
~~ERROR (Code: 33557097)~~

~~ERROR (Message: 'XML VALUE' is not currently supported in Babelfish)~~
~~START~~
varchar
John Doe
~~END~~


DECLARE @xml XML = '<artists> <artist name="John Doe"/> <artist name="Edward Poe"/> <artist name="Mark The Great"/> </artists>'
Expand Down
Loading
Loading