Skip to content

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):

1
2
3
4
5
6
7
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.