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