Skip to content

Commit

Permalink
Support unary '+' operators for strings (#3013)
Browse files Browse the repository at this point in the history
The T-SQL grammar allows an arbitrary number of unary + operators to precede an expression,
but PG only supports that for numeric expressions. For string expressions, the + will be parsed fine by the T-SQL ANTLR parser, but these will raise an error in PG.
In SQL those unary + string operators may look like they are additional concatenation operators, like: SELECT 'a' ++ 'b'. Expressions such as +++(+++@v)) are also valid syntax according to the T-SQL grammar even though they look unusual.
In this fix we remove such unary + operators, which are redundant anyway.
However we do not touch numeric constants (e.g. +123) since the +, though still redundant, may
have been included for code clarity (e.g. +123 as opposed to -123).

Signed-off-by: Rob Verschoor [email protected]

Issues Resolved
BABEL-1924 Support unary '+' operator for string expressions
  • Loading branch information
robverschoor authored Oct 14, 2024
1 parent 95dbb0b commit 8ad86ff
Show file tree
Hide file tree
Showing 9 changed files with 1,347 additions and 7 deletions.
57 changes: 51 additions & 6 deletions contrib/babelfishpg_tsql/src/tsqlIface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2499,11 +2499,11 @@ class tsqlBuilder : public tsqlCommonMutator
{
// Check for comparison operators directly followed by an '@@' variable, like =@@
handleAtAtVarInPredicate(ctx);
}
}
void exitUnary_op_expr(TSqlParser::Unary_op_exprContext *ctx) override
{
handleBitNotOperator(ctx);
}
}
void exitPlus_minus_bit_expr(TSqlParser::Plus_minus_bit_exprContext *ctx) override
{
handleBitOperators(ctx);
Expand Down Expand Up @@ -3217,10 +3217,7 @@ class tsqlMutator : public TSqlParserBaseListener
// Check for comparison operators directly followed by an '@@' variable, like =@@
handleAtAtVarInPredicate(ctx);
}
void exitUnary_op_expr(TSqlParser::Unary_op_exprContext *ctx) override
{
handleBitNotOperator(ctx);
}

void exitPlus_minus_bit_expr(TSqlParser::Plus_minus_bit_exprContext *ctx) override
{
handleBitOperators(ctx);
Expand All @@ -3229,6 +3226,54 @@ class tsqlMutator : public TSqlParserBaseListener
{
handleModuloOperator(ctx);
}

void enterUnary_op_expr(TSqlParser::Unary_op_exprContext *ctx) override
{
/*
* The T-SQL grammar allows an arbitrary number of unary '+' operators to precede an expression,
* but PG only supports that for numeric expressions. For string expressions, such a '+' will raise an error in PG.
* In SQL this shows as redundant operators, for example for concatenation: SELECT 'a' ++ 'b'. Expressions
* such as +++(+++@v)) are also valid syntax according to the T-SQL grammar even though they look unusual.
* Here we remove such unary '+' operators, which are redundant anyway.
* However we do not touch numeric constants (e.g. +123) since the '+', although still redundant, may
* have been included for code clarity (e.g. +123 as opposed to -123).
*/
std::string op = getFullText(ctx->op);
if (op.front() == '+') {
auto rhsctx = ctx->expression();
while (true) {
std::string rhs = getFullText(rhsctx);
if (
(rhs.front() == '\'') || // single-quoted strings
(rhs.front() == '"') || // both double-quoted strings and double-quoted identifiers
(rhs.front() == '@') || // variables
(rhs.front() == '(') || // bracketed expressions
(rhs.front() == '[') || // bracket-delimited identifiers
(rhs.front() == '_') || // identifiers starting with an underscore
std::isalpha(rhs.front()) // identifiers as well as the N'...' string notation
) {
stream.setText(ctx->op->getStartIndex(), " ");
break;
}
if (rhs.front() == '+') {
if (dynamic_cast<TSqlParser::Unary_op_exprContext *>(rhsctx)) {
TSqlParser::Unary_op_exprContext *uctx = static_cast<TSqlParser::Unary_op_exprContext *>(rhsctx);
op = getFullText(uctx->op);
if (op.front() == '+') {
rhsctx = uctx->expression();
continue;
}
}
}
break;
}
}
return;
}
void exitUnary_op_expr(TSqlParser::Unary_op_exprContext *ctx) override
{
handleBitNotOperator(ctx);
}

};

Expand Down
11 changes: 11 additions & 0 deletions test/JDBC/expected/unary_plus_op_string-vu-cleanup.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
drop procedure p1_unary_plus_op_string
go

drop function f1_unary_plus_op_string
go

drop view v1_unary_plus_op_string
go

drop table t1_unary_plus_op_string
go
76 changes: 76 additions & 0 deletions test/JDBC/expected/unary_plus_op_string-vu-prepare.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
create table t1_unary_plus_op_string(i int, vc varchar(30))
go

create view v1_unary_plus_op_string as
select 'view '+++(+N'value1') +
+ /*comment*/
(
+ /*comment*/
+ -- comment +
(
+
'value2'))++(+(select 'value3')) as col1,
1 + -2 as col2,
1 ++ -2 as col3,
1 + ~2 as col4,
1 +++++ ~2 as col5,
1 ++++ (++++ -2) as col6,
1 ++++ (++++ ~2) as col7
go

create procedure p1_unary_plus_op_string
as
declare @v varchar(20) = ' test'
declare @i int = 1
declare @d datetime='2024-Jan-01 01:02:03'
declare @dc decimal(10,4)=1
select 'proc '+'line1'
select 'proc '++'line2'
select 'proc '++N'line3'
select 'proc '+++++'line4'
select 'proc '+++++"line5"
select 'proc '+
+ /*comment*/
(
+ /*comment*/
+ -- comment +
(
+
"line6"))+++@v
select 'proc ' ++(++(++(select N'line7')))++(+@v)
select 'proc ' ++ case when len('a'+++ 'b')=2 then 'true' else 'false' end
select 'proc line8' where 'x' in (+'x')
select 'proc line9' where 'x' like +'x'
set @v = 'x'
select 'proc line10' where 'x' like (+@v)
select 'proc line11' where 'x' like +(+(+@v))
select 'proc line 12 ' ++ vc from t1_unary_plus_op_string order by 1
select 'proc line 13 ' ++(+vc) from t1_unary_plus_op_string order by 1
select 'proc line 14 ' ++ [vc] from t1_unary_plus_op_string order by 1
select 1 + @i, 2 + (+@i), 3 ++++(++(+++@i))
select +@d, + (+@d), ++++(++(+++@d))
select 1 +@dc, 2 + (+@dc), 3 ++++(++(+++@dc))
select 1 + -2 as expr1
select 1 ++ -2 as expr2
select 1 + ~2 as expr3
select 1 +++++ ~2 as expr4
select 1 ++++ (++++ -2) as expr5
select 1 ++++ (++++ ~2) as expr6
EXECUTE('select ''execimm ''++''line1''')
EXECUTE('select ''execimm ''++N''line2''')
EXECUTE('select ''execimm ''++(+(select N''line3''))')
go

create function f1_unary_plus_op_string (@v varchar(10)) returns varchar(30)
as
begin
declare @s varchar(30)
set @s = 'func '+++(+N'value1') +
+
(
+ /*comment*/
+ -- comment +
(++(select "value2")))++(+@v)
return @s
end
go
Loading

0 comments on commit 8ad86ff

Please sign in to comment.