Konstantin Bereznyakov created HIVE-30031:
---------------------------------------------
Summary: numeric = boolean comparison: legacy planner treats TRUE
as 1; CBO folds, rewrites to nonzero-is-true, or fails depending on query shape
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
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)