Apache Calcite 1.34.0 版本深度解析:QUALIFY 子句落地与 BigQuery 日期时间函数全面增强
后端数据库【免费下载链接】calciteApache Calcite项目地址https://gitcode.com/gh_mirrors/ca/calcite点击查看免费下载导读Apache Calcite 于 2023 年 3 月 14 日发布 1.34.0 版本这是继 1.33.0 之后仅时隔一个月的小版本迭代由 18 位贡献者共同完成共解决 34 个问题。本篇文章以官方发布公告site/_posts/2023-03-14-release-1.34.0.md为主线结合仓库内 SQL 语法文档、解析器模板、校验器与关系代数转换器源码以及测试用例深入讲解该版本的两大核心主题QUALIFY 子句的正式引入用于直接过滤窗口函数计算结果CALCITE-5268与BigQuery 库中约 15 个日期/时间/时间戳处理新函数同时剖析一处破坏性变更SqlToRelConverter.Config.expand默认值翻转与DATE_TRUNC行为回归修复。读完本文你将理解 QUALIFY 的语法、校验规则与底层转换原理并掌握 BigQuery 日期时间函数的使用方法。一、版本概况18 位贡献者、34 个问题、1 个月迭代1.34.0 发布于 2023 年 3 月 14 日距离上一个版本 1.33.02023-02-06仅约一个月。根据仓库内的完整发布说明site/_docs/history.md#v1-34-0贡献者 18 人解决 34 个 issue核心亮点一新增QUALIFY子句CALCITE-5268可对窗口函数结果直接做过滤核心亮点二BigQuery 库新增约 15 个用于处理日期DATE、时间TIME、时间戳TIMESTAMP的函数附带修复1.33.0 中意外引入的DATE_TRUNC破坏性变更CALCITE-5447在 1.34.0 通过 CALCITE-5522 修复。发布说明还列出了一份完整的贡献者名单包括 release manager Stamatis Zampetakis 以及 Julian Hyde 等 Calcite 核心成员。二、核心新特性QUALIFY 子句2.1 背景为什么需要 QUALIFY在使用ROW_NUMBER()、RANK()、DENSE_RANK()等窗口函数做“分组取前 N 行”类查询时SQL 标准语法只能把窗口函数放进 SELECT 列表再借助子查询或 CTE 包裹一层才能按窗口计算结果过滤例如SELECT * FROM ( SELECT empno, ename, deptno, ROW_NUMBER() OVER (PARTITION BY ename ORDER BY deptno) AS rn FROM emp ) WHERE rn 1;这种写法冗长且可读性差。QUALIFY子句源自 BigQuery、Snowflake 等方言的实践允许把过滤条件直接写在查询末尾从而简化上述写法是处理“每组分组的 Top-N”类问题的常用手段。2.2 语法位置与 SQL 参考文档在官方 SQL 参考文档site/_docs/reference.md中SELECT 的完整语法骨架如下select: SELECT [ hintComment ] [ STREAM ] [ ALL | DISTINCT [ ON ( expression [, expression ]* ) ] ] { starWithExclude | projectItem [, projectItem ]* } [ BY expression [, expression ]* ] FROM tableExpression [ WHERE booleanExpression ] [ GROUP BY { ALL | [ALL | DISTINCT ] groupItem [, groupItem ]* } ] [ HAVING booleanExpression ] [ WINDOW windowName AS windowSpec [, windowName AS windowSpec ]* ] [ QUALIFY booleanExpression ] [ ORDER BY orderItem [, orderItem ]* ] [ LIMIT expression [ OFFSET { start | expression } ] ]可见QUALIFY位于HAVING/WINDOW之后、ORDER BY之前接收一个布尔表达式。同时QUALIFY已成为 Calcite 解析器的保留关键字见 reference.md 的保留关键字清单并被 Parser 测试testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java覆盖。2.3 解析层实现Parser.jj语法层由 JavaCC 解析器模板实现。在核心解析器模板 core/src/main/codegen/templates/Parser.jj 中第 3200 行附近的SqlQualify()规则读取QUALIFY关键字后解析一个表达式并允许子查询ExprContext.ACCEPT_SUB_QUERY第 9163 行定义关键字QUALIFY的 token。SqlSelect抽象语法树节点core/src/main/java/org/apache/calcite/sql/SqlSelect.java为此新增了qualify字段构造参数序号QUALIFY_OPERAND 7并提供getQualify()/setQualify()访问方法用于在解析、校验、转换各阶段传递该子句。2.4 校验层QUALIFY 的两个硬性约束在校验器 core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java 的validateQualifyClause方法第 5194 行起中QUALIFY 表达式要经过完整的扩展、类型推断与校验流程并施加两个硬性约束类型必须为布尔表达式类型必须属于布尔族SqlTypeUtil.inBooleanFamily否则抛出condMustBeBoolean(QUALIFY)错误必须包含窗口函数表达式内部必须出现窗口函数overFinder.findAgg(qualifyNode) ! null否则抛出qualifyExpressionMustContainWindowFunction错误。这一约束确保了 QUALIFY 只用于过滤窗口计算结果而不是替代 WHERE/HAVING。2.5 转换层如何变成关系代数QUALIFY 在 SQL 到关系代数转换器 core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java 的convertSelectList方法第 5160 行起中实现。其核心技巧是三步转换把 QUALIFY 表达式作为一个别名为QualifyExpression的字段追加到 SELECT 列表转换完整列表后用filter(last(fields))按最后一个字段即 QUALIFY 计算结果过滤行再用project(skipLast(fields))把该临时字段从结果中投影去除。这解释了为什么 QUALIFY 可以引用 SELECT 列表中的别名如QUALIFY row_num 1——它在逻辑上被挂到 select 列表末尾进行求值因此能访问窗口函数及其别名。相关实现细节见 SqlToRelConverter.convertSelectList。2.6 使用示例来自集成测试 qualify.iq仓库提供了专门的集成测试脚本 core/src/test/resources/sql/qualify.iq其中包含多种典型场景1QUALIFY 不引用任何 SELECT 别名直接内联窗口函数SELECT empno, ename, deptno FROM scott.emp QUALIFY ROW_NUMBER() over (partition by ename order by deptno) 1;2与 WHERE 组合先 WHERE 过滤再按窗口结果过滤SELECT empno, ename, deptno FROM scott.emp WHERE deptno 20 QUALIFY ROW_NUMBER() over (partition by ename order by deptno) 1;3引用 SELECT 中的别名SELECT empno, ename, deptno, ROW_NUMBER() over (partition by ename order by deptno) as row_num FROM scott.emp QUALIFY row_num 1;4与 WINDOW 子句配合CALCITE-6691 也覆盖了子查询内 QUALIFY 的场景SELECT empno, ename, deptno, SUM(deptno) OVER myWindow as sumDeptNo FROM scott.emp WINDOW myWindow AS (PARTITION BY ename ORDER BY empno) QUALIFY sumDeptNo 1;5聚合查询上使用 QUALIFY按组内 COUNT 排序取每组第一SELECT deptno FROM emp GROUP BY deptno, job QUALIFY row_number() OVER (PARTITION BY deptno ORDER BY COUNT(*) DESC) 1;6子查询中投影后再 QUALIFY每个部门薪水最高的员工所在部门WITH t0 AS (SELECT deptno, sal FROM emp), t1 AS (SELECT deptno FROM t0 QUALIFY row_number() OVER (PARTITION BY deptno ORDER BY sal DESC) 1) SELECT deptno FROM t1;此外单元测试 core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java第 4452 行起还覆盖了 QUALIFY 与DISTINCT、INSERT INTO ... SELECTDDL 场景、相关子查询QUALIFY rank_val (SELECT COUNT(*) FROM emp)、派生列、多别名引用及“WHERE QUALIFY ORDER BY LIMIT”全组合等场景验证了该特性的鲁棒性。三、BigQuery 库日期时间函数增强约 15 个新函数3.1 新函数清单1.34.0 在 BigQuery 函数库中新增了约 15 个日期、时间、时间戳处理函数对应 site/_docs/history.md 中的 New features 列表函数对应 Issue功能说明DATE_ADD、DATE_DIFF、DATETIME_ADD、DATETIME_DIFFCALCITE-5469对日期/日期时间做加减与差值计算DATETIME_SUBCALCITE-5484日期时间减法FORMAT_TIME、FORMAT_DATE、FORMAT_DATETIME、FORMAT_TIMESTAMPCALCITE-5357按格式字符串格式化时间值DATE、TIME、TIMESTAMP、DATETIME构造器函数CALCITE-5508从字符串/时间戳构造各类型值这些函数定义在 core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java 中例如DATE_ADD(date, interval)返回类型为ARG0_NULLABLE即与第一个参数类型一致的可空类型DATE_DIFF(date, date2, timeUnit)/DATETIME_DIFF基于SqlTimestampDiffFunction实现接收时间单位参数FORMAT_DATE、FORMAT_TIME、FORMAT_TIMESTAMP、FORMAT_DATETIME返回VARCHAR_NULLABLE第一个参数是格式化字符串DATETIME_ADD、DATETIME_SUB直接复用TIMESTAMP_ADD2/TIMESTAMP_SUB的实现并通过withName改名而来DATE_TRUNC(date, timeUnit)按时间单位截断日期。所有函数均通过LibraryOperators注册到BIG_QUERY函数库中因此仅在启用 BigQuery 库的解析/校验配置下可用如通过Lex或SqlLibrary.BIG_QUERY设置。3.2 使用示例来自 Babel 集成测试Babel 模块的 BigQuery 方言集成测试脚本 babel/src/test/resources/sql/big-query.iq 给出了大量可直接运行的真实示例DATETIME_ADD / DATETIME_SUBDATETIME_ADD(DATETIME 2008-12-25 15:30:00, INTERVAL 10 MINUTE) as later; DATETIME_ADD(TIMESTAMP 2008-12-25 15:30:00, INTERVAL 20 MINUTE) as later;DATETIME_DIFF支持 WEEK、WEEK(MONDAY)、ISOWEEK、YEAR 等时间单位并遵循“按单位边界计数”的 BigQuery 语义DATETIME_DIFF(2017-12-18, 2017-12-17, WEEK) AS week_diff, DATETIME_DIFF(2017-12-18, 2017-12-17, WEEK(MONDAY)) AS week_weekday_diff, DATETIME_DIFF(2017-12-18, 2017-12-17, ISOWEEK) AS isoweek_diff;DATE_TRUNC支持 YEAR、ISOYEAR、QUARTER、MONTH、WEEK、WEEK(SUNDAY)、WEEK(MONDAY)、ISOWEEK、DAY 等粒度可作用于 DATE、DATETIME、TIMESTAMPSELECT DATE_TRUNC(DATE 2008-12-25, YEAR) as d , year as frame UNION ALL SELECT DATE_TRUNC(DATE 2008-12-25, ISOYEAR), isoyear UNION ALL SELECT DATE_TRUNC(DATE 2008-12-25, QUARTER), quarter UNION ALL SELECT DATE_TRUNC(DATE 2008-12-25, MONTH), month UNION ALL SELECT DATE_TRUNC(DATE 2008-12-25, WEEK), week UNION ALL SELECT DATE_TRUNC(DATE 2008-12-25, WEEK(MONDAY)), week(monday) UNION ALL SELECT DATE_TRUNC(DATE 2008-12-25, ISOWEEK), isoweek UNION ALL SELECT DATE_TRUNC(DATE 2008-12-25, DAY), day;FORMAT_DATE / FORMAT_DATETIME / FORMAT_TIMESTAMP%x、%b-%d-%Y、%c等格式化占位符SELECT FORMAT_DATE(%x, DATE 2008-12-25) AS US_format; SELECT FORMAT_DATE(%b-%d-%Y, DATE 2008-12-25) AS formatted; SELECT FORMAT_TIMESTAMP(%c, TIMESTAMP 2008-12-25 15:30:00, UTC) AS formatted;这些测试脚本同时也验证了 Babel 解析器对上述函数的兼容性Redshift 库的测试见 babel/src/test/resources/sql/redshift.iq。四、DATE_TRUNC 回归修复1.33.0 引入的破坏性变更1.33.0 在实现 BigQueryDATE_TRUNCCALCITE-5447时意外引入了一个破坏性变更部分DATE_TRUNC的重载形式无法被 Babel 解析器正确处理CALCITE-5522。1.34.0 修复了该问题使DATE_TRUNC(date, timeUnit)的完整重载在 Babel 与 BigQuery 库下均可用。修复后的行为可通过 babel/src/test/resources/sql/big-query.iq 中第 3806 行起的DATE_TRUNC用例验证包括对TIMESTAMP、DATETIME输入的截断以及WEEK(MONDAY)、ISOYEAR等边界语义。五、重要的破坏性变更SqlToRelConverter.Config.expand 默认值翻转1.34.0 带来的最值得注意的破坏性变更是 CALCITE-3870SqlToRelConverter.Config.expand的默认值由true改为false。影响如下此前SqlToRelConverter会把子查询如IN、EXISTS、标量子查询在转换阶段直接展开expand为关系表达式现在默认改为生成RexSubQuery表达式不再展开若需要展开这些RexSubQuery必须在计划阶段启用SubQueryRemoveRule规则如需保留旧行为不推荐但受支持可用SqlToRelConverter.config().withExpand(true)初始化转换器。这一变更会影响所有基于 Calcite 做 SQL 到关系代数转换的引擎升级时需注意测试计划形状的变化。相关配置在转换器源码 core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java 中定义且从源码结构看测试层已普遍采用.withExpand(false)显式声明该行为例如 SqlToRelConverterTest.java 中的testQuantifyOperatorsWithTypeCoercion。六、其他修复、增强与依赖变化6.1 主要 Bug 修复CALCITE-5504SparkSqlDialect中数组值构造器 unparse 错误CALCITE-5518RelToSqlConverter生成ROLLUP字段顺序错误CALCITE-5510RelToSqlConverter在方言允许时应使用序号进行ORDER BYCALCITE-5478SqlTypeFactoryImpl.leastRestrictive对日期时间类型应使用最高输入精度CALCITE-5531COALESCE函数抛出ClassCastExceptionCALCITE-5507HAVING别名在混合使用别名与聚合函数时失败CALCITE-5503CheapestPlanReplacer应复用 DAG 计划中的重复节点CALCITE-5468SqlToRelConverter在ORDER BY含IN时抛出异常CALCITE-5515为CassandraSchema与CassandraTable增加 keyspace 参数CALCITE-5416JDBC adapter 对 MySQL 5 的GROUP BY ROLLUP与ORDER BY组合生成错误 SQLCALCITE-5505JavaCC 警告 Parser.jj 中缺少 LOOKAHEAD 指令CALCITE-5514RelJson增加公开的toRex()实例方法CALCITE-5442调整EnumerableInterpretable的 janino 代码生成以支持调试CALCITE-5521移除RelSubset#add中冗余的 rowtype 检查CALCITE-5483ProjectAggregateMergeRule在字面量为非数值时抛异常TryThreadLocal的值默认变为非空。6.2 API 与行为增强CALCITE-5545允许覆盖SqlValidator以支持自定义SqlNode校验。6.3 依赖与构建Quidem 测试框架由 0.10 升级至 0.11新增 Presto 方言LIMIT/OFFSET顺序测试CALCITE-5524、CAST ... FORMAT子句测试CALCITE-2980修复LatticeSuggesterTest慢测试CALCITE-5537Autostyle 规则新增“方法声明或调用中)前禁止空格或换行”的检查。七、兼容性与测试矩阵根据发布说明1.34.0 在 Linux、macOS、Microsoft Windows 上测试通过支持 JDK/OpenJDK 8 至 18Guava 版本范围为 16.0.1 至 31.1-jre其他软件版本以仓库根目录 gradle.properties 及各子模块的 gradle.properties 为准。升级前请重点核对第五节提到的expand默认值变更对自身计划形状的影响。八、总结Apache Calcite 1.34.0 虽然是一个体量较小的迭代版本但含金量不低QUALIFY子句从语法解析Parser.jj、校验必须为布尔且必须包含窗口函数到关系代数转换追加临时列→过滤→投影去除的完整链路都已落地并有充分的集成测试佐证BigQuery 库约 15 个日期时间函数补齐了与主流云数仓方言对齐的能力DATE_TRUNC回归修复消除了 1.33.0 引入的兼容性隐患。对于需要做“分组 Top-N”“窗口结果过滤”以及 BigQuery 方言兼容的引擎开发者而言1.34.0 是值得跟进的一个重要里程碑。相关语法细节可继续查阅 site/_docs/reference.md完整变更清单见 site/_docs/history.md。赞分享后端数据库【免费下载链接】calciteApache Calcite项目地址https://gitcode.com/gh_mirrors/ca/calcite点击查看免费下载相关推荐Apache Calcite 1.33.0 版本发布解析BigQuery 方言增强、MEASURE 类型与自定义时间帧Apache Calcite 1.33.0 版本发布解析BigQuery 方言增强、MEASURE 类型与自定义时间帧 Apache Calcite 1.33后端数据库Apache Beam 2.34.0 版本深度解读BigQuery 读取增强、Python 死信模式与 Calcite SQL 转正Apache Beam 2.34.0 版本深度解读BigQuery 读取增强、Python 死信模式与 Calcite SQL 转正 Apache BeamApache DataFusion 50.0.0 版本解析QUALIFY 子句、Parquet 元数据缓存与查询引擎关键改进Apache DataFusion 50.0.0 版本解析QUALIFY 子句、Parquet 元数据缓存与查询引擎关键改进 Apache DataFusio大数据数据分析后端上一篇wifit3 双卡分工实战一张卡注入、一张卡监听的Wi-Fi嗅探完整指南下一篇MaaNTE安全风险警示仿冒助手为何可能带毒如何识别官方下载渠道与正版验证创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考