【发布时间】: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