Skip to content

[fix](expr opt) Preserve division comparison semantics - #67895

Open
morrySnow wants to merge 1 commit into
apache:masterfrom
morrySnow:fix/reject-zero-divisor-rearrangement
Open

[fix](expr opt) Preserve division comparison semantics#67895
morrySnow wants to merge 1 commit into
apache:masterfrom
morrySnow:fix/reject-zero-divisor-rearrangement

Conversation

@morrySnow

@morrySnow morrySnow commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary:

Problem: Comparisons whose left side divides a value by a constant could return incorrect booleans or lose NULL results after expression simplification.

Root cause: The arithmetic comparison rule treated division as algebraically invertible and rewrote x / c op r as x op r * c. That equivalence does not hold under SQL numeric coercion, floating-point and decimal rounding, overflow, or division-by-zero semantics.

Reproduction: Division by zero should yield NULL, but the rewrite changed x / 0 > 1 into x > 0. Finite non-zero counterexamples also differ at rounding boundaries, including DOUBLE 0.30000000000000004 / 3.0 > 0.1, integer-to-DOUBLE -100 / 11 > -9.090909090909092, and DECIMAL 1 / 3 > 0.333333.

Fix: Remove division from the inverse-rearrangement allowlist so the complete Divide expression remains on its original side of the comparison. Keep the existing additive and supported date/time rearrangements unchanged.

Tests: Add expression-rule unit coverage for zero representations, folded zero, NULL, non-finite values, finite rounding boundaries, negative divisors, and nested arithmetic. Update the two existing positive- and negative-divisor assertions in SimplifyArithmeticRuleTest to expect the division expression to remain in place. Add end-to-end plan and result regression coverage with the rule enabled and disabled. Local runs passed all 5 SimplifyArithmeticRuleTest tests and all 3 SimplifyArithmeticComparisonRuleTest tests.

CI triage: The FE unit-test failure was the pair of stale division-rearrangement expectations corrected here. The other reported failures are infrastructure failures outside this code path: the cloud job could not install or invoke Maven, the performance job used FE image metadata newer than the tested binary supports, and the vault job could not pull the MinIO image.

Release note

Fix incorrect comparison results and lost NULL values for division expressions with constant divisors.

Check List (For Author)

  • Test:
    • Unit Test
    • Regression test
    • Full FE build and checkstyle
  • Behavior changed: Yes. Division comparisons are kept intact unless a future rewrite can prove SQL-semantic equivalence.
  • Does this need documentation: No

@morrySnow
morrySnow requested a review from yujun777 as a code owner September 11, 2026 21:00
@hello-stephen

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

@morrySnow

Copy link
Copy Markdown
Contributor Author

run buildall

@morrySnow morrySnow changed the title [fix](nereids) Preserve division comparison semantics [fix](expr opt) Preserve division comparison semantics Sep 12, 2026
@morrySnow
morrySnow marked this pull request as draft September 12, 2026 03:34
### What problem does this PR solve?

Issue Number: None

Related PR: None

Problem Summary:

Problem: Comparisons whose left side divides a value by a constant could return incorrect booleans or lose NULL results after expression simplification.

Root cause: The arithmetic comparison rule treated division as algebraically invertible and rewrote `x / c op r` as `x op r * c`. That equivalence does not hold under SQL numeric coercion, floating-point and decimal rounding, overflow, or division-by-zero semantics.

Reproduction: Division by zero should yield NULL, but the rewrite changed `x / 0 > 1` into `x > 0`. Finite non-zero counterexamples also differ at rounding boundaries, including DOUBLE `0.30000000000000004 / 3.0 > 0.1`, integer-to-DOUBLE `-100 / 11 > -9.090909090909092`, and DECIMAL `1 / 3 > 0.333333`.

Fix: Remove division from the inverse-rearrangement allowlist so the complete Divide expression remains on its original side of the comparison. Keep the existing additive and supported date/time rearrangements unchanged.

Tests: Add expression-rule unit coverage for zero representations, folded zero, NULL, non-finite values, finite rounding boundaries, negative divisors, and nested arithmetic. Add end-to-end plan and result regression coverage with the rule enabled and disabled.

### Release note

