0

如果我有带有一组语句的 TqlFragment 或 TsqlScript,如何使用 DacFX 注释掉特定语句?没有 CommentStatement 或类似的东西。如何用它的注释版本替换语法树中的该语句?

我知道这可以通过纯文本编辑或正则表达式来完成,但此时我正在使用 DacFx 访问者模式来扫描某些语句。所以我需要继续使用那个约束。

4

2 回答 2

2

正如您所发现的,没有用于注释的脚本 dom 语句,我们所拥有的是一个类似于语句但级别略低的令牌流。

我不是 100% 确定你在追求什么,但如果我假设你有一个脚本并且想要注释掉一个语句,那么就有可能像这样。您说您需要保持访问者模式,但访问者不会返回评论,所以这应该做您想做的事情,您可以再次重新解析脚本或将其保留为 sql 并做您喜欢的事情用它:

   static void Main(string[] args)
    {

        var sqlText = @"
         create procedure something
            as
                select 100;
                select 200

                exec sp_who2;              
        ";

        var sql = new StringReader(sqlText);

        var parser = new TSql140Parser(false);
        IList<ParseError> errors;
        var script = parser.Parse(sql, out errors);
        var visitor = new visitor();
        script.Accept(visitor);

        TSqlParserToken startComment = new TSqlParserToken(TSqlTokenType.SingleLineComment, "/*");
        TSqlParserToken endComment = new TSqlParserToken(TSqlTokenType.SingleLineComment, "*/");


        var newScriptSql = "";

        for (var i = 0; i < script.LastTokenIndex; i++)
        {
            if (i == visitor.Statement.FirstTokenIndex)
            {
                newScriptSql += startComment.Text;
            }

            if (i == visitor.Statement.LastTokenIndex)
            {
                newScriptSql += endComment.Text;
            }

            newScriptSql += script.ScriptTokenStream[i].Text;
        }

        script = parser.Parse(new StringReader(newScriptSql), out errors);

        Console.WriteLine(newScriptSql);
    }

    class visitor : TSqlFragmentVisitor
    {
        public ExecuteStatement Statement;
        public override void Visit(ExecuteStatement s)
        {
            Statement = s;
        }
    }
}

它的作用是传递上面的过程(将其放入代码文件或其他东西中)并使用访问者模式来查找 exec,一旦我们记录下该语句在脚本令牌流中的开始和结束位置,然后我们遍历将它们转换为 t-sql 的标记,当我们到达我们的语句时,我们插入一个 startComment,在语句之后我们插入一个 endToken(注意它在 ; 之前,所以你可能想要添加一些额外的逻辑来处理它)。

我尝试仅更改原始脚本的 ScriptTokenStream ,但即使它允许您,如果您对它执行任何操作,它也会被忽略,因此认为它是不可变的(除非我遗漏了什么)。据我所知,这不起作用,但如果这样做的话,会使往返文本/解析变得不必要:

        /* - changes the token stream but if you use a generator on script, they don't appear
        script.ScriptTokenStream.Insert(visitor.Statement.LastTokenIndex, endComment);
        script.ScriptTokenStream.Insert(visitor.Statement.FirstTokenIndex, startComment);
        */

希望能帮助到你!

于 2018-03-08T18:07:42.897 回答
1

埃德,我重新设计了你的答案代码。

  • 修改了访问者以处理多个语句
  • 将代码更改为通过访问者语句向后循环,这样位置就不会被破坏
  • 修改了代码以插入注释标记

观察:

  • 正如您推断的那样,当尝试写出令牌流时,sql 生成器会愉快地删除注释。
  • 以这种方式添加注释会从注释中排除行尾的分号。

代码:

class Program
{
    static void Main(string[] args)
    {
        var sqlText = @"
create procedure something
as
    select 100;
    select 200

    exec sp_who;              

    exec sp_who2;              
";

        var sql = new StringReader(sqlText);

        var parser = new TSql140Parser(false);
        var script = parser.Parse(sql, out IList<ParseError> errors);

        var visitor = new ExecVisitor();
        script.Accept(visitor);

        TSqlParserToken startComment = new TSqlParserToken(TSqlTokenType.SingleLineComment, "/*");
        TSqlParserToken endComment = new TSqlParserToken(TSqlTokenType.SingleLineComment, "*/");

        var newScriptTokenStream = new List<TSqlParserToken>(script.ScriptTokenStream);

        for(var i = visitor.Statements.Count - 1; i >= 0; i--)
        {
            var stmt = visitor.Statements[i];
            newScriptTokenStream.Insert(stmt.LastTokenIndex, endComment);
            newScriptTokenStream.Insert(stmt.FirstTokenIndex, startComment);
        }
        var newFragment = parser.Parse(newScriptTokenStream, out errors);
        Console.WriteLine(GetScript(newFragment.ScriptTokenStream));
    }

    private static string GetScript(IList<TSqlParserToken> tokenStream)
    {
        var sb = new StringBuilder();
        foreach(var t in tokenStream)
        {
            sb.Append(t.Text);
        }

        return sb.ToString();
    }
}

class ExecVisitor : TSqlFragmentVisitor
{
    public IList<ExecuteStatement> Statements { get; set; } = new List<ExecuteStatement>();
    public override void Visit(ExecuteStatement s)
    {
        Statements.Add(s);
    }
}

结果:

create procedure something
as
        select 100;
        select 200

        /*exec sp_who*/;

        /*exec sp_who2*/;
于 2018-03-12T16:48:49.960 回答