You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
SanityChecker rejects certain valid UNION plans #12446
There is a regression that was added that in a very very specific circumstance with sorted data and constant predicates and UNION queries where the query will now error with a SanityCheckPlan error when it should complete.
# Test: inputs into union with different orderings
query TT
explain select*from (select b, c, a, NULL::intas a0 from ordered_table order by a, c) t1
union allselect*from (select b, c, NULL::intas a, a0 from ordered_table order by a0, c) t2
order by d, c, a, a0, b
limit2;
----
logical_plan
01)Projection: t1.b, t1.c, t1.a, t1.a002)--Sort: t1.d ASC NULLS LAST, t1.c ASC NULLS LAST, t1.a ASC NULLS LAST, t1.a0 ASC NULLS LAST, t1.b ASC NULLS LAST, fetch=203)----Union04)------SubqueryAlias: t105)--------Projection: ordered_table.b, ordered_table.c, ordered_table.a, Int32(NULL) AS a0, ordered_table.d06)----------TableScan: ordered_table projection=[a, b, c, d]07)------SubqueryAlias: t208)--------Projection: ordered_table.b, ordered_table.c, Int32(NULL) AS a, ordered_table.a0, ordered_table.d09)----------TableScan: ordered_table projection=[a0, b, c, d]# Test: run the query from above# TODO: query fails since the constant columns t1.a0 and t2.a are not in the ORDER BY subquery,# and SanityCheckPlan does not allow this.
statement error DataFusion error: SanityCheckPlan
select*from (select b, c, a, NULL::intas a0 from ordered_table order by a, c) t1
union allselect*from (select b, c, NULL::intas a, a0 from ordered_table order by a0, c) t2
order by d, c, a, a0, b
limit2;
statement ok
droptable ordered_table;
Expected behavior
Query should run
Additional context
We believe this was uncovered by #11196 . The error in the sort order calculation has existed for awhile but #11196 now uncovered the issue
I spent some time finding a smaller standalone reproducer
createtablet(a0 int, a int, b int, c int) asvalues (1, 2, 3, 4), (5, 6, 7, 8);
select*from (select c, a, NULL::intas a0 from t order by a, c) t1
union allselect*from (select c, NULL::intas a, a0 from t order by a0, c) t2
order by c, a, a0, b
limit2;
When run
>select*from (select c, a, NULL::intas a0 from t order by a, c) t1
union allselect*from (select c, NULL::intas a, a0 from t order by a0, c) t2
order by c, a, a0, b
limit2;
SanityCheckPlan
caused by
Error during planning: Plan: ["SortPreservingMergeExec: [c@0 ASC NULLS LAST,a@1 ASC NULLS LAST,a0@2 ASC NULLS LAST,b@3 ASC NULLS LAST], fetch=2", " UnionExec", " SortExec: TopK(fetch=2), expr=[c@0 ASC NULLS LAST,a@1 ASC NULLS LAST,b@3 ASC NULLS LAST], preserve_partitioning=[false]", " ProjectionExec: expr=[c@2 as c, a@0 as a, NULL as a0, b@1 as b]", " MemoryExec: partitions=1, partition_sizes=[1]", " SortExec: TopK(fetch=2), expr=[c@0 ASC NULLS LAST,a0@2 ASC NULLS LAST,b@3 ASC NULLS LAST], preserve_partitioning=[false]", " ProjectionExec: expr=[c@2 as c, NULL as a, a0@0 as a0, b@1 as b]", " MemoryExec: partitions=1, partition_sizes=[1]"] does not satisfy order requirements: [PhysicalSortRequirement { expr: Column { name: "c", index: 0 }, options: Some(SortOptions { descending: false, nulls_first: false }) }, PhysicalSortRequirement { expr: Column { name: "a", index: 1 }, options: Some(SortOptions { descending: false, nulls_first: false }) }, PhysicalSortRequirement { expr: Column { name: "a0", index: 2 }, options: Some(SortOptions { descending: false, nulls_first: false }) }, PhysicalSortRequirement { expr: Column { name: "b", index: 3 }, options: Some(SortOptions { descending: false, nulls_first: false }) }]. Child-0 order: OrderingEquivalenceClass { orderings: [[PhysicalSortExpr { expr: Column { name: "c", index: 0 }, options: SortOptions { descending: false, nulls_first: false } }, PhysicalSortExpr { expr: Column { name: "a", index: 1 }, options: SortOptions { descending: false, nulls_first: false } }], [PhysicalSortExpr { expr: Column { name: "c", index: 0 }, options: SortOptions { descending: false, nulls_first: false } }, PhysicalSortExpr { expr: Column { name: "a0", index: 2 }, options: SortOptions { descending: false, nulls_first: false } }]] }
--- Create basic tablecreatetablet(a0 int, a int, b int, c int) asvalues (1, 2, 3, 4), (5, 6, 7, 8);
--- Use a sorted union with limitselect*from (select c, a, NULL::intas a0 from t order by a, c) t1
union allselect*from (select c, NULL::intas a, a0 from t order by a0, c) t2
order by a, a0, c
limit2;
input1: [[a, c]], constants: [a0]
input2: [[a0, c]], constants: [a]
The current code computes the output equivalence properties as
result: [a, a0], constants: []
However, this seems correct to me. I worked an example to show this 🤔
Actually IMO the result should be [[a, a0, c], [a0, a, c]]. To do that, input orderings should be adjusted such that:
input1: [[a, c]], constants: [a0] => [[a0, a, c], [a, a0, c], [a, c, a0]]
input2: [[a0, c]], constants: [a] => [[a, a0, c], [a0, a, c], [a0, c, a]]
I could show what is in my mind in a draft if you think my proposal can fix the problem.
I made progress and started a PR here: #12562. It is all tests at the moment, but I think I now have a better handle on what is needed. I'll try and write more tests and finish the code fix soon
An update here is I have a WIP PR with tests, etc. #12562
I am now trying to figure out an appropriate algorithm to implement the adjustment that @berkaysynnada suggests in #12446 (comment)
Actually IMO the result should be [[a, a0, c], [a0, a, c]]. To do that, input orderings should be adjusted such that:
input1: [[a, c]], constants: [a0] => [[a0, a, c], [a, a0, c], [a, c, a0]]
input2: [[a0, c]], constants: [a] => [[a, a0, c], [a0, a, c], [a0, c, a]]
I worry that if we are not careful, such an expansion will be some sort of expontential blow up. Maybe I can just hard code a limit of orderings to try 🤔
An update here is I have a WIP PR with tests, etc. #12562
I will share my thoughts on it tomorrow
I worry that if we are not careful, such an expansion will be some sort of expontential blow up.
I have the same concern. We need to provide comprehensive tests. It would also be better if we observe the equivalence properties in the plans that include any UnionExec.
I have the same concern. We need to provide comprehensive tests. It would also be better if we observe the equivalence properties in the plans that include any UnionExec.
I will do so. I think I have a solution that we can start discussing tomorrow
An update here is that I think I have a reasonable algorithm in #12562 and have it working for many unit tests. I need to debug one last issue and then polish the PR up for review
Activity
alamb commentedon Sep 19, 2024
I spent some time finding a smaller standalone reproducer
When run
alamb commentedon Sep 19, 2024
Even simpler:
alamb commentedon Sep 19, 2024
I am trying to figure out where the bug is (is it in the union equivalence properties calculation). I am not sure it is.
I added some debugging information to my small reproducer from #12446 (comment) to try and figure out what the code should be doing
The debugging (full output below) shows the input equivalence properties are:
The current code computes the output equivalence properties as
However, this seems correct to me. I worked an example to show this 🤔
Worked example
If
input1is (sorted ona,c,constanta0):And
input2is (sorted ona0,c, constanta):There are at least two possible outputs
Possible output 1: (sorted on
aanda0)Possible output 2: ( also sorted on
aanda0)Details
Here is the full output
berkaysynnada commentedon Sep 20, 2024
Actually IMO the result should be [[a, a0, c], [a0, a, c]]. To do that, input orderings should be adjusted such that:
input1: [[a, c]], constants: [a0] => [[a0, a, c], [a, a0, c], [a, c, a0]]
input2: [[a0, c]], constants: [a] => [[a, a0, c], [a0, a, c], [a0, c, a]]
I could show what is in my mind in a draft if you think my proposal can fix the problem.
alamb commentedon Sep 20, 2024
Ah, yes that makes sense -- that
ccan be at the end of either order.That would be amazing -- thank you @berkaysynnada -- I would be happy to try and help make it happen / productionize itUpdate: let me give it a try this afternoon and report back here. If I can't make it work, perhaps a draft would help
alamb commentedon Sep 20, 2024
I made progress and started a PR here: #12562. It is all tests at the moment, but I think I now have a better handle on what is needed. I'll try and write more tests and finish the code fix soon
alamb commentedon Sep 23, 2024
An update here is I have a WIP PR with tests, etc. #12562
I am now trying to figure out an appropriate algorithm to implement the adjustment that @berkaysynnada suggests in #12446 (comment)
I worry that if we are not careful, such an expansion will be some sort of expontential blow up. Maybe I can just hard code a limit of orderings to try 🤔
berkaysynnada commentedon Sep 23, 2024
I will share my thoughts on it tomorrow
I have the same concern. We need to provide comprehensive tests. It would also be better if we observe the equivalence properties in the plans that include any
UnionExec.Thank you for your effort
alamb commentedon Sep 23, 2024
I will do so. I think I have a solution that we can start discussing tomorrow
alamb commentedon Sep 30, 2024
An update here is that I think I have a reasonable algorithm in #12562 and have it working for many unit tests. I need to debug one last issue and then polish the PR up for review
alamb commentedon Oct 1, 2024
I debugged the issue, and it appers to be a stack overflow unrelated to this particular issue: #12700
alamb commentedon Oct 4, 2024
Update here is that with these two PRs
The end to end tests (added in #12721) now pass successfully without error. 😅 So now it will be a matter of working through those PRs
alamb commentedon Oct 7, 2024
Update here is that we have the issued fixed now -- all that is left to close this ticket is to merge in the end to end tests: #12721