Apache Doris 源码分析2 - 词法分析和语法分析

1
2
3
4
Apache Doris 源码分析2 - 词法分析和语法分析
-- yakun
-- 2022-02-08 21:59:45
-- Apache Doris 源码基于 branch-0.15, commit: a1d1bd8965c3b97f2c4eaf4fd8405efd3b35368a

上回介绍了从 fe 进程启动, 到接收到一条 SQL 并执行的过程, 这回深入分析下 SQL 是如何从一个平平无奇的字符串, 变成了一个适合分析处理的对象的(AST 抽象语法树). 故事的起点还是回到上次的 handleQuery().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
//位置: fe/fe-core/src/main/java/org/apache/doris/qe/ConnectProcessor.java

private void handleQuery() {
MetricRepo.COUNTER_REQUEST_ALL.increase(1L);
// convert statement to Java string
String originStmt = null;
try {
byte[] bytes = packetBuf.array();
int ending = packetBuf.limit() - 1;
while (ending >= 1 && bytes[ending] == '\0') {
ending--;
}
originStmt = new String(bytes, 1, ending, "UTF-8");
// 执行到这里, 已经获取到了用户的查询语句最原始的字符串: originStmt
} catch (UnsupportedEncodingException e) {
// impossible
LOG.error("UTF8 is not supported in this environment.");
ctx.getState().setError("Unsupported character set(UTF-8)");
return;
}

// 忽略了一些逻辑:
// 通过正则表达式的方式直接拒绝特定的 SQL
// 一些审计的工作, 记录一下谁在什么时候发来了一个什么查询语句

// execute this query.
StatementBase parsedStmt = null;
List<Pair<StatementBase, Data.PQueryStatistics>> auditInfoList = Lists.newArrayList();
boolean alreadyAddedToAuditInfoList = false;
try {
List<StatementBase> stmts = analyze(originStmt); // 重点1: 词法分析和语法分析
for (int i = 0; i < stmts.size(); ++i) {
alreadyAddedToAuditInfoList = false;
ctx.getState().reset();
if (i > 0) {
ctx.resetReturnRows();
}
parsedStmt = stmts.get(i);
parsedStmt.setOrigStmt(new OriginStatement(originStmt, i));
parsedStmt.setUserInfo(ctx.getCurrentUserIdentity());
executor = new StmtExecutor(ctx, parsedStmt);
ctx.setExecutor(executor);
executor.execute(); // 重点2: 执行一条语句

if (i != stmts.size() - 1) {
ctx.getState().serverStatus |= MysqlServerStatusFlag.SERVER_MORE_RESULTS_EXISTS;
finalizeCommand();
}
auditInfoList.add(new Pair<>(executor.getParsedStmt(), executor.getQueryStatisticsForAuditLog()));
alreadyAddedToAuditInfoList = true;
}
} catch (IOException e) {
// 忽略了异常处理逻辑
}

// that means execute some statement failed
if (!alreadyAddedToAuditInfoList && executor != null) {
auditInfoList.add(new Pair<>(executor.getParsedStmt(), executor.getQueryStatisticsForAuditLog()));
}

// 忽略执行后的审计逻辑
}

先看重点1: 词法分析和语法分析 analyze(originStmt)的内部逻辑, 一会儿再看重点2.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//位置:fe/fe-core/src/main/java/org/apache/doris/qe/ConnectProcessor.java

// analyze the origin stmt and return multi-statements
private List<StatementBase> analyze(String originStmt) throws AnalysisException, DdlException {
LOG.debug("the originStmts are: {}", originStmt);
// Parse statement with parser generated by CUP&FLEX
SqlScanner input = new SqlScanner(new StringReader(originStmt), ctx.getSessionVariable().getSqlMode());
// 上面这条语句, 使用jflex创建了一个词法分析器
// 词法配置文件位置: fe/fe-core/src/main/jflex/sql_scanner.flex
// 编译后生成的类文件位置: ./fe/fe-core/target/generated-sources/jflex/org/apache/doris/analysis/SqlScanner.java
// 相关介绍参考: https://github.com/jflex-de/jflex

SqlParser parser = new SqlParser(input);
// 上面这条语句, 使用cup创建了一个语法分析器
// 语法配置文件位置: fe/fe-core/src/main/cup/sql_parser.cup
// 相关介绍参考: http://www2.cs.tum.edu/projects/cup/

try {
return SqlParserUtils.getMultiStmts(parser);
} catch (Error e) {
// 忽略异常处理逻辑
}
}

词法分析 jflex

词法分析的主类是 SqlScanner.java, 这个代码文件是通过工具处理 sql_scanner.flex 而生成的代码. 那咱们就先分析一下这个原始的 .flex 文件.

flex 文件的主要内容由 UserCode, Options and declarations, Lexical rules 三段组成, 段与段之间使用%%分割, 下面结合各段的内容, 分析一下其原理.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// 位置: fe/fe-core/src/main/jflex/sql_scanner.flex
// 下面只是节选了部分 sql_scanner.flex 中的代码

// 1. UserCode
// 第一段内容会原封不动的生成的将来的 Java 类中, 主要是 package 和 import 这些.
package org.apache.doris.analysis;

import java.io.StringWriter;

%%

// 2. Options and declarations
// 第二段内容中以%开头的字符串是 options, 这些配置内容会由后续的生成器读取,
// 其实就是所有配置项, 部分配置项是有参数的, 就直接在空格后写参数
// 第二段内容中不以%开头的内容, 是宏定义的部分,
// 这些宏定义都是 单词 = 正则表达式 的形式,
// 比如: LineTerminator = \r|\n|\r\n, 单词也可以作为其它单词的正则表达式组成部分,
// 这些宏定义会被第三段的 Rule 部分使用
%class SqlScanner //代表了要生成的 JAVA 文件名是SqlScanner.java
%cup //代表和要介绍的语法生成工具 CUP 进行配合
LineTerminator = \r|\n|\r\n // 宏定义都是 单词 = 正则表达式
%state EOLHINT 代表了 EOLHINT 是一个合法的词


%%

// 3. Lexical rules
// 第三段内容的形式是 正则表达式和对应的 Action(Java代码), 代表了扫描到这个正则表达式时要执行的操作
// 这里的操作主要是识别出是不是关键词, 还是普通的标识符, 或者注释, 或者运行操作符等,
// 然后把扫描出的结果修改封装成一个token对象, 返回.

// 每当扫描到字符,的时候, 就返回一个COMMA类型的token实例
"," { return newToken(SqlParserSymbols.COMMA, null); }

// 扫描到一个注释的时候就把注释去掉
{QuotedIdentifier} {
// Remove the quotes
String trimmedIdent = yytext().substring(1, yytext().length() - 1);
return newToken(SqlParserSymbols.IDENT, SqlUtils.escapeUnquote(trimmedIdent));
}

// 扫描到一个关键字就返回kw_id.intValue()类型的token实例, 如果不是关键字而是一个普通的标识符就返回IDENT类型的token实例
{IdentifierOrKw} {
String text = yytext();
Integer kw_id = keywordMap.get(text.toLowerCase());
/* Integer kw_id = keywordMap.get(text); */
if (kw_id != null) {
// if MODE_PIPES_AS_CONCAT is not active, treat '||' symbol as same as 'or' symbol
if ((kw_id == SqlParserSymbols.KW_PIPE) &&
((this.sql_mode & SqlModeHelper.MODE_PIPES_AS_CONCAT) == 0)) {
return newToken(SqlParserSymbols.KW_OR, text);
}
return newToken(kw_id.intValue(), text);
} else {
return newToken(SqlParserSymbols.IDENT, text);
}
}

通过上面的代码片段, 可以看出 jflex 词法分析这个函数的输入和输出分别为:

  • 输入:
    • 一个 sql 字符串
    • 词法上来说, 所有合法的单词的正则表达式
    • 特定类型的单词的独特处理方法
    • 关键词/操作符列表
  • 输出:
    • 一个可以返回单词列表, 或者抛出异常函数: public java_cup.runtime.Symbol next_token()

要完成从这种输入到输出的转换逻辑, flex 会构造有限状态自动机并放到输出的 JAVA 类中, JAVA类在扫描字符串的过程中通过这个有限状态自动机来找到一个单词(匹配过程使用最长匹配优先的原则), 有限状态自动机的原理比较通用, 大学编译原理课程里面都会学, 感兴趣的同学可以自行了解.

语法分析 cup

下面以 fe/fe-core/src/main/cup/sql_parser.cup 文件的内容, 介绍一下 CUP 的规范说明.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
// 1. package and import specifications
// 第一部分是包名和 imports
package org.apache.doris.analysis;
import java.math.BigDecimal;

// class name;生成目标文件的类名, 但是doris里面没有写, 而是写在 pom 文件中了, 效果一致


// 2. user code components
// 第二部分是用户要嵌入的代码, 这些代码会原封不动的嵌入到目标生成的类文件中
// 根据需要不同, 共有4个可选的嵌入用户自定义代码区域
// - a. action code {: ... :}; // doris 暂未使用
// 这部分代码会直接放到未来生成的 action 类中
// - b. parser code {: ... :}; // doris 声明了这部分
// 这部分代码会直接放到未来生成的 解析器类 中
// doris 中重写了一部分逻辑包含:
// - public void syntax_error(Symbol token)
// - public void unrecovered_syntax_error(Symbol cur_token)
// - c. init with {: ... :}; // doris 暂未使用
// 这部分在解析器获取第一个符号之前被执行, 通常用来初始化词法扫描器
// - d. scan with{: ... :}; // doris 暂未使用
// 这部分代码是解析器应该怎么从词法扫描器获得下一个 token 的逻辑方法


// 3. symbol (terminal and non-terminal) lists
// 第三部分是终结符和非终结符的列表
// 终结符是不可再分割的符号, 在语法树的叶子节点上; 非终结符是除了终结符以外的其它的符号

// 像关键字, 运算符这些都属于终结符
terminal String KW_ADD, KW_ADMIN, KW_AFTER, KW_AGGREGATE, KW_ALIAS, KW_ALL, KW_ALTER, KW_AND
terminal EQUAL, NOT, LESSTHAN, GREATERTHAN, SET_VAR;

// 各种类型的 SQL 语句属于非终结符号
nonterminal StatementBase stmt, show_stmt, show_param, help_stmt, load_stmt,
nonterminal ValueList value_clause;


// 4. precedence declarations
// 第四部分优先级声明, 主要是介绍所有终结符的优先关系.
// 优先级关系包含两个层级:
// - 第1层级: 不同终结符之间的对比, 比如 1 + 2/3 乘法优先级比加法要高
// 第1层级规则是:
// - 越早声明的语句中的终结符优先级越低, 写在最后一条的语句中的终结符优先级最高
// - 没写优先级的所有终结符优先级都是最低的
// - 第2层级: 相同终结符的不同实例的对比, 比如 1+2+3 从左往右开始计算, 1==2==3 不合法.
// 第2层级规则是: 通过下面 left / right / nonassoc 符号来区分:
// precedence left terminal[, terminal...]; 左结合, 从左往右执行
// precedence right terminal[, terminal...]; 右结合, 从右往左执行
// precedence nonassoc terminal[, terminal...]; 不结合, 不允许连续出现

// 优先级上来看, not > and > or, 且这些符号都是左结合的, 从左往右执行
precedence left KW_OR;
precedence left KW_AND;
precedence left KW_NOT, NOT;

// 临时表是右结合的, 代表从右往左执行
precedence right KW_TEMPORARY;


// 5. the grammar
// 第五部分声明语法, 是语法规则的集合

start with stmts; // 语法部分的开始, 代表了非终结符 stmts 是语法解析的目标

// 语法往往开始于start声明之后, 每条语法规则的格式如下:
// - 每条规则最左边是一个非终结符,接着是一个“::=”符号,
// - 紧跟着是零个或多个动作、终结符、非终结符,这些符号后面加一个":"符号就可以定义一个别名
// - {: ... :} 中间的...部分是用户定义的代码, 代表当识别到这个规则时要执行的动作.
// - 再接着是一个可选的语境优先级分配,
// - 最后由一个分号结束。
// 如果一个非终结符可以由多种规则定义,那么这些规则应该放在一块声明, 规则之间用"|"分隔.

// Show statement
// 总共有3种规则, 分别用|分开了
show_stmt ::=
KW_SHOW show_param:stmt // stmt是show_param的别名, show_param是一个非终结符这里不展开了
{:
RESULT = stmt; // 用户自定义 code
:}
| KW_SHOW KW_SQL_BLOCK_RULE KW_FOR ident:ruleName // 规则show sql_block_rule for 标识符ident
{:
RESULT = new ShowSqlBlockRuleStmt(ruleName);
// 用户自定义 code, 这里返回的对象就是一个 AST 抽象语法树
// 这个对象的定义位置在:
// fe/fe-core/src/main/java/org/apache/doris/analysis/ShowSqlBlockRuleStmt.java
:}
| KW_SHOW KW_SQL_BLOCK_RULE // 规则 show sql_block_rule;
{:
RESULT = new ShowSqlBlockRuleStmt(null); // 用户自定义 code, 也是返回一个 AST.
:}
;

// 语境优先级分配的示例
analytic_expr ::=
function_call_expr:e KW_OVER LPAREN opt_partition_by_clause:p order_by_clause:o opt_window_clause:w RPAREN
{:
// Handle cases where function_call_expr resulted in a plain Expr
if (!(e instanceof FunctionCallExpr)) {
parser.parseError("over", SqlParserSymbols.KW_OVER);
}
FunctionCallExpr f = (FunctionCallExpr)e;
f.setIsAnalyticFnCall(true);
RESULT = new AnalyticExpr(f, p, o, w);
:}
%prec KW_OVER //给予这个规则 KW_OVER 相同的优先级 (默认情况下优先级是来自最后一个终结符)
;

CUP 的目标在用户给定语法规则的前提下, 构造一个语法解析器函数, 这个构造出来的函数可以接受一系列的 token, 返回一棵抽象语法树(AST). CUP 构造的语法解析器是 LALR(1) 解析器的一种实现, 感兴趣的同学可以去搜索一下相关的原理介绍.

什么是抽象语法树 (AST)

在 Doris 的场景中, 使用了 jflex 作为词法分析器产生一系列的 token, 然后再使用 CUP 来将这些 token 组合成一系列的 AST 抽象语法树 List stmts. 每棵 AST 树代表了一个 SQL 语句, 它在 Doris 中的数据结构是: StatementBase, 它是一个抽象类, 针对不同类型的 SQL 会基于这个抽象类有不同的实现, 比如 SelectStmt, InsertStmt, CreateTableStmt 等 170 种语法分别有各自的实现.

以删库跑路的语句 DropDbStmt 为例来分析一下 AST 的结构:

  • DropDbStmt
    • boolean ifExists
    • String dbName
    • boolean forceDrop

完整的代码可参考如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
//位置: fe/fe-core/src/main/java/org/apache/doris/analysis/DropDbStmt.java
//DropDbStmt 这棵树比较简单, 只有两个层级, 语句里面只包含三个叶子节点

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package org.apache.doris.analysis;

import org.apache.doris.catalog.Catalog;
import org.apache.doris.catalog.InfoSchemaDb;
import org.apache.doris.cluster.ClusterNamespace;
import org.apache.doris.common.ErrorCode;
import org.apache.doris.common.ErrorReport;
import org.apache.doris.common.UserException;
import org.apache.doris.mysql.privilege.PrivPredicate;
import org.apache.doris.qe.ConnectContext;

import com.google.common.base.Strings;

// DROP DB表达式
// 该语句用于删除数据库(database)
// 语法:
// DROP DATABASE [IF EXISTS] db_name [FORCE];
// 说明:
// 1) 执行 DROP DATABASE 一段时间内,可以通过 RECOVER 语句恢复被删除的数据库。详见 RECOVER 语句
// 2) 如果执行 DROP DATABASE FORCE,则系统不会检查该数据库是否存在未完成的事务,
// 数据库将直接被删除并且不能被恢复,一般不建议执行此操作

public class DropDbStmt extends DdlStmt {
// 这棵树比较简单, 只包含三个叶子节点: ifExists, dbName, forceDrop
private boolean ifExists;
private String dbName;
private boolean forceDrop;

public DropDbStmt(boolean ifExists, String dbName, boolean forceDrop) {
this.ifExists = ifExists;
this.dbName = dbName;
this.forceDrop = forceDrop;
}

public boolean isSetIfExists() {
return ifExists;
}

public String getDbName() {
return this.dbName;
}

public boolean isForceDrop() {
return this.forceDrop;
}

@Override
public void analyze(Analyzer analyzer) throws UserException {
super.analyze(analyzer);
if (Strings.isNullOrEmpty(dbName)) { // db 名错误异常
ErrorReport.reportAnalysisException(ErrorCode.ERR_WRONG_DB_NAME, dbName);
}
dbName = ClusterNamespace.getFullName(getClusterName(), dbName);
// Don't allowed to drop 'information_schema'
if (dbName.equalsIgnoreCase(ClusterNamespace.getFullName(getClusterName(), InfoSchemaDb.DATABASE_NAME))) {
// 不允许删除 'information_schema'这个 db
ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED, analyzer.getQualifiedUser(), dbName);
}

if (!Catalog.getCurrentCatalog().getAuth().checkDbPriv(ConnectContext.get(), dbName, PrivPredicate.DROP)) {
// 此用户没有权限
ErrorReport.reportAnalysisException(ErrorCode.ERR_DB_ACCESS_DENIED,
ConnectContext.get().getQualifiedUser(), dbName);
}
}

@Override
public String toSql() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("DROP DATABASE ").append("`").append(dbName).append("`");
return stringBuilder.toString();
}

@Override
public String toString() {
return toSql();
}

}

如果分析 SelectStmt 会发现它的 AST 树要复杂很多, 结构如下:

  • SelectStmt
    • SelectList selectList
      • boolean isDistinct
      • Map<String, String> optHints
      • List<SelectListItem> items
        • Expr expr
        • TableName tblName
        • boolean isStar
        • String alias
    • ArrayList<String> colLabels
    • FromClause fromClause_
      • ArrayList<TableRef> tableRefs_
    • GroupByClause groupByClause
      • GroupingType groupingType
      • ArrayList<Expr> groupingExprs
      • ArrayList<Expr> oriGroupingExprs
      • List<ArrayList<Expr>> groupingSetList
    • List<Expr> originalExpr
    • Expr havingClause
    • Expr whereClause
    • Expr havingPred
    • AggregateInfo aggInfo
      • AggregateInfo mergeAggInfo_
      • AggregateInfo secondPhaseDistinctAggInfo_
      • AggPhase aggPhase_;
      • ExprSubstitutionMap intermediateTupleSmap_
      • ExprSubstitutionMap outputTupleSmap_
      • ExprSubstitutionMap outputToIntermediateTupleSmap_
      • List<Expr> partitionExprs_
      • ArrayList<Integer> materializedAggregateSlots_
      • boolean isDistinctAgg
      • boolean isMultiDistinct_
      • ArrayList<Integer> firstIdx_
      • ArrayList<Integer> lastIdx_
    • AnalyticInfo analyticInfo
      • ArrayList<Expr> analyticExprs_
      • List<Expr> commonPartitionExprs_
      • ExprSubstitutionMap analyticTupleSmap_
        • List<Expr> lhs_
        • List<Expr> rhs_
    • ExprSubstitutionMap baseTblSmap
      • List<Expr> lhs_
      • List<Expr> rhs_
    • ValueList valueList
      • List<ArrayList<Expr>> rows
    • GroupingInfo groupingInfo
      • VirtualSlotRef groupingIDSlot
        • TupleDescriptor tupleDescriptor
          • TupleId id
            • int id
          • String debugName
          • ArrayList<SlotDescriptor> slots
            • SlotId id
            • TupleDescriptor parent
            • SlotDescriptor src
        • List<Expr> realSlots
      • TupleDescriptor virtualTuple
        • TupleId id
          • int id
        • String debugName
        • ArrayList<SlotDescriptor> slots
          • SlotId id
          • TupleDescriptor parent
          • SlotDescriptor src
      • Set<VirtualSlotRef> groupingSlots
        • TupleDescriptor tupleDescriptor
          • TupleId id
            • int id
          • String debugName
          • ArrayList<SlotDescriptor> slots
            • SlotId id
            • TupleDescriptor parent
            • SlotDescriptor src
        • List<Expr> realSlots
      • List<BitSet> groupingIdList
      • GroupByClause.GroupingType groupingType
      • BitSet bitSetAll
    • Expr havingClauseAfterAnaylzed

首层子树结构可参考如下的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 位置: fe/fe-core/src/main/java/org/apache/doris/analysis/SelectStmt.java
public class SelectStmt extends QueryStmt {

// 省略前面内容 ...

protected SelectList selectList;
private final ArrayList<String> colLabels; // lower case column labels
protected final FromClause fromClause_;
protected GroupByClause groupByClause;
private List<Expr> originalExpr;
//
private Expr havingClause; // original having clause
protected Expr whereClause;
// havingClause with aliases and agg output resolved
private Expr havingPred;

// set if we have any kind of aggregation operation, include SELECT DISTINCT
private AggregateInfo aggInfo;
// set if we have analytic function
private AnalyticInfo analyticInfo;
// substitutes all exprs in this select block to reference base tables
// directly
private ExprSubstitutionMap baseTblSmap = new ExprSubstitutionMap();

private ValueList valueList;

// if we have grouping extensions like cube or rollup or grouping sets
private GroupingInfo groupingInfo;

// having clause which has been analyzed
// For example: select k1, sum(k2) a from t group by k1 having a>1;
// this parameter: sum(t.k2) > 1
private Expr havingClauseAfterAnaylzed;

// 省略后续内容 ...
}

语法分析小结

通过上面的分析可得到, 可以看出语法分析 cup 这个函数的输入和输出分别为:

  • 输入:
    • 上面语法分析得到的单词列表 (或扫描器)
    • 终结符和非终结符的列表
    • 所有终结符的优先关系
    • 语法规则的集合
  • 输出:
    • 返回错误, 或者一个抽象语法树 (AST)

埋个伏笔

以上就是词法分析和语法分析的所有相关的所有内容了, 想了解更多其它语句的 AST 结构, 可以自行去源码中查找. 另外还有一个值得一提的事情是, 所有的语句, 最终都会实现了接口: ParseNode.java, 这个接口的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 位置: fe/fe-core/src/main/java/org/apache/doris/analysis/ParseNode.java

// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package org.apache.doris.analysis;

import org.apache.doris.common.AnalysisException;
import org.apache.doris.common.UserException;

public interface ParseNode {

/**
* Perform semantic analysis of node and all of its children.
* Throws exception if any errors found.
*
* @param analyzer
* @throws AnalysisException, InternalException
*/
void analyze(Analyzer analyzer) throws UserException;

/**
* @return SQL syntax corresponding to this node.
*/
String toSql();

}

这个接口主要包含两个方法, 一个是转换为 String 类型的 Sql 语句, 一个是 analyze(Analyzer analyzer) 方法, 后面的分析中, 会重点分析一下这个 analyze 方法.