Fix incorrect comparison results and lost NULL values for division expressions with constant divisors.

### Check List (For Author)

- Test:
    - Unit Test
    - Regression test
    - Full FE build and checkstyle
- Behavior changed: Yes. Division comparisons are kept intact unless a future rewrite can prove SQL-semantic equivalence.
- Does this need documentation: No
@morrySnow
morrySnow force-pushed the fix/reject-zero-divisor-rearrangement branch from b58789b to f8d6651 Compare September 12, 2026 08:35
@morrySnow

Copy link
Copy Markdown
Contributor Author

run buildall

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-H: Total hot run time: 17053 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit f8d665134ece5e48d975f9b6f00fba71143f7e7a, data reload: false

------ Round 1 ----------------------------------
============================================
q1	17619	3060	3046	3046
q2	2078	255	212	212
q3	10274	874	518	518
q4	4667	251	211	211
q5	7659	563	385	385
q6	141	125	97	97
q7	538	495	390	390
q8	9240	946	978	946
q9	3702	2408	2435	2408
q10	6527	887	732	732
q11	401	209	185	185
q12	610	264	201	201
q13	18133	1539	1164	1164
q14	157	150	144	144
q15	q16	444	406	379	379
q17	1431	916	844	844
q18	3134	2313	2311	2311
q19	1267	946	753	753
q20	386	288	207	207
q21	5621	1685	1878	1685
q22	331	275	235	235
Total cold run time: 94360 ms
Total hot run time: 17053 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	3443	3363	3352	3352
q2	516	392	394	392
q3	2273	2489	2281	2281
q4	1209	1191	903	903
q5	2241	2138	2143	2138
q6	168	121	88	88
q7	1054	927	871	871
q8	1602	1397	1411	1397
q9	3184	3160	3158	3158
q10	1884	1843	1661	1661
q11	368	268	258	258
q12	459	443	361	361
q13	1482	1588	1169	1169
q14	166	182	173	173
q15	q16	401	404	372	372
q17	3690	3341	3302	3302
q18	4933	4505	5076	4505
q19	918	855	829	829
q20	1024	994	837	837
q21	3828	3081	3254	3081
q22	400	356	328	328
Total cold run time: 35243 ms
Total hot run time: 31456 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 83281 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit f8d665134ece5e48d975f9b6f00fba71143f7e7a, data reload: false

query5	4267	419	346	346
query6	380	138	123	123
query7	4942	406	238	238
query8	293	132	130	130
query9	8728	2921	2890	2890
query10	396	223	186	186
query11	5401	1064	955	955
query12	119	72	73	72
query13	1210	432	323	323
query14	6046	2285	2169	2169
query14_1	2016	2032	1988	1988
query15	172	119	113	113
query16	907	369	343	343
query17	767	437	349	349
query18	2312	324	226	226
query19	156	130	104	104
query20	71	71	73	71
query21	197	98	85	85
query22	5540	5611	5544	5544
query23	6751	6474	6131	6131
query23_1	6220	6274	6209	6209
query24	7254	1127	776	776
query24_1	771	803	785	785
query25	415	281	243	243
query26	1224	238	125	125
query27	2794	424	241	241
query28	4696	1500	1508	1500
query29	913	462	328	328
query30	249	164	130	130
query31	815	397	330	330
query32	121	78	72	72
query33	451	225	167	167
query34	1003	837	473	473
query35	398	408	346	346
query36	581	583	542	542
query37	117	80	71	71
query38	1016	867	831	831
query39	524	493	518	493
query39_1	494	484	473	473
query40	198	90	73	73
query41	53	50	52	50
query42	75	70	73	70
query43	236	240	212	212
query44	993	541	549	541
query45	125	105	100	100
query46	772	824	511	511
query47	786	781	720	720
query48	308	305	229	229
query49	542	238	181	181
query50	719	272	188	188
query51	8305	8157	8114	8114
query52	69	71	58	58
query53	193	205	144	144
query54	218	160	149	149
query55	72	60	55	55
query56	194	163	157	157
query57	690	692	677	677
query58	210	157	151	151
query59	1224	1250	1077	1077
query60	261	173	176	173
query61	113	110	112	110
query62	343	200	171	171
query63	180	143	144	143
query64	2907	753	736	736
query65	1643	1652	1654	1652
query66	1860	291	216	216
query67	10061	10020	9904	9904
query68	2879	1174	679	679
query69	347	238	209	209
query70	665	628	637	628
query71	262	182	172	172
query72	2445	1652	1498	1498
query73	647	569	351	351
query74	1829	1256	1156	1156
query75	1191	1113	966	966
query76	2304	705	518	518
query77	250	270	216	216
query78	3981	3794	3284	3284
query79	2744	861	612	612
query80	1598	321	270	270
query81	495	156	137	137
query82	615	130	100	100
query83	285	219	194	194
query84	296	108	90	90
query85	788	348	282	282
query86	400	180	167	167
query87	1037	998	902	902
query88	2890	2125	2141	2125
query89	278	194	172	172
query90	1985	126	126	126
query91	128	125	99	99
query92	77	63	72	63
query93	1706	1086	744	744
query94	636	224	233	224
query95	516	270	221	221
query96	808	571	269	269
query97	1069	1083	1060	1060
query98	172	133	134	133
query99	424	352	323	323
Total cold run time: 179286 ms
Total hot run time: 83281 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
ClickBench: Total hot run time: 14.89 s
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
ClickBench test result on commit f8d665134ece5e48d975f9b6f00fba71143f7e7a, data reload: false

query1	0.00	0.01	0.01
query2	0.08	0.04	0.04
query3	0.25	0.11	0.12
query4	1.60	0.10	0.10
query5	0.17	0.16	0.16
query6	1.26	0.70	0.68
query7	0.03	0.01	0.00
query8	0.05	0.03	0.02
query9	0.29	0.22	0.22
query10	0.35	0.37	0.35
query11	0.17	0.11	0.11
query12	0.15	0.12	0.12
query13	0.31	0.30	0.32
query14	0.46	0.45	0.48
query15	0.38	0.36	0.36
query16	0.21	0.21	0.23
query17	0.71	0.67	0.71
query18	0.19	0.18	0.17
query19	1.19	1.20	1.15
query20	0.01	0.01	0.01
query21	15.43	0.15	0.12
query22	5.09	0.05	0.04
query23	16.17	0.26	0.11
query24	2.98	0.35	0.24
query25	0.11	0.04	0.03
query26	0.74	0.18	0.11
query27	0.03	0.03	0.04
query28	3.65	0.53	0.30
query29	12.43	3.19	2.55
query30	0.26	0.12	0.12
query31	2.76	0.37	0.18
query32	3.53	0.32	0.22
query33	1.39	1.65	1.54
query34	15.39	2.25	1.81
query35	1.83	1.77	1.73
query36	0.46	0.29	0.30
query37	0.06	0.04	0.04
query38	0.04	0.03	0.03
query39	0.03	0.03	0.03
query40	0.11	0.07	0.08
query41	0.07	0.03	0.02
query42	0.04	0.03	0.02
query43	0.04	0.03	0.02
Total cold run time: 90.5 s
Total hot run time: 14.89 s

@morrySnow
morrySnow marked this pull request as ready for review September 12, 2026 11:19

@yujun777 yujun777 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM

@morrySnow

Copy link
Copy Markdown
Contributor Author

/review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Static review result: REQUEST_CHANGES.

The direct-root edit is locally correct: it stops the unsafe x / c op r inverse for every comparison kind, preserves analyzed Divide coercion/nullability, and the new unit/regression oracles are deterministic and pre-patch-sensitive for that narrow path. However, production normalization still changes query truth values through the three mechanisms described inline.

Critical checkpoint conclusions:

  • Goal/proof: not achieved end to end; direct Divide roots are fixed and tested, but MF-1/MF-2/MF-3 remain production-reachable.
  • Scope/clarity: the five-file patch is small and clear locally, but the functionally parallel predecessor rule and retained containing Add/Subtract path are not fenced.
  • Concurrency/thread safety: not involved; no threads, shared mutable state, locks, atomics, or blocking work are added.
  • Lifecycle/static initialization: not involved; no ownership, cleanup, circular-reference, release, or static-initialization behavior changes.
  • Configuration: no product configuration is added; the test-only existing disabled-rule session setting targets the intended rule and is restored in finally.
  • Compatibility: no symbols, storage formats, serialized metadata, RPCs, or rolling-upgrade surfaces change.
  • Parallel paths: analyzer/coercion, production rule order, both SimplifyArithmeticRule branches, bottom-up re-entry, folding, and FE-to-BE evaluation were traced; the three inline mechanisms are the remaining gaps.
  • Conditions: direct-root exact-class gating and all comparison orientations are sound, but the new local condition does not protect division-containing parent/subtree shapes.
  • Tests: direct zero/-0/NULL/folded/NaN/infinity/negative/finite-double/integer/decimal cases are broad; analyzed runtime negative cases for the three remaining wrapper/predecessor paths are missing.
  • Test results: the changed .out ordering, column mapping, and enabled/disabled results are internally correct. No builds or tests were run in this review because the automation contract permits static review only.
  • Observability: no operational/distributed path is introduced; analyzed plan shapes and deterministic runtime result comparisons are the appropriate diagnostics.
  • Transactions/persistence/data writes: not involved; no EditLog, versioning, MoW, transaction, write, or crash-atomicity behavior changes.
  • FE/BE variables: no variable or protocol propagation is added.
  • Error handling/memory safety/BE nullable handling: no new Status/exception, allocation/ownership, or BE column-nullability code is introduced; existing Divide nullability and zero-divisor NULL behavior were traced.
  • Performance: preserving more division-containing trees may reduce simplification/pushdown, but that is the intended correctness tradeoff; no separate CPU, memory, or complexity issue was found.
  • Other: all five changed files and all comparison/operator, wrapper, cast, decimal, special-value, and rule-reentry paths were swept; no fourth distinct issue remains.

Review convergence is complete after three rounds: every final-round reviewer returned NO_NEW_VALUABLE_FINDINGS, the finding set was stable, and no candidate remains unresolved. No additional user focus was provided.

public static SimplifyArithmeticComparisonRule INSTANCE = new SimplifyArithmeticComparisonRule();

// don't rearrange multiplication because divide may loss precision
// Do not rearrange multiplication or division because their inverse operations can change

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P1] Fence the earlier arithmetic simplifier too

Removing Divide here is too late for division-containing trees because production runs SimplifyArithmeticRule first. Its multiply/divide branch rewrites x / (y / z) < 1 as (x / y) * z < 1; for DOUBLE x=1, y=2, z=0, the filter changes from UNKNOWN to TRUE. Its add/subtract branch also regroups abs(((x / 1.0) + 1.0) + 1.0) = 10000000000000000 as abs((x / 1.0) + (1.0 + 1.0)) = 10000000000000000; for DOUBLE x=10000000000000000, the predicate changes from true to false, and Abs prevents this comparison rule from matching. Please fence both SimplifyArithmeticRule branches for subtrees containing Divide unless exact SQL equivalence is proven, and add analyzed runtime cases with that rule enabled/disabled.

assertRewriteAfterSimplify("1 - IA / 2 > 3", "(IA < cast(((1 - 3) * 2) as INT))");
assertRewriteAfterSimplify("(1 - (IA + 4)) / 2 > 3", "(IA < cast(((1 - 6) - 4) as INT))");
assertDivisionPreservedAfterConstantFolding("(1 - IA) / 2 > 3");
assertRewriteAfterSimplify("1 - IA / 2 > 3", "((IA / 2) < cast((1 - 3) as DOUBLE))");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[P1] Do not retain additive inversion around a Divide

This expected partial rewrite is still unsafe for analyzed DOUBLE values. With x=10000000000000000, (x / 1.0 + 1.0) >= CAST('10000000000000002' AS DOUBLE) is false because the left rounds to 10000000000000000; the retained Add-to-Subtract rewrite produces x / 1.0 >= (CAST('10000000000000002' AS DOUBLE) - 1.0), whose right side rounds to the same 10000000000000000, so it becomes true. Preserving the inner node is insufficient: please fence add/subtract rearrangement when the comparison contains Divide (unless exactness is proven) and add an enabled/disabled runtime boundary case.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants