[ 
https://issues.apache.org/jira/browse/HIVE-30031?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18113579#comment-18113579
 ] 

Konstantin Bereznyakov commented on HIVE-30031:
-----------------------------------------------

Precision note on the fallback default: Hive 4.0.0 shipped 
{{hive.cbo.fallback.strategy=CONSERVATIVE}} (HiveConf at rel/release-4.0.0, 
which also still accepted the {{TEST}} value). HIVE-27831 changed the default 
to NEVER in 4.1.0. So "NEVER, the default" in the table applies to 4.1.0 and 
later, including master. On 4.0.x the default masks the shape 3 failure: 
{{recompile_without_cbo}} recompiles without CBO and returns the legacy row, as 
described in the CONSERVATIVE paragraph. Shapes 1 and 2 on that line are 
unaffected by the setting.

> numeric = boolean comparison: inconsistencies between CBO and non-CBO
> ---------------------------------------------------------------------
>
>                 Key: HIVE-30031
>                 URL: https://issues.apache.org/jira/browse/HIVE-30031
>             Project: Hive
>          Issue Type: Bug
>          Components: CBO, Query Planning
>    Affects Versions: 4.0.0
>            Reporter: Konstantin Bereznyakov
>            Priority: Major
>         Attachments: bool_int_shape1.q, bool_int_shape2.master.q.out, 
> bool_int_shape2.q, bool_int_shape3.q, 
> bool_int_shape3_conservative.master.q.out
>
>
> h3. Summary
> For {{numeric_col = TRUE}}, {{hive.cbo.enable}} changes the outcome of every 
> WHERE-clause shape below (rows, or success versus failure), and under CBO the 
> same term is accepted in one shape and fails in two others. The legacy 
> planner is consistent (TRUE is compared as 1). Root cause: Hive's Rex 
> translation finds no common comparison type for (bigint, boolean) and emits 
> {{=(BIGINT, BOOLEAN)}} without a cast; Calcite's simplification rules assume 
> a well-typed tree. Verified with the attached q files on master 9019223a86 
> (Calcite 1.42.0) and on a 4.0.0 build (Calcite 1.25.0).
> h3. Reproduction
> {code:sql}
> create table bool_int_cmp (code bigint, ts bigint);
> insert into bool_int_cmp values (200, 10), (404, 20), (0, 30), (1, 40);
> set hive.fetch.task.conversion=none;
> select code from bool_int_cmp where code = true;                 /* shape 1 */
> select code from bool_int_cmp where ts > 0 and code = true;      /* shape 2: 
> unrelated conjunct */
> select code from bool_int_cmp where code > -1 and code = true;   /* shape 3: 
> same-column range */
> {code}
> The row {{code=1}} discriminates the candidates: TRUE-equals-1 returns only 
> 1; nonzero-is-true returns 200, 404, 1.
> h3. Observed ({{hive.cbo.fallback.strategy=NEVER}}, the default)
> || shape || cbo=true, master (1.42.0) || cbo=true, 4.0.0 (1.25.0) || 
> cbo=false, both ||
> | 1 {{code = true}} | AssertionError "Cannot add expression of different type 
> to set" in {{HiveProjectFilterPullUpConstantsRule}} | folds {{code}} to 
> {{1L}}, returns 1 | {{(code = true)}}, returns 1 |
> | 2 {{ts > 0 and code = true}} | {{((ts > 0L) and UDFToBoolean(code))}}, 
> returns 200, 404, 1 | same | {{((ts > 0L) and (code = true))}}, returns 1 |
> | 3 {{code > -1 and code = true}} | ClassCastException Boolean to BigDecimal 
> in {{RexSimplify.residue}} (line 2173) | same ({{residue}} line 1692) | 
> {{((code > -1L) and (code = true))}}, returns 1 |
> With {{CONSERVATIVE}} (same files): shape 3 returns 1 on both builds because 
> {{ReCompileWithoutCBOPlugin}} recompiles without CBO (the CLI prints 
> "[ReExecDriver] ReCompileWithoutCBOPlugin triggered re-compile"); shape 2 is 
> unchanged, there is no exception to fall back from; shape 1 on master still 
> fails, because the AssertionError is an {{Error}} and skips the {{catch 
> (Exception e)}} in {{CalcitePlanner.genOPTree}} (CalcitePlanner.java:665), so 
> no fallback strategy can intercept it. The fallback setting thus decides the 
> semantics of shape 3 while shape 2 keeps CBO's reading.
> {code}
> java.lang.AssertionError: Cannot add expression of different type to set:
> set type is RecordType(BIGINT code) NOT NULL
> expression type is RecordType(BIGINT NOT NULL code) NOT NULL
> expression is HiveProject(code=[1:BIGINT])
>   HiveFilter(condition=[=($0, true)])
>       at 
> org.apache.calcite.plan.RelOptUtil.verifyTypeEquivalence(RelOptUtil.java:436)
>       at 
> org.apache.calcite.plan.hep.HepRuleCall.transformTo(HepRuleCall.java:58)
>       at 
> org.apache.hadoop.hive.ql.optimizer.calcite.rules.HiveProjectFilterPullUpConstantsRule.onMatch(HiveProjectFilterPullUpConstantsRule.java:94)
> {code}
> {code}
> java.lang.ClassCastException: class java.lang.Boolean cannot be cast to class 
> java.math.BigDecimal
>       at java.base/java.math.BigDecimal.compareTo(BigDecimal.java:309)
>       at 
> org.apache.hive.com.google.common.collect.Range.compareOrThrow(Range.java:679)
>       at 
> org.apache.hive.com.google.common.collect.ImmutableRangeSet.encloses(ImmutableRangeSet.java:165)
>       at org.apache.calcite.rex.RexSimplify.residue(RexSimplify.java:2173)
>       at 
> org.apache.calcite.rex.RexSimplify.simplifyUsingPredicates(RexSimplify.java:2096)
>       at 
> org.apache.calcite.rex.RexSimplify.simplifyComparison(RexSimplify.java:781)
>       at 
> org.apache.calcite.rel.rules.ReduceExpressionsRule.reduceExpressions(ReduceExpressionsRule.java:729)
> {code}
> h3. Analysis
> * *Root.* {{HiveFunctionHelper.convertInputs}} (type-check path) and 
> {{RexNodeConverter}} (ExprNodeDesc path) take the cast target from 
> {{FunctionRegistry.getCommonClassForComparison}}. For (bigint, boolean) every 
> branch falls through, boolean being implicitly convertible to no numeric 
> type, and the method returns null, so the operands pass through unconverted. 
> Per the CALCITE-2745 discussion, Rex is strictly typed and the SQL-to-Rel 
> layer must insert the casts; here that layer is Hive.
> * *Shape 1 (master only).* {{HiveProjectFilterPullUpConstantsRule}} replaces 
> the projected {{$0}} with the filter constant through 
> {{rexBuilder.makeCast(fieldType, constant, true)}} ({{RexReplacer.visit}}). 
> For a BOOLEAN constant and an exact-numeric field the result is the 
> non-nullable literal {{1:BIGINT}}, the row type changes from BIGINT to BIGINT 
> NOT NULL, and {{RelOptUtil.verifyTypeEquivalence}} throws an explicit 
> AssertionError (not a JVM assert). 4.0.0 folds the same query to {{1L}} and 
> returns 1.
> * *Shape 2.* {{RexSimplify.simplifyAnd2ForUnknownAsFalse}} (1.42.0 lines 
> 1887-1899, same in 1.25.0) strips {{= TRUE}} from an AND term whenever one 
> operand is always true, without checking that the other operand is BOOLEAN 
> ({{simplifyComparison}}, line 677, does check). The plan becomes 
> {{HiveFilter(condition=[AND(>($1, 0), $0)])}} with a bare BIGINT operand, 
> which Hive's operator-tree translation wraps as {{UDFToBoolean(code)}}: 
> nonzero is true. Excluding the ReduceExpressions rules with 
> {{hive.cbo.rule.exclusion.regex}} removes the rewrite, and the query then 
> fails like shape 1.
> * *Shape 3.* {{RexSimplify.simplifyUsingPredicates}} derives a 
> {{Range<BigDecimal>}} from {{code > -1}}; {{residue}} compares the Boolean 
> literal against it and throws. Same class as CALCITE-2745 (open since 2018), 
> sibling site; present in 1.25.0 and 1.42.0.
> * *Legacy planner.* {{GenericUDFBaseCompare.initialize}} gets the same null 
> and takes the "for now, we always convert to double" fallback (HIVE-2248, 
> 2011), so TRUE is compared as 1.0. Consistent, but undocumented.
> * *Documentation.* The Relational Operators table lists {{A = B}} for all 
> primitive types; the Allowed Implicit Conversions matrix has no boolean to 
> numeric or numeric to boolean cell. The comparison is documented as legal and 
> its meaning as undefined.
> h3. Proposed fix
> Hive already rejects a fixed list of unsafe comparisons by default through 
> {{hive.strict.checks.type.safety}} (bigint versus string, char, varchar, 
> double; decimal versus string, char, varchar), checked once per comparison in 
> {{TypeCheckProcFactory.validateUDF}}, which both planners share; with the 
> flag off it warns and proceeds.
> * (a) Add the boolean versus numeric-group pairs to that check 
> ({{TypeInfoUtils.LOSSY_TYPE_CONVERSIONS}}, or a sibling set with an accurate 
> message). Default behavior becomes a clean SemanticException on both 
> planners; no new configuration.
> * (b) With the flag off, cast the boolean operand to the numeric operand's 
> type in {{HiveFunctionHelper.convertInputs}} / {{RexNodeConverter}}, so CBO 
> gives the legacy TRUE-equals-1 result (Spark's {{BooleanEquality}} coercion 
> does the same and attributes it to Hive).
> Either half alone removes both planner failures for this operand pair; 
> together, all shapes agree on both planners under both flag values. 
> Independently of the semantics, {{HiveProjectFilterPullUpConstantsRule}} 
> should not accept a replacement whose nullability differs from the field 
> (skip it, or cast preserving nullability). The Calcite-side gaps are noted on 
> CALCITE-2745.
> h3. Attachments
> bool_int_shape1.q, bool_int_shape2.q, bool_int_shape3.q (each runs the shape 
> with cbo on, then off); bool_int_shape2.master.q.out (the nonzero-is-true 
> plan and rows next to the legacy plan); 
> bool_int_shape3_conservative.master.q.out (the recompile console line and 
> legacy predicate). Further outputs, including the 4.0.0 build's, available on 
> request.
> h3. Related
> HIVE-2248 (double fallback in the comparison UDFs; HIVE-1638 introduced 
> them); HIVE-13861, HIVE-20913, HIVE-29447 (nullability in constant pull-up 
> and simplification); CALCITE-2745 (RexSimplify ClassCastException on 
> mixed-type comparison operands).



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to