【问题标题】:Using ANTLR 4 to parse TSQL Joins in Views, Stored Procedures and Functions使用 ANTLR4 解析视图、存储过程和函数中的 SQL 连接
【发布时间】:2020-03-02 08:12:55
【问题描述】:

寻找一些关于使用 ANTLR 4 解析 SQL 连接的方法的验证。

我从以下语法生成了一个词法分析器、解析器和访问者。

https://github.com/antlr/grammars-v4/tree/master/tsql

然后我可以创建一个解析树(在这个例子中是一个视图),我可以使用我已经实现的侦听器开始遍历树。

ICharStream stream = CharStreams.fromstring(view);
ITokenSource lexer = new TSqlLexer(stream);
ITokenStream tokens = new CommonTokenStream(lexer);
TSqlParser parser = new TSqlParser(tokens);
parser.BuildParseTree = true;
IParseTree tree = parser.create_view();

TSqlListener listener = new TSqlListener();
ParseTreeWalker.Default.Walk(listener, tree);

我的问题是。我为连接提取标记的方法是“正确”且最有效的方法吗?

我的实现如下,我基于在连接条件下监听树的行走。我需要捕获表别名并将它们与列相关联,因此当我走树时我需要处于相同的上下文中。因此,我以一种方法手动下降。

public void EnterTable_sources([NotNull] TSqlParser.Table_sourcesContext context)
{
    var table_sources = context.table_source().ToList();

    foreach (var table_source in table_sources)
    {
        var item = table_source.table_source_item_joined();

        if (item != null)
        {
            //first aliases
            var source_item = item.table_source_item();

            if (source_item != null)
            {
                TableAlias tableAlias = new TableAlias();

                var table_name = source_item.table_name_with_hint();

                if (table_name != null)
                {
                    var fullTableName = table_name.GetText();

                    if (fullTableName.Contains('.'))
                    {
                        var nameParts = fullTableName.Split('.').ToList();

                        for (int i = 0; i <nameParts.Count; i++)
                        {
                            tableAlias.AddParts(nameParts);
                        }
                    }
                    else
                    {
                        tableAlias.AddParts(fullTableName);
                    }
                }

                var table_alias = source_item.as_table_alias();

                if (table_alias != null)
                {
                    tableAlias.Alias = table_alias.GetText();
                }

                JoinAnalysis.Aliases.Add(tableAlias);
            }

            var join_parts = item.join_part();

            foreach (var join_part in join_parts)
            {
                var table_source_joins = join_part.table_source();

                if (table_source_joins != null)
                {
                    //The join table and alias
                    var table_source_item_joined = table_source_joins.table_source_item_joined();

                    if (table_source_item_joined != null)
                    {
                        var joinAlias = new TableAlias();
                        var table_source_item = table_source_item_joined.table_source_item();
                        var table_name = table_source_item.table_name_with_hint();

                        if (table_name != null)
                        {
                            var fullTableName = table_name.GetText();

                            if (fullTableName.Contains('.'))
                            {
                                var nameParts = fullTableName.Split('.').ToList();
                                joinAlias.AddParts(nameParts);
                            }
                            else
                            {
                                joinAlias.AddParts(fullTableName);
                            }
                        }

                        if (table_source_item != null)
                        {
                            var table_alias = table_source_item.as_table_alias();

                            if (table_alias != null)
                            {
                                joinAlias.Alias = table_alias.GetText();
                            }
                        }

                        if (joinAlias.Alias != null)
                        {
                            JoinAnalysis.Aliases.Add(joinAlias);
                        }
                    }
                }

                var search_condition = join_part.search_condition();

                if (search_condition != null)
                {
                    //The join conditions
                    var conditions = search_condition.search_condition_and();

                    if (conditions != null)
                    {
                        foreach (var condition in conditions)
                        {
                            if (condition != null)
                            {
                                foreach (var search_condition_not in condition.search_condition_not())
                                {
                                    JoinCondition joinCondition = new JoinCondition();
                                    joinCondition.LineNumber = search_condition_not.Start.Line;
                                    var conditionText = search_condition_not.GetText();
                                    joinCondition.JoinConditionText = conditionText;
                                    var splitCondition = conditionText.Split("=");

                                    if (splitCondition.Length == 2)
                                    {
                                        joinCondition.LeftPart = splitCondition[0];
                                        joinCondition.RightPart = splitCondition[1];
                                    }

                                    JoinAnalysis.JoinConditions.Add(joinCondition);
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

是否有更好的方法可以使用所有其他已生成的侦听器方法来执行此操作,而无需手动下降到子节点?我缺少一些在节点行走时保持节点之间上下文的魔法吗?

【问题讨论】:

  • 不要削弱这个问题的有效性,但对于 .NET,已经有一个由 Microsoft 自己编写的 T-SQL parser。它有一个相对简单的访问者界面; here's an example 用于解析连接(用于查找对其他数据库的引用的目标略有不同)。

标签: sql-server parsing token antlr4


【解决方案1】:

您可以简单地聆听这些子规则的进入/退出调用,而不是手动深入到规则中的子元素。例如:您收听table_sources 并从那里下降到table_source_item_joinedtable_name_with_hint。相反,您可以简单地覆盖 EnterTable_name_with_hint 方法。

编写一个监听器是完全可以的,它只处理语言的非常特定的部分。查看this (C++) code in MySQL Workbench,我在其中创建了几个侦听器,每个侦听器只处理较大构造或单个对象的子部分。有用于创建表、更改表、列定义、触发器等的侦听器。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-30
    相关资源
    最近更新 更多