使用TransactSql.ScriptDom无法检测T-SQL语法错误,如何解决?
问题
我开发了一个C#应用,试图创建用于T-SQL解析的函数,核心目标是检测T-SQL脚本中的语法错误。相关代码如下:
using Microsoft.SqlServer.TransactSql.ScriptDom; public void Parser(string tsql) { TSql160Parser parser = new TSql160Parser(true);// false); IList<ParseError> errors1; errors1 = new List<ParseError> { }; using (StringReader sr = new StringReader(tsql)) { TSqlFragment fragment = parser.Parse(sr, out errors1); if (errors1.Count > 0) { Console.WriteLine($"PARSE: Syntax error(s): {errors1.Count}"); } IEnumerable<string> batches = GetBatches(fragment); foreach (var batch in batches) { Console.WriteLine(batch); } } }
当我使用以下测试脚本进行测试时:
DECLARE @x INT = 0; DECLARE @y INT = NULL; DECLARE @xy INTUGER = 0; DECLARE @xy INT = 0; IF @x > @xy PRINT 1; ELSE PRINT 0;
该函数未检测到任何错误,但SSMS解析会报出错误:
Msg 134, Level 15, State 1, Line 4 The variable name '@xy' has already
been declared. Variable names must be unique within a query batch or
stored procedure.
此外,测试以下脚本时,该C#函数同样未检测到错误:
DECLARE @xy INT = 0; DECLARE @xy MOO = 0;
请问如何通过TransactSql.ScriptDom正确解析T-SQL脚本以检测语法错误?
解决方案
TSql160Parser的Parse方法仅负责语法层面的解析,即检查SQL语句是否符合T-SQL的语法规则(比如关键字拼写、语句结构是否正确),但不会进行语义分析(比如变量重复声明、类型是否存在这类逻辑层面的错误)。你遇到的变量重复声明、自定义类型不存在的问题都属于语义错误,需要自行遍历解析后的TSqlFragment实现检测。
实现步骤
- 创建自定义访问器:继承
TSqlFragmentVisitor,遍历脚本中的变量声明节点,跟踪每个批次内的变量名。 - 检测重复变量声明:在访问
DeclareVariableElement节点时,记录变量名,遇到重复则标记错误。 - 检测无效数据类型:检查变量声明中的数据类型是否为SQL Server内置有效类型(可根据需求扩展自定义类型检测)。
示例代码
首先实现自定义访问器:
using Microsoft.SqlServer.TransactSql.ScriptDom; using System.Collections.Generic; public class VariableValidationVisitor : TSqlFragmentVisitor { private readonly HashSet<string> _declaredVariables = new HashSet<string>(); private readonly HashSet<string> _validSqlTypes = new HashSet<string> { "INT", "VARCHAR", "NVARCHAR", "DATE", "DATETIME", "BIT", "DECIMAL", "FLOAT" // 可根据SQL Server版本补充更多内置类型 }; public List<string> ValidationErrors { get; } = new List<string>(); public override void Visit(DeclareVariableElement node) { // 检查变量名重复 var varName = node.VariableName.Value; if (_declaredVariables.Contains(varName)) { ValidationErrors.Add($"Line {node.StartLine}: 变量名 '@{varName}' 已重复声明"); } else { _declaredVariables.Add(varName); } // 检查数据类型是否有效 var typeName = node.DataType.Name.BaseIdentifier.Value.ToUpper(); if (!_validSqlTypes.Contains(typeName)) { ValidationErrors.Add($"Line {node.StartLine}: 无效的数据类型 '{typeName}'"); } base.Visit(node); } // 重置状态,处理下一个批次 public void Reset() { _declaredVariables.Clear(); ValidationErrors.Clear(); } }
修改解析函数,添加语义检查:
public void Parser(string tsql) { TSql160Parser parser = new TSql160Parser(true); IList<ParseError> syntaxErrors; using (StringReader sr = new StringReader(tsql)) { TSqlFragment fragment = parser.Parse(sr, out syntaxErrors); // 输出语法错误 if (syntaxErrors.Count > 0) { Console.WriteLine($"语法错误数量: {syntaxErrors.Count}"); foreach (var error in syntaxErrors) { Console.WriteLine($"Line {error.Line}: {error.Message}"); } } // 处理每个批次的语义检查 var visitor = new VariableValidationVisitor(); if (fragment is TSqlScript script) { foreach (var batch in script.Batches) { visitor.Reset(); batch.Accept(visitor); if (visitor.ValidationErrors.Count > 0) { Console.WriteLine($"批次语义错误:"); foreach (var error in visitor.ValidationErrors) { Console.WriteLine(error); } } } } } }
说明
- 上述代码可检测变量重复声明和无效数据类型,覆盖你测试脚本中的两个问题。
- 若需要检测更多语义错误(比如未声明变量引用、函数不存在等),可扩展
TSqlFragmentVisitor的其他Visit方法,遍历对应节点进行检查。 - 内置数据类型集合可根据需求补充完整,也可添加自定义类型的检测逻辑。
内容的提问来源于stack exchange,提问作者ZedZip
相关产品推荐
相关产品推荐

