Skip to content

HIVE-29641: Upgrade Calcite to 1.42.0#6523

Open
rubenada wants to merge 40 commits into
apache:masterfrom
rubenada:HIVE-29641
Open

HIVE-29641: Upgrade Calcite to 1.42.0#6523
rubenada wants to merge 40 commits into
apache:masterfrom
rubenada:HIVE-29641

Conversation

@rubenada

@rubenada rubenada commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

Upgrade Calcite to 1.42.0.

Why are the changes needed?

Upgrade to latest Calcite version.

Does this PR introduce any user-facing change?

Plan changes, see details below.

How was this patch tested?

Running all existing tests, making test adjustments where needed, see details below.

Details

pom changes

  • parent pom.xml: change Calcite version (from 1.33.0 to 1.42.0)
  • ql/pom.xml: Add httpclient5/httpcore5 as runtime dependencies to fix NoClassDefFoundError: org/apache/hc/core5/util/Timeout when running certain tests (see more info on the comment of this file)
  • ql/pom.xml: Add org.jooq:joou-java-6 to fix NoClassDefFoundError: org/joou/UShort (seen on Jenkins init-metastore); this is due to "[CALCITE-1466] Support for UNSIGNED types of TINYINT, SMALLINT, INT, BIGINT in type system" (1.41)
  • druid-handler/pom.xml: remove Calcite shade include and relocation to avoid Error in ASM processing class org/apache/hive/druid/org/apache/calcite/runtime/SqlFunctions.class: Index 65536 out of bounds for length 334 during shading / javadoc; the problem originates due to invalid byte code on Calcite: "[CALCITE-6393] Byte code of SqlFunctions is invalid" (already fixed on Calcite side for 1.43.0, so this workaround could be removed on a subsequent upgrade to said version).

Code adjustments due to Calcite breaking changes

  • "[CALCITE-5557] Add SAFE_CAST function (enabled in BigQuery library)" (1.35)
    Use non-deprecated version of RexBuilder#makeAbstractCast in HiveSqlSumAggFunction, RexNodeConverter, ASTConverter
  • "ddb4200 Refactor: Add fields AggregateCall.rexList and RelBuilder.AggCall.preOperands" (1.35)
    AggCall construction adjustments in HiveAggregate and HiveSqlSumAggFunction
  • "[CALCITE-3679] Allow lambda expressions in SQL queries" (1.37)
    Add visitLambda and visitLambdaRef in RexVisitor implementation (HiveCalciteUtil#ConstantFinder)
  • "[CALCITE-6961] Support LogicalRepeatUnion in RelShuttle" + "[CALCITE-6959] Support LogicalAsofJoin in RelShuttle" (1.40)
    Add visit(LogicalRepeatUnion) and visit(LogicalAsofJoin) to HiveRelShuttleImpl
  • "[CALCITE-5387] Type-mismatch on nullability in JoinPushTransitivePredicatesRule RelRule" (1.40)
    Adjust RexPermuteInputsShuttle constructor in HiveJoinConstraintsRule
  • "[CALCITE-7029] Support DPhyp to handle various join types" (1.41)
    Add visitNodeAndFieldIndex in RexVisitor implementation (HiveCalciteUtil#ConstantFinder)
  • "[CALCITE-7351] Make getMaxNumericScale() and getMaxNumericPrecision() final" (1.42)
    Adjust HiveTypeSystemImpl

Other code changes

  • Hive.java: Update Calcite default charset system property (use UTF16 instead of the default ISO-8859-1), otherwise we get "charset pollution" on char literals in explained plans (e.g. _UTF-16LE'1' instead of simply '1') due to the modification in SqlImplementor#toSql(RexLiteral) via "[CALCITE-6006] RelToSqlConverter loses charset information" (introduced in 1.36)
    As a consequence of defining Calcite default charset system property, now RexLiteral#appendAsJava (which already contained the literal charset vs default charset verification, added to SqlImplementor in CALCITE-6006 1.36), literals printed via this method (i.e. used to print the charset) don't do it any more, e.g. _UTF-16LE'ten' => 'ten', which aligns them with the ones printed via SqlImplementor#toSql(RexLiteral)
  • Due to "7fc3e1b Refactor: Add RelNode.stripped" (included in Calcite 1.35), which added a new check in HepRelVertex constructor (inner rel cannot be another HepRelVertex), we basically cannot apply a HepProgram on a tree containing already HepVertex. This is violated by HiveHepExtractRelNodeRule, so all its callers now use an existing auxiliary method for this unwrapping (HiveCalciteUtil.stripHepVertices).
    This was also violated by materialization rules in HiveMaterializedViewRule, which used its own private static class HiveHepExtractRelNodeRule in a HepProgram as unionRewritingPullProgram. As a workaround, it is proposed to extend the MaterializedView rules (as HiveMaterializedViewRule), override the rewriteQuery
    (where the UnionRewritingPullProgram is called), and unwrap the HepRelVertex tree ourselves (using HiveCalciteUtil.stripHepVertices) before calling the super.rewriteQuery. This Calcite issue is being tracked via CALCITE-7641, (already fixed on Calcite side, so this workaround could be removed on a subsequent upgrade to Calcite 1.43.0).
  • Add new case CHAR_LENGTH in SqlFunctionConverter#buildAST, since a new SqlKind.CHAR_LENGTH has been added (previously length functions had SqlKind.OTHER_FUNCTION) via "[CALCITE-6182] Add LENGTH/LEN function (enabled in Snowflake library)" (in Calcite 1.37). Problem observed in several tests, where length(x) function not being evaluated (returning 'length'literal instead of the actual value), e.g. TestJdbcDriver2#testExprCol: Illegal conversion to int from column 2 [length]
  • Due to "[CALCITE-6044] RelMetadataQuery should regard single-row relational expressions as unique" (1.37), RelMdUniqueKeys can return now a set containing a single, empty set in cases like an empty Values or Values with 1 tuple; so we need to consider this circumstance when checking the uniqueKeys of Aggregate's input in HiveRelFieldTrimmer#generateNewGroupset, otherwise it can convert a HiveAggregate(group=[{0}], COUNT)
    into HiveAggregate(group=[{}], COUNT) which is NOT the same semantics when the input is an empty Values (empty resultset vs 0). Issue seen on TestMiniLlapLocalCliDriver with empty_result.q which was incorrectly generating a Project+Agg+EmptyValues plan instead of simply EmptyValues before fixing HiveRelFieldTrimmer#generateNewGroupset:
select count(a1) from t1 where 1=0 group by a1 order by a1;
< HiveProject(_c0=[$0])
<   HiveAggregate(group=[{}], agg#0=[count($0)])
<     HiveValues(tuples=[[]])
---
> HiveValues(tuples=[[]])
  • The upgrade unveiled a bug in HiveRelFieldTrimmer#rewriteGBConstantKeys: the condition on its javadoc "none of the relnode above aggregate refers to these group keys" should not be checked via fieldsUsed.contains(aggregate.getGroupSet()) but rather fieldsUsed.intersects(aggregate.getGroupSet()).
    Also took the opportunity here to get rid of the unnecessary check aggregate.getIndicatorCount() > 0 which is always false (this method is deprecated and always returns zero)
  • Disable simplifyValues in HiveRelBuilder config. Due to "[CALCITE-5717] RelBuilder.project of literals on a single-row Aggregate should create a Values" (introduced 1.35), cases like TestMiniLlapLocalCliDriver with file explainuser_1.q led to: java.lang.UnsupportedOperationException: Values with non-empty tuples are not supported. at org.apache.hadoop.hive.ql.optimizer.calcite.translator.ASTConverter.convert(ASTConverter.java:264). The reason is that RelBuilder#project_ contains the simplification (only applicable if config.simplifyValues is true): "If the expressions are all literals, and the input is a Values with N rows [...], replace with a Values with same tuple N times"; and with CALCITE-5717 that simplification was extended to not only "Values with N rows" but also "Aggregates with 1 row", and this case would lead to creating a non-empty HiveValues, which is not supported in our case. The easiest solution to prevent that from happening is disabling simplifyValues in HiveRelBuilder config.
  • Due to a new assertion introduced in 1.41 via "[CALCITE-7070] FILTER_REDUCE_EXPRESSIONS crashes on expression BETWEEN ( NULL) AND X", now Calcite can throw an AssertionError in case of a wrong binary comparison predicate with null value java.lang.AssertionError: Comparison with NULL in pulledUpPredicates, so in order to avoid that, a few classes needed to be adjusted. Firstly HiveFunctionHelper#getExpression must not create such expressions (seen NULL = NULL in TestMiniLlapLocalCliDriver with subquery_null_agg.q); secondly avoid incorrect comparison ($snapshotIdInputRef <= NULL seen in TestIcebergCliDriver with mv_iceberg_orc.q) in HiveAugmentSnapshotMaterializationRule, use snapshotId -1 instead, and then switch it back in HivePushdownSnapshotFilterRule.
  • Override HiveTypeSystemImpl#deriveDecimalDivideType because "[CALCITE-6464] Type inference for DECIMAL division seems incorrect" (introduced in 1.38) changed the default behavior to match MS-SQL-Server-style algorithm, which can cause a drop in the scale computation, hence a precision loss in certain cases. We override this method to keep the "old behavior" (pre-CALCITE-6464). Note: an alternative could be not overriding it (and keep the new Calcite default MS-SQL-style decimal-divide semantics), but that would lead to "regressions" (precision loss) and would require test adjustments, so not considered in the current patch.

Test plan adjustments

  • Perf Imp 1 ) materialized_view_rewrite_4.q.out
    Plan improvement. On test query "EXAMPLE 32" MV rewriting was NOT applied so far (full join+aggregate plan, Stage-1 Tez with 2 maps + 2 reducers); but now it is: direct scan of mv1_n3 (Stage-0 Fetch only)

  • Perf Reg 1 ) materialized_view_partitioned_2.q
    REGRESSION, most efficient alternative (partition_mv_3) is no longer used.
    Reason: mv partition_mv_3 PARTITIONED ON (key) is created as SELECT value, key FROM src_txn_2 where key > 220 and key < 230 The regression queries are ... where key > 224 and key < 226; and ... where key > 223 and key < 225; so partition_mv_3 should be used.
    However, in Calcite's SubstitutionVisitor#splitFilter, instead of returning the appropriate filter, it now returns null.
    The cause is that this line RexNode r = canonizeNode(rexBuilder, simplify.simplifyUnknownAsFalse(x2)); leads to an extra IS NOT NULL predicate on r, which makes it not equivalent to condition2 on the following line.
    The reason is an existing bug on a Calcite simplification: CALCITE-7635. This bug was so far "hidden", because this simplification was only applied for comparisons of the form ref op literal, and in our query they are literal op ref. But Calcite 1.35 introduced an improvement (CALCITE-5780) to support all comparisons formats, which enabled the problematic simplification that leads to the bug CALCITE-7635. This bug has already been fixed on Calcite side, so this shall be corrected with the eventual upgrade to Calcite 1.43.0.

  • 0a ) Many plan changes of literals without charset, due to Calcite system property default charset definitions (see change on Hive.java above), after "CALCITE-6006: RelToSqlConverter loses charset information" (introduced in 1.36). Example:

<     HiveFilter(condition=[IN($15, '2000Q1', '2000Q2', '2000Q3')])
---
>     HiveFilter(condition=[IN($15, _UTF-16LE'2000Q1', _UTF-16LE'2000Q2', _UTF-16LE'2000Q3')])
  • 0b ) Many plan differences due to Double format change, because of RexLiteral using Doubles instead of BigDecimal: "[CALCITE-2067] RexLiteral cannot represent accurately floating point values, including NaN, Infinity" (included in 1.38). Example
<     WHERE `key` < 2.000E2 AND `ds` = '2008-04-08'
---
>     WHERE `key` < 200 AND `ds` = '2008-04-08'
  • 1 ) New IS NOT NULL simplifications involving CAST, like in: dynamic_partition_pruning.q
<                   filterExpr: ((UDFToDouble(hour) = 11.0D) and hr is not null) (type: boolean)
---
>                   filterExpr: ((UDFToDouble(hour) = 11.0D) and CAST( UDFToInteger((hr / 2.0D)) AS STRING) is not null) (type: boolean)

Reason: "[CALCITE-5769] Optimizing 'CAST(e AS t) IS NOT NULL' to 'e IS NOT NULL'" (1.35) (only for "lossless" casts)

  • 2 ) New IS NOT NULL simplifications, like in: merge_iceberg_copy_on_write_unpartitioned.q
<                   predicate: ((_col5 <> _col8) or (_col5 <= 100) or (_col5 is null or ((_col5 = _col8) and (_col5 > 100))) is null) (type: boolean)
---
>                   predicate: ((((_col5 <> _col8) or (_col5 <= 100)) and _col5 is not null) or (_col5 is null or ((_col5 = _col8) and (_col5 > 100))) is null) (type: boolean)

Reason: "[CALCITE-5639] RexSimplify should remove IS NOT NULL check when LIKE comparison is present" (actually more generic than just LIKE) (1.35)

  • 3 ) Some IS [NOT] NULL simplifications have been removed, like in: explainuser_1
<                               predicate:(UDFToDouble(_col0) / _col1) is not null
---
>                               predicate:(_col1 is not null and UDFToDouble(_col0) is not null)

or

<                       predicate:(UDFToDouble(key) + 1.0D) is not null
---
>                       predicate:UDFToDouble(key) is not null

Reason: Some IS [NOT] NULL simplifications have been removed from RexSimplify for "non-safe" operations, e.g. division (if it could "hide" a runtime division by zero) or operations involving cast (only "lossless" casts can be simplified)
"[CALCITE-7145] RexSimplify should not simplify IS NULL(10/0)" (1.42)
"[CALCITE-7296] RexSimplify should not simplify IS (NOT) NULL(CAST(10/0 AS BIGINT))" (1.42)
"[CALCITE-7295] RexSimplify should simplify a division with a NULL argument" (1.42) — added a new x/NULL → NULL rule, but only when the surrounding context is "safe".
"[CALCITE-7032] Simplify 'NULL>ALL (ARRAY[1,2,NULL])' to 'NULL'" (1.41)

  • 4 ) Simplifications around IS NOT TRUE, like: subquery_ANY
<                   expressions: _col1 (type: int), _col0 (type: string), (_col2 <> 0L) (type: boolean), (_col2 <= _col3) (type: boolean)
---
>                   expressions: _col1 (type: int), _col0 (type: string), (_col2 <> 0L) (type: boolean), (_col2 > _col3) is not true (type: boolean)

