如何精准提取SELECT类SQL依赖字段 剥离函数保留原始字段
SELECT SQL字段血缘解析:函数包裹字段提取优化
问题现状
- 现有实现基于Antlr
SqlBaseBaseListener完成Java端解析逻辑,已支持SELECT语句输入表、依赖字段的基础提取 - 现存缺陷:输出结果
inPutTableAndFields会保留完整函数表达式,例如datediff(current_date(),registration_time)会被整体作为依赖字段存储,不符合血缘提取的最小粒度要求 - 优化目标:剥离所有函数包裹层,仅提取表达式内的原始物理字段,上述示例仅保留
registration_time作为有效依赖字段
核心实现逻辑
- 废弃原有直接读取表达式整段文本存入结果的逻辑,改为递归遍历解析树节点的方式提取字段
- 遍历规则:
- 遇到函数调用节点时,不采集函数本身文本,递归遍历函数的所有入参节点
- 遇到常量、字面量、无参内置函数(如
current_date())节点时直接跳过,不做采集 - 递归遍历到最底层列引用节点时,才将对应字段名加入依赖字段集合
- 自动支持嵌套函数穿透,无需为每个函数单独写适配规则
关键代码修改
import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.tree.ParseTree; import java.util.HashSet; import java.util.Set; // 在自定义的SqlBaseListener实现类中新增以下方法 /** * 从表达式节点递归提取原始物理字段 */ private Set<String> extractRawFields(ParserRuleContext exprNode) { Set<String> result = new HashSet<>(); // 命中列引用节点,直接加入结果集 if (exprNode instanceof ColumnReferenceContext) { result.add(exprNode.getText()); return result; } // 命中函数节点,仅遍历参数,不采集函数本身 if (exprNode instanceof FunctionCallContext) { FunctionCallContext funcNode = (FunctionCallContext) exprNode; if (funcNode.expression() != null) { for (ExpressionContext argExpr : funcNode.expression()) { result.addAll(extractRawFields(argExpr)); } } return result; } // 命中常量节点直接跳过 if (exprNode instanceof ConstantContext) { return result; } // 其他类型节点继续向下遍历子节点 for (int i = 0; i < exprNode.getChildCount(); i++) { ParseTree child = exprNode.getChild(i); if (child instanceof ParserRuleContext) { result.addAll(extractRawFields((ParserRuleContext) child)); } } return result; } // 修改原有字段采集入口逻辑,示例为select单字段退出时的处理 @Override public void exitSelectSingle(SelectSingleContext ctx) { // 原有逻辑:String fullExpr = ctx.expression().getText(); 直接存入inPutTableAndFields // 替换为递归提取原始字段 Set<String> rawFields = extractRawFields(ctx.expression()); // 对齐原有逻辑里当前上下文对应的输入表,将提取到的字段存入inPutTableAndFields for (String field : rawFields) { // addFieldToCurrentInputTable为原有实现中表和字段映射的存储方法 addFieldToCurrentInputTable(field); } }
覆盖场景说明
- 无参内置函数:如
datediff(current_date(), reg_time)会自动过滤无参的current_date(),仅提取reg_time - 常量混合参数:如
concat('prefix_', user_id, '_suffix')会过滤字符串常量,仅提取user_id - 多层嵌套函数:如
date_format(from_unixtime(create_time), '%Y%m%d')会穿透两层函数,仅提取create_time - 运算表达式:如
price * discount as actual_price会跳过运算符,提取price、discount两个物理字段
内容的提问来源于stack exchange,提问作者Huanqing Cheng
相关产品推荐
相关产品推荐

