-
Notifications
You must be signed in to change notification settings - Fork 2.5k
[CALCITE-7657] Apply the absorption law to simplify boolean expressions #5110
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -322,9 +322,8 @@ class JdbcAdapterTest { | |
| + " JdbcProject($f2=[OR(IS NOT NULL($7), IS NOT NULL($1))])\n" | ||
| + " JdbcTableScan(table=[[SCOTT, EMP]])\n" | ||
| + " JdbcToEnumerableConverter\n" | ||
| + " JdbcAggregate(group=[{0, 1}], i=[LITERAL_AGG(true)], em=[MAX($2)])\n" | ||
| + " JdbcProject(DEPTNO=[$7], ENAME=[CAST($1):VARCHAR(14)]," | ||
| + " $f2=[AND(IS NOT NULL($7), IS NOT NULL($1))])\n" | ||
| + " JdbcAggregate(group=[{0, 1}], i=[LITERAL_AGG(true)])\n" | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The intermediate variable |
||
| + " JdbcProject(DEPTNO=[$7], ENAME=[CAST($1):VARCHAR(14)])\n" | ||
| + " JdbcFilter(condition=[OR(IS NOT NULL($7), IS NOT NULL($1))])\n" | ||
| + " JdbcTableScan(table=[[SCOTT, EMP]])\n\n"); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4315,7 +4315,7 @@ select * from "scott".emp where (empno, deptno) not in ((1, 2), (3, null)); | |
|
|
||
| !ok | ||
| !if (use_old_decorr) { | ||
| EnumerableCalc(expr#0..14=[{inputs}], expr#15=[0], expr#16=[=($t8, $t15)], expr#17=[IS NULL($t7)], expr#18=[IS NOT NULL($t13)], expr#19=[AND($t14, $t18)], expr#20=[<($t9, $t8)], expr#21=[OR($t17, $t19, $t18, $t20)], expr#22=[IS NOT TRUE($t21)], expr#23=[OR($t16, $t22)], proj#0..7=[{exprs}], $condition=[$t23]) | ||
| EnumerableCalc(expr#0..13=[{inputs}], expr#14=[0], expr#15=[=($t8, $t14)], expr#16=[IS NULL($t13)], expr#17=[>=($t9, $t8)], expr#18=[IS NOT NULL($t7)], expr#19=[AND($t16, $t17, $t18)], expr#20=[OR($t15, $t19)], proj#0..7=[{exprs}], $condition=[$t20]) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since |
||
| EnumerableMergeJoin(condition=[AND(=($10, $11), OR(IS NULL($12), =(CAST($7):INTEGER, $12)))], joinType=[left]) | ||
| EnumerableSort(sort0=[$10], dir0=[ASC]) | ||
| EnumerableCalc(expr#0..9=[{inputs}], expr#10=[CAST($t0):INTEGER NOT NULL], proj#0..10=[{exprs}]) | ||
|
|
@@ -4324,7 +4324,7 @@ EnumerableCalc(expr#0..14=[{inputs}], expr#15=[0], expr#16=[=($t8, $t15)], expr# | |
| EnumerableAggregate(group=[{}], c=[COUNT()], ck=[COUNT() FILTER $0]) | ||
| EnumerableValues(tuples=[[{ true }, { true }]]) | ||
| EnumerableSort(sort0=[$0], dir0=[ASC]) | ||
| EnumerableCalc(expr#0..1=[{inputs}], expr#2=[true], expr#3=[IS NOT NULL($t1)], proj#0..3=[{exprs}]) | ||
| EnumerableCalc(expr#0..1=[{inputs}], expr#2=[true], proj#0..2=[{exprs}]) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After the main Calc node is simplified, the subquery no longer needs to additionally output the |
||
| EnumerableValues(tuples=[[{ 3, null }, { 1, 2 }]]) | ||
| !plan | ||
| !} | ||
|
|
@@ -4339,14 +4339,14 @@ select * from "scott".emp where (mgr, deptno) not in ((1, 2), (3, null), (cast(n | |
|
|
||
| !ok | ||
| !if (use_old_decorr) { | ||
| EnumerableCalc(expr#0..13=[{inputs}], expr#14=[0], expr#15=[=($t8, $t14)], expr#16=[IS NULL($t3)], expr#17=[IS NULL($t7)], expr#18=[IS NOT NULL($t12)], expr#19=[AND($t13, $t18)], expr#20=[<($t9, $t8)], expr#21=[OR($t16, $t17, $t19, $t18, $t20)], expr#22=[IS NOT TRUE($t21)], expr#23=[OR($t15, $t22)], proj#0..7=[{exprs}], $condition=[$t23]) | ||
| EnumerableCalc(expr#0..12=[{inputs}], expr#13=[0], expr#14=[=($t8, $t13)], expr#15=[IS NULL($t12)], expr#16=[>=($t9, $t8)], expr#17=[IS NOT NULL($t3)], expr#18=[IS NOT NULL($t7)], expr#19=[AND($t15, $t16, $t17, $t18)], expr#20=[OR($t14, $t19)], proj#0..7=[{exprs}], $condition=[$t20]) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Redundant terms within OR(...) are absorbed by IS NOT NULL/IS NULL-related terms, shortening the expression. |
||
| EnumerableNestedLoopJoin(condition=[AND(OR(IS NULL($10), =(CAST($3):INTEGER, $10)), OR(IS NULL($11), =(CAST($7):INTEGER, $11)))], joinType=[left]) | ||
| EnumerableNestedLoopJoin(condition=[true], joinType=[inner]) | ||
| EnumerableTableScan(table=[[scott, EMP]]) | ||
| EnumerableAggregate(group=[{}], c=[COUNT()], ck=[COUNT() FILTER $0]) | ||
| EnumerableCalc(expr#0..1=[{inputs}], expr#2=[IS NOT NULL($t0)], expr#3=[IS NOT NULL($t1)], expr#4=[OR($t2, $t3)], $f2=[$t4]) | ||
| EnumerableValues(tuples=[[{ 3, null }, { null, null }, { 1, 2 }]]) | ||
| EnumerableCalc(expr#0..1=[{inputs}], expr#2=[true], expr#3=[IS NOT NULL($t0)], expr#4=[IS NOT NULL($t1)], expr#5=[AND($t3, $t4)], expr#6=[OR($t3, $t4)], proj#0..2=[{exprs}], $f20=[$t5], $condition=[$t6]) | ||
| EnumerableCalc(expr#0..1=[{inputs}], expr#2=[true], expr#3=[IS NOT NULL($t0)], expr#4=[IS NOT NULL($t1)], expr#5=[OR($t3, $t4)], proj#0..2=[{exprs}], $condition=[$t5]) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The original plan involved calculating two columns simultaneously: After simplification, the upper-level condition only requires "at least one non-null column" to handle the NULL semantics of NOT IN; AND($t3, $t4) has been absorbed: |
||
| EnumerableValues(tuples=[[{ 3, null }, { null, null }, { 1, 2 }]]) | ||
| !plan | ||
| !} | ||
|
|
@@ -4381,7 +4381,7 @@ select * from "scott".emp where (empno, deptno) not in ((7369, 20), (7499, 30)); | |
|
|
||
| !ok | ||
| !if (use_old_decorr) { | ||
| EnumerableCalc(expr#0..15=[{inputs}], expr#16=[0], expr#17=[=($t8, $t16)], expr#18=[IS NULL($t7)], expr#19=[IS NOT NULL($t14)], expr#20=[AND($t15, $t19)], expr#21=[<($t9, $t8)], expr#22=[OR($t18, $t20, $t19, $t21)], expr#23=[IS NOT TRUE($t22)], expr#24=[OR($t17, $t23)], proj#0..7=[{exprs}], $condition=[$t24]) | ||
| EnumerableCalc(expr#0..14=[{inputs}], expr#15=[0], expr#16=[=($t8, $t15)], expr#17=[IS NULL($t14)], expr#18=[>=($t9, $t8)], expr#19=[IS NOT NULL($t7)], expr#20=[AND($t17, $t18, $t19)], expr#21=[OR($t16, $t20)], proj#0..7=[{exprs}], $condition=[$t21]) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Similarly, the expression is compressed because the |
||
| EnumerableMergeJoin(condition=[AND(=($10, $12), =($11, $13))], joinType=[left]) | ||
| EnumerableSort(sort0=[$10], sort1=[$11], dir0=[ASC], dir1=[ASC]) | ||
| EnumerableCalc(expr#0..9=[{inputs}], expr#10=[CAST($t0):INTEGER NOT NULL], expr#11=[CAST($t7):INTEGER], proj#0..11=[{exprs}]) | ||
|
|
@@ -4391,7 +4391,7 @@ EnumerableCalc(expr#0..15=[{inputs}], expr#16=[0], expr#17=[=($t8, $t16)], expr# | |
| EnumerableCalc(expr#0..1=[{inputs}], expr#2=[true], $f2=[$t2]) | ||
| EnumerableValues(tuples=[[{ 7369, 20 }, { 7499, 30 }]]) | ||
| EnumerableSort(sort0=[$0], sort1=[$1], dir0=[ASC], dir1=[ASC]) | ||
| EnumerableCalc(expr#0..1=[{inputs}], expr#2=[true], proj#0..2=[{exprs}], $f20=[$t2]) | ||
| EnumerableCalc(expr#0..1=[{inputs}], expr#2=[true], proj#0..2=[{exprs}]) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After simplification in Calc, the helper column $f20 is no longer needed. |
||
| EnumerableValues(tuples=[[{ 7369, 20 }, { 7499, 30 }]]) | ||
| !plan | ||
| !} | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks useful, but these two function calls are not cheap.
I hope that this does not matter in practice, but this could be expensive for some very complex boolean expressions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right that for each term we may call RelOptUtil.disjunctions / RelOptUtil.conjunctions, and then scan the sibling terms to check containment. In the worst case this is O(n²·m) where n is the number of terms and m is the size of the disjunction/conjunction.
In practice, however, the number of top-level conjuncts/disjuncts in a WHERE/FILTER predicate is usually small, so the cost should be bounded.
But I agree we should not rely on that assumption, I added a guard to skip absorption when the term list is large(The threshold is 20,referenced the threshold value for the IN-to-OR transition), avoid the performance impact associated with complex situations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are more and more machine/AI generated queries, what used to be reasonable it's not necessarily the same today, so I agree on being conservative here with a bloat parameter