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
58 changes: 58 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,64 @@ END
$BODY$
LANGUAGE plpgsql STABLE STRICT PARALLEL SAFE;

-- helper functions for XML VALUE(xpath)
CREATE OR REPLACE FUNCTION sys.bbf_xmlvalue(xpath_pattern TEXT, xml_element ANYELEMENT, datatype 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
-- for User Defined Datatype, use immediate base type to check for xml_element datatype validation
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;

temp_datatype := sys.translate_pg_type_to_tsql(datatype::regtype::oid);
IF temp_datatype IS NULL THEN
-- for User Defined Datatype, use immediate base type to check for datatype validation
temp_basetype := sys.bbf_get_immediate_base_type_of_UDT(datatype::regtype::oid);
temp_datatype := sys.translate_pg_type_to_tsql(temp_basetype);
END IF;

IF (temp_datatype IN ('xml', 'image', 'text', 'ntext', 'sql_variant', 'geometry', 'geography', 'vector', 'sparsevec', 'halfvec')) THEN
RAISE EXCEPTION 'The data type ''%'' used in the VALUE method is invalid.', datatype;
END IF;

result_set := xpath(xpath_pattern, xml_element);
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))[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,64 @@ RETURNS INTEGER AS
'babelfishpg_tsql', 'isnumeric'
LANGUAGE C IMMUTABLE PARALLEL SAFE;

CREATE OR REPLACE FUNCTION sys.bbf_xmlvalue(xpath_pattern TEXT, xml_element ANYELEMENT, datatype 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
-- for User Defined Datatype, use immediate base type to check for xml_element datatype validation
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;

temp_datatype := sys.translate_pg_type_to_tsql(datatype::regtype::oid);
IF temp_datatype IS NULL THEN
-- for User Defined Datatype, use immediate base type to check for datatype validation
temp_basetype := sys.bbf_get_immediate_base_type_of_UDT(datatype::regtype::oid);
temp_datatype := sys.translate_pg_type_to_tsql(temp_basetype);
END IF;

IF (temp_datatype IN ('xml', 'image', 'text', 'ntext', 'sql_variant', 'geometry', 'geography', 'vector', 'sparsevec', 'halfvec')) THEN
RAISE EXCEPTION 'The data type ''%'' used in the VALUE method is invalid.', datatype;
END IF;

result_set := xpath(xpath_pattern, xml_element);
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))[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;


CREATE OR REPLACE FUNCTION sys.tsql_type_precision_helper(IN type TEXT, IN typemod INT) RETURNS sys.TINYINT
AS $$
DECLARE
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
14 changes: 14 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,19 @@ func_expr_common_subexpr:
COERCE_EXPLICIT_CALL,
@1);
}
| TSQL_XML_VALUE '(' c_expr ',' Typename ',' c_expr ')'
{
Node *helperFuncCall;
List *args;

args = list_make3($3, $7, makeStringConst(TypeNameToString($5), @5));
helperFuncCall = (Node *) makeFuncCall(TsqlSystemFuncName("bbf_xmlvalue"),
args,
COERCE_EXPLICIT_CALL,
@1);

$$ = TsqlFunctionConvert($5, helperFuncCall, NULL, false, @1);
}
;

opt_var_name_list:
Expand Down Expand Up @@ -4855,6 +4868,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, RESERVED_KEYWORD)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not qualify for Reserved keyword so lets try to explore other option to achieve the same.

PG_KEYWORD("before", BEFORE, UNRESERVED_KEYWORD)
PG_KEYWORD("begin", BEGIN_P, UNRESERVED_KEYWORD)
PG_KEYWORD("between", BETWEEN, COL_NAME_KEYWORD)
Expand Down
2 changes: 0 additions & 2 deletions contrib/babelfishpg_tsql/src/pltsql_instr.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,7 @@ typedef enum PgTsqlInstrMetricType
INSTR_UNSUPPORTED_TSQL_FREETEXT,
INSTR_UNSUPPORTED_TSQL_NEXT_VALUE_FOR,
INSTR_UNSUPPORTED_TSQL_XML_NODES,
INSTR_UNSUPPORTED_TSQL_XML_VALUE,
INSTR_UNSUPPORTED_TSQL_XML_QUERY,
INSTR_UNSUPPORTED_TSQL_XML_EXIST,
INSTR_UNSUPPORTED_TSQL_XML_MODIFY,
INSTR_UNSUPPORTED_TSQL_ALTER_FUNCTION,
INSTR_UNSUPPORTED_TSQL_ALTER_FUNCTION_ENCRYPTION_OPTION,
Expand Down
27 changes: 23 additions & 4 deletions 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 @@ -9282,12 +9282,16 @@ handleGeospatialFunctionsInFunctionCall(TSqlParser::Function_callContext *ctx)
}

static void
validateXMLFunctionArgs(TSqlParser::Xml_func_argContext *xml_func, TSqlParser::Expression_listContext *expr_list)
validateAndRewriteXMLFunctionArgs(TSqlParser::Xml_func_argContext *xml_func, TSqlParser::Expression_listContext *expr_list)
{
/* XML EXIST function requires only 1 argument */
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 @@ -9303,6 +9307,21 @@ validateXMLFunctionArgs(TSqlParser::Xml_func_argContext *xml_func, TSqlParser::E
getLineAndPos(expr));
}
}

/*
* Rewrite second argument of VALUE method by removing the quotes around it as follows
* 'typename(typmod)' -> typename(typmod)
*/
if (xml_func->VALUE())
{
TSqlParser::ExpressionContext *expr = expr_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)));
}
}

static void
Expand All @@ -9312,7 +9331,7 @@ handleXMLFunctionsInFunctionCall(TSqlParser::Function_callContext *ctx)
if (ctx->xml_proc_name_table_column())
{
/* validate the xml method arguments before rewriting */
validateXMLFunctionArgs(ctx->xml_proc_name_table_column()->xml_func_arg(), ctx->expression_list());
validateAndRewriteXMLFunctionArgs(ctx->xml_proc_name_table_column()->xml_func_arg(), ctx->expression_list());

rewrite_function_call_dot_func_ref_args(ctx);
}
Expand Down Expand Up @@ -9348,7 +9367,7 @@ handleClrUdtFuncCall(TSqlParser::Clr_udt_func_callContext *ctx)
if (method->xml_methods())
{
/* validate the xml method arguments before rewriting */
validateXMLFunctionArgs(method->xml_methods()->xml_func_arg(), method->xml_methods()->expression_list());
validateAndRewriteXMLFunctionArgs(method->xml_methods()->xml_func_arg(), method->xml_methods()->expression_list());

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();
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
Loading
Loading