一、背景
随着得物离线业务的快速增长,为了脱离全托管服务的一些限制和享受技术发展带来的成本优化,公司提出了大数据Galaxy开源演进项目,将离线业务从全托管且封闭的环境迁移到一个开源且自主可控的生态系统中,而离线开发治理套件是Galaxy自研体系中一个核心的项目,在数据开发IDE中最核心的就是SQL编辑器,我们需要一个SQL解析引擎在SQL编辑提供适配得物自研Spark引擎的语法定义,实时语法解析,语法补全,语法校验等能力,结合业内dataworks和dataphin的实践,我们最终选用ANTLR作为SQL解析引擎底座。
二、ANTLR4 简介
ANTLR(一种语法解析引擎工具)是一个功能强大的解析器生成器,用于读取、处理、执行或翻译结构化文本或二进制文件。它广泛用于构建语言、工具和框架。ANTLR可以根据语法规则文件生成一个可以构建和遍历解析树的解析器。
ANTLR4 特性
ANTLR4 是一个强大的工具,适合用于语言处理、编译器构建、代码分析等多种场景。它的易用性、灵活性和强大的特性使得它成为开发者的热门选择。
- 强大的文法定义:ANTLR4 允许用户使用简单且易读的文法语法来定义语言的结构。这使得创建和维护语言解析器变得更加直观,同时在复杂文法构造上支持左递归文法、嵌套结构以及其他复杂的文法构造,使得能够解析更复杂的语言结构。
- 抽象语法树遍历:ANTLR4 可以生成抽象语法树,使得在解析源代码时能够更容易地进行分析和变换。AST 是编译器和解释器的核心组件。同时提供了简单的 API 来遍历生成的语法树,使得实现代码分析、转换等操作变得简单
- 自动语法错误处理:ANTLR4 提供了内置的错误处理机制,可以在解析过程中自动处理语法错误,并且可以自定义错误消息和处理逻辑
- 可扩展性:ANTLR4 允许用户扩展和自定义生成的解析器的行为。例如,您可以自定义解析器的方法、错误处理以及其他功能。
- 工具&社区生态:ANTLR4 提供了丰富的工具支持,包括命令行工具、集成开发环境插件和可视化工具,可以帮助您更轻松地开发和调试解析器。同时拥有活跃的社区,提供了大量的文档、示例和支持。这使得新用户能够快速上手,并得到必要的帮助。
ANTLR4 的应用场景
Apache Spark: 流行的大数据处理框架,使用ANTLR作为其SQL解析器的一部分,支持SQL查询。
Twitter: Twitter 使用ANTLR来解析和分析用户的查询语言,这有助于他们的搜索和分析功能。
IBM: IBM使用ANTLR来支持一些其产品和工具中的DSL(领域特定语言)解析需求,例如,在其企业集成解决方案中。
ANTLR4入门
ANTLR元语言
为了实现一门计算机编程语言,我们需要构建一个程序来读取输入语句,对其中的词组和符号进行识别处理,即我们需要语法解释器或者翻译器来识别出一门特定语言的所有词组,子词组,语句。我们将语法分析过程拆分为两个独立的阶段则为词法分析和语法分析。

ANTLR语法遵循了一种专门用来描述其他语言的语法,我们称之为ANTLR元语言(ANTLR’s meta-language)。ANTLR元语句是一个强大的工具,可以用来定义编程语言的语法。通过定义词法和语法规则,可以基于antlr生成解析器和词法分析器。
1、自顶向下
在语言结构中,整体的辨识都是从最粗的粒度开始,一直进行到最详细的层次,并把它们编写成为语法规则,ANTLR4就是采用自顶向下的,词法语法分离,上下文无关的语法框架来描述语言。
// MyGLexer.g4
lexer grammar MyGLexer;
SEMICOLON: ';';
LEFT_PAREN: '(';
RIGHT_PAREN: ')';
COMMA: ',';
DOT: '.';
LEFT_BRACKET: '[';
RIGHT_BRACKET: ']';
LEFT_BRACES: '{';
RIGHT_RACES: '}';
EQ: '=';
FUNCTOM: 'FUNCTION';
LET: 'LET';
CONST: 'CONST';
VAR: 'VAR';
IF: 'IF';
ELSE: 'ELSE';
WHILE: 'WHILE';
FOR: 'FOR';
RETURN: 'RETURN';
// MyGParser.g4
parser grammar MyGParser;
options {
tokenVocab = MyGLexer;
}
// 入口规则
program: statement* EOF;
statement:
variableDeclaration
| functionDeclaration
| expressionStatement
| blockStatement
| ifStatement
| whileStatement
| forStatement
| returnStatement;
......
2、语言模式
计算机语言常见4种语言模式:序列(sequence)、选择(choice)、词法符号依赖 (token dependency),以及嵌套结构(nested phrase)。以下是ANTLR对4种模式的语法规则描述。

3、语法歧义
在自顶向下的语法和手工编写的递归下降语法分析器中,处理表达式都是一件相当棘手的事情,这首先是因为大多数语法都存在歧义,其次是因为大多数语言的规范使用了一种特殊的递归方式,称为左递归。
expr : expr '*' expr
| expr '+' expr
| INT
;
我们举个运算符优先级带来的语法歧义问题,同样的规则可以匹配多个输入字符流。

在其他语法工具中,通常通过指定额外的标记来指定运算符优先级。而在ANTLR4中通过备选分支的排序来指定优先级,越靠前优先级越高。
代码自动生成
ANTLR可以根据lexer.g4和parser.g4自动生成词法分析器,语法分析器,监听器,访问器等。
antlr4ng -Dlanguage=TypeScript -visitor -listener -Xexact-output-dir -o ./src/lib ./src/grammar/*.g

语法解析与业务逻辑解耦
在ANTLR4中语法解析和业务逻辑的高度解耦是一个重要的设计理念,优点就是同一个 AST 结构能够在不同的业务逻辑实现之间实现复用。不同的业务逻辑(如执行、转换、优化等)可以对同一个 AST 进行不同的处理,而不需要关心解析过程。核心几个设计方案如下:
- 访问者模式:ANTLR4通过访问者模式支持业务代码可访问特定“词法”或“语法”节点执行自定义的操作,通过这个方式完全解耦AST(抽象语法树)生成和业务逻辑,词法分析器和解释器专注于AST生成,而业务可以通过访问器的扩展支持业务定制化诉求。
- 语法和语义的独立性:ANTLR4中可以独立进行语法解析和语义分析,可以在 AST 中进行语义检查和业务逻辑处理。这种分离使得开发者可以更灵活地处理输入的语法和语义。
- AST生成:ANRL4通过语法解析器生成结构化AST(抽象语法树),不同业务逻辑可以不断复用同一个AST。
- 上下文模式:解析器在处理输入数据时,上下文会在解析树中传递信息。每当进入一个新的语法规则时,都会创建一个新的上下文实例上下文可以存储解析过程中需要的临时信息,例如变量的值、数据类型等。上下文信息主要结合访问器模式进行使用,同时也解决了在解析复杂语句如多层嵌套结构的层级调用问题。
三、SparkSQL介绍
Spark SQL 是 Apache Spark 的一个模块,专门用于处理结构化数据,Spark SQL 的特点包括:
- 高效的查询执行:通过 Catalyst 优化器和 Tungsten 执行引擎,Spark SQL 能够优化查询执行计划,提升查询性能。
- 与 Hive 的兼容性:Spark SQL 支持 HiveQL 语法,使得用户可以轻松迁移现有的 Hive 查询。
- 支持多种数据源:Spark SQL 可以从多种数据源读取数据,包括 HDFS、Parquet、ORC、JDBC 等。
四、技术实现
语法设计
在Aparch Spark源码中就是使用ANTLR4来解析和处理SQL语句,以下为Apach Spark中基于ANTLR元语言定义的词法分析器和语法分析器,在语法定义上我们只需要基于这套标准的SparkSQL语法去适配得物自研引擎的能力,做能力对齐。
Lexer.g4
https://github.com/apache/spark/blob/master/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseLexer.g4
Parser.g4
https://github.com/apache/spark/blob/master/sql/api/src/main/antlr4/org/apache/spark/sql/catalyst/parser/SqlBaseParser.g4
语法补全
以下我们以字段补全场景为例解析从语法定义,语法解析,语法补全,上下文信息采集各个流程节点剖析最后完成的表字段信息精准推荐。在下列语法场景中,存在多层Select语法嵌套,同时表du_emr_test.empsalary tableB和表du_emr_test.hujh_type_tk AS tableB设置了同一别名, 如图在父子查询中都使用了同一个表别名(tableB),当用户在父子查询中分别输入**tableB.**时,这时候需要结合当前上下文语境,对tableB别名推荐不同表的字段。
SELECT
tableB.c1
FROM
(
SELECT
tableB.empno,
tableC.department
FROM
du_emr_test.empsalary as tableB
LEFT JOIN du_emr_test.employees AS tableC
WHERE tableC.department = tableB.depname
) AS tableA
LEFT JOIN du_emr_test.hujh_type_tk AS tableB
WHERE tableB.c1 = tableA.dename



最低0.47元/天 解锁文章
600

被折叠的 条评论
为什么被折叠?