Reason: "[CALCITE-5798] Improve simplification of '(x < y) IS NOT TRUE' when x and y are not nullable" (1.35)

  • 5 ) Fixed padding on fixed-length string literals inside OR/IN/SEARCH, like in: cbo_query12
<             HiveFilter(condition=[IN($12, 'Books                                             ', 'Jewelry                                           ', 'Sports                                            ')])
---
>             HiveFilter(condition=[IN($12, 'Books', 'Jewelry', 'Sports')])

Reason: "[CALCITE-4590] Incorrect query result with fixed-length string" (1.39)
NOTE: This fix only impacts predicates with OR/IN/SEARCH, and aligns these plans with single value condition ones (which already contained the padding, e.g. ql/src/test/results/clientpositive/perf/tpcds30tb/tez/cbo_query33.q.out)

  • 6 ) Array construction adjustment, like in: insert_into_dynamic_partitions
< FROM TABLE(INLINE(ARRAY ((1, '101'), (2, '202'), (3, '303'), (4, '404'), (5, '505'))))
---
> FROM TABLE(INLINE(ARRAY[(1, '101'), (2, '202'), (3, '303'), (4, '404'), (5, '505')]))

Reason: "[CALCITE-6417] Map value constructor and Array value constructor unparsed incorrectly for HiveSqlDialect" (1.38)

  • 7 ) New return type on MINUS in JSON, like in: query2.q TestExplainCBOFormattedCliDriver
<               ],
<               "type": {
<                 "type": "INTEGER",
<                 "nullable": true
<               }
---
>               ]

Reason: "[CALCITE-5607] Serialize return type during RelJson.toJson(RexNode node) for SqlKind.MINUS" (1.37)

  • 8 ) New IS [NOT] DISTINCT FROM simplifications, like: orc_predicate_pushdown
<             predicate: ((t = -1Y) and (s like 'bob%')) (type: boolean)
---
>             predicate: ((t IS NOT DISTINCT FROM -1Y) and (s like 'bob%')) (type: boolean)

Reason: "[CALCITE-7160] Simplify AND/OR with DISTINCT predicates to SEARCH" (1.41)

  • 9 ) Small fix on on multi_count_distinct_null.q
<     HiveProject($f0=[CASE(=($2, 1), 1, null:INTEGER)], $f1=[CASE(AND(=($2, 2), IS NOT NULL($1)), 1, null:INTEGER)])
---
>     HiveProject($f0=[CASE(=($2, 1), 1, null:INTEGER)], $f1=[null:INTEGER])

Reason: "[CALCITE-6317] Incorrect constant replacement when group keys are NULL" (1.37)

  • 10 ) IS [NOT] TRUE/FALSE adjustments, like in: sharedwork
< WHERE (`t17`.`$f2` IS NULL OR `t17`.`c` IS NULL OR `t17`.`c` = 0) AND (`t17`.`p_type` IS NULL OR `t17`.`c` IS NULL OR `t17`.`c` = 0 OR (`t17`.`ck` < `t17`.`c` IS NULL OR NOT `t17`.`ck` < `t17`.`c`) OR `t17`.`$f2` IS NOT NULL) AND (`t17`.`c` IS NULL OR `t17`.`c` = 0 OR `t17`.`p_type` IS NOT NULL OR `t17`.`$f2` IS NOT NULL)
---
> WHERE (`t17`.`$f2` IS NULL OR `t17`.`c` IS NULL OR `t17`.`c` = 0) AND (`t17`.`p_type` IS NULL OR `t17`.`c` IS NULL OR `t17`.`c` = 0 OR `t17`.`ck` < `t17`.`c` IS NOT TRUE OR `t17`.`$f2` IS NOT NULL) AND (`t17`.`c` IS NULL OR `t17`.`c` = 0 OR `t17`.`p_type` IS NOT NULL OR `t17`.`$f2` IS NOT NULL)

Reason: "[CALCITE-7005] Invalid unparse for IS TRUE,IS FALSE,IS NOT TRUE and IS NOT FALSE in Hive/Presto Dialect" (1.40)

  • 11 ) Filter predicates in different order, like in: materialized_view_rewrite_ssb.q,materialized_view_rewrite_ssb_2.q
<                   filterExpr: ((s_region = 'AMERICA') and (p_category = 'MFGR#12')) (type: boolean)
---
>                   filterExpr: ((p_category = 'MFGR#12') and (s_region = 'AMERICA')) (type: boolean)
  • 12 ) NOT BETWEEN expression replaced by comparisons: udf_between
<           filterExpr: (((UDFToDouble(key) + 100.0D) < 100.0D) or ((UDFToDouble(key) + 100.0D) > 200.0D)) (type: boolean)
---
>           filterExpr: (UDFToDouble(key) + 100.0D) NOT BETWEEN 100.0D AND 200.0D (type: boolean)
...
<             predicate: (((UDFToDouble(key) + 100.0D) < 100.0D) or ((UDFToDouble(key) + 100.0D) > 200.0D)) (type: boolean)
---
>             predicate: (UDFToDouble(key) + 100.0D) NOT BETWEEN 100.0D AND 200.0D (type: boolean)

Reason: "[CALCITE-7194] Simplify comparisons between function calls and literals to SEARCH" (1.41)
The plan difference comes from:

1.33
BETWEEN -> RexSimplify: 100 <= x AND x <= 200 -> HivePointLookupOptimizerRule: x BETWEEN 100, 200 -> SearchTransformer: no-op
NOT BETWEEN -> RexSimplify: x < 100 OR x > 200 -> HivePointLookupOptimizerRule: x NOT BETWEEN 100, 200 -> SearchTransformer: no-op
vs
1.42 (1.41. actually)
BETWEEN -> RexSimplify: Sarg x [100..200] -> HivePointLookupOptimizerRule: no-op -> SearchTransformer: x BETWEEN 100, 200
NOT BETWEEN -> RexSimplify: Sarg x (-inf..100, 200..+inf) -> HivePointLookupOptimizerRule: no-op -> SearchTransformer: x < 100 OR x > 200

It could be back to the original plan if we make SearchTransformer to detect and handle NOT BETWEEN (but that would impact other tests, so tbd separately)

  • 13 ) Rowcount adjustments, like: TestExplainCBOFormattedCliDriver query24, 30, 38, 54, 81, 87
<         "rowCount": 2.1331637755562098E35
---
>         "rowCount": 6.998796201537001E10

Reason: "[CALCITE-7083] RelMdDistinctRowCount aggregates implementation problems" (1.41)

  • 14 ) Fix in udtf_explode.q
< OPTIMIZED SQL: SELECT `col` AS `a.mycol`, COUNT(*) AS `_c1`
< FROM (SELECT `col`
< FROM TABLE(EXPLODE(ARRAY (1, 2, 3)))
< LIMIT 3) AS `t2`
---
> OPTIMIZED SQL: SELECT `col` AS `a.mycol`, `$f1` AS `_c1`
> FROM (SELECT `col`, COUNT(*) AS `$f1`
> FROM TABLE(EXPLODE(ARRAY[1, 2, 3]))

Reason: "[CALCITE-6748] RelToSqlConverter returns the wrong result when Aggregate is on Sort" (1.39) (and also "[CALCITE-6417] Map value constructor and Array value constructor unparsed incorrectly for HiveSqlDialect" (1.38))

  • 15 ) Stats change in: stats_histogram.q
<                     predicate: ((e > 0) and ((d < 3.0) or (d > 7.0))) (type: boolean)
<                     Statistics: Num rows: 9 Data size: 1040 Basic stats: COMPLETE Column stats: COMPLETE
---
>                     predicate: (((d < 3.0) or (d > 7.0)) and (e > 0)) (type: boolean)
>                     Statistics: Num rows: 8 Data size: 924 Basic stats: COMPLETE Column stats: COMPLETE
719c719
<                       Statistics: Num rows: 9 Data size: 1040 Basic stats: COMPLETE Column stats: COMPLETE
---
>                       Statistics: Num rows: 8 Data size: 924 Basic stats: COMPLETE Column stats: COMPLETE
722c722
<                         minReductionHashAggr: 0.8888889
---
>                         minReductionHashAggr: 0.875

Reason: condition in different order (see 11), which leads to numRows diff, which in turn leads to minReductionHashAggr diff; all comes from StatsRulesProcFactory.java:367-391 treats AND non-commutatively, order matters due to iteratively rounding

  • 16 ) SUBSTRING change, like: jdbc_project_pushdown.q
< WHERE SUBSTRING("title", 0, 4) = 'Les'
---
> WHERE SUBSTRING("title" FROM 0 FOR 4) = 'Les'

Reason: "[CALCITE-5677] SUBSTR signature incorrect for BigQuery" (1.35)
The new emitted SQL is valid in every dialect that Hive is realistically going to push down to (Derby, MySQL, PostgreSQL, MS SQL, Oracle, BigQuery all accept comma-separated SUBSTRING/SUBSTR).
It's actually more dialect-portable than the old form: the old SUBSTRING(x FROM y FOR z) is SQL-92/99 syntax that some older or stricter dialects don't accept, while comma-form is universal. CALCITE-5677 was made specifically to fix the BigQuery breakage.

  • 17 ) Adjustment in timestamp.q
< FROM (SELECT TRUE AS `$f0`
112c111
< GROUP BY TRUE) AS `t3`
---
> GROUP BY TRUE

Reason: "[CALCITE-4334] LITERAL_AGG, an aggregate function that returns a constant value" (1.35)

  • 18 ) cross_prod_1.q.out
    Equivalent plan: expression "A.value BETWEEN 'val_103' AND 'val_105'" is pushed down and pre-computed before the join. The change comes from a new computation of Strong#isNull for SEARCH, introduced via
    "[CALCITE-7019] Simplify 'NULL IN (20, 10)' to 'NULL'" (1.40)

  • 19 ) join46.q.out, mapjoin46.q.out, vectorized_join46.q.out, vectorized_join46_mr.q.out, smb_mapjoin_46.q.out:
    Equivalent plan: expression "key BETWEEN 100 AND 102" is pushed down and pre-computed before the join. The change comes from a new computation of Strong#isNull for SEARCH, introduced via
    "[CALCITE-7019] Simplify 'NULL IN (20, 10)' to 'NULL'" (1.40) (Same as previous point)

  • 20 ) vector_aggregate_9.q.out
    RexSimplify.simplifyCast DECIMAL casts are never removed, since they perform bounds checking, via
    "[CALCITE-6685] Support checked arithmetic" (1.39)

  • 21 ) vector_coalesce.q.out: equivalent plan, with some IS_NOT_NULL simplification adjustment:
    Before:

IF IsNotNull(cdouble) AND IsNotNull(log2(cint))
THEN cdouble + log2(cint)
ELSE 0.0

After:

IF IsNotNull(cdouble + log2(cint))
THEN cdouble + log2(cint)
ELSE 0.0

Due to some IS [NOT] NULL simplifications have been removed from RexSimplify for "non-safe" operations (see 3).

  • 22 ) vector_interval_2.q.out: simplified predicates
str2 >= '1-2' AND str2 > '1-2' ==> str2 > '1-2'
CAST(str1) IS NOT NULL AND (dt + CAST(str1)) = '2002-03-01' ==> (dt + CAST(str1)) = '2002-03-01'
t <> '2002-04-01' AND t = '2002-03-01' ==> t = '2002-03-01'
(dt + I) > T AND T < (dt + I) ==> T < (dt + I)

Due to more aggressive simplifications:
"[CALCITE-4364] a IN (1,2) AND a = 1 should be simplified to a = 1"
"[CALCITE-5759] SEARCH(1, Sarg[IS NOT NULL]) should be simplified to TRUE"
"[CALCITE-5780] Simplify '1 > x OR 1 <= x OR x IS NULL' to TRUE"

  • 23 ) join45.q.out, join47.q.out, mapjoin47.q, smb_mapjoin_47.q
    Simplified: (x >= 100 OR x <= 102) as a tautology on non-null x
    Thus, residual filter predicates: {(((_col2 + _col5) >= 100.0D) or ((_col2 + _col5) <= 102.0D))} is removed, and instead we simply have IS_NOT_NULL(_col2) and IS_NOT_NULL(_col5)
    Thanks to
    "[CALCITE-7194] Simplify comparisons between function calls and literals to SEARCH" (1.41.0)

  • 24 ) query53.q.out, query63.q.out
    Equivalent plan: conjunct reordering in AND of IN predicates
    SARG/predicate canonicalisation via "[CALCITE-7194] Simplify comparisons between function calls and literals to SEARCH" (1.41.0).
    Side effect: The row-count change is not caused by the predicate reordering itself (the filter is semantically identical). It comes from a different evaluation path that Hive's statistics estimator (HiveReduceExpressionsWithStatsRule / HiveReduceExpressionsRule) takes when the conjuncts are in a different order. Hive's statistics-based conjunct selectivity evaluation works by applying conjuncts left-to-right and cascadingly, this is inherently order-dependent when the column NDV/histogram data is imprecise (see 15).

  • 25 ) iceberg_bucket_map_join_1.q.out
    Simplified predicate (primary difference):

if(decimal_col is not null, (CAST(decimal_col AS STRING) = '...'), false) AND date_col is not null AND decimal_col is not null
=>
(CAST(decimal_col AS STRING) = '...') AND date_col is not null

Secondary changes derived from the simpler predicate:
All row-count estimates collapse to rows=1 (was: target_table TS=20/Filter=10, source_table TS=7/Filter=3); target_table Filter width 260→252.
The two queries with SELECT DISTINCT in the subquery (4 of the 8 explains, across bucket-map-join × vectorized configs) restructure their vertex dependency: Map 1 <- Reducer 3 becomes Reducer 3 <- Map 1, Map 2. With row estimates that small, target_table is shuffled/broadcast into Reducer 3 where the join now happens. The non-DISTINCT queries keep their original shape.
Grouping Num Buckets:7, Grouping Partition Columns annotation drops from target_table's TS on the 4 restructured plans (metadata surface only). Result rows 2022-08-16 and 2022-08-30 swap order in 4 places — same result set, different emission order (final projection now in Reducer 3).

rubenada added 20 commits July 10, 2026 10:14
…ange in udf_between.q

The plan difference comes from:
1.33
BETWEEN -> RexSimplify: 100 <= x AND x <= 200 -> HivePointLookupOptimizerRule: x BETWEEN 100, 200 -> SearchTransformer: no-op
NOT BETWEEN -> RexSimplify: x < 100 OR x > 200 -> HivePointLookupOptimizerRule: x NOT BETWEEN 100, 200 -> SearchTransformer: no-op
vs
1.42
BETWEEN -> RexSimpliy: Sarg x [100..200] -> HivePointLookupOptimizerRule: no-op -> SearchTransformer: x BETWEEN 100, 200
NOT BETWEEN -> RexSimpliy: Sarg x (-inf..100, 200..+inf) -> HivePointLookupOptimizerRule: no-op -> SearchTransformer: x < 100 OR x > 200

Thus, we need to adjust SearchTransformer to detect the last case and generate: x NOT BETWEEN 100, 200
… to avoid plan change in udf_between.q) solved 1 plan change

but "broke" 8 other tests (see below *); so instead, simply revert the change in SearchTransformer and adjust udf_between.q.out.
The "identify NOT BETWEEN expression in SearchTransformer" (to deal with "regression" caused by ""[CALCITE-7194] Simplify comparisons
between function calls and literals to SEARCH (1.41)") can be handled in a separate, dedicated PR.

*
testCliDriver[vector_between_columns] – org.apache.hadoop.hive.cli.split28.TestMiniLlapLocalCliDriver4s
testCliDriver[external_jdbc_table_perf] – org.apache.hadoop.hive.cli.split8.TestMiniLlapLocalCliDriver48s
testCliDriver[filter_numeric] – org.apache.hadoop.hive.cli.split8.TestMiniLlapLocalCliDriver3s
testCliDriver[correlationoptimizer8] – org.apache.hadoop.hive.cli.split5.TestMiniLlapLocalCliDriver5s
testCliDriver[vector_between_in] – org.apache.hadoop.hive.cli.split29.TestMiniLlapLocalCliDriver36s
testCliDriver[stats_histogram_null] – org.apache.hadoop.hive.cli.split22.TestMiniLlapLocalCliDriver28s
testCliDriver[join34] – org.apache.hadoop.hive.cli.split11.TestMiniLlapLocalCliDriver2s
testCliDriver[join35] – org.apache.hadoop.hive.cli.split11.TestMiniLlapLocalCliDriver
…r query24, 30, 38, 54, 81, 87 due to

[CALCITE-7083] RelMdDistinctRowCount aggregates implementation problems (1.41)

Test adjustments: udtf_explode.q.out plan with GROUP BY + SORT fixed due to
[CALCITE-6748] RelToSqlConverter returns the wrong result when Aggregate is on Sort (1.39)
(and also array changes due to [CALCITE-6417] Map value constructor and Array value constructor unparsed incorrectly for HiveSqlDialect (1.38))

Test adjustments: vectorized_dynamic_partition_pruning.q.out

Some IS NOT NULL simplifications added, e.g. lossless cast double to int to double + division by non-zero:
UDFToDouble(UDFToInteger((hr / 2.0D))) is not null => hr is not null
due to
[CALCITE-5769] Optimizing 'CAST(e AS t) IS NOT NULL' to 'e IS NOT NULL' (1.35) (only if "lossless" cast)

Some IS NOT NULL simplifications removed, e.g. not lossless cast str to double:
UDFToDouble(hr) is not null => (UDFToDouble(hr) * 2.0D) is not null
due to
[CALCITE-7296] RexSimplify should not simplify IS (NOT) NULL(CAST(10/0 AS BIGINT)) (1.42) (non-lossless cast is considered a non-safe operation, so no simplification is applied)

Test adjustments: subquery_scalar.q.out

Changes around IS [NOT] NULL simplifications:
[CALCITE-5769] Optimizing 'CAST(e AS t) IS NOT NULL' to 'e IS NOT NULL' (1.35) (only if "lossless" cast)
[CALCITE-7296] RexSimplify should not simplify IS (NOT) NULL(CAST(10/0 AS BIGINT)) (1.42)

Also if(..) predicate expanded into equivalent OR due to widened SafeRexVisitor's allowlist (enough that simplifyBooleanCaseGeneric could be applied)
[CALCITE-7032] Simplify 'NULL>ALL (ARRAY[1,2,NULL])' to 'NULL' (1.41)
As a consequence of this simplification, there's a row-count delta in the expression (fallout in Hive's selectivity heuristic)

Add new "case CHAR_LENGTH" in SqlFunctionConverter#buildAST, since a new SqlKind.CHAR_LENGTH has been added (previously length functions had SqlKind.OTHER_FUNCTION) via
[CALCITE-6182] Add LENGTH/LEN function (enabled in Snowflake library) (1.37)

Problem observed in several tests, where lenght(x) function not being evaluated (returning 'length' instead of the actual value), e.g. TestJdbcDriver2#testExprCol:
Illegal conversion to int from column 2 [length]
java.sql.SQLException: Illegal conversion to int from column 2 [length]
	at org.apache.hive.jdbc.HiveBaseResultSet.getInt(HiveBaseResultSet.java:445)
	at org.apache.hive.jdbc.TestJdbcDriver2.testExprCol(TestJdbcDriver2.java:2217)
…adoc ASM error

"Error in ASM processing class org/apache/hive/druid/org/apache/calcite/runtime/SqlFunctions.class: Index 65536 out of bounds for length 334"
…464 (introduce in 1.38) changed the default behavior to match MS-SQL-Server-style algorithm,

which can cause a drop in the scale computation, hence a precision loss in certain cases. We override this method to keep the "old behavior" (pre-CALCITE-6464);
an alternative could be not overridding it (and keep the new Calcite default MS-SQL-style decimal-divide semantics), but that would lead to "regressions"
(precision loss) and would require test adjustments.
… to fix the ASM error on Jenkins)

plus
Add httpclient5/httpcore5 as runtime dependencies of hive-exec to fix NoClassDefFoundError after Calcite 1.42 upgrade

The Calcite 1.42 upgrade pulls in avatica-core 1.28.0, which adds runtime
dependencies on httpclient5 (5.5) and httpcore5 (5.3.5). These show up
indirectly: BuiltInConnectionProperty's static initializer references
org.apache.hc.core5.util.Timeout, and that class is loaded the first time
any Calcite planner / driver code runs - which happens on every query
compilation through CalcitePlanner.logicalPlan, not just remote-HTTP
transport.

Avatica is shaded into hive-exec (see the build-exec-bundle execution in
ql/pom.xml's artifactSet). The shade plugin removes avatica from the
generated dependency-reduced-pom but does not promote avatica's transitive
dependencies, so consumers of hive-exec did not pick up httpclient5 /
httpcore5 from anywhere, and any test or runtime that triggered Calcite
planning failed with:

  java.lang.NoClassDefFoundError: org/apache/hc/core5/util/Timeout
    at org.apache.calcite.avatica.BuiltInConnectionProperty.<clinit>(...)
    at org.apache.calcite.avatica.ConnectionConfigImpl.transparentReconnectionEnabled(...)
    at org.apache.calcite.avatica.AvaticaConnection.<init>(...)
    ...
    at org.apache.hadoop.hive.ql.parse.CalcitePlanner.logicalPlan(...)

The failure was masked on Jenkins (and on machines with a fresh full
build) because itests modules transitively depend on hive-iceberg-handler,
and iceberg/iceberg-catalog/pom.xml declares httpclient5 / httpcore5
directly for the REST catalog client. So those jars happened to land on
the test classpath via a different path. On a partial local rebuild the
chain breaks and the error surfaces.

Declare httpclient5 and httpcore5 as direct dependencies of hive-exec
(versions inherited from the parent pom's dependencyManagement) so they
flow transitively to every consumer regardless of whether iceberg-handler
is in the picture. Compile scope (the default) is required - test scope
would not propagate to consumers, and the classes are needed at production
runtime, not only under tests.

Add asm-tree 9.9.1 dependency for druid handler shade plugin (still trying to fix the ASM error on Jenkins)

temp diagnose actions on Jenkinsfile to investigate ASM error
Condition changed simply as swap of conjunctions (same semantics):
old filterExpr: (((d < 3.0) or (d > 7.0)) and (e > 0))
new filterExpr: ((e > 0) and ((d < 3.0) or (d > 7.0)))

However, this led to a change on "Statistics: Num rows" (and that in turn led to a change on minReductionHashAggr value).
The change on statistics num rows comes from the fact that StatsRulesProcFactory.evaluateExpression treats AND non-commutatively: child processing order matters due to
the iterative rounding to long on each step:
- Old order: 15 * sel((d<3 OR d>7)) rounded → then * sel(e>0) rounded → 8
- New order: 15 * sel(e>0) rounded → then * sel((d<3 OR d>7)) rounded → 9

Then minReductionHashAggr changed too, due to this numRows change, see SetHashGroupByMinReduction: minReductionHashAggrFactor = 1f - ((float) ndvProduct / numRows)

Test adjustments decimal_udf.q.out: some IS [NOT] NULL simplifications have been removed from RexSimplify for "non-safe" operations.
[CALCITE-7145] RexSimplify should not simplify IS NULL(10/0) (1.42)
[CALCITE-7296] RexSimplify should not simplify IS (NOT) NULL(CAST(10/0 AS BIGINT)) (1.42)
[CALCITE-7295] RexSimplify should simplify a division with a NULL argument (1.42) — added a new x/NULL → NULL rule, but only when the surrounding context is "safe".

Adjust test file jdbc_project_pushdown.q.out: Calcite SQL unparse for SUBSTRING/SUBSTR changed SUBSTRING(s FROM start FOR length) => SUBSTRING(s, start, length) due to
[CALCITE-5677] SUBSTR signature incorrect for BigQuery (1.35)
Note: The new emitted SQL is valid in every dialect that Hive is realistically going to push down to (Derby, MySQL, PostgreSQL, MS SQL, Oracle, BigQuery all accept comma-separated SUBSTRING/SUBSTR). It's actually more dialect-portable than the old form: the old SUBSTRING(x FROM y FOR z) is SQL-92/99 syntax that some older or stricter dialects don't accept, while comma-form is universal. CALCITE-5677 was made specifically to fix the BigQuery breakage;
…s, double literals formatting

Adjust test files timestamp.q.out, plan change due to
[CALCITE-4334] LITERAL_AGG, an aggregate function that returns a constant value (1.35) (plus AggregateProjectPullUpConstantsRule)

Test adjustments auto_join13.q.out, join13.q.out: some IS [NOT] NULL simplifications have been removed from RexSimplify for "non-safe" operations.
In this case operations involving "non-lossless" casts cannot be simplified, so IS_NOT_NULL(PLUS(CAST(_col0), CAST(_col2))) => IS_NOT_NULL(_col0) AND IS_NOT_NULL(_col2)
does not happen anymore (so they can't be pushed down), hence residual filter remains.
[CALCITE-7296] RexSimplify should not simplify IS (NOT) NULL(CAST(10/0 AS BIGINT)) (1.42)
…tional expressions as unique" (1.37), RelMdUniqueKeys can return now

a set containing a single, empty set in cases like an empty Values or Values with 1 tuple; so we need to consider this circumstance when checking
the uniqueKeys of Aggregate's input in HiveRelFieldTrimmer#generateNewGroupset, otherwise it can convert an HiveAggregate(group=[{0}], COUNT)
into HiveAggregate(group=[{}], COUNT) which is NOT the same semantics when the input is an empty Values (empty resultset vs 0).
Issue seen on TestMiniLlapLocalCliDriver with empty_result.q which was incorrectly generating a Project+Agg+EmptyValues plan instead
of simply EmptyValues before this patch:
select count(a1) from t1 where 1=0 group by a1 order by a1;
< HiveProject(_c0=[$0])
<   HiveAggregate(group=[{}], agg#0=[count($0)])
<     HiveValues(tuples=[[]])
---
> HiveValues(tuples=[[]])
…onstant_expr.q.out): some IS [NOT] NULL simplifications have been removed from RexSimplify

for "non-safe" operations, which may impact lineage; e.g. division (if it could "hide" a runtime division by zero) or operations involving cast
(only "lossless" casts can be simplified), via:
[CALCITE-7145] RexSimplify should not simplify IS NULL(10/0) (1.42)
[CALCITE-7296] RexSimplify should not simplify IS (NOT) NULL(CAST(10/0 AS BIGINT)) (1.42)
[CALCITE-7032] Simplify 'NULL>ALL (ARRAY[1,2,NULL])' to 'NULL' (1.41)

Also, in lineage3.q.out, some condition re-ordering plus some new simplifications around IS_NOT_NULL:
[CALCITE-5639] RexSimplify should remove IS NOT NULL check when LIKE comparison is present (1.35) (actually more generic than just LIKE)
[CALCITE-5769] Optimizing 'CAST(e AS t) IS NOT NULL' to 'e IS NOT NULL' (1.35) (only "lossless" casts)
…alue BETWEEN 'val_103' AND 'val_105'" is pushed down and pre-computed before the join.

The change comes from a new computation of Strong#isNull for SEARCH, introduced via [CALCITE-7019] Simplify 'NULL IN (20, 10)' to 'NULL' (1.40)
RexSimplify.simplifyCast DECIMAL casts are never removed, since they perform bounds checking, via
[CALCITE-6685] Support checked arithmetic (1.39)
…NOT_NULL simplification adjustment:

Before:
- IF IsNotNull(cdouble) AND IsNotNull(log2(cint))
- THEN cdouble + log2(cint)
- ELSE 0.0
After:
- IF IsNotNull(cdouble + log2(cint))
- THEN cdouble + log2(cint)
- ELSE 0.0
Due to some IS [NOT] NULL simplifications have been removed from RexSimplify for "non-safe" operations:
[CALCITE-7145] RexSimplify should not simplify IS NULL(10/0) (1.42)
[CALCITE-7296] RexSimplify should not simplify IS (NOT) NULL(CAST(10/0 AS BIGINT)) (1.42)
str2 >= '1-2' AND str2 > '1-2' ==> str2 > '1-2'
CAST(str1) IS NOT NULL AND (dt + CAST(str1)) = '2002-03-01' ==> (dt + CAST(str1)) = '2002-03-01'
t <> '2002-04-01' AND t = '2002-03-01' ==> t = '2002-03-01'
(dt + I) > T AND T < (dt + I) ==> T < (dt + I)

Due to more aggressive simplifications:
[CALCITE-4364] a IN (1,2) AND a = 1 should be simplified to a = 1
[CALCITE-5759] SEARCH(1, Sarg[IS NOT NULL]) should be simplified to TRUE
[CALCITE-5780] Simplify '1 > x OR 1 <= x OR x IS NULL' to TRUE
…7.q:

Simplified (x >= 100 OR x <= 102) as a tautology on non-null x
Thus, residual filter predicates: {(((_col2 + _col5) >= 100.0D) or ((_col2 + _col5) <= 102.0D))} is removed, and instead we simply have IS_NOT_NULL(_col2) and IS_NOT_NULL(_col5)
[CALCITE-7194] "Simplify comparisons between function calls and literals to SEARCH" (1.41.0)
…t, vectorized_join46_mr.q.out, smb_mapjoin_46.q.out:

Equivalent plan: expression "key BETWEEN 100 AND 102" is pushed down and pre-computed before the join. The change comes from a new computation of Strong#isNull for SEARCH, introduced via
[CALCITE-7019] Simplify 'NULL IN (20, 10)' to 'NULL' (1.40)

Test adjustment query53.q.out, query63.q.out
Equivalent plan: conjunct reordering in AND of IN predicates
SARG/predicate canonicalisation via [CALCITE-7194] "Simplify comparisons between function calls and literals to SEARCH" (1.41.0).
Side effect: The row-count change is not caused by the predicate reordering itself (the filter is semantically identical). It comes from a different
evaluation path that Hive's statistics estimator (HiveReduceExpressionsWithStatsRule / HiveReduceExpressionsRule) takes when the conjuncts are
in a different order. Hive's statistics-based conjunct selectivity evaluation works by applying conjuncts left-to-right and cascadingly,
this is inherently order-dependent when the column NDV/histogram data is imprecise.
Simplified predicate:
if(decimal_col is not null, (CAST(decimal_col AS STRING) = '...'), false) AND date_col is not null AND decimal_col is not null
=>
(CAST(decimal_col AS STRING) = '...') AND date_col is not null

As a consequence, plan structure is modified due to statistics change: row count estimation has been reduced thanks to the predicate simplification.
Finally: result order change. The plan restructuring changed which operator produces the final output rows and in what order, causing the two rows to swap:
same output in different order; but since neither ORDER BY nor SORT_QUERY_RESULTS is used on the input file, the new result is also correct.
- materialized_view_rewrite_4.q.out
Plan improvement. On test query "EXAMPLE 32" so far MV rewriting was NOT applied (full join+aggregate plan, Stage-1 Tez with 2 maps + 2 reducers),
but now it is: direct scan of mv1_n3 (Stage-0 Fetch only)

- materialized_view_partitioned_2.q
REGRESSION, most efficient partition (partition_mv_3) is no longer used.
Reason: mv "partition_mv_3 PARTITIONED ON (key)" is created as "SELECT value, key FROM src_txn_2 where key > 220 and key < 230"
The regression queries are "... where key > 224 and key < 226;"  and "... where key > 223 and key < 225;" so partition_mv_3 should be used.
However, in Calcite's SubstitutionVisitor#splitFilter, instead of returning the appropriate filter, it now returns null.
The cause is that this line "RexNode r = canonizeNode(rexBuilder, simplify.simplifyUnknownAsFalse(x2));" leads to an extra IS NOT NULL predicate
on "r", which makes it not equivalent to "condition2" on the following line.
The reason is an existing bug on a Calcite simplification: CALCITE-7635. This bug was so far "hidden", because this simplification was only applied
for comparisons of the form "ref op literal", and in our query they are "literal op ref". But Calcite 1.35 introduced an improvement (CALCITE-5780)
to support all comparisons formats, which enabled the problematic simplification that leads to the bug CALCITE-7635.

Cleanup

Adjust new test file (remove charset from string literals)
…rror:

Caused by: java.lang.NoClassDefFoundError: org/joou/UShort
	at java.base/java.lang.Class.getDeclaredMethods0(Native Method)
	at java.base/java.lang.Class.privateGetDeclaredMethods(Class.java:3580)
	at java.base/java.lang.Class.getMethodsRecursive(Class.java:3721)
	at java.base/java.lang.Class.getMethod0(Class.java:3707)
	at java.base/java.lang.Class.getMethod(Class.java:2395)
	at org.apache.calcite.linq4j.tree.Types.lookupMethod(Types.java:288)
	at org.apache.calcite.util.BuiltInMethod.<init>(BuiltInMethod.java:1043)
	at org.apache.calcite.util.BuiltInMethod.<clinit>(BuiltInMethod.java:320)
	at org.apache.calcite.rel.metadata.BuiltInMetadata$PercentageOriginalRows.<clinit>(BuiltInMetadata.java:396)
	...
This is required due to "[CALCITE-1466] Support for UNSIGNED types of TINYINT, SMALLINT, INT, BIGINT in type system" (1.41), which added this library in Calcite.

Revert upgrade maven.shade.plugin.version in parent pom (not required for the ASM error)

Review

Adjust test iceberg_bucket_map_join_1.q.out
@thomasrebele

Copy link
Copy Markdown
Contributor

By chance I've stumbled upon the Bug class. It references workarounds for Calcite bugs. The Calcite upgrade fixes some of the bugs making the workarounds unnecessary. I suggest to open a follow-up ticket for removing the workarounds, to avoid making this big PR even bigger.

@rubenada

Copy link
Copy Markdown
Contributor Author

By chance I've stumbled upon the Bug class. It references workarounds for Calcite bugs. The Calcite upgrade fixes some of the bugs making the workarounds unnecessary. I suggest to open a follow-up ticket for removing the workarounds, to avoid making this big PR even bigger.

Thanks for pointing this out. I have created a follow-up ticket HIVE-29734 for this purpose.

@zabetak

zabetak commented Jul 16, 2026

Copy link
Copy Markdown
Member

I looked into the performance regression around partitioned materialized views (i.e., materialized_view_partitioned_2.q) and from my point of view is not blocking for merging the PR.

First of all, it appears the regression appears only in the context of MV rewrites so the impact is limited. Secondly, I assume that the performance/rewrite bug is already there even before the upgrade. If we modify the query to use ref op literal I guess the rewrite will fail to use the MV for the same reason. Finally, since we are not planning to make an immediate Hive release the performance regression will never reach end-users since it will be fixed by the time we deliver 4.3.0.

For tracking purposes, please raise a JIRA ticket for this bug/regression and mark it as a blocker for the Hive 4.3.0 release adding the appropriate links with HIVE-29641 and potentially the next HIVE ticket tracking the upgrade to calcite 1.43.0.

@rubenada

Copy link
Copy Markdown
Contributor Author

@zabetak thanks for the feedback; your analysis is correct.
I have filed these for followup:
HIVE-29742 materialized_view_partitioned_2.q chooses the wrong partitioned MV
HIVE-29743 Upgrade Calcite to 1.43.0

@zabetak zabetak left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a first pass covering mostly changes on .java and pom.xml. I will to do a pass over the .q.out files in the coming week.

Statistics: Num rows: 2 Data size: 188 Basic stats: COMPLETE Column stats: COMPLETE
Filter Operator
predicate: ((UDFToDouble(hour) = 11.0D) and CAST( UDFToInteger((hr / 2.0D)) AS STRING) is not null) (type: boolean)
predicate: ((UDFToDouble(hour) = 11.0D) and hr is not null) (type: boolean)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new plan is equivalent but it seems that the simplification didn't lead to a significant improvement. The simplification here seems to have introduced a new Filter[col0 is not null] higher up so at the end of the day the number of comparisons remains the same.

Comment on lines +563 to +570
public static void setCalciteSystemProperties() {
// turn off calcite rexnode normalization
System.setProperty("calcite.enable.rexnode.digest.normalize", "false");
// update calcite default charset, consistent with HiveTypeFactory#getDefaultCharset
System.setProperty("calcite.default.charset", ConversionUtil.NATIVE_UTF16_CHARSET_NAME);
System.setProperty("calcite.default.nationalcharset", ConversionUtil.NATIVE_UTF16_CHARSET_NAME);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting calcite properties at this stage is very brittle. The CalciteSystemProperty class is initialized during class loading and remains immutable afterwards. This means that if during the JVM startup we load CalciteSystemProperty.class before Hive.class this settings will never take effect.

I know that this pattern was used before and it may have been my idea to do it as such in the first place but its definitely something that we should fix. I guess a better alternative would be to put these settings in saffron.properties file and package this inside the Hive jar (e.g., under src/main/resources).

This is not a blocker but something that we definitely need to follow-up if not treated here.

Comment on lines +78 to +79
aggCall.rexList, aggCall.getArgList(), aggCall.filterArg, aggCall.distinctKeys, aggCall.getCollation(),
aggCall.getType(), aggCall.getName());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logged https://issues.apache.org/jira/browse/CALCITE-7659 for having a more convenient way to do this copy.

super(relBuilderFactory, generateUnionRewriting, unionRewritingPullProgram, fastBailOut);
}

// Overridden to avoid CALCITE-7641; TODO remove this once fixed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: We can drop the TODO comments since we already have the Bug reference in the method. By doing this I guess we can get rid of some Sonar warnings.

.withUnionRewritingPullProgram(PROGRAM)
.withRelBuilderFactory(HiveRelFactories.HIVE_BUILDER)
.toRule();
new HiveMaterializedViewProjectFilterRule(HiveRelFactories.HIVE_BUILDER, true, PROGRAM, false);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we could simplify the constructor to new HiveMaterializedViewProjectFilterRule() since we are always passing in the same param values.

}
return null;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am in favor of keeping the existing behavior via overriding for now. However, it makes me wonder when and why we are using the Calcite derivation logic. Hive has its own type derivation rules for division which are applied via org.apache.hadoop.hive.ql.udf.generic.GenericUDFOPDivide#deriveResultDecimalTypeInfo. I get the feeling that this method should use the logic in GenericUDFOPDivide. This is another important follow-up I guess.

Comment on lines +61 to +62
Hive.setCalciteSystemProperties();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Too intrusive; let's find another way to set these.

Comment thread ql/pom.xml
Comment on lines +368 to +385
<!--
Runtime dependencies required by org.apache.calcite.avatica:avatica-core:1.28.0
(pulled in by Calcite 1.42+): Avatica now uses Apache HttpComponents 5 for its
HTTP/JDBC transport, so hive-exec must bundle httpcore5/httpclient5 or tests fail
with "NoClassDefFoundError: org/apache/hc/core5/util/Timeout". Avatica declares
these as <scope>runtime</scope> transitively, but they must be declared here
explicitly because Hive's enforcer bans org.apache.hc.core5/client5.** imports
elsewhere and the hive-exec shade plugin only bundles ql-level dependencies.
Can be removed once Hive migrates to HttpComponents 5 across the board.
-->
<dependency>
<groupId>org.apache.httpcomponents.core5</groupId>
<artifactId>httpcore5</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
</dependency>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First of all its unfortunate that we inherited yet another dependency from Avatica that we need to bundle in hive-exec but we don't have much of a choice right now. We could try to get rid of it in a subsequent Avatica release by repackaging the client differently.

Secondly, we may need to include the httpcore5 in the shaded jar otherwise queries may break if the dependencies are required at query runtime. Query execution happens in Tez and in a real cluster this has a different classpath than HS2. The shaded jar is part of the Tez classpath but not the HS2/ql dependencies. Tests cannot capture this kind of behavior so to test if everything works fine the easier would be to build the Hive docker image in the packaging module, run it, and test some DDL, DML, SQL, operations to see if everything works fine.

Thirdly, the statement that shade plugin bundles only ql-level dependencies is not true. You can pick which transitive dependencies are included by the appropriate include statements. The most vivid example is Avatica; it is not direct dependency of the ql module but it is included in the shaded jar.

Finally, if we end-up declaring httpcore5 explicitly here then probably we have to use runtime scope if we don't want to accidentally start using it the code.

Comment thread druid-handler/pom.xml
Comment on lines +356 to +369
<!--
Calcite is intentionally NOT included or relocated here.
Calcite's calcite-core ships org/apache/calcite/runtime/SqlFunctions.class with a
malformed RuntimeVisibleTypeAnnotations attribute (LOCAL_VARIABLE start_pc/length
pointing inside instruction operands in the static initializer). The JVM tolerates it;
ASM's ClassRemapper does not, and shading + javadoc fails with
"Index 65536 out of bounds for length 334 [...] Error in ASM processing class".
Present in Calcite 1.35.0 through at least 1.42.0. Upstream: ASM gitlab issue
https://gitlab.ow2.org/asm/asm/-/issues/318008 (closed as invalid: root cause is on
Calcite's side); Calcite ticket: https://issues.apache.org/jira/browse/CALCITE-6393
(still unresolved). Druid 0.17.1 uses Calcite APIs compatible with Hive's Calcite 1.42+,
so skipping this relocation is safe. If Druid is upgraded to an incompatible Calcite,
restore the relocation (either after Calcite fixes their bytecode or by adding a shade
filter that excludes SqlFunctions.class from remapping).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relocation is one thing and inclusion in the shaded jar is another. I suppose they included calcite in the shaded jar because the latter is send to workers (running in different JVMs) with a different classpath. Not sure if the Druid module can still work if you remove calcite from shading.

Druid 0.17.1 is using calcite 1.21.0. I highly doubt that all the used APIs are compatible with 1.42.0. In fact, I am rather skeptical about the compatibility with previous upgrades as well (1.25.0 and 1.33.0).

Out of curiosity, I tried running TestMiniDruidCliDriver today and realized that the Druid module is broken since 2021. I send also an email to https://lists.apache.org/thread/qr0xxkvvg4wrlz4kg0bmktldw96yvpn2 explaining the current status and favoring the complete removal of the module.

All in all, the changes here do not really matter and we don't have to sweat too much about it since it's only a matter of days before getting rid of the entire module.

Comment on lines +339 to +351
// Avoid creating incorrect expressions like $1 < NULL or $1 = NULL or NULL = NULL
// which may be problematic for Calcite later on
if (expr.isA(SqlKind.BINARY_COMPARISON) && expr.getKind() != SqlKind.IS_DISTINCT_FROM
&& expr.getKind() != SqlKind.IS_NOT_DISTINCT_FROM) {
RexCall call = (RexCall) expr;
RexNode op0 = call.getOperands().get(0);
RexNode op1 = call.getOperands().get(1);
if ((op0.getKind() == SqlKind.LITERAL && ((RexLiteral) op0).isNull()) ||
(op1.getKind() == SqlKind.LITERAL && ((RexLiteral) op1).isNull())) {
expr = rexBuilder.makeNullLiteral(expr.getType());
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we replace this code with a call to RexSimplify#simplifyComparisonWithNull?

The fact that we have to call simplification code here in order to avoid an AssertionError later on is worrisome. The choice to throw AssertionError from core calcite APIs was somewhat brutal. If we fail to handle "invalid" comparisons on some other part of the code we may end-up with irrecoverable errors at runtime. Anyways let's hope that its sufficient to update the code here and get done with it.

rubenada added 2 commits July 17, 2026 17:48
- refactor Calcite systems properties into saffron.properties file (remove them from Hive.java and TestHiveRelJsonReader.java)
- minor changes in HiveMaterializedViewRule
- use RexSimplify#simplifyComparisonWithNull in HiveFunctionHelper instead of ad-hoc code
- minor improvements in HiveRelFieldTrimmer
- minor improvements in hive-exec/pom.xml file
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants