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的各个节点,然后构建未解析的逻辑方案。在逻辑方案剖析阶段,剖析器将解析未解析的逻辑方案中的表名、列名和数据类型,生成一个彻底解析的逻辑方案。
