语义分析
问题
- 为什么要有语义分析
SQL(Structured Query Language)的语义分析目的是确保查询语句的语法正确,同时也验证查询的语义是否合法,以便数据库系统能够正确执行这些查询。语义分析的主要目标包括:
- 语法验证: 首先,语义分析会验证 SQL 查询的语法是否正确。这包括检查查询中的关键字、标识符、操作符等是否按照 SQL 语法规则排列正确。
- 数据表和列验证: 语义分析会检查查询中引用的数据表和列是否存在于数据库中。这是为了确保查询操作的对象在数据库中是有效的。
- 数据类型匹配: 确保查询中的各种操作符、函数和表达式的数据类型是兼容的。例如,不允许将字符串和数字相加,因此语义分析会检查这些数据类型是否匹配。
- 权限验证: 确保查询执行者有足够的权限来执行所提交的查询。这包括对数据表的读取、写入、删除等权限的验证。
- 查询合法性: 确保查询的语义合法,不会导致歧义或不一致的结果。例如,不允许在同一查询中使用聚合函数和非聚合列,因为这可能会导致不确定的结果。
- 优化准备: 在语义分析阶段,数据库系统还可以根据查询的语义信息为查询生成执行计划。这个执行计划将决定查询的最佳执行方式,以提高查询性能。
总的来说,语义分析是确保 SQL 查询在数据库中正确执行的关键步骤。它有助于防止错误的查询进入数据库系统,同时也为执行查询提供了必要的信息和准备工作。这有助于提高数据库系统的稳定性、安全性和性能。
注意,我这里只是修改了列名,所以语法分析时不会报错。如果将from修改为to,在语法分析阶段就会报错,因为语法文件并没有对to进行定义。
- 语义分析的作用
遍历SQL AST,将AST中表达的含义,拆解为多个Map结构以便后续生成执行计划时,不再频繁需要遍历SQL AST。同时还去获取了表和字段的元数据,生成了对应的ConnectorTableHandle, ColumnHandle等与数据源Connector相关的对象实例,也是为了之后拿来即用。在此过程中生成的所有对象,都维护在一个实例化的Analysis对象中,你可以把它理解为是一个Context对象。(如果还是不明白Analysis是什么意思,可以直接看看Analysis.java的源码)
源码阅读
会根据sql类型选取执行的sqlQueryExecution,主要分为DataDefinitionExecutionFactory和SqlQueryExecutionFactory。其中DataDefinitionExecutionFactory不会进行语义分析。
语义分析在上图的queryExecutionFactory.createQueryExecution()就会进行创建,接下来看下具体怎么分析的。
最终会调用如下方法,并返回analysis
//io.trino.sql.analyzer.Analyzer
public Analysis analyze(Statement statement, QueryType queryType)
{
//对statement进行重写,只有5种rewrites,如果statement是这5种类型就会重写
//比如show tables就会被重写
Statement rewrittenStatement = statementRewrite.rewrite(analyzerFactory, session, statement, parameters, parameterLookup, warningCollector, planOptimizersStatsCollector);
Analysis analysis = new Analysis(rewrittenStatement, parameterLookup, queryType);
StatementAnalyzer analyzer = statementAnalyzerFactory.createStatementAnalyzer(analysis, session, warningCollector, CorrelationSupport.ALLOWED);
try (var ignored = scopedSpan(tracer, "analyze")) {
//在这里对rewrittenStatement进行分析,就是遍历SQL AST,将AST中表达的含义,拆解为多个
//Map结构以便后续生成执行计划时,不再频繁需要遍历SQL AST
analyzer.analyze(rewrittenStatement);
}
try (var ignored = scopedSpan(tracer, "access-control")) {
// check column access permissions for each table
analysis.getTableColumnReferences().forEach((accessControlInfo, tableColumnReferences) ->
tableColumnReferences.forEach((tableName, columns) ->
accessControlInfo.getAccessControl().checkCanSelectFromColumns(
accessControlInfo.getSecurityContext(session.getRequiredTransactionId(), session.getQueryId()),
tableName,
columns)));
}
return analysis;
}
5种重写的rewrites
analyzer.analyzer(rewrittenStatement)最终会对AST进行visit访问
StatementAnalyzer中有个内部类Visitor,其继承了io.trino.sql.tree.AstVisitor,重写了其中的visit方法
在visit方法中对analysis(就是个map)进行赋值