如何使用Microsoft.SqlServer.Management.SqlParser中的类将T-SQL解析为AST

时间:2015-12-05 08:24:35

标签: c# sql-server tsql

Parser 只有 Parse 方法返回 ParseResult ,似乎我无法对 ParseResult <做什么/ strong>即可。 如何获取我的sql语句的抽象语法树,或者只是从sql解析元数据标记就可以了。

2 个答案:

答案 0 :(得分:4)

我做了一些研究,发现我可以使用反射来生成使用 SqlScript.WriteXml 解析的信息xml文件。 以下是示例代码,我不知道是否有更好的方法。

var rst = Parser.Parse(File.ReadAllText(@"*.sql"));
var fieldInfo = rst.GetType().GetField("sqlScript", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField);
var script = fieldInfo.GetValue(rst);
var writer = XmlWriter.Create("*.xml");
script.GetType().InvokeMember("WriteXml", BindingFlags.NonPublic| BindingFlags.Instance | BindingFlags.InvokeMethod
    , null, script, new object[] { writer });
writer.Close();

答案 1 :(得分:1)

我很兴奋你发现了AST!直接使用它需要使用动态变量来访问内部--命名空间中对象的.Child集合。

我建议访问现有的Xml字符串属性,而不是调用WriteXml。这样就不必处理因在XML注释中嵌套SQL注释而导致的问题(在XML注释中不能包含--; - -变为var rst = Parser.Parse(File.ReadAllText(@"*.sql")); var script = rst.GetType().GetProperty("Script", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(rst); var xml = script.GetType().BaseType.GetProperty("Xml").GetValue(script) as String; )。

// using Microsoft.SqlServer.Management.SqlParser.Parser;
// ...

var sql = File.ReadAllText(@"*.sql");
var scanner = new Scanner(new ParseOptions());
int scannerState = 0;
scanner.SetSource(sql, 0);
var allTokens = new List<MSSQL_Token_JS>();
MSSQL_Token_JS curToken = null;
do
{
    curToken = MSSQL_Token_JS.GetNext(scanner, sql, ref scannerState);
    allTokens.Add(curToken);
}
while (curToken.Value != Tokens.EOF);

//...

public class MSSQL_Token_JS
{
    public readonly string SourceSQL;
    public readonly Tokens Value;
    public readonly string Text;
    public readonly int ScannerState;
    public readonly int Start;
    public readonly int End;
    public readonly bool IsPairMatch;
    public readonly bool IsExecAutoParamHelp;

    private MSSQL_Token_JS(string SourceSQL, int tokenId, int ScannerState, int Start, int End, bool IsPairMatch, bool IsExecAutoParamHelp)
    {
        this.SourceSQL = SourceSQL;
        this.Value = (Tokens)tokenId;
        if (this.Value != Tokens.EOF)
        {
            this.Text = SourceSQL.Substring(Start, End - Start + 1);
        }

        this.ScannerState = ScannerState;
        this.Start = Start;
        this.End = End;
        this.IsPairMatch = IsPairMatch;
        this.IsExecAutoParamHelp = IsExecAutoParamHelp;
    }

    public static MSSQL_Token_JS GetNext(Scanner scanner, string SourceSQL, ref int ScannerState)
    {
        int start, end;
        bool isPairMatch, isExecAutoParamHelp;
        int tokenId = scanner.GetNext(ref ScannerState, out start, out end, out isPairMatch, out isExecAutoParamHelp);
        return new MSSQL_Token_JS(SourceSQL, tokenId, ScannerState, start, end, isPairMatch, isExecAutoParamHelp);
    }

    public override string ToString()
    {
        return String.Format("{0}:{1}", this.Value, this.Text);
    }
}

如果您真的愿意仅仅考虑元数据令牌,我找到了一个PowerShell示例here;它可以解决这个问题(为了完整性而包含样板元数据工厂):

$arrayOfIds = array('A0000-000000000001','B0000-000000000001','C0000-000000000001');  

$status = 1;

$sql = "SELECT name FROM my_table WHERE status = ? AND id IN ?";

$query = $this->db->query($sql, array($status, $arrayOfIds));