sparksql源码解析
1.sparkSQL的首要组件以及效果
Spark SQL是Apache Spark的一个模块,用于处理结构化和半结构化数据。它提供了编程接口,用于在大型分布式集群上进行数据查询、剖析和转化。Spark SQL支撑SQL查询,同时还答应运用Dataset和DataFrame API进行编程。它集成了Catalyst查询优化器和Tungsten履行引擎,以完成高性能和高可扩展性。
以下是Spark SQL首要组件和源码剖析:
-
SQL解析器 (Parser)
Spark SQL运用ANTLR4(另一个言语识别东西)生成的解析器将SQL句子解析成笼统语法树(AST)。解析器的源码坐落
org.apache.spark.sql.catalyst.parser
包中。 -
剖析器 (Analyzer)
剖析器担任对AST进行语义剖析,首要包括:表名、列名解析,类型检查等。剖析器将AST转化成一个称为”逻辑方案”的目标,逻辑方案用于表明Spark SQL查询的逻辑结构。剖析器的源码坐落
org.apache.spark.sql.catalyst.analysis
包中。 -
优化器 (Optimizer)
Spark SQL运用名为Catalyst的查询优化器。Catalyst是基于规矩的优化器,运用一组预界说的规矩进行查询优化。优化器的目标是生成一个更有效的逻辑方案,然后进步查询性能。优化器的源码坐落
org.apache.spark.sql.catalyst.optimizer
包中。 -
物理方案 (Physical Plan)
在优化后的逻辑方案经过物理方案生成阶段,将逻辑方案转化为物理方案。物理方案表明了在Spark集群上履行查询的详细操作。Spark SQL运用名为Tungsten的履行引擎来生成物理方案。物理方案生成阶段的源码坐落
org.apache.spark.sql.execution
包中。 -
履行引擎 (Execution Engine)
履行引擎担任运转物理方案,并生成查询成果。履行引擎会将物理方案转化为RDD操作,然后经过Spark Core履行这些操作。履行引擎的源码坐落
org.apache.spark.sql.execution
包中。 -
数据源 API (DataSource API)
Spark SQL支撑多种数据源,如Hive、HDFS、Parquet、Avro、JSON、JDBC等。数据源API答应用户扩展并自界说数据源。数据源API的源码坐落
org.apache.spark.sql.sources
包中。
这只是对Spark SQL源码的一个简要概述。为了深入了解源码,建议从org.apache.spark.sql.SparkSession
类开端,由于它是Spark SQL的进口点。从这儿,你能够深入研究每个组件的详细完成。
那你从SparkSession类作为sql进口点进行写sparksql源码的剖析
从org.apache.spark.sql.SparkSession
类开端,咱们能够剖析怎么运用Spark SQL来履行查询。SparkSession
是用户与Spark SQL交互的首要进口点。下面是一个简略的比如,展现了怎么运用Spark SQL进行数据查询。
假定咱们有一个JSON数据文件example.json
,其内容如下:
jsonCopy code
{"name":"Alice","age":30}
{"name":"Bob","age":28}
现在,咱们将运用SparkSession
来加载这个JSON数据文件,并履行一个简略的查询。
scalaCopy code
import org.apache.spark.sql.SparkSession
object SparkSQLExample {
def main(args: Array[String]): Unit = {
val spark = SparkSession.builder()
.appName("Spark SQL Example")
.master("local[*]")
.getOrCreate()
import spark.implicits._
// 1. 加载JSON数据
val df = spark.read.json("example.json")
// 2. 创立一个临时视图
df.createOrReplaceTempView("people")
// 3. 履行SQL查询
val result = spark.sql("SELECT name, age FROM people WHERE age >= 28")
// 4. 显现查询成果
result.show()
// 5. 关闭SparkSession
spark.stop()
}
}
这个查询涉及到以下几个要害进程:
- SQL解析
- 逻辑方案剖析
- 逻辑方案优化
- 物理方案生成
- 查询履行
咱们将剖析SparkSession.sql()
办法是怎么处理这些进程的。
SparkSession.sql()
办法的源码如下:
scalaCopy code
def sql(sqlText: String): DataFrame = {
Dataset.ofRows(self, sessionState.sqlParser.parsePlan(sqlText))
}
这个办法首要调用sessionState.sqlParser.parsePlan(sqlText)
解析SQL文本。parsePlan
办法的完成坐落org.apache.spark.sql.catalyst.parser.CatalystSqlParser
类。它将SQL文本解析为一个笼统语法树(AST)并回来一个未剖析的逻辑方案(Unresolved Logical Plan)。
接下来,咱们将看到Dataset.ofRows()
办法怎么处理其他进程。
scalaCopy code
def ofRows(sparkSession: SparkSession, logicalPlan: LogicalPlan): DataFrame = {
val qe = sparkSession.sessionState.executePlan(logicalPlan)
qe.assertAnalyzed() // 保证逻辑方案已剖析
new Dataset[Row](sparkSession, qe, RowEncoder(qe.analyzed.schema))
}
Dataset.ofRows()
办法接纳一个未剖析的逻辑方案。它首要调用sparkSession.sessionState.executePlan(logicalPlan)
办法,该办法担任逻辑方案剖析、优化和物理方案生成。
executePlan
办法的完成坐落org.apache.spark.sql.internal.SessionState
类:
scalaCopy code
def executePlan(plan: LogicalPlan): QueryExecution = {
new QueryExecution(sparkSession, plan)
}
executePlan
办法创立一个QueryExecution
目标。QueryExecution
类坐落org.apache.spark.sql.execution
包。这个类担任处理逻辑方案剖析、优化、物理方案生成以及查询履行。
QueryExecution
类的要害特点和办法如下:
- analyzed: LogicalPlan
- optimizedPlan: LogicalPlan
- sparkPlan: SparkPlan
- executedPlan: SparkPlan
- toRdd: RDD[InternalRow]
在QueryExecution
目标创立时,它会自动触发逻辑方案剖析(analyzed
特点)。优化逻辑方案(optimizedPlan
特点)和生成物理方案(sparkPlan
特点)是惰性计算的,仅在需求时计算。executedPlan
特点是最终用于履行查询的物理方案。
toRdd
办法担任将物理方案转化为一个RDD[InternalRow]
,用于履行查询。这个办法调用executedPlan.execute()
办法来履行物理方案。
最后,Dataset.ofRows()
办法运用QueryExecution
目标创立一个新的`DataFrame
2.将sparkSQL解析为 未解析的逻辑方案源码做下剖析
未解析的逻辑方案(Unresolved Logical Plan)是从原始SQL文本解析得到的逻辑方案。在这个阶段,表名、列名等都还没有解析,数据类型也还未确定。下面,咱们将详细剖析怎么从SQL文本生成未解析的逻辑方案。
在SparkSession.sql()
办法中,sessionState.sqlParser.parsePlan(sqlText)
办法担任将原始SQL文本解析为一个未解析的逻辑方案。parsePlan
办法的完成坐落org.apache.spark.sql.catalyst.parser.CatalystSqlParser
类。
CatalystSqlParser.parsePlan
办法的源码如下:
override def parsePlan(sqlText: String): LogicalPlan = parse(sqlText) { parser =>
astBuilder.visitSingleStatement(parser.singleStatement()) match {
case plan: LogicalPlan => plan
case _ =>
val position = Origin(None, None)
throw new ParseException(Option(sqlText), "Unsupported SQL statement", position, position)
}
}
protected def parse[T](command: String)(toResult: SqlBaseParser => T): T = {
logDebug(s"Parsing command: $command")
val lexer = new SqlBaseLexer(new UpperCaseCharStream(CharStreams.fromString(command)))
lexer.removeErrorListeners()
lexer.addErrorListener(ParseErrorListener)
val tokenStream = new CommonTokenStream(lexer)
val parser = new SqlBaseParser(tokenStream)
parser.addParseListener(PostProcessor)
parser.removeErrorListeners()
parser.addErrorListener(ParseErrorListener)
parser.legacy_setops_precedence_enbled = conf.setOpsPrecedenceEnforced
parser.legacy_exponent_literal_as_decimal_enabled = conf.exponentLiteralAsDecimalEnabled
parser.SQL_standard_keyword_behavior = conf.ansiEnabled
try {
try {
// first, try parsing with potentially faster SLL mode
parser.getInterpreter.setPredictionMode(PredictionMode.SLL)
toResult(parser)
}
catch {
case e: ParseCancellationException =>
// if we fail, parse with LL mode
tokenStream.seek(0) // rewind input stream
parser.reset()
// Try Again.
parser.getInterpreter.setPredictionMode(PredictionMode.LL)
toResult(parser)
}
}
catch {
case e: ParseException if e.command.isDefined =>
throw e
case e: ParseException =>
throw e.withCommand(command)
case e: AnalysisException =>
val position = Origin(e.line, e.startPosition)
throw new ParseException(Option(command), e.message, position, position)
}
}
}
这儿,
parsePlan办法承受一个 SQL 文本字符串
sqlText,然后调用父类中的
parse办法经过 SqlBaseParser 加载词法剖析器和语法剖析器来解析SQL文本。
parse办法承受一个函数(to_result)作为参数,该函数接纳一个类型为
SqlBaseParser的
parser` 目标。
在 parse
办法的代码块中,首要调用 parser.singleStatement()
办法从 SQL 文本中解分出单个 SQL 句子。然后,调用 astBuilder.visitSingleStatement()
办法遍历解析得到的语法树,并回来一个笼统语法树(AST)节点。astBuilder
是一个 AstBuilder
实例,它承继自 SqlBaseBaseVisitor
,用于处理拜访语法树的进程。
以下是SqlBaseBaseVisitor
办法的一些要害完成:
- visitSelect:处理SELECT句子,创立一个
Project
节点。 - visitFromClause:处理FROM子句,创立一个
UnresolvedRelation
节点。 - visitWhere:处理WHERE子句,创立一个
Filter
节点。 - visitJoin:处理JOIN子句,创立一个
Join
节点。 - visitGroupBy:处理GROUP BY子句,创立一个
Aggregate
节点。 - visitOrderBy:处理ORDER BY子句,创立一个
Sort
节点。 - visitLimit:处理LIMIT子句,创立一个
GlobalLimit
节点。
未解析的逻辑方案包括一系列未解析的逻辑方案节点,这些节点对应于SQL查询的各个组成部分。在逻辑方案剖析阶段,Spark SQL将解析表名、列名和数据类型,生成一个彻底解析的逻辑方案。
3.解析后生成的未解析的逻辑方案 是什么
在生成未解析的逻辑方案(Unresolved Logical Plan)之后,咱们会得到一个逻辑方案的树形结构,该结构表明SQL查询的逻辑方式。此刻,表名、列名和数据类型仍然是未解析的,因而逻辑方案中的这些信息需求在后续的剖析阶段进行解析。
未解析的逻辑方案是由一系列的org.apache.spark.sql.catalyst.plans.logical.LogicalPlan
节点构成的。这些节点表明SQL查询的各个组成部分,例如Project
(表明选择操作)、Filter
(表明过滤操作)、Join
(表明衔接操作)等。详细来说,未解析的逻辑方案树包括以下几种类型的节点:
- UnresolvedRelation:表明SQL查询中引证的表,此刻表名仍未解析。
- UnresolvedAttribute:表明查询中引证的列,此刻列名仍未解析。
- UnresolvedFunction:表明查询中引证的函数,此刻函数名仍未解析。
- 其他节点,如Project、Filter、Join等,表明查询中的各种操作,但它们的子节点或许包括未解析的表名、列名或函数名。
在逻辑方案剖析阶段,Spark SQL的剖析器将处理这些未解析的信息。剖析器首要解析表名,然后解析列名和数据类型。最后,剖析器将这些解析后的信息填充到逻辑方案中,生成一个彻底解析的逻辑方案,该方案包括实践的数据表、列和类型信息。
剖析器会运用org.apache.spark.sql.catalyst.analysis.Analyzer
类中界说的一系列规矩进行处理。这些规矩包括但不限于:
- ResolveRelations:解析表名,将
UnresolvedRelation
节点替换为LogicalRelation
节点。 - ResolveReferences:解析列名,将
UnresolvedAttribute
节点替换为已解析的AttributeReference
节点。 - ResolveFunctions:解析函数名,将
UnresolvedFunction
节点替换为已解析的详细函数节点(如Count
、Sum
等)。
完成逻辑方案剖析后,Spark SQL将持续进行逻辑方案优化、物理方案生成和查询履行。
那解析履行方案是怎么遍历语法树的?
在Spark SQL中,解析履行方案的进程中,ANTLR4生成的解析器担任遍历语法树。ANTLR4是一个用于构建编译器和解析器的强大东西,能够解析各种方式的语法(如SQL)。
ANTLR4生成的解析器从SQL文本中创立一个笼统语法树(AST),这是一种树形数据结构,用于表明源代码的逻辑结构。遍历AST是解析进程的核心部分,由于它答应解析器识别SQL查询中的各种组件(如SELECT子句、WHERE子句、JOIN子句等)。
Spark SQL运用org.apache.spark.sql.catalyst.parser.SqlBaseBaseVisitor
类作为ANTLR4生成的解析器的自界说拜访器。SqlBaseBaseVisitor
类完成了一组visit*
办法,这些办法用于处理特定类型的AST节点。在遍历AST时,解析器会依据AST节点类型调用相应的visit*
办法。
以下是SqlBaseBaseVisitor
类中一些要害的visit*
办法及其功用:
- visitSelect:处理SELECT子句。在遍历AST时,当遇到SELECT子句时,解析器会调用此办法。这个办法会创立一个
Project
逻辑方案节点。 - visitFromClause:处理FROM子句。当遇到FROM子句时,解析器会调用此办法。这个办法会创立一个
UnresolvedRelation
逻辑方案节点。 - visitWhere:处理WHERE子句。当遇到WHERE子句时,解析器会调用此办法。这个办法会创立一个
Filter
逻辑方案节点。 - visitJoin:处理JOIN子句。当遇到JOIN子句时,解析器会调用此办法。这个办法会创立一个
Join
逻辑方案节点。 - visitGroupBy:处理GROUP BY子句。当遇到GROUP BY子句时,解析器会调用此办法。这个办法会创立一个
Aggregate
逻辑方案节点。 - visitOrderBy:处理ORDER BY子句。当遇到ORDER BY子句时,解析器会调用此办法。这个办法会创立一个
Sort
逻辑方案节点。 - visitLimit:处理LIMIT子句。当遇到LIMIT子句时,解析器会调用此办法。这个办法会创立一个
GlobalLimit
逻辑方案节点。
经过这些visit*
办法,SqlBaseBaseVisitor
遍历AST并创立逻辑方案树,该树包括表明查询逻辑的逻辑方案节点。在此进程中,表名、列名和函数名仍然是未解析的。在逻辑方案剖析阶段,剖析器将解析这些未解析的信息并生成一个彻底解析的逻辑方案。
4.解析成语法树以及将拜访语法树来生成逻辑方案样例什么样的
以一个简略的非嵌套SELECT句子为例,例如:SELECT name, age FROM users WHERE age > 18
,其生成的笼统语法树(AST)结构相似于以下层次结构:
csharpCopy code
statement
|- query
|- select
| |- name
| |- age
|- from
| |- users
|- where
|- age > 18
此刻,SqlBaseBaseVisitor
会依据以下进程拜访AST节点并遍历一切节点:
-
首要,解析器将调用
visit(ctx.statement)
作为进口点,其间ctx
是一个SqlBaseParser.StatementContext
目标。这将触发解析器拜访statement
节点。 -
在
visitStatement
办法中,它会调用visitQuery
办法,传入query
节点。 -
在
visitQuery
办法中,解析器将持续拜访子节点:- 调用
visitSelect
办法,传入select
节点,然后拜访name
和age
子节点。 - 调用
visitFromClause
办法,传入from
节点,然后拜访users
子节点。 - 调用
visitWhere
办法,传入where
节点,然后拜访age > 18
子节点。
- 调用
在遍历进程中,SqlBaseBaseVisitor
会依据AST节点类型调用相应的visit*
办法。假如某个节点没有对应的visit*
办法,那么解析器将调用visitChildren(visitor)
,遍历当时节点的一切子节点。
关于这个简略的SELECT句子,SqlBaseBaseVisitor
会依照上述次序拜访AST的各个节点,然后构建未解析的逻辑方案。在逻辑方案剖析阶段,剖析器将解析未解析的逻辑方案中的表名、列名和数据类型,生成一个彻底解析的逻辑方案。
假定咱们有一个包括多个处理函数的SQL查询,例如:SELECT UPPER(name), AVG(age) FROM users WHERE LOWER(city) = 'new york' GROUP BY department HAVING COUNT(*) > 5
。关于这个查询,笼统语法树(AST)结构将相似于以下层次结构:
sqlCopy code
statement
|- query
|- select
| |- UPPER(name)
| |- AVG(age)
|- from
| |- users
|- where
| |- LOWER(city) = 'new york'
|- group by
| |- department
|- having
|- COUNT(*) > 5
此刻,SqlBaseBaseVisitor
拜访AST节点并遍历一切节点的进程与之前相似,但需求处理额定的函数节点:
-
首要,解析器将调用
visit(ctx.statement)
作为进口点,其间ctx
是一个SqlBaseParser.StatementContext
目标。这将触发解析器拜访statement
节点。 -
在
visitStatement
办法中,它会调用visitQuery
办法,传入query
节点。 -
在
visitQuery
办法中,解析器将持续拜访子节点:- 调用
visitSelect
办法,传入select
节点,然后拜访UPPER(name)
和AVG(age)
子节点。在拜访这些节点时,它会调用相应的函数处理办法。 - 调用
visitFromClause
办法,传入from
节点,然后拜访users
子节点。 - 调用
visitWhere
办法,传入where
节点,然后拜访LOWER(city) = 'new york'
子节点。在拜访这个节点时,它会调用LOWER
函数的处理办法。 - 调用
visitGroupBy
办法,传入group by
节点,然后拜访department
子节点。 - 调用
visitHaving
办法,传入having
节点,然后拜访COUNT(*) > 5
子节点。在拜访这个节点时,它会调用COUNT
函数的处理办法。
- 调用
关于包括多个处理函数的SQL查询,SqlBaseBaseVisitor
会依照上述次序拜访AST的各个节点,处理每个函数节点,然后构建未解析的逻辑方案。在逻辑方案剖析阶段,剖析器将解析未解析的逻辑方案中的表名、列名、数据类型和函数,生成一个彻底解析的逻辑方案。
让咱们剖析visit(ctx.statement)办法及其背面的逻辑:
visit(ctx.statement).asInstanceOf[LogicalPlan]
是在CatalystSqlParser
中将SQL文本解析为逻辑方案的要害部分。这行代码表明ANTLR4生成的解析器开端遍历笼统语法树(AST),并生成未解析的逻辑方案。
-
ctx
是一个SqlBaseParser.StatementContext
目标,它表明ANTLR4生成的解析器解析SQL文本后的上下文。ctx.statement
表明SQL查询的顶层节点。 -
visit()
办法是SqlBaseBaseVisitor
类的一个实例办法,用于遍历AST并创立逻辑方案树。visit(ctx.statement)
表明从顶层节点开端遍历AST,并依据AST节点类型调用相应的visit*
办法。 -
.asInstanceOf[LogicalPlan]
是一个类型转化操作,将visit()
办法的成果转化为LogicalPlan
类型。由于visit()
办法回来的是一个泛型类型T
,咱们需求将其转化为LogicalPlan
类型,以便后续的逻辑方案处理和履行。
经过visit(ctx.statement).asInstanceOf[LogicalPlan]
这行代码,CatalystSqlParser
类将SQL文本解析为一个未解析的逻辑方案。然后,在逻辑方案剖析阶段,剖析器将解析未解析的逻辑方案中的表名、列名和数据类型,生成一个彻底解析的逻辑方案。
4.再深入visit源码 里面代码:
在SqlBaseBaseVisitor
类中,visit
办法的完成如下:
scalaCopy code
override def visit(tree: ParseTree): T = {
tree.accept(this)
}
这儿的visit
办法接纳一个ParseTree
类型的参数tree
,该参数表明ANTLR4生成的笼统语法树(AST)的节点。tree.accept(this)
是调用ParseTree
的accept
办法,并将当时SqlBaseBaseVisitor
实例作为参数传递。这个调用是ANTLR4遍历AST的要害部分。
accept
办法完成坐落ANTLR4的 SingleStatementContext
类中也就是上面 ctx.statement
’,其源码如下:
public static class SingleStatementContext extends ParserRuleContext {
public StatementContext statement() {
return getRuleContext(StatementContext.class,0);
}
public TerminalNode EOF() { return getToken(SqlBaseParser.EOF, 0); }
public SingleStatementContext(ParserRuleContext parent, int invokingState) {
super(parent, invokingState);
}
@Override public int getRuleIndex() { return RULE_singleStatement; }
@Override
public void enterRule(ParseTreeListener listener) {
if ( listener instanceof SqlBaseListener ) ((SqlBaseListener)listener).enterSingleStatement(this);
}
@Override
public void exitRule(ParseTreeListener listener) {
if ( listener instanceof SqlBaseListener ) ((SqlBaseListener)listener).exitSingleStatement(this);
}
@Override
public <T> T accept(ParseTreeVisitor<? extends T> visitor) {
if ( visitor instanceof SqlBaseVisitor) return ((SqlBaseVisitor<? extends T>)visitor).visitSingleStatement(this);
else return visitor.visitChildren(this);
}
}
这个办法承受一个ParseTreeVisitor
类型的参数visitor
,在咱们的情况下,它是SqlBaseBaseVisitor
实例。这个办法首要检查传入的visitor
是否是ParseTreeVisitor
类型。假如是,它将调用visitor.visit(this)
;否则,它将调用visitChildren(visitor)
。在咱们的场景中,visitor
确实是一个ParseTreeVisitor
类型的实例,因而visitor.visit(this)
将被调用。
visitor.visit(this)
调用将使遍历进入下一个层次的AST节点。SqlBaseBaseVisitor
类的每个visit*
办法将处理AST中的特定类型节点,然后逐渐构建未解析的逻辑方案。
经过这种方式,ANTLR4使用tree.accept(this)
遍历AST,SqlBaseBaseVisitor
类处理各种类型的节点,并逐渐生成未解析的逻辑方案。在逻辑方案剖析阶段,剖析器将解析未解析的逻辑方案中的表名、列名和数据类型,生成一个彻底解析的逻辑方案。
visitor.visit(this) 和 visitChildren(visitor) 差异是什么
visitor.visit(this)
和visitChildren(visitor)
之间的首要差异在于它们在遍历笼统语法树(AST)时所采取的行为。
-
visitor.visit(this)
:这个办法调用visitor
目标中与当时节点类型匹配的visit*
办法。在咱们的场景中,visitor
是一个SqlBaseBaseVisitor
实例,它包括一系列为特定节点类型编写的visit*
办法。当调用visitor.visit(this)
时,将依据当时节点类型履行相应的visit*
办法。这是一种定制化的处理进程,针对不同类型的节点进行特定的操作。 -
visitChildren(visitor)
:这个办法用于遍历当时节点的一切子节点,并依次调用这些子节点的accept(visitor)
办法。它是一种通用办法,用于处理那些没有特定visit*
办法的节点。visitChildren(visitor)
保证整个AST的一切节点都被拜访和处理。
以下是它们之间的首要差异:
-
visitor.visit(this)
针对详细的节点类型履行特定的操作,而visitChildren(visitor)
则是一种通用的处理办法,遍历当时节点的一切子节点。 -
visitor.visit(this)
通常用于处理那些需求特定操作的节点类型,而visitChildren(visitor)
则用于处理那些没有为其完成特定visit*
办法的节点类型。
在遍历AST时,假如有针对特定节点类型的visit*
办法,那么visitor.visit(this)
将被调用,以履行定制化的处理。假如没有为特定节点类型完成visit*
办法,那么visitChildren(visitor)
将被调用,以保证整个AST的一切节点都能被拜访和处理。
那给一个嵌套 的select 句子生成语法树结构是什么?
假定咱们有一个包括嵌套查询的SQL查询,例如:SELECT name, AVG(age) FROM (SELECT name, age FROM users WHERE age > 18) AS filtered_users GROUP BY name
。关于这个查询,笼统语法树(AST)结构将相似于以下层次结构:
csharpCopy code
statement
|- query
|- select
| |- name
| |- AVG(age)
|- from
| |- subquery
| |- query
| |- select
| | |- name
| | |- age
| |- from
| | |- users
| |- where
| |- age > 18
|- group by
|- name
此刻,SqlBaseBaseVisitor
会依据以下进程拜访AST节点并遍历一切节点:
-
首要,解析器将调用
visit(ctx.statement)
作为进口点,其间ctx
是一个SqlBaseParser.StatementContext
目标。这将触发解析器拜访statement
节点。 -
在
visitStatement
办法中,它会调用visitQuery
办法,传入外部查询的query
节点。 -
在外部查询的
visitQuery
办法中,解析器将持续拜访子节点:- 调用
visitSelect
办法,传入外部查询的select
节点,然后拜访name
和AVG(age)
子节点。 - 调用
visitFromClause
办法,传入外部查询的from
节点,然后拜访subquery
子节点。 - 在拜访
subquery
节点时,解析器将调用内部查询的visitQuery
办法,传入内部查询的query
节点。
- 调用
-
在内部查询的
visitQuery
办法中,解析器将持续拜访子节点:- 调用
visitSelect
办法,传入内部查询的select
节点,然后拜访name
和age
子节点。 - 调用
visitFromClause
办法,传入内部查询的from
节点,然后拜访users
子节点。 - 调用
visitWhere
办法,传入内部查询的where
节点,然后拜访age > 18
子节点。
- 调用
-
回到外部查询的
visitQuery
办法中,解析器将持续拜访子节点:- 调用
visitGroupBy
办法,传入外部查询的group by
节点,然后拜访name
子节点。
- 调用
关于嵌套的SELECT句子,SqlBaseBaseVisitor
会依照上述次序拜访AST的各个节点,然后构建未解析的逻辑方案。在逻辑方案剖析阶段,剖析器将解析未解析的逻辑方案中的表名、列名和数据类型,生成一个彻底解析的逻辑方案。