How to Parse T-SQL / SQL Server in Java
To parse SQL Server T-SQL in Java, create a TGSqlParser with EDbVendor.dbvmssql and call parse() — GSP splits GO batches automatically and returns every statement in getSqlstatements(). T-SQL control-of-flow constructs map to dedicated classes in gudusoft.gsqlparser.stmt.mssql: TMssqlBlock for BEGIN...END, TMssqlIfElse for IF/WHILE, TMssqlTryCatch for TRY...CATCH, and TMssqlCreateProcedure for stored procedures.
This guide walks through each of these, plus DECLARE @var statements and #temp / ##global temporary tables.
Prerequisites
Batches and GO Separators
GO is a batch separator, not a SQL statement — and GSP handles it for you. After parse(), each batch's statements appear as separate flat entries in getSqlstatements(); the GO itself surfaces as a TMssqlGo statement (type sstmssqlgo), so skip it when iterating:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 | import gudusoft.gsqlparser.TGSqlParser;
import gudusoft.gsqlparser.EDbVendor;
import gudusoft.gsqlparser.ESqlStatementType;
import gudusoft.gsqlparser.TCustomSqlStatement;
public class ParseBatches {
public static void main(String[] args) {
TGSqlParser parser = new TGSqlParser(EDbVendor.dbvmssql);
parser.sqltext =
"CREATE TABLE dbo.orders (id INT, total MONEY)\n" +
"GO\n" +
"INSERT INTO dbo.orders VALUES (1, 99.50)\n" +
"GO\n" +
"SELECT TOP 10 * FROM dbo.orders";
if (parser.parse() != 0) {
System.err.println(parser.getErrormessage());
return;
}
for (int i = 0; i < parser.getSqlstatements().size(); i++) {
TCustomSqlStatement stmt = parser.getSqlstatements().get(i);
if (stmt.sqlstatementtype == ESqlStatementType.sstmssqlgo) {
continue; // batch separator
}
System.out.println(stmt.sqlstatementtype + ": "
+ stmt.toString().replaceAll("\\s+", " "));
}
}
}
|
GO n (repeat the batch n times) is parsed as well. If you only need to split a huge script into batches without building a full parse tree, use getrawsqlstatements() instead of parse() — see Performance Optimization.
BEGIN...END Blocks and IF/ELSE
BEGIN ... END parses to TMssqlBlock (type sstmssqlblock); the enclosed statements are in getBodyStatements(). IF and WHILE both parse to TMssqlIfElse (type sstmssqlif) with getCondition(), getStmt() for the body, and getElseStmt() for the optional ELSE branch:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39 | import gudusoft.gsqlparser.TGSqlParser;
import gudusoft.gsqlparser.EDbVendor;
import gudusoft.gsqlparser.TCustomSqlStatement;
import gudusoft.gsqlparser.stmt.mssql.TMssqlBlock;
import gudusoft.gsqlparser.stmt.mssql.TMssqlIfElse;
public class ParseIfElse {
public static void main(String[] args) {
TGSqlParser parser = new TGSqlParser(EDbVendor.dbvmssql);
parser.sqltext =
"IF EXISTS (SELECT 1 FROM dbo.orders WHERE total > 1000)\n" +
"BEGIN\n" +
" UPDATE dbo.customers SET tier = 'gold' WHERE id = @cust_id;\n" +
" INSERT INTO dbo.audit_log (msg) VALUES ('upgraded');\n" +
"END\n" +
"ELSE\n" +
" PRINT 'no large orders';";
if (parser.parse() != 0) {
System.err.println(parser.getErrormessage());
return;
}
TMssqlIfElse ifStmt = (TMssqlIfElse) parser.getSqlstatements().get(0);
System.out.println("Condition: " + ifStmt.getCondition());
if (ifStmt.getStmt() instanceof TMssqlBlock) {
TMssqlBlock block = (TMssqlBlock) ifStmt.getStmt();
System.out.println("THEN block statements: "
+ block.getBodyStatements().size());
for (TCustomSqlStatement s : block.getBodyStatements()) {
System.out.println(" " + s.sqlstatementtype);
}
}
if (ifStmt.getElseStmt() != null) {
System.out.println("ELSE: " + ifStmt.getElseStmt().sqlstatementtype);
}
}
}
|
Because containers nest (IF inside WHILE inside BEGIN...END), the robust way to reach every DML statement is the recursive getStatements() walk shown in Parse Oracle PL/SQL — it works identically for T-SQL.
TRY...CATCH
Inside procedure and block bodies, BEGIN TRY ... END TRY BEGIN CATCH ... END CATCH parses to TMssqlTryCatch (type sstmssqlTryCatch) with the two branches cleanly separated:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 | import gudusoft.gsqlparser.TGSqlParser;
import gudusoft.gsqlparser.EDbVendor;
import gudusoft.gsqlparser.stmt.mssql.TMssqlBlock;
import gudusoft.gsqlparser.stmt.mssql.TMssqlCreateProcedure;
import gudusoft.gsqlparser.stmt.mssql.TMssqlTryCatch;
public class ParseTryCatch {
public static void main(String[] args) {
TGSqlParser parser = new TGSqlParser(EDbVendor.dbvmssql);
parser.sqltext =
"CREATE PROCEDURE dbo.usp_safe_delete AS\n" +
"BEGIN\n" +
" BEGIN TRY\n" +
" DELETE FROM dbo.orders WHERE id = 1;\n" +
" END TRY\n" +
" BEGIN CATCH\n" +
" SELECT ERROR_NUMBER() AS err_no, ERROR_MESSAGE() AS err_msg;\n" +
" END CATCH;\n" +
"END;";
if (parser.parse() != 0) {
System.err.println(parser.getErrormessage());
return;
}
TMssqlCreateProcedure proc =
(TMssqlCreateProcedure) parser.getSqlstatements().get(0);
TMssqlBlock body = (TMssqlBlock) proc.getBodyStatements().get(0);
TMssqlTryCatch tryCatch = (TMssqlTryCatch) body.getBodyStatements().get(0);
System.out.println("TRY statements: " + tryCatch.getTryStatements().size());
System.out.println("CATCH statements: " + tryCatch.getCatchStatements().size());
// Both return TStatementList — iterate with size()/get(i)
}
}
|
Stored Procedures
CREATE PROCEDURE parses to TMssqlCreateProcedure (type sstmssqlcreateprocedure). It exposes the same accessors as the Oracle PL/SQL classes: getProcedureName(), getParameterDeclarations(), and getBodyStatements().
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 | import gudusoft.gsqlparser.TGSqlParser;
import gudusoft.gsqlparser.EDbVendor;
import gudusoft.gsqlparser.TCustomSqlStatement;
import gudusoft.gsqlparser.stmt.mssql.TMssqlCreateProcedure;
import gudusoft.gsqlparser.nodes.TParameterDeclaration;
public class ParseProcedure {
public static void main(String[] args) {
TGSqlParser parser = new TGSqlParser(EDbVendor.dbvmssql);
parser.sqltext =
"CREATE PROCEDURE dbo.usp_close_orders @cutoff DATETIME AS\n" +
"BEGIN\n" +
" UPDATE dbo.orders SET status = 'closed' WHERE created_at < @cutoff;\n" +
" DELETE FROM dbo.order_staging WHERE created_at < @cutoff;\n" +
"END";
if (parser.parse() != 0) {
System.err.println(parser.getErrormessage());
return;
}
TMssqlCreateProcedure proc =
(TMssqlCreateProcedure) parser.getSqlstatements().get(0);
System.out.println("Procedure: " + proc.getProcedureName());
for (int i = 0; i < proc.getParameterDeclarations().size(); i++) {
TParameterDeclaration param =
proc.getParameterDeclarations().getParameterDeclarationItem(i);
System.out.println(" param: " + param);
}
System.out.println("Body statements: " + proc.getBodyStatements().size());
}
}
|
The body typically contains a single TMssqlBlock; descend into its getBodyStatements() (or use the recursive walk) to reach the DML.
DECLARE @variables
DECLARE @n INT parses to TMssqlDeclare (type sstmssqldeclare):
| import gudusoft.gsqlparser.stmt.mssql.TMssqlDeclare;
TMssqlDeclare declare = (TMssqlDeclare) stmt;
for (int i = 0; i < declare.getVariables().size(); i++) {
System.out.println(declare.getVariables().getDeclareVariable(i).getVariableName()
+ " : " + declare.getVariables().getDeclareVariable(i).getDatatype());
}
|
Temporary Tables
Local (#name) and global (##name) temp tables appear as ordinary TTable references — the # prefix is kept in the name, and the kind is flagged on the table name:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 | import gudusoft.gsqlparser.TGSqlParser;
import gudusoft.gsqlparser.EDbVendor;
import gudusoft.gsqlparser.ETableKind;
import gudusoft.gsqlparser.stmt.TCreateTableSqlStatement;
public class TempTables {
public static void main(String[] args) {
TGSqlParser parser = new TGSqlParser(EDbVendor.dbvmssql);
parser.sqltext = "CREATE TABLE #order_snapshot (id INT, total MONEY)";
if (parser.parse() == 0) {
TCreateTableSqlStatement create =
(TCreateTableSqlStatement) parser.getSqlstatements().get(0);
System.out.println("Name: " + create.getTargetTable().getTableName());
System.out.println("Local temp? "
+ (create.getTargetTable().getTableName().getTableKind()
== ETableKind.etkTemporary));
}
}
}
|
getTableName().getTableKind() returns ETableKind.etkTemporary for #local and ETableKind.etkGlobalTemporary for ##global tables.
SELECT ... INTO #temp FROM ... works the same way — the temp table shows up in the statement's table list with the kind flag set.
This matters for lineage and impact analysis: you usually want to trace through temp tables rather than report them as real dependencies.
Statement Type Reference
| T-SQL construct |
Class (in gudusoft.gsqlparser.stmt.mssql) |
ESqlStatementType |
GO |
TMssqlGo |
sstmssqlgo |
BEGIN ... END |
TMssqlBlock |
sstmssqlblock |
IF / WHILE |
TMssqlIfElse |
sstmssqlif |
BEGIN TRY ... END CATCH |
TMssqlTryCatch |
sstmssqlTryCatch |
CREATE PROCEDURE |
TMssqlCreateProcedure |
sstmssqlcreateprocedure |
DECLARE @var |
TMssqlDeclare |
sstmssqldeclare |
Common Pitfalls
- Don't pre-split on
GO yourself. The parser understands batches; naive string splitting breaks on GO inside comments or strings.
IF and WHILE share one class. Both are TMssqlIfElse; distinguish them with getStmtType() if needed.
- Bracketed identifiers (
[dbo].[Order Details]) and @variables are handled natively — no preprocessing needed.
- Wrong vendor = syntax errors.
TOP, #temp, and @vars only parse under dbvmssql; see Error Handling for diagnosing vendor mismatches.
Related Pages