上一篇分析了 sharding-jdbc sql 解析过程,从本篇分析 sql 提取过程,解析成功之后将语法树封装成为 SQLAST ,然后依靠 SQLAST 和提取规则的配置,进行 sql 提取。
1、首先看一下提取部分的初始化
当 ParseRuleRegistry 类被加载的时候
static {
...略
instance = new ParseRuleRegistry();
}
private ParseRuleRegistry() {
initParseRuleDefinition();
}
创建 ParseRuleRegistry 实例和调用 initParseRuleDefinition 方法,此方法的目的是为了加载默认的提取规则配置文件,和根据数据库类型加载对应目录的提取规则配置文件
1、加载 ETA-INF/parsing-rule-definition/extractor-rule-definition.xml 配置文件
2、获取SQLParserEntry 所有实现类循环所有的数据库类型,根据数据库类型加载对应的配置文件例如mysql
META-INF/parsing-rule-definition/sharding.mysql/extractor-rule-definition.xml
META-INF/parsing-rule-definition/sharding.mysql/sql-statement-rule-definition.xml
/**
* load sql 解析规范 例如sql-statement-rule-definition.xml
*/
private void initParseRuleDefinition() {
//利用JAXB加载META-INF/parsing-rule-definition/extractor-rule-definition.xml配置文件
ExtractorRuleDefinitionEntity generalExtractorRuleEntity = extractorRuleLoader.load(RuleDefinitionFileConstant.getExtractorRuleDefinitionFile());
//利用JAXB加载下META-INF/parsing-rule-definition/filler-rule-definition.xml配置文件
FillerRuleDefinitionEntity generalFillerRuleEntity = fillerRuleLoader.load(RuleDefinitionFileConstant.getFillerRuleDefinitionFile());
//根据数据库类型加载对应的配置文件
for (SQLParserEntry each : NewInstanceServiceLoader.newServiceInstances(SQLParserEntry.class)) {
String databaseTypeName = each.getDatabaseTypeName();
//META-INF/parsing-rule-definition/sharding.mysql/filler-rule-definition.xml
//databaseType:rules<segment,filler>
fillerRuleDefinitions.put(databaseTypeName, createFillerRuleDefinition(generalFillerRuleEntity, databaseTypeName));
//META-INF/parsing-rule-definition/sharding.mysql/extractor-rule-definition.xml
//META-INF/parsing-rule-definition/sharding.mysql/sql-statement-rule-definition.xml
//databaseType:rules<xxxContext,SQLStatementRule>
sqlStatementRuleDefinitions.put(databaseTypeName, createSQLStatementRuleDefinition(generalExtractorRuleEntity, databaseTypeName));
}
}
1.1、根据数据库类型加载对应的配置文件例如mysql META-INF/parsing-rule-definition/sharding.mysql/extractor-rule-definition.xml META-INF/parsing-rule-definition/sharding.mysql/sql-statement-rule-definition.xml
private SQLStatementRuleDefinition createSQLStatementRuleDefinition(final ExtractorRuleDefinitionEntity generalExtractorRuleEntity, final String databaseTypeName) {
//加载 META-INF/parsing-rule-definition/sharding.mysql/extractor-rule-definition.xml
ExtractorRuleDefinitionEntity databaseDialectExtractorRuleEntity = extractorRuleLoader.load(RuleDefinitionFileConstant.getExtractorRuleDefinitionFile(databaseTypeName));
ExtractorRuleDefinition extractorRuleDefinition = new ExtractorRuleDefinition(generalExtractorRuleEntity, databaseDialectExtractorRuleEntity);
//加载 META-INF/parsing-rule-definition/sharding.mysql/sql-statement-rule-definition.xml
return new SQLStatementRuleDefinition(statementRuleLoader.load(RuleDefinitionFileConstant.getSQLStatementRuleDefinitionFile(databaseTypeName)), extractorRuleDefinition);
}
//ExtractorRuleDefinitionEntityLoader
@Override
@SneakyThrows
public ExtractorRuleDefinitionEntity load(final String extractorRuleDefinitionFile) {
InputStream inputStream = ExtractorRuleDefinitionEntityLoader.class.getClassLoader().getResourceAsStream(extractorRuleDefinitionFile);
return null == inputStream
? new ExtractorRuleDefinitionEntity() : (ExtractorRuleDefinitionEntity) JAXBContext.newInstance(ExtractorRuleDefinitionEntity.class).createUnmarshaller().unmarshal(inputStream);
}
@XmlRootElement(name = "extractor-rule-definition")
@Getter
public final class ExtractorRuleDefinitionEntity implements RuleDefinitionEntity {
@XmlElement(name = "extractor-rule")
private Collection<ExtractorRuleEntity> rules = new LinkedList<>();
}
这里只列举出一个 ExtractorRuleDefinitionEntityLoader,其他Loader 类都是根据 JAXBContext 加载解析 xml。
ExtractorRuleDefinitionEntityLoader 解析结果封装成ExtractorRuleDefinitionEntity ,然后创建 SQLStatementRuleDefinition
1、根据 extractor-rule-definition.xml 封装成 ExtractorRuleDefinitionEntity 包含 Collection<ExtractorRuleEntity> rules
根据 sql-statement-rule-definition.xml 封装成 SQLStatementRuleDefinitionEntity
2、根据 sql-statement-rule-definition.xml 配置的 extractor-rule-refs 对应到 ExtractorRuleEntity
//SQLStatementRuleDefinition.java
@SneakyThrows
public SQLStatementRuleDefinition(final SQLStatementRuleDefinitionEntity entity, final ExtractorRuleDefinition extractorRuleDefinition) {
for (SQLStatementRuleEntity each : entity.getRules()) {
//获取 xml 配置 class名称 sql-statement-class 创建处理类 SQLStatementRule
rules.put(getContextClassName(each.getContext()), new SQLStatementRule(each, extractorRuleDefinition));
}
}
//SQLStatementRuleDefinition.java
private String getContextClassName(final String context) {
return CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_CAMEL, context + "Context");
}
//SQLStatementRule.java
public SQLStatementRule(final SQLStatementRuleEntity entity, final ExtractorRuleDefinition extractorRuleDefinition) {
contextName = entity.getContext();
//反射创建 sql-statement-class
sqlStatementClass = (Class<? extends SQLStatement>) Class.forName(entity.getSqlStatementClass());
extractors = getExtractors(entity.getExtractorRuleRefs(), extractorRuleDefinition);
}
//SQLStatementRule.java
//通过 sql-statement-rule-definition.xml 中配置 extractor-rule-refs 获取 extractorRuleDefinition 对应 extractor-rule-definition.xml
private Collection<SQLSegmentExtractor> getExtractors(final String extractorRuleRefs, final ExtractorRuleDefinition extractorRuleDefinition) {
if (null == extractorRuleRefs) {
return Collections.emptyList();
}
Collection<SQLSegmentExtractor> result = new LinkedList<>();
for (String each : Splitter.on(',').trimResults().splitToList(extractorRuleRefs)) {
// sql 每段的提取的规则
result.add(extractorRuleDefinition.getExtractor(each));
}
return result;
}
初始化阶段类的关系图
2、sql 提取
入口是在 SQLParseKernel 类中
1、用 antlr4 执行解析生成语法树
2、根据语法树提取 sql 分段
3、 将sql 分段 SQLSegment 分类后封装成 SQLStatement
public SQLStatement parse() {
//获取数据解析工具,并且执行解析生成语法树
SQLAST ast = parserEngine.parse();
//根据语法树提取可以提取的部分
Collection<SQLSegment> sqlSegments = extractorEngine.extract(ast);
Map<ParserRuleContext, Integer> parameterMarkerIndexes = ast.getParameterMarkerIndexes();
//将 SQLSegment 分类后封装成 SQLStatement
return fillerEngine.fill(sqlSegments, parameterMarkerIndexes.size(), ast.getSqlStatementRule());
}
ParserEngine 的 parse 方法,调用 parseRuleRegistry 方法根据数据库类型,和 antlr g4 中配置的规则名称+Context(parseTree.getClass().getSimpleName()),提取 SQLStatementRule 然后封装成 SQLAST 返回
public SQLAST parse() {
...略
SQLStatementRule rule = parseRuleRegistry.getSQLStatementRule(databaseTypeName, parseTree.getClass().getSimpleName());
...略
return new SQLAST((ParserRuleContext) parseTree, getParameterMarkerIndexes((ParserRuleContext) parseTree), rule);
}
然后 SQLParseKernel 继续调用了SQLSegmentsExtractorEngine 的 extract 方法
循环 ast 中所有的 Extractors,调用 extract 方法,获取 sql 分段。
public Collection<SQLSegment> extract(final SQLAST ast) {
Collection<SQLSegment> result = new LinkedList<>();
//循环 sql-statement-rule-definition.xml 文件配置的 extractor-rule-refs 中的没一项
for (SQLSegmentExtractor each : ast.getSqlStatementRule().getExtractors()) {
if (each instanceof OptionalSQLSegmentExtractor) {
Optional<? extends SQLSegment> sqlSegment = ((OptionalSQLSegmentExtractor) each).extract(ast.getParserRuleContext(), ast.getParameterMarkerIndexes());
if (sqlSegment.isPresent()) {
result.add(sqlSegment.get());
}
} else if (each instanceof CollectionSQLSegmentExtractor) {
result.addAll(((CollectionSQLSegmentExtractor) each).extract(ast.getParserRuleContext(), ast.getParameterMarkerIndexes()));
}
}
return result;
}
这里以 WhereExtractor 为例
1、遍历树查找第一个为 node.getClass().getSimpleName() 等于 WhereClause 的节点
2、提取第一个 where 内部子查询 where 部分
3、提取参数部分 where 语句中的 ? 号部分
public final class WhereExtractor implements OptionalSQLSegmentExtractor {
private final PredicateExtractor predicateExtractor = new PredicateExtractor();
@Override
public Optional<WhereSegment> extract(final ParserRuleContext ancestorNode, final Map<ParserRuleContext, Integer> parameterMarkerIndexes) {
//遍历树查找第一个为 node.getClass().getSimpleName() 等于 WhereClause 的节点
Optional<ParserRuleContext> whereNode = ExtractorUtils.findFirstChildNode(ancestorNode, RuleName.WHERE_CLAUSE);
if (!whereNode.isPresent()) {
return Optional.absent();
}
//创建 WhereSegment
WhereSegment result = new WhereSegment(whereNode.get().getStart().getStartIndex(), whereNode.get().getStop().getStopIndex(), parameterMarkerIndexes.size());
//提取第一个 where 内部子查询 where 部分
Optional<OrPredicateSegment> orPredicateSegment = predicateExtractor.extract(whereNode.get(), parameterMarkerIndexes);
if (orPredicateSegment.isPresent()) {
result.getAndPredicates().addAll(orPredicateSegment.get().getAndPredicates());
}
//提取参数部分 sql 语句中的 ? 号部分
Collection<ParserRuleContext> parameterMarkerNodes = ExtractorUtils.getAllDescendantNodes(whereNode.get(), RuleName.PARAMETER_MARKER);
if (!parameterMarkerNodes.isEmpty()) {
result.setParameterStartIndex(parameterMarkerIndexes.get(parameterMarkerNodes.iterator().next()));
}
return Optional.of(result);
}
}
2.1、遍历树查找第一个为 node.getClass().getSimpleName() 等于 WhereClause 的节点
这里涉及到了一个遍历树的算法
1、用一个队列保存,即将被判断是否匹配的树节点集合
2、如果不匹配那么如果节点包括子节点,那么将子节点添加到队列尾部,为下次匹配做准备。
public static Optional<ParserRuleContext> findFirstChildNode(final ParserRuleContext node, final RuleName ruleName) {
//保存解析过程中即将被匹配的树节点
Queue<ParserRuleContext> parserRuleContexts = new LinkedList<>();
//将树的根节点入队列,代表下一个即将要被解析到的节点为根节点
parserRuleContexts.add(node);
ParserRuleContext parserRuleContext;
//从队列头部 poll 出来一个节点,poll 弹出节点后,队列中将不存在此数据了
while (null != (parserRuleContext = parserRuleContexts.poll())) {
//如果匹配那么退出 ruleName.getName().equals(node.getClass().getSimpleName()); 即只查找一个
if (isMatchedNode(parserRuleContext, ruleName)) {
return Optional.of(parserRuleContext);
}
//如果不匹配那么将该节点的所有子节点都添加到队列尾部,即子节点也要参与 匹配 isMatchedNode
for (int i = 0; i < parserRuleContext.getChildCount(); i++) {
if (parserRuleContext.getChild(i) instanceof ParserRuleContext) {
parserRuleContexts.add((ParserRuleContext) parserRuleContext.getChild(i));
}
}
}
return Optional.absent();
}
此处为什么不用传统的递归解决遍历树呢? 因为递归同样是一种栈的保存方式,只不过是对程序员不可见罢了,递归保存的方法调用栈内容多例如:程序基数器,操作数栈,返回地址等等。所以这里用parserRuleContexts 只保存解析过程中即将被匹配的树节点即可。
2.2 将sql 分段 SQLSegment 封装成 SQLStatement
1、遍历所有分段,通过 parseRuleRegistry 按照数据库类型和分段类型,查找到对应的填充规则,填充规则在 filler-rule-definition.xml 中配置。
2、如果填充规则不为空,那么调用 fill 方法填充 SQLStatement
SQLStatementFillerEngine.java
@SneakyThrows
public SQLStatement fill(final Collection<SQLSegment> sqlSegments, final int parameterMarkerCount, final SQLStatementRule rule) {
SQLStatement result = rule.getSqlStatementClass().newInstance();
Preconditions.checkArgument(result instanceof AbstractSQLStatement, "%s must extends AbstractSQLStatement", result.getClass().getName());
((AbstractSQLStatement) result).setParametersCount(parameterMarkerCount);
result.getAllSQLSegments().addAll(sqlSegments);
for (SQLSegment each : sqlSegments) {
Optional<SQLSegmentFiller> filler = parseRuleRegistry.findSQLSegmentFiller(databaseTypeName, each.getClass());
if (filler.isPresent()) {
filler.get().fill(each, result);
}
}
return result;
}
这里以 TableFiller 为例子:
public final class TableFiller implements SQLSegmentFiller<TableSegment> {
@Override
public void fill(final TableSegment sqlSegment, final SQLStatement sqlStatement) {
//填充表名分段
if (sqlStatement instanceof TableSegmentAvailable) {
((TableSegmentAvailable) sqlStatement).setTable(sqlSegment);
} else if (sqlStatement instanceof TableSegmentsAvailable) {
((TableSegmentsAvailable) sqlStatement).getTables().add(sqlSegment);
}
}
}
到此处sql 提取过程就讲完了,在付一张图,解析过程涉及到的类关系图。