Skip to content

Commit

Permalink
Table constraint in column list without comma for table variables and…
Browse files Browse the repository at this point in the history
… TVFs (#2233)

T-SQL allows specifying a table constraint (like PK/UNIQUE) in a column definition without a comma as a separator. Babelfish supports this for tables, but raises an error for table variables and for the table return type in TVFs. This fix inserts a comma in ANTLR for these cases.

Signed-off-by: Rob Verschoor [email protected]
Task: BABEL-3341
  • Loading branch information
robverschoor authored Jan 22, 2024
1 parent 675ecc1 commit ee1a595
Show file tree
Hide file tree
Showing 31 changed files with 1,260 additions and 39 deletions.
98 changes: 59 additions & 39 deletions contrib/babelfishpg_tsql/src/tsqlIface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ static const std::string fragment_SELECT_prefix = "SELECT "; // fragment prefix
static const std::string fragment_EXEC_prefix = "EXEC "; // fragment prefix for execute_body_batch
static PLtsql_stmt *makeChangeDbOwnerStatement(TSqlParser::Alter_authorizationContext *ctx);
static void handleFloatWithoutExponent(TSqlParser::ConstantContext *ctx);
static void handleTableConstraintWithoutComma(TSqlParser::Column_def_table_constraintsContext *ctx);
static void handleBitNotOperator(TSqlParser::Unary_op_exprContext *ctx);
static void handleBitOperators(TSqlParser::Plus_minus_bit_exprContext *ctx);
static void handleModuloOperator(TSqlParser::Mult_div_percent_exprContext *ctx);
Expand Down Expand Up @@ -2132,13 +2133,6 @@ class tsqlBuilder : public tsqlCommonMutator
// TO-DO
}

// NB: this is copied in tsqlMutator
void exitConstant(TSqlParser::ConstantContext *ctx) override
{
// Check for floating-point number without exponent
handleFloatWithoutExponent(ctx);
}

void exitPredicate(TSqlParser::PredicateContext *ctx) override
{
// For comparison operators directly followed by an '@@' variable, insert a space
Expand Down Expand Up @@ -2196,6 +2190,19 @@ class tsqlBuilder : public tsqlCommonMutator
}
}

// NB: this is copied in tsqlMutator
void exitColumn_def_table_constraints(TSqlParser::Column_def_table_constraintsContext *ctx)
{
handleTableConstraintWithoutComma(ctx);
}

// NB: this is copied in tsqlMutator
void exitConstant(TSqlParser::ConstantContext *ctx) override
{
// Check for floating-point number without exponent
handleFloatWithoutExponent(ctx);
}

// NB: this is copied in TsqlMutator
void exitUnary_op_expr(TSqlParser::Unary_op_exprContext *ctx) override
{
Expand Down Expand Up @@ -2406,31 +2413,6 @@ class tsqlBuilder : public tsqlCommonMutator
process_query_specification(ctx, statementMutator.get());
}

void exitColumn_def_table_constraints(TSqlParser::Column_def_table_constraintsContext *ctx)
{
/*
* It is not documented but it seems T-SQL allows that column-definition is followed by table-constraint without COMMA.
* PG backend parser will throw a syntax error when this kind of query is inputted.
* It is not easy to add a rule accepting this syntax to backend parser because of many shift/reduce conflicts.
* To handle this case, add a compensation COMMA between column-definition and table-constraint here.
*
* This handling should be only applied to table-constraint following column-definition. Other cases (such as two column definitions without comma) should still throw a syntax error.
*/

ParseTree* prev_child = nullptr;
for (ParseTree* child : ctx->children)
{
TSqlParser::Column_def_table_constraintContext* cdtctx = dynamic_cast<TSqlParser::Column_def_table_constraintContext*>(child);
if (cdtctx && cdtctx->table_constraint())
{
TSqlParser::Column_def_table_constraintContext* prev_cdtctx = (prev_child ? dynamic_cast<TSqlParser::Column_def_table_constraintContext*>(prev_child) : nullptr);
if (prev_cdtctx && prev_cdtctx->column_definition())
rewritten_query_fragment.emplace(std::make_pair(cdtctx->start->getStartIndex(), std::make_pair("", ","))); // add comma
}
prev_child = child;
}
}

void exitDrop_relational_or_xml_or_spatial_index(TSqlParser::Drop_relational_or_xml_or_spatial_indexContext *ctx) override
{
/*
Expand Down Expand Up @@ -2914,15 +2896,21 @@ class tsqlMutator : public TSqlParserBaseListener
}
}
}


// NB: this is copied in tsqlBuilder
void exitColumn_def_table_constraints(TSqlParser::Column_def_table_constraintsContext *ctx)
{
handleTableConstraintWithoutComma(ctx);
}

// NB: this is copied in tsqlBuilder
void exitConstant(TSqlParser::ConstantContext *ctx) override
{
// Check for floating-point number without exponent
handleFloatWithoutExponent(ctx);
}

// NB: this is copied in tsqlBuilder
// NB: this is copied in tsqlBuilder
void exitUnary_op_expr(TSqlParser::Unary_op_exprContext *ctx) override
{
handleBitNotOperator(ctx);
Expand Down Expand Up @@ -6994,8 +6982,6 @@ post_process_declare_table_statement(PLtsql_stmt_decl_table *stmt, TSqlParser::T
{
if (ctx->column_def_table_constraints())
{
bool rewrite = false;

for (auto cdtctx : ctx->column_def_table_constraints()->column_def_table_constraint())
{
/*
Expand All @@ -7009,17 +6995,22 @@ post_process_declare_table_statement(PLtsql_stmt_decl_table *stmt, TSqlParser::T
auto tctx = cdtctx->column_definition()->TIMESTAMP();
std::string rewritten_text = "timestamp " + ::getFullText(tctx);
rewritten_query_fragment.emplace(std::make_pair(tctx->getSymbol()->getStartIndex(), std::make_pair(::getFullText(tctx), rewritten_text)));
rewrite = true;
}
}

if (rewrite)
/*
* Need to run the mutator to perform rewriting not only when items were added above,
* but also if rewrite items were added earlier - for example, in exitColumn_def_table_constraints()
* in case a table constraint was specified without a separator comma.
*/
if (rewritten_query_fragment.size() > 0)
{
PLtsql_expr *expr = makeTsqlExpr(ctx, false);
PLtsql_expr_query_mutator mutator(expr, ctx);
add_rewritten_query_fragment_to_mutator(&mutator);
mutator.run();
char *rewritten_query = expr->query;

// Save the rewritten column definition list
stmt->coldef = pstrdup(&rewritten_query[5]);
}
Expand Down Expand Up @@ -7952,6 +7943,34 @@ handleBitNotOperator(TSqlParser::Unary_op_exprContext *ctx)
return;
}

static void
handleTableConstraintWithoutComma(TSqlParser::Column_def_table_constraintsContext *ctx)
{
/*
* It is not documented but it seems T-SQL allows that column-definition is followed by table-constraint without COMMA.
* PG backend parser will throw a syntax error when this kind of query is inputted.
* It is not easy to add a rule accepting this syntax to backend parser because of many shift/reduce conflicts.
* To handle this case, add a compensation COMMA between column-definition and table-constraint here.
*
* This handling should be only applied to table-constraint following column-definition. Other cases (such as two column definitions without comma) should still throw a syntax error.
*/
ParseTree* prev_child = nullptr;
for (ParseTree* child : ctx->children)
{
TSqlParser::Column_def_table_constraintContext* cdtctx = dynamic_cast<TSqlParser::Column_def_table_constraintContext*>(child);
if (cdtctx && cdtctx->table_constraint())
{
TSqlParser::Column_def_table_constraintContext* prev_cdtctx = (prev_child ? dynamic_cast<TSqlParser::Column_def_table_constraintContext*>(prev_child) : nullptr);
if (prev_cdtctx && prev_cdtctx->column_definition())
{
rewritten_query_fragment.emplace(std::make_pair(cdtctx->start->getStartIndex(), std::make_pair("", ","))); // add comma
}
}
prev_child = child;
}
return;
}

static void
handleBitOperators(TSqlParser::Plus_minus_bit_exprContext *ctx)
{
Expand Down Expand Up @@ -7986,4 +8005,5 @@ handleModuloOperator(TSqlParser::Mult_div_percent_exprContext *ctx)
}
}
return;
}
}

48 changes: 48 additions & 0 deletions test/JDBC/expected/table_constraint_without_comma-vu-cleanup.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
DROP FUNCTION f1_tvf_nocomma
go
DROP FUNCTION f2_tvf_nocomma
go
DROP FUNCTION f3_tvf_nocomma
go
DROP FUNCTION f4_tvf_nocomma
go
DROP FUNCTION f5_tvf_nocomma
go
DROP FUNCTION f6_tvf_nocomma
go
DROP FUNCTION f7_tvf_nocomma
go
DROP FUNCTION f8_tvf_nocomma
go
DROP FUNCTION f9_tvf_nocomma
go
DROP PROCEDURE p1_tv_nocomma
go
DROP PROCEDURE p2_tv_nocomma
go
DROP PROCEDURE p3_tv_nocomma
go
DROP PROCEDURE p4_tv_nocomma
go
DROP PROCEDURE p5_tv_nocomma
go
DROP PROCEDURE p6_tv_nocomma
go
DROP PROCEDURE p7_tv_nocomma
go
DROP PROCEDURE p8_tv_nocomma
go
DROP PROCEDURE p9_tv_nocomma
go
DROP TABLE t1_tvf_nocomma
go
DROP TABLE t2_tvf_nocomma
go
DROP TABLE t3_tvf_nocomma
go
DROP TABLE t4_tvf_nocomma
go
DROP TABLE t5_tvf_nocomma
go
DROP TABLE t6_tvf_nocomma
go
153 changes: 153 additions & 0 deletions test/JDBC/expected/table_constraint_without_comma-vu-prepare.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
CREATE FUNCTION f1_tvf_nocomma(@p INT)
RETURNS @tv TABLE (a INT NOT NULL, b INT NOT NULL PRIMARY KEY(a))
AS
BEGIN
INSERT @tv VALUES(1,2)
IF @p > 0 INSERT @tv VALUES(1,3)
RETURN
END
go
CREATE FUNCTION f2_tvf_nocomma(@p INT)
RETURNS @tv TABLE (a INT NOT NULL, b INT NOT NULL PRIMARY KEY(a,b))
AS
BEGIN
INSERT @tv VALUES(1,2)
IF @p > 0 INSERT @tv VALUES(1,2)
RETURN
END
go
CREATE FUNCTION f3_tvf_nocomma(@p INT)
RETURNS @tv TABLE (a INT NOT NULL, b INT NOT NULL PRIMARY KEY(a))
AS
BEGIN
INSERT @tv VALUES(1,2)
IF @p > 0 INSERT @tv VALUES(1,3)
RETURN
END
go
CREATE FUNCTION f4_tvf_nocomma(@p INT)
RETURNS @tv TABLE (a INT NOT NULL, b INT NOT NULL UNIQUE(a,b))
AS
BEGIN
INSERT @tv VALUES(1,2)
IF @p > 0 INSERT @tv VALUES(1,2)
RETURN
END
go
CREATE FUNCTION f5_tvf_nocomma(@p INT)
RETURNS @tv TABLE (a INT NOT NULL, b INT NOT NULL PRIMARY KEY)
AS
BEGIN
INSERT @tv VALUES(1,2)
IF @p > 0 INSERT @tv VALUES(2,2)
RETURN
END
go
CREATE FUNCTION f6_tvf_nocomma(@p INT)
RETURNS @tv TABLE (a INT NOT NULL PRIMARY KEY, b INT NOT NULL)
AS
BEGIN
INSERT @tv VALUES(1,2)
IF @p > 0 INSERT @tv VALUES(1,3)
RETURN
END
go
CREATE FUNCTION f7_tvf_nocomma(@p INT)
RETURNS @tv TABLE (a INT NOT NULL, b INT NOT NULL UNIQUE)
AS
BEGIN
INSERT @tv VALUES(1,2)
IF @p > 0 INSERT @tv VALUES(2,2)
RETURN
END
go
CREATE FUNCTION f8_tvf_nocomma(@p INT)
RETURNS @tv TABLE (a INT NOT NULL UNIQUE, b INT NOT NULL)
AS
BEGIN
INSERT @tv VALUES(1,2)
IF @p > 0 INSERT @tv VALUES(1,3)
RETURN
END
go
CREATE FUNCTION f9_tvf_nocomma(@p INT)
RETURNS @tv TABLE (a INT NOT NULL, b INT NOT NULL CHECK(a>0))
AS
BEGIN
INSERT @tv VALUES(1,2)
IF @p > 0 INSERT @tv VALUES(0,3)
RETURN
END
go
CREATE PROCEDURE p1_tv_nocomma
AS
BEGIN
DECLARE @tv TABLE (a INT NOT NULL, b INT NOT NULL PRIMARY KEY(a))
INSERT @tv VALUES(1,2)
INSERT @tv VALUES(1,3)
END
go
CREATE PROCEDURE p2_tv_nocomma
AS
BEGIN
DECLARE @tv TABLE (a INT NOT NULL, b INT NOT NULL PRIMARY KEY(a,b))
INSERT @tv VALUES(1,2)
INSERT @tv VALUES(1,2)
END
go
CREATE PROCEDURE p3_tv_nocomma
AS
BEGIN
DECLARE @tv TABLE (a INT NOT NULL, b INT NOT NULL PRIMARY KEY(a))
INSERT @tv VALUES(1,2)
INSERT @tv VALUES(1,3)
END
go
CREATE PROCEDURE p4_tv_nocomma
AS
BEGIN
DECLARE @tv TABLE (a INT NOT NULL, b INT NOT NULL UNIQUE(a,b))
INSERT @tv VALUES(1,2)
INSERT @tv VALUES(1,2)
END
go
CREATE PROCEDURE p5_tv_nocomma
AS
BEGIN
DECLARE @tv TABLE (a INT NOT NULL, b INT NOT NULL PRIMARY KEY)
INSERT @tv VALUES(1,2)
INSERT @tv VALUES(2,2)
END
go
CREATE PROCEDURE p6_tv_nocomma
AS
BEGIN
DECLARE @tv TABLE (a INT NOT NULL PRIMARY KEY, b INT NOT NULL)
INSERT @tv VALUES(1,2)
INSERT @tv VALUES(1,3)
END
go
CREATE PROCEDURE p7_tv_nocomma
AS
BEGIN
DECLARE @tv TABLE (a INT NOT NULL, b INT NOT NULL UNIQUE)
INSERT @tv VALUES(1,2)
INSERT @tv VALUES(2,2)
END
go
CREATE PROCEDURE p8_tv_nocomma
AS
BEGIN
DECLARE @tv TABLE (a INT NOT NULL UNIQUE, b INT NOT NULL)
INSERT @tv VALUES(1,2)
INSERT @tv VALUES(1,3)
END
go
CREATE PROCEDURE p9_tv_nocomma
AS
BEGIN
DECLARE @tv TABLE (a INT NOT NULL, b INT NOT NULL CHECK(a>0))
INSERT @tv VALUES(1,2)
INSERT @tv VALUES(0,3)
END
go
Loading

0 comments on commit ee1a595

Please sign in to comment.