-
Notifications
You must be signed in to change notification settings - Fork 28.1k
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
[SPARK-13739] [SQL] Push Predicate Through Window #11635
Changes from 68 commits
01e4cdf
6835704
9180687
b38a21e
d2b84af
fda8025
ac0dccd
6e0018b
0546772
b37a64f
c2a872c
ab6dbd7
4276356
2dab708
0458770
1debdfa
763706d
4de6ec1
9422a4f
52bdf48
1e95df3
fab24cf
8b2e33b
2ee1876
b9f0090
ade6f7e
9fd63d2
5199d49
404214c
c001dd9
59daa48
41d5f64
472a6e3
458f7be
92136dd
f401d8b
0fba10a
3aea1da
d420246
c05b4ae
6db1940
8fa0294
cbf73b3
c08f561
474df88
3d9828d
72d2361
07afea5
8bf2007
87a165b
b9359cd
b0d7b3b
65bd090
babf2da
9e09469
50a8e4a
f3337fa
09cc36d
83a1915
0483145
236a5f4
417bdb4
cc5f0bd
436359f
fae2694
875d6b6
0469923
5c4f4d3
c4dedd2
3eaeaa5
e427ce9
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 |
---|---|---|
|
@@ -958,6 +958,25 @@ object PushDownPredicate extends Rule[LogicalPlan] with PredicateHelper { | |
|
||
project.copy(child = Filter(replaceAlias(condition, aliasMap), grandChild)) | ||
|
||
// Push [[Filter]] operators through [[Window]] operators. Parts of the predicate that can be | ||
// pushed beneath must satisfy three conditions: | ||
// 1. All the columns are part of window partitioning key. | ||
// 2. Window partitioning key should be just a sequence of [[AttributeReference]]. | ||
// 3. Deterministic | ||
case filter @ Filter(condition, w: Window) | ||
if w.partitionSpec.forall(_.isInstanceOf[AttributeReference]) => | ||
val partitionAttrs = AttributeSet(w.partitionSpec.flatMap(_.references)) | ||
val (pushDown, stayUp) = splitConjunctivePredicates(condition).partition { cond => | ||
cond.references.subsetOf(partitionAttrs) && cond.deterministic | ||
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. This is not right. Will fix it later. |
||
} | ||
if (pushDown.nonEmpty) { | ||
val pushDownPredicate = pushDown.reduce(And) | ||
val newWindow = w.copy(child = Filter(pushDownPredicate, w.child)) | ||
if (stayUp.isEmpty) newWindow else Filter(stayUp.reduce(And), newWindow) | ||
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. Math.pow(NIT, 100): What does the style guide say about ternary expression onliners? 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. nevermind, I looked it up. It is allowed. |
||
} else { | ||
filter | ||
} | ||
|
||
case filter @ Filter(condition, aggregate: Aggregate) => | ||
// Find all the aliased expressions in the aggregate list that don't include any actual | ||
// AggregateExpression, and create a map from the alias to the expression | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -758,4 +758,142 @@ class FilterPushdownSuite extends PlanTest { | |||||||||||||||||||||||||||||||||||||||
val correctedAnswer = agg.copy(child = agg.child.where(a > 1 && b > 2)).analyze | ||||||||||||||||||||||||||||||||||||||||
comparePlans(optimized, correctedAnswer) | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
test("Window: predicate push down -- basic") { | ||||||||||||||||||||||||||||||||||||||||
val winExpr = windowExpr(count('b), windowSpec('a :: Nil, 'b.asc :: Nil, UnspecifiedFrame)) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
val originalQuery = testRelation.select('a, 'b, 'c, winExpr.as('window)).where('a > 1) | ||||||||||||||||||||||||||||||||||||||||
val correctAnswer = testRelation | ||||||||||||||||||||||||||||||||||||||||
.where('a > 1).select('a, 'b, 'c) | ||||||||||||||||||||||||||||||||||||||||
.window(winExpr.as('window) :: Nil, 'a :: Nil, 'b.asc :: Nil) | ||||||||||||||||||||||||||||||||||||||||
.select('a, 'b, 'c, 'window).analyze | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
comparePlans(Optimize.execute(originalQuery.analyze), correctAnswer) | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
test("Window: predicate push down -- predicates with compound predicate using only one column") { | ||||||||||||||||||||||||||||||||||||||||
val winExpr = | ||||||||||||||||||||||||||||||||||||||||
windowExpr(count('b), windowSpec('a.attr :: 'b.attr :: Nil, 'b.asc :: Nil, UnspecifiedFrame)) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
val originalQuery = testRelation.select('a, 'b, 'c, winExpr.as('window)).where('a * 3 > 15) | ||||||||||||||||||||||||||||||||||||||||
val correctAnswer = testRelation | ||||||||||||||||||||||||||||||||||||||||
.where('a * 3 > 15).select('a, 'b, 'c) | ||||||||||||||||||||||||||||||||||||||||
.window(winExpr.as('window) :: Nil, 'a.attr :: 'b.attr :: Nil, 'b.asc :: Nil) | ||||||||||||||||||||||||||||||||||||||||
.select('a, 'b, 'c, 'window).analyze | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
comparePlans(Optimize.execute(originalQuery.analyze), correctAnswer) | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
test("Window: predicate push down -- multi window expressions with the same window spec") { | ||||||||||||||||||||||||||||||||||||||||
val winSpec = windowSpec('a.attr :: 'b.attr :: Nil, 'b.asc :: Nil, UnspecifiedFrame) | ||||||||||||||||||||||||||||||||||||||||
val winExpr1 = windowExpr(count('b), winSpec) | ||||||||||||||||||||||||||||||||||||||||
val winExpr2 = windowExpr(sum('b), winSpec) | ||||||||||||||||||||||||||||||||||||||||
val originalQuery = testRelation | ||||||||||||||||||||||||||||||||||||||||
.select('a, 'b, 'c, winExpr1.as('window1), winExpr2.as('window2)).where('a > 1) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
val correctAnswer = testRelation | ||||||||||||||||||||||||||||||||||||||||
.where('a > 1).select('a, 'b, 'c) | ||||||||||||||||||||||||||||||||||||||||
.window(winExpr1.as('window1) :: winExpr2.as('window2) :: Nil, | ||||||||||||||||||||||||||||||||||||||||
'a.attr :: 'b.attr :: Nil, 'b.asc :: Nil) | ||||||||||||||||||||||||||||||||||||||||
.select('a, 'b, 'c, 'window1, 'window2).analyze | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
comparePlans(Optimize.execute(originalQuery.analyze), correctAnswer) | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
test("Window: predicate push down -- multi window specification - 1") { | ||||||||||||||||||||||||||||||||||||||||
// order by clauses are different between winSpec1 and winSpec2 | ||||||||||||||||||||||||||||||||||||||||
val winSpec1 = windowSpec('a.attr :: 'b.attr :: Nil, 'b.asc :: Nil, UnspecifiedFrame) | ||||||||||||||||||||||||||||||||||||||||
val winExpr1 = windowExpr(count('b), winSpec1) | ||||||||||||||||||||||||||||||||||||||||
val winSpec2 = windowSpec('a.attr :: 'b.attr :: Nil, 'a.asc :: Nil, UnspecifiedFrame) | ||||||||||||||||||||||||||||||||||||||||
val winExpr2 = windowExpr(count('b), winSpec2) | ||||||||||||||||||||||||||||||||||||||||
val originalQuery = testRelation | ||||||||||||||||||||||||||||||||||||||||
.select('a, 'b, 'c, winExpr1.as('window1), winExpr2.as('window2)).where('a > 1) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
val correctAnswer1 = testRelation | ||||||||||||||||||||||||||||||||||||||||
.where('a > 1).select('a, 'b, 'c) | ||||||||||||||||||||||||||||||||||||||||
.window(winExpr1.as('window1) :: Nil, 'a.attr :: 'b.attr :: Nil, 'b.asc :: Nil) | ||||||||||||||||||||||||||||||||||||||||
.window(winExpr2.as('window2) :: Nil, 'a.attr :: 'b.attr :: Nil, 'a.asc :: Nil) | ||||||||||||||||||||||||||||||||||||||||
.select('a, 'b, 'c, 'window1, 'window2).analyze | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
val correctAnswer2 = testRelation | ||||||||||||||||||||||||||||||||||||||||
.where('a > 1).select('a, 'b, 'c) | ||||||||||||||||||||||||||||||||||||||||
.window(winExpr2.as('window2) :: Nil, 'a.attr :: 'b.attr :: Nil, 'a.asc :: Nil) | ||||||||||||||||||||||||||||||||||||||||
.window(winExpr1.as('window1) :: Nil, 'a.attr :: 'b.attr :: Nil, 'b.asc :: Nil) | ||||||||||||||||||||||||||||||||||||||||
.select('a, 'b, 'c, 'window1, 'window2).analyze | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
val optimizedQuery = Optimize.execute(originalQuery.analyze) | ||||||||||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||||||||||
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. Are you doing this because you cannot predict how the Analyzer will structure the window functions? In this case you could just have the analyzer take care of it, by writing: testRelation.where('a > 1).select('a, 'b, 'c, winExpr1.as('window1), winExpr2.as('window2)).analyze 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 non-deterministic results are introduced when we grouping extractedWindowExprBuffer based on their Partition and Order Specs. spark/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/Analyzer.scala Lines 1318 to 1336 in 890abd1
I did not change the behavior of the source codes since the orders of Previously, I saw different orders in multiple runs. Thus, I am afraid the way you mentioned does not work too. |
||||||||||||||||||||||||||||||||||||||||
comparePlans(optimizedQuery, correctAnswer1) | ||||||||||||||||||||||||||||||||||||||||
} catch { | ||||||||||||||||||||||||||||||||||||||||
case ae: Throwable => comparePlans(optimizedQuery, correctAnswer2) | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
test("Window: predicate push down -- multi window specification - 2") { | ||||||||||||||||||||||||||||||||||||||||
// partitioning clauses are different between winSpec1 and winSpec2 | ||||||||||||||||||||||||||||||||||||||||
val winSpec1 = windowSpec('a.attr :: Nil, 'b.asc :: Nil, UnspecifiedFrame) | ||||||||||||||||||||||||||||||||||||||||
val winExpr1 = windowExpr(count('b), winSpec1) | ||||||||||||||||||||||||||||||||||||||||
val winSpec2 = windowSpec('b.attr :: Nil, 'b.asc :: Nil, UnspecifiedFrame) | ||||||||||||||||||||||||||||||||||||||||
val winExpr2 = windowExpr(count('a), winSpec2) | ||||||||||||||||||||||||||||||||||||||||
val originalQuery = testRelation | ||||||||||||||||||||||||||||||||||||||||
.select('a, winExpr1.as('window1), 'b, 'c, winExpr2.as('window2)).where('b > 1) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
val correctAnswer1 = testRelation.select('a, 'b, 'c) | ||||||||||||||||||||||||||||||||||||||||
.window(winExpr1.as('window1) :: Nil, 'a.attr :: Nil, 'b.asc :: Nil) | ||||||||||||||||||||||||||||||||||||||||
.where('b > 1) | ||||||||||||||||||||||||||||||||||||||||
.window(winExpr2.as('window2) :: Nil, 'b.attr :: Nil, 'b.asc :: Nil) | ||||||||||||||||||||||||||||||||||||||||
.select('a, 'window1, 'b, 'c, 'window2).analyze | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
val correctAnswer2 = testRelation.select('a, 'b, 'c) | ||||||||||||||||||||||||||||||||||||||||
.window(winExpr2.as('window2) :: Nil, 'b.attr :: Nil, 'b.asc :: Nil) | ||||||||||||||||||||||||||||||||||||||||
.window(winExpr1.as('window1) :: Nil, 'a.attr :: Nil, 'b.asc :: Nil) | ||||||||||||||||||||||||||||||||||||||||
.where('b > 1) | ||||||||||||||||||||||||||||||||||||||||
.select('a, 'window1, 'b, 'c, 'window2).analyze | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
val optimizedQuery = Optimize.execute(originalQuery.analyze) | ||||||||||||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||||||||||||
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. Please add a line of documentation to explain why you are using try/catch. 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. Will do it. |
||||||||||||||||||||||||||||||||||||||||
comparePlans(optimizedQuery, correctAnswer1) | ||||||||||||||||||||||||||||||||||||||||
} catch { | ||||||||||||||||||||||||||||||||||||||||
case ae: Throwable => comparePlans(optimizedQuery, correctAnswer2) | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
test("Window: predicate push down -- predicates with multiple partitioning columns") { | ||||||||||||||||||||||||||||||||||||||||
val winExpr = | ||||||||||||||||||||||||||||||||||||||||
windowExpr(count('b), windowSpec('a.attr :: 'b.attr :: Nil, 'b.asc :: Nil, UnspecifiedFrame)) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
val originalQuery = testRelation.select('a, 'b, 'c, winExpr.as('window)).where('a + 'b > 1) | ||||||||||||||||||||||||||||||||||||||||
val correctAnswer = testRelation | ||||||||||||||||||||||||||||||||||||||||
.where('a + 'b > 1).select('a, 'b, 'c) | ||||||||||||||||||||||||||||||||||||||||
.window(winExpr.as('window) :: Nil, 'a.attr :: 'b.attr :: Nil, 'b.asc :: Nil) | ||||||||||||||||||||||||||||||||||||||||
.select('a, 'b, 'c, 'window).analyze | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
comparePlans(Optimize.execute(originalQuery.analyze), correctAnswer) | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
test("Window: no predicate push down -- predicates are not from partitioning keys") { | ||||||||||||||||||||||||||||||||||||||||
val winExpr = | ||||||||||||||||||||||||||||||||||||||||
windowExpr(count('b), windowSpec('a.attr :: 'b.attr :: Nil, 'b.asc :: Nil, UnspecifiedFrame)) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
val originalQuery = testRelation.select('a, 'b, 'c, winExpr.as('window)).where('c > 1) | ||||||||||||||||||||||||||||||||||||||||
val correctAnswer = testRelation.select('a, 'b, 'c) | ||||||||||||||||||||||||||||||||||||||||
.window(winExpr.as('window) :: Nil, 'a.attr :: 'b.attr :: Nil, 'b.asc :: Nil) | ||||||||||||||||||||||||||||||||||||||||
.where('c > 1).select('a, 'b, 'c, 'window).analyze | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
comparePlans(Optimize.execute(originalQuery.analyze), correctAnswer) | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
test("Window: no predicate push down -- compound partition key") { | ||||||||||||||||||||||||||||||||||||||||
val winSpec = windowSpec('a.attr + 'b.attr :: 'b.attr :: Nil, 'b.asc :: Nil, UnspecifiedFrame) | ||||||||||||||||||||||||||||||||||||||||
val winExpr = windowExpr(count('b), winSpec) | ||||||||||||||||||||||||||||||||||||||||
val originalQuery = testRelation.select('a, 'b, 'c, winExpr.as('window)).where('a > 1) | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
val winSpecAnalyzed = windowSpec('_w0.attr :: 'b.attr :: Nil, 'b.asc :: Nil, UnspecifiedFrame) | ||||||||||||||||||||||||||||||||||||||||
val winExprAnalyzed = windowExpr(count('b), winSpecAnalyzed) | ||||||||||||||||||||||||||||||||||||||||
val correctAnswer = testRelation.select('a, 'b, 'c, ('a + 'b).as("_w0")) | ||||||||||||||||||||||||||||||||||||||||
.window(winExprAnalyzed.as('window) :: Nil, '_w0 :: 'b.attr :: Nil, 'b.asc :: Nil) | ||||||||||||||||||||||||||||||||||||||||
.where('a > 1).select('a, 'b, 'c, 'window).analyze | ||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||
comparePlans(Optimize.execute(originalQuery.analyze), correctAnswer) | ||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||
} |
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.
I think this is almost guaranteed by the analyzer.
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.
The purpose is to prohibit the push down of predicate
(key + value) > '2'
when the partition by iskey + value
in the following example,The example is also copied from the test case of Hive. https://issues.apache.org/jira/secure/attachment/12788757/HIVE-12808.05.patch
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.
How about this case? It appears like
key + value
is also constant when evaluating window functions. It should be OK to push it down? This restriction could be also related to Hive implementation?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.
@gatorsmile this restriction is probably more related to the fact that if we push down an entire expression, e.g.:
a + b
, we have to evaluate the expression twice, once in the Filter and once in the Window function. The double evaluation could be avoided by planning a Project.I am pretty sure that we move all expressions used in Window clauses into an underlying Project during Analysis. So this shouldn't be to big of a problem.
[I updated the comment]
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.
Yeah, thank you very much! I just added an extra condition to ensure that Analyzer converts all the compound expressions to alias. Added a couple of test cases to ensure it.
To enable predicate push down when the partitioning columns is
a + b
and the predicate isa + b > 3
, we need to add a rule in Analyzer for converting the expressions to the underlying alias that has the exactly same original expressions. This will be submitted in a separate PR.