使用Calcite执行含NULL值多列排序查询时触发NullPointerException
问题描述
执行以下SQL查询时触发NullPointerException:
select distinct tbl.col1 , tbl.col2 from infoset.SUBEKT tbl order by col1 asc nulls last , col2 asc nulls last
异常堆栈信息:
java.lang.NullPointerException at org.apache.calcite.runtime.Utilities.compare(Utilities.java:202) at Baz$2.compare(ANONYMOUS.java:15) at Baz$2.compare(ANONYMOUS.java:27) at java.util.TreeMap.getEntryUsingComparator(TreeMap.java:376) at java.util.TreeMap.getEntry(TreeMap.java:345) at java.util.TreeMap.get(TreeMap.java:278) at org.apache.calcite.linq4j.EnumerableDefaults.toLookup_(EnumerableDefaults.java:3608) at org.apache.calcite.linq4j.EnumerableDefaults$14.enumerator(EnumerableDefaults.java:2660) at org.apache.calcite.linq4j.AbstractEnumerable.iterator(AbstractEnumerable.java:33) at org.apache.calcite.avatica.MetaImpl.createCursor(MetaImpl.java:90) at org.apache.calcite.avatica.AvaticaResultSet.execute(AvaticaResultSet.java:186) at org.apache.calcite.jdbc.CalciteResultSet.execute(CalciteResultSet.java:64) at org.apache.calcite.jdbc.CalciteResultSet.execute(CalciteResultSet.java:43) at org.apache.calcite.avatica.AvaticaConnection$1.execute(AvaticaConnection.java:669) at org.apache.calcite.jdbc.CalciteMetaImpl.prepareAndExecute(CalciteMetaImpl.java:636) at org.apache.calcite.avatica.AvaticaConnection.prepareAndExecuteInternal(AvaticaConnection.java:677) at org.apache.calcite.avatica.AvaticaStatement.executeInternal(AvaticaStatement.java:157) at org.apache.calcite.avatica.AvaticaStatement.executeQuery(AvaticaStatement.java:228) at ru.spi2.modules.infosetquery.InfosetQueryPlugin.executeQuery(InfosetQueryPlugin.java:101)
经排查,问题由col1存在NULL值导致,仅按col1或col2单一列排序时可正常执行。
解决方案
1. 升级Calcite版本
该NPE是Calcite的已知bug,在1.28.0及以上版本已修复。升级到对应版本可直接解决多列排序含NULL值时的比较逻辑漏洞。
2. 临时规避方案(无法升级时)
- 为可能为NULL的列添加非空默认值,确保排序时无NULL参与比较:
select distinct coalesce(tbl.col1, 'DEFAULT_MARKER') as col1, tbl.col2 from infoset.SUBEKT tbl order by coalesce(tbl.col1, 'DEFAULT_MARKER') asc nulls last , col2 asc nulls last
注意:DEFAULT_MARKER需与col1数据类型匹配,比如字符串类型用空串,数值类型用0或业务允许的默认值。
- 拆分去重与排序逻辑,调整执行计划:
select col1, col2 from ( select distinct tbl.col1, tbl.col2 from infoset.SUBEKT tbl ) t order by col1 asc nulls last, col2 asc nulls last
这种写法会让Calcite先完成去重再执行排序,绕过原bug触发的执行路径。
3. 检查自定义比较器(若有)
如果项目中自定义了Calcite的比较逻辑,需确认是否正确处理NULL值场景:在调用比较方法前先判断NULL,避免直接对NULL值执行比较操作导致NPE。
内容的提问来源于stack exchange,提问作者Kirill Kim
相关产品推荐
相关产品推荐

