使用 DacFX 如何注释掉 TsqlFragment/TSqlScript 中的语句
Using DacFX how do I comment out a statement in a TsqlFragment/TSqlScript
如果我有一个带有一组语句的 TqlFragment 或 TsqlScript,我该如何使用 DacFX 注释掉特定的语句?没有 CommentStatement 或类似的东西。如何用它的注释版本替换语法树中的该语句?
我知道这可以通过纯文本编辑或正则表达式来完成,但此时我正在使用 DacFx 访问者模式来扫描某些语句。所以我需要继续使用那个约束。
正如您所发现的,没有用于评论的脚本 dom 语句,我们所拥有的是一个令牌流,它类似于一个语句,但级别稍低。
我不是 100% 确定您想要的是什么,但是如果我假设您有一个脚本并且想要注释掉一个语句,那么它可能是这样的。您说您需要保持访问者模式,但访问者不会返回评论,所以这应该做您想要的,您可以再次 re-parse 脚本或将其保留为 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,但即使它允许您,如果您对它进行任何操作,它也会被忽略,因此请将其视为不可变的(除非我遗漏了什么)。据我所知,这不起作用,但如果这样做的话,往返 text/parse 是不必要的:
/* - 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);
*/
希望对您有所帮助!
编辑
Ed,我re-worked对你的答案代码了解了一下。
- 修改访问者以处理多个语句
- 更改了代码以通过访问者语句向后循环,这样位置就不会被破坏
- 修改了插入评论标记的代码
观察:
- 正如您推断的那样,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*/;
如果我有一个带有一组语句的 TqlFragment 或 TsqlScript,我该如何使用 DacFX 注释掉特定的语句?没有 CommentStatement 或类似的东西。如何用它的注释版本替换语法树中的该语句?
我知道这可以通过纯文本编辑或正则表达式来完成,但此时我正在使用 DacFx 访问者模式来扫描某些语句。所以我需要继续使用那个约束。
正如您所发现的,没有用于评论的脚本 dom 语句,我们所拥有的是一个令牌流,它类似于一个语句,但级别稍低。
我不是 100% 确定您想要的是什么,但是如果我假设您有一个脚本并且想要注释掉一个语句,那么它可能是这样的。您说您需要保持访问者模式,但访问者不会返回评论,所以这应该做您想要的,您可以再次 re-parse 脚本或将其保留为 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,但即使它允许您,如果您对它进行任何操作,它也会被忽略,因此请将其视为不可变的(除非我遗漏了什么)。据我所知,这不起作用,但如果这样做的话,往返 text/parse 是不必要的:
/* - 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);
*/
希望对您有所帮助!
编辑
Ed,我re-worked对你的答案代码了解了一下。
- 修改访问者以处理多个语句
- 更改了代码以通过访问者语句向后循环,这样位置就不会被破坏
- 修改了插入评论标记的代码
观察:
- 正如您推断的那样,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*/;