You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 00:23:17