ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Druid ODPS 方言 toString 与 clone 行为修复实战:括号保留、not 语义与 QUALIFY 子句

Druid ODPS 方言 toString 与 clone 行为修复实战:括号保留、not 语义与 QUALIFY 子句 数据库后端【免费下载链接】druid阿里云计算平台DataWorks(https://help.aliyun.com/document_detail/137663.html) 团队出品为监控而生的数据库连接池项目地址https://gitcode.com/gh_mirrors/druid/druid点击查看免费下载导读本文聚焦 Apache Druid阿里巴巴 DruidSQL 解析引擎中ODPS 方言的一次定向优化修复 AST 转 SQL 字符串toString时not表达式括号丢失/错乱、多层括号被吞、子查询表达式缺括号以及 Select 语句clone()后 QUALIFY 子句丢失等问题。通过阅读本文你将掌握 ODPS 方言序列化与克隆的底层机制、具体修复方案以及如何用docs/odps_fix.md中的用例和单测进行回归验证可直接应用于依赖 Druid 做 ODPS SQL 解析、改写与克隆的下游场景。一、变更背景ODPS 方言在 toString 与 clone 路径上的行为缺陷ODPS 方言位于core模块的com.alibaba.druid.sql.dialect.odps包中序列化由 OdpsOutputVisitor 负责Select 克隆由 OdpsSelectQueryBlock 负责。根据 proposal.md 的描述该方言存在四类可复现的错误行为问题类别正确输出错误输出修复前not括号丢失not (a) and (not b) and (c)not a and not b and (c)not括号错乱(not a) ! b(NOT a ! b)多括号不打印((a 1))(a 1)子查询表达式缺括号1 (SELECT ...)生成错误 SQL此外clone 后的 Select 会丢失 QUALIFY 子句SELECT * FROM a WHERE b 100 QUALIFY ROW_NUMBER() OVER (...) 1克隆后变成SELECT * FROM a WHERE b 100。这些问题会导致解析→序列化往返或 clone 后的 SQL 与原始 SQL 语义不一致直接影响依赖 Druid 处理 ODPS SQL 的下游链路。本变更的边界仅修正 ODPS 方言的错误输出/克隆行为不改变 ODPS 的解析规则与词法不新增可选语法也不改动 Hive、MySQL 等其他方言见 design.md 的 Goals / Non-Goals。二、修复一not表达式括号的保留与重排在OdpsOutputVisitor的visit(SQLNotExpr)实现OdpsOutputVisitor.java#L1143-L1194中修复后的判定逻辑为若not的操作数是逻辑运算符SQLBinaryOpExpr且 operator 为 logical需要加括号若操作数是SQLInListExpr、SQLNotExpr、SQLBinaryOpExprGroup、SQLQueryExpr这类复合结构同样需要括号包裹但若操作数本身是SQLExprImpl且getParenthesizedCount() 0即源码中自带括号则不再额外加括号避免括号叠加错乱。// 简化自 OdpsOutputVisitor.visit(SQLNotExpr) boolean needParentheses false; if (expr instanceof SQLBinaryOpExpr) { SQLBinaryOpExpr binaryOpExpr (SQLBinaryOpExpr) expr; needParentheses binaryOpExpr.getOperator().isLogical(); if (binaryOpExpr.isParenthesized()) { needParentheses false; // 已带括号则不再重复包裹 } } else if (expr instanceof SQLInListExpr || expr instanceof SQLNotExpr || expr instanceof SQLBinaryOpExprGroup || expr instanceof SQLQueryExpr) { needParentheses true; } if (expr instanceof SQLExprImpl) { SQLExprImpl exprImpl (SQLExprImpl) expr; if (exprImpl.getParenthesizedCount() 0) { needParentheses false; // parenthesized 标记优先 } } if (needParentheses) { print((); }由此not (a) and (not b) and (c)不再被吞括号输出为not a and not b and (c)(not a) ! b不再被错误输出为(NOT a ! b)——外层括号先于NOT输出语义保持为(not a) ! b。design.md 指出该问题集中在 ODPS 输出访客的表达式打印规则上与现有SQLNotExpr、括号标记parenthesized/getParenthesizedCount()逻辑一致因此只在 ODPS 侧修正不做更底层的统一改造以控制影响面。三、修复二多层括号的完整保留多括号场景的修复同样落在visit(SQLBinaryOpExpr)OdpsOutputVisitor.java#L1197-L1227。修复前((a 1))被输出为(a 1)根因是序列化时只根据isParenthesized()布尔标记输出一层括号丢弃了实际括号层数。修复逻辑读取表达式记录的括号计数并按层输出int parenCount x.getParenthesizedCount(); if (parenCount 0 x.isParenthesized()) { parenCount 1; // 兼容仅有标记无计数的场景 } for (int i 0; i parenCount; i) { print((); } boolean rs visitInternal(x); for (int i 0; i parenCount; i) { print()); }同时visit(SQLBinaryOpExpr)中shouldForceParentheses(x)的强制括号逻辑与parenthesized标记配合未带括号但按运算符优先级必须加括号的表达式也会被正确包裹。这样((a 1))可以原样保留多层括号输出。四、修复三子查询表达式作为操作数时自动补括号ODPS 方言中当SQLQueryExpr子查询作为二元表达式的一侧操作数时序列化必须为其添加外层括号否则会生成形如1 SELECT ...的错误 SQL。OdpsOutputVisitor.visit(SQLQueryExpr)OdpsOutputVisitor.java#L1253 起中新增了 ODPS 特判向上追溯表达式父节点当父节点是SQLBinaryOpExpr注意先跳过SQLSelect中间层再取父级时强制打印左括号再输出子查询内容。格式化输出场景下pretty format 且存在缩进还会配合换行与缩进使嵌套子查询可读性更好SQLObject parent x.getParent(); if (parent instanceof SQLSelect) { parent parent.getParent(); } // ODPS特殊处理在二元运算符中query表达式需要括号 if (parent instanceof SQLBinaryOpExpr) { print((); SQLSelect subQuery x.getSubQuery(); if (subQuery ! null) { if (isPrettyFormat() indentCount 0) { this.indentCount; println(); subQuery.accept(this); // ... 缩进与换行后闭合右括号 } } }五、修复四Select clone 后 QUALIFY 子句的保留5.1 根因accept0 未遍历 qualifyOdpsSelectQueryBlock重写了accept0(OdpsASTVisitor)OdpsSelectQueryBlock.java#L57-L74修复前该方法对selectList、from、where、groupBy、orderBy等都执行了acceptChild唯独遗漏了this.qualify。这导致两个连锁后果遍历侧OdpsOutputVisitor通过 visitor 机制访问 AST 时看不到 QUALIFY无法打印clone 侧QUALIFY 没有被纳入遍历克隆链路中对它的复制也就无从谈起clone 后子句丢失。修复方案是在accept0中补上acceptChild(visitor, this.qualify)且位置与基类SQLSelectQueryBlock的 accept 顺序一致位于groupBy之后、orderBy之前public void accept0(OdpsASTVisitor visitor) { if (visitor.visit(this)) { acceptChild(visitor, this.hints); acceptChild(visitor, this.selectList); acceptChild(visitor, this.from); acceptChild(visitor, this.where); acceptChild(visitor, this.groupBy); acceptChild(visitor, this.qualify); // 补全QUALIFY 子句进入遍历 acceptChild(visitor, this.orderBy); acceptChild(visitor, this.zOrderBy); acceptChild(visitor, this.clusterBy); acceptChild(visitor, this.distributeBy); acceptChild(visitor, this.sortBy); acceptChild(visitor, this.limit); acceptChild(visitor, this.into); } visitor.endVisit(this); }5.2 clone 路径依赖基类 cloneToOdpsSelectQueryBlock.clone()的实现OdpsSelectQueryBlock.java#L41-L45如下public OdpsSelectQueryBlock clone() { OdpsSelectQueryBlock x new OdpsSelectQueryBlock(); cloneTo(x); return x; }cloneTo来自基类SQLSelectQueryBlock其中已包含对qualify字段的复制逻辑。因此修复策略为优先保证 accept 路径一致把 qualify 纳入遍历clone 继续依赖基类 cloneTo 对 qualify 的复制design.md 同时注明若实测 clone 后仍丢失则在OdpsSelectQueryBlock.cloneTo中显式复制qualify作为兜底。5.3 输出侧printQualify 的调用顺序OdpsOutputVisitor.visit(OdpsSelectQueryBlock)OdpsOutputVisitor.java#L397-L441中QUALIFY 的打印位置在printGroupBy与printOrderBy之间printSelectList(x.getSelectList()); printFrom(x); printWhere(x); printGroupBy(x); printWindow(x); printQualify(x); // QUALIFY 在 GROUP BY / WINDOW 之后、ORDER BY 之前 printOrderBy(x);这与 ODPS 语义中 QUALIFY 出现在 WHERE/GROUP BY 之后、ORDER BY 之前的语法位置一致clone 后输出的 SQL 也符合原始语句结构。六、测试与回归验证6.1 原始用例清单docs/odps_fix.mdodps_fix.md 是本次修复的验收用例基准覆盖五类场景TestToStringNotMissBracketnot (a) and (not b) and (c)→ 修复前输出not a and not b and (c)括号丢失TestToStringNotErrorBracket(not a) ! b→ 修复前输出(NOT a ! b)括号错乱TestToStringMultiBracket((a 1))→ 修复前输出(a 1)多括号被吞TestToStringSubQueryExpr手工构造1 (SELECT a_1 FROM table_a)的SQLBinaryOpExpr验证子查询表达式 toString 时自动补括号TestCloneQualifySELECT * FROM table_a WHERE a_1 100 QUALIFY ROW_NUMBER() OVER (PARTITION BY b_1 ORDER BY b_2 DESC) 1解析后 clone验证 QUALIFY 不丢失。其中 clone 用例的关键断言思路是对解析得到的SQLSelectStatement调用clone()分别对原对象与克隆对象执行SQLUtils.toOdpsString(...)对比输出是否一致——这直接验证了clone 后 QUALIFY 保留且能正确序列化。6.2 落库单测OdpsFixTest仓库已将 clone QUALIFY 用例固化为正式单测 OdpsFixTest.java位于 ODPS 测试目录core/src/test/java/com/alibaba/druid/bvt/sql/odps使用SQLUtils.parseSingleStatement(sql, DbType.odps)解析、oldStmt.clone()克隆并以assertEquals(oldStr, newStr)断言原语句与克隆语句的 ODPS 序列化结果一致。其余括号用例按 tasks.md 的约定沿用docs/odps_fix.md中的测试思路放入 core 的 ODPS 测试目录作为回归保障。6.3 运行验证tasks.md 要求完成两类验证动作运行现有 ODPS 相关测试core模块com.alibaba.druid.bvt.sql.odps包下的测试确认无回归运行 Checkstyle确认代码风格合规仓库根目录存在 src/checkstyle/druid-checks.xml 校验配置。ODPS 输出入口为SQLUtils.toOdpsString(SQLObject)SQLUtils.java#L167-L175支持传入FormatOption定制格式化行为也是单测中统一使用的序列化 API。七、变更影响与兼容性说明依据 proposal.md 的 Impact 分析受影响模块仅corecom.alibaba.druid.sql.dialect.odps及 ODPS Select/AST 输出、clone 相关节点受影响 API无新增、无移除公开 APISQLUtils.toOdpsString(...)与SQLSelectStatement.clone()在 ODPS 场景下的输出/克隆结果更正确兼容性本变更属向后兼容的 bug fix——不改变合法 SQL 的解析结果仅修正此前错误序列化或错误 clone 的用例无 BREAKING 变更。需要注意的风险与权衡见 design.md修改not与括号规则可能影响极少数依赖当前错误输出的下游脚本——该变更不提供兼容旧错误输出的开关视为 bug fix 处理在 accept0 中新增 qualify 的acceptChild可能影响其他依赖遍历顺序的 visitor——由于顺序与基类SQLSelectQueryBlock一致仅补全遗漏若有回归按用例调整顺序即可。八、总结本次 ODPS 方言优化通过四个定点修复提升了 AST 序列化与克隆的正确性not括号按语义保留与重排、多层括号按计数完整输出、子查询表达式在二元运算中自动补括号、accept0补全 qualify 遍历从而使 QUALIFY 子句在输出与 clone 两条路径上均不再丢失。所有修复均以 docs/odps_fix.md 中的可复现用例为验收基准并有 OdpsFixTest.java 等单测持续回归适合直接作为依赖 Druid 做 ODPS SQL 往返改写、克隆复用的下游系统的正确性参考。赞分享数据库后端【免费下载链接】druid阿里云计算平台DataWorks(https://help.aliyun.com/document_detail/137663.html) 团队出品为监控而生的数据库连接池项目地址https://gitcode.com/gh_mirrors/druid/druid点击查看免费下载相关推荐Druid ODPS 方言优化实战toString 括号语义与 QUALIFY 克隆修复的源码级解析Druid ODPS 方言优化实战toString 括号语义与 QUALIFY 克隆修复的源码级解析 导读 本文围绕 Druid 仓库中 openspec/c数据库后端Druid ODPS 方言优化实战修复 SQL 序列化括号丢失与 QUALIFY 克隆一致性Druid ODPS 方言优化实战修复 SQL 序列化括号丢失与 QUALIFY 克隆一致性 导读 本文基于 Druid 开源仓库中 ODPS 方言优化变更提数据库后端Druid SQL 解析器 ODPS 方言修复实践NOT 括号、QUALIFY 克隆与 Query 表达式输出问题全解析Druid SQL 解析器 ODPS 方言修复实践NOT 括号、QUALIFY 克隆与 Query 表达式输出问题全解析 导读 本文以当前仓库 docs/od数据库后端上一篇解密Qwen1.5-4B-Chat从Transformer架构到高效训练技术的完整指南下一篇终极指南如何用SwiftUI打造《动物森友会》最佳伴侣应用创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表