diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/CreatePartitionTopNFromWindow.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/CreatePartitionTopNFromWindow.java index 8a4d7a42d3..834367cdec 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/CreatePartitionTopNFromWindow.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/CreatePartitionTopNFromWindow.java @@ -17,32 +17,16 @@ package org.apache.doris.nereids.rules.rewrite; +import org.apache.doris.common.Pair; import org.apache.doris.nereids.rules.Rule; import org.apache.doris.nereids.rules.RuleType; -import org.apache.doris.nereids.trees.expressions.BinaryOperator; -import org.apache.doris.nereids.trees.expressions.EqualTo; -import org.apache.doris.nereids.trees.expressions.ExprId; -import org.apache.doris.nereids.trees.expressions.Expression; -import org.apache.doris.nereids.trees.expressions.LessThan; -import org.apache.doris.nereids.trees.expressions.LessThanEqual; -import org.apache.doris.nereids.trees.expressions.NamedExpression; -import org.apache.doris.nereids.trees.expressions.SlotReference; import org.apache.doris.nereids.trees.expressions.WindowExpression; -import org.apache.doris.nereids.trees.expressions.literal.IntegerLikeLiteral; import org.apache.doris.nereids.trees.plans.Plan; import org.apache.doris.nereids.trees.plans.logical.LogicalEmptyRelation; import org.apache.doris.nereids.trees.plans.logical.LogicalFilter; import org.apache.doris.nereids.trees.plans.logical.LogicalPartitionTopN; import org.apache.doris.nereids.trees.plans.logical.LogicalWindow; -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableSet; - -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.function.Predicate; - /** * Push down the 'partitionTopN' into the 'window'. * It will convert the filter condition to the 'limit value' and push down below the 'window'. @@ -89,82 +73,17 @@ public class CreatePartitionTopNFromWindow extends OneRewriteRuleFactory { return filter; } - List windowExprs = window.getWindowExpressions(); - if (windowExprs.size() != 1) { + Pair windowFuncPair = window.getPushDownWindowFuncAndLimit(filter, Long.MAX_VALUE); + if (windowFuncPair == null) { return filter; + } else if (windowFuncPair.second == -1) { + // limit -1 indicating a empty relation case + return new LogicalEmptyRelation(ctx.statementContext.getNextRelationId(), filter.getOutput()); + } else { + Plan newWindow = window.pushPartitionLimitThroughWindow(windowFuncPair.first, + windowFuncPair.second, false); + return filter.withChildren(newWindow); } - NamedExpression windowExpr = windowExprs.get(0); - if (windowExpr.children().size() != 1 || !(windowExpr.child(0) instanceof WindowExpression)) { - return filter; - } - - // Check the filter conditions. Now, we currently only support simple conditions of the form - // 'column conjuncts = filter.getConjuncts(); - Set relatedConjuncts = extractRelatedConjuncts(conjuncts, windowExpr.getExprId()); - - boolean hasPartitionLimit = false; - long partitionLimit = Long.MAX_VALUE; - - for (Expression conjunct : relatedConjuncts) { - Preconditions.checkArgument(conjunct instanceof BinaryOperator); - BinaryOperator op = (BinaryOperator) conjunct; - Expression leftChild = op.children().get(0); - Expression rightChild = op.children().get(1); - - Preconditions.checkArgument(leftChild instanceof SlotReference - && rightChild instanceof IntegerLikeLiteral); - - long limitVal = ((IntegerLikeLiteral) rightChild).getLongValue(); - // Adjust the value for 'limitVal' based on the comparison operators. - if (conjunct instanceof LessThan) { - limitVal--; - } - if (limitVal < 0) { - return new LogicalEmptyRelation(ctx.statementContext.getNextRelationId(), filter.getOutput()); - } - if (hasPartitionLimit) { - partitionLimit = Math.min(partitionLimit, limitVal); - } else { - partitionLimit = limitVal; - hasPartitionLimit = true; - } - } - - if (!hasPartitionLimit) { - return filter; - } - - Optional newWindow = window.pushPartitionLimitThroughWindow(partitionLimit, false); - if (!newWindow.isPresent()) { - return filter; - } - return filter.withChildren(newWindow.get()); }).toRule(RuleType.CREATE_PARTITION_TOPN_FOR_WINDOW); } - - private Set extractRelatedConjuncts(Set conjuncts, ExprId slotRefID) { - Predicate condition = conjunct -> { - if (!(conjunct instanceof BinaryOperator)) { - return false; - } - BinaryOperator op = (BinaryOperator) conjunct; - Expression leftChild = op.children().get(0); - Expression rightChild = op.children().get(1); - - if (!(conjunct instanceof LessThan || conjunct instanceof LessThanEqual || conjunct instanceof EqualTo)) { - return false; - } - - // TODO: Now, we only support the column on the left side. - if (!(leftChild instanceof SlotReference) || !(rightChild instanceof IntegerLikeLiteral)) { - return false; - } - return ((SlotReference) leftChild).getExprId() == slotRefID; - }; - - return conjuncts.stream() - .filter(condition) - .collect(ImmutableSet.toImmutableSet()); - } } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownLimit.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownLimit.java index 69a7faa6aa..68cf03d7f5 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownLimit.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownLimit.java @@ -17,8 +17,10 @@ package org.apache.doris.nereids.rules.rewrite; +import org.apache.doris.common.Pair; import org.apache.doris.nereids.rules.Rule; import org.apache.doris.nereids.rules.RuleType; +import org.apache.doris.nereids.trees.expressions.WindowExpression; import org.apache.doris.nereids.trees.plans.Plan; import org.apache.doris.nereids.trees.plans.algebra.Limit; import org.apache.doris.nereids.trees.plans.algebra.SetOperation.Qualifier; @@ -31,7 +33,6 @@ import org.apache.doris.nereids.trees.plans.logical.LogicalWindow; import com.google.common.collect.ImmutableList; import java.util.List; -import java.util.Optional; /** * Rules to push {@link org.apache.doris.nereids.trees.plans.logical.LogicalLimit} down. @@ -72,11 +73,17 @@ public class PushDownLimit implements RewriteRuleFactory { .then(limit -> { LogicalWindow window = limit.child(); long partitionLimit = limit.getLimit() + limit.getOffset(); - Optional newWindow = window.pushPartitionLimitThroughWindow(partitionLimit, true); - if (!newWindow.isPresent()) { + if (partitionLimit <= 0) { return limit; } - return limit.withChildren(newWindow.get()); + Pair windowFuncLongPair = window + .getPushDownWindowFuncAndLimit(null, partitionLimit); + if (windowFuncLongPair == null) { + return limit; + } + Plan newWindow = window.pushPartitionLimitThroughWindow(windowFuncLongPair.first, + windowFuncLongPair.second, true); + return limit.withChildren(newWindow); }).toRule(RuleType.PUSH_LIMIT_THROUGH_WINDOW), // limit -> project -> window @@ -85,11 +92,17 @@ public class PushDownLimit implements RewriteRuleFactory { LogicalProject> project = limit.child(); LogicalWindow window = project.child(); long partitionLimit = limit.getLimit() + limit.getOffset(); - Optional newWindow = window.pushPartitionLimitThroughWindow(partitionLimit, true); - if (!newWindow.isPresent()) { + if (partitionLimit <= 0) { return limit; } - return limit.withChildren(project.withChildren(newWindow.get())); + Pair windowFuncLongPair = window + .getPushDownWindowFuncAndLimit(null, partitionLimit); + if (windowFuncLongPair == null) { + return limit; + } + Plan newWindow = window.pushPartitionLimitThroughWindow(windowFuncLongPair.first, + windowFuncLongPair.second, true); + return limit.withChildren(project.withChildren(newWindow)); }).toRule(RuleType.PUSH_LIMIT_THROUGH_PROJECT_WINDOW), // limit -> union diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownTopNThroughWindow.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownTopNThroughWindow.java index 7a0eb06887..8dc4cd6f73 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownTopNThroughWindow.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/PushDownTopNThroughWindow.java @@ -17,6 +17,7 @@ package org.apache.doris.nereids.rules.rewrite; +import org.apache.doris.common.Pair; import org.apache.doris.nereids.properties.OrderKey; import org.apache.doris.nereids.rules.Rule; import org.apache.doris.nereids.rules.RuleType; @@ -33,7 +34,6 @@ import org.apache.doris.nereids.trees.plans.logical.LogicalWindow; import com.google.common.collect.ImmutableList; import java.util.List; -import java.util.Optional; /** * PushdownTopNThroughWindow push down the TopN through the Window and generate the PartitionTopN. @@ -54,11 +54,14 @@ public class PushDownTopNThroughWindow implements RewriteRuleFactory { return topn; } long partitionLimit = topn.getLimit() + topn.getOffset(); - Optional newWindow = window.pushPartitionLimitThroughWindow(partitionLimit, true); - if (!newWindow.isPresent()) { + Pair windowFuncLongPair = window + .getPushDownWindowFuncAndLimit(null, partitionLimit); + if (windowFuncLongPair == null) { return topn; } - return topn.withChildren(newWindow.get()); + Plan newWindow = window.pushPartitionLimitThroughWindow(windowFuncLongPair.first, + windowFuncLongPair.second, true); + return topn.withChildren(newWindow); }).toRule(RuleType.PUSH_DOWN_TOP_N_THROUGH_WINDOW), // topn -> projection -> window @@ -74,11 +77,14 @@ public class PushDownTopNThroughWindow implements RewriteRuleFactory { return topn; } long partitionLimit = topn.getLimit() + topn.getOffset(); - Optional newWindow = window.pushPartitionLimitThroughWindow(partitionLimit, true); - if (!newWindow.isPresent()) { + Pair windowFuncLongPair = window + .getPushDownWindowFuncAndLimit(null, partitionLimit); + if (windowFuncLongPair == null) { return topn; } - return topn.withChildren(project.withChildren(newWindow.get())); + Plan newWindow = window.pushPartitionLimitThroughWindow(windowFuncLongPair.first, + windowFuncLongPair.second, true); + return topn.withChildren(project.withChildren(newWindow)); }).toRule(RuleType.PUSH_DOWN_TOP_N_THROUGH_PROJECT_WINDOW) ); } diff --git a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalWindow.java b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalWindow.java index ed99c26516..fceb36e677 100644 --- a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalWindow.java +++ b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalWindow.java @@ -17,19 +17,27 @@ package org.apache.doris.nereids.trees.plans.logical; +import org.apache.doris.common.Pair; import org.apache.doris.nereids.memo.GroupExpression; import org.apache.doris.nereids.properties.FdItem; import org.apache.doris.nereids.properties.FunctionalDependencies; import org.apache.doris.nereids.properties.FunctionalDependencies.Builder; import org.apache.doris.nereids.properties.LogicalProperties; +import org.apache.doris.nereids.trees.expressions.BinaryOperator; +import org.apache.doris.nereids.trees.expressions.EqualTo; +import org.apache.doris.nereids.trees.expressions.ExprId; import org.apache.doris.nereids.trees.expressions.Expression; +import org.apache.doris.nereids.trees.expressions.LessThan; +import org.apache.doris.nereids.trees.expressions.LessThanEqual; import org.apache.doris.nereids.trees.expressions.NamedExpression; import org.apache.doris.nereids.trees.expressions.Slot; +import org.apache.doris.nereids.trees.expressions.SlotReference; import org.apache.doris.nereids.trees.expressions.WindowExpression; import org.apache.doris.nereids.trees.expressions.WindowFrame; import org.apache.doris.nereids.trees.expressions.functions.window.DenseRank; import org.apache.doris.nereids.trees.expressions.functions.window.Rank; import org.apache.doris.nereids.trees.expressions.functions.window.RowNumber; +import org.apache.doris.nereids.trees.expressions.literal.IntegerLikeLiteral; import org.apache.doris.nereids.trees.plans.Plan; import org.apache.doris.nereids.trees.plans.PlanType; import org.apache.doris.nereids.trees.plans.algebra.Window; @@ -44,6 +52,8 @@ import com.google.common.collect.ImmutableSet; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; /** * logical node to deal with window functions; @@ -170,62 +180,172 @@ public class LogicalWindow extends LogicalUnary getPushDownWindowFuncAndLimit(LogicalFilter filter, long partitionLimit) { + if (!ConnectContext.get().getSessionVariable().isEnablePartitionTopN()) { + return null; + } + // We have already done such optimization rule, so just ignore it. + if (child(0) instanceof LogicalPartitionTopN + || (child(0) instanceof LogicalFilter + && child(0).child(0) != null + && child(0).child(0) instanceof LogicalPartitionTopN)) { + return null; + } + + // Check the window function. There are some restrictions for window function: + // 1. The window function should be one of the 'row_number()', 'rank()', 'dense_rank()'. + // 2. The window frame should be 'UNBOUNDED' to 'CURRENT'. + // 3. The 'PARTITION' key and 'ORDER' key can not be empty at the same time. + WindowExpression chosenWindowFunc = null; + long chosenPartitionLimit = Long.MAX_VALUE; + long chosenRowNumberPartitionLimit = Long.MAX_VALUE; + boolean hasRowNumber = false; + for (NamedExpression windowExpr : windowExpressions) { + if (windowExpr == null || windowExpr.children().size() != 1 + || !(windowExpr.child(0) instanceof WindowExpression)) { + continue; + } + WindowExpression windowFunc = (WindowExpression) windowExpr.child(0); + + // Check the window function name. + if (!(windowFunc.getFunction() instanceof RowNumber + || windowFunc.getFunction() instanceof Rank + || windowFunc.getFunction() instanceof DenseRank)) { + continue; + } + + // Check the partition key and order key. + if (windowFunc.getPartitionKeys().isEmpty() && windowFunc.getOrderKeys().isEmpty()) { + continue; + } + + // Check the window type and window frame. + Optional windowFrame = windowFunc.getWindowFrame(); + if (windowFrame.isPresent()) { + WindowFrame frame = windowFrame.get(); + if (!(frame.getLeftBoundary().getFrameBoundType() == WindowFrame.FrameBoundType.UNBOUNDED_PRECEDING + && frame.getRightBoundary().getFrameBoundType() == WindowFrame.FrameBoundType.CURRENT_ROW)) { + continue; + } + } else { + continue; + } + + // Check filter conditions. + if (filter != null) { + // We currently only support simple conditions of the form 'column conjuncts = filter.getConjuncts(); + Set relatedConjuncts = extractRelatedConjuncts(conjuncts, windowExpr.getExprId()); + for (Expression conjunct : relatedConjuncts) { + // Pre-checking has been done in former extraction + BinaryOperator op = (BinaryOperator) conjunct; + Expression rightChild = op.children().get(1); + long limitVal = ((IntegerLikeLiteral) rightChild).getLongValue(); + // Adjust the value for 'limitVal' based on the comparison operators. + if (conjunct instanceof LessThan) { + limitVal--; + } + if (limitVal < 0) { + // Set return limit value as -1 for indicating a empty relation opt case + chosenPartitionLimit = -1; + chosenRowNumberPartitionLimit = -1; + break; + } + if (hasPartitionLimit) { + curPartitionLimit = Math.min(curPartitionLimit, limitVal); + } else { + curPartitionLimit = limitVal; + hasPartitionLimit = true; + } + } + if (chosenPartitionLimit == -1) { + chosenWindowFunc = windowFunc; + break; + } else if (windowFunc.getFunction() instanceof RowNumber) { + // choose row_number first any way + // if multiple exists, choose the one with minimal limit value + if (curPartitionLimit < chosenRowNumberPartitionLimit) { + chosenRowNumberPartitionLimit = curPartitionLimit; + chosenWindowFunc = windowFunc; + hasRowNumber = true; + } + } else if (!hasRowNumber) { + // if no row_number, choose the one with minimal limit value + if (curPartitionLimit < chosenPartitionLimit) { + chosenPartitionLimit = curPartitionLimit; + chosenWindowFunc = windowFunc; + } + } + } else { + // limit + chosenWindowFunc = windowFunc; + chosenPartitionLimit = partitionLimit; + if (windowFunc.getFunction() instanceof RowNumber) { + break; + } + } + } + if (chosenWindowFunc == null || (chosenPartitionLimit == Long.MAX_VALUE + && chosenRowNumberPartitionLimit == Long.MAX_VALUE)) { + return null; + } else { + return Pair.of(chosenWindowFunc, hasRowNumber ? chosenRowNumberPartitionLimit : chosenPartitionLimit); + } + } + /** * pushPartitionLimitThroughWindow is used to push the partitionLimit through the window * and generate the partitionTopN. If the window can not meet the requirement, * it will return null. So when we use this function, we need check the null in the outside. */ - public Optional pushPartitionLimitThroughWindow(long partitionLimit, boolean hasGlobalLimit) { - if (!ConnectContext.get().getSessionVariable().isEnablePartitionTopN()) { - return Optional.empty(); - } - // We have already done such optimization rule, so just ignore it. - if (child(0) instanceof LogicalPartitionTopN) { - return Optional.empty(); - } - - // Check the window function. There are some restrictions for window function: - // 1. The number of window function should be 1. - // 2. The window function should be one of the 'row_number()', 'rank()', 'dense_rank()'. - // 3. The window frame should be 'UNBOUNDED' to 'CURRENT'. - // 4. The 'PARTITION' key and 'ORDER' key can not be empty at the same time. - if (windowExpressions.size() != 1) { - return Optional.empty(); - } - NamedExpression windowExpr = windowExpressions.get(0); - if (windowExpr.children().size() != 1 || !(windowExpr.child(0) instanceof WindowExpression)) { - return Optional.empty(); - } - - WindowExpression windowFunc = (WindowExpression) windowExpr.child(0); - // Check the window function name. - if (!(windowFunc.getFunction() instanceof RowNumber - || windowFunc.getFunction() instanceof Rank - || windowFunc.getFunction() instanceof DenseRank)) { - return Optional.empty(); - } - - // Check the partition key and order key. - if (windowFunc.getPartitionKeys().isEmpty() && windowFunc.getOrderKeys().isEmpty()) { - return Optional.empty(); - } - - // Check the window type and window frame. - Optional windowFrame = windowFunc.getWindowFrame(); - if (windowFrame.isPresent()) { - WindowFrame frame = windowFrame.get(); - if (!(frame.getLeftBoundary().getFrameBoundType() == WindowFrame.FrameBoundType.UNBOUNDED_PRECEDING - && frame.getRightBoundary().getFrameBoundType() == WindowFrame.FrameBoundType.CURRENT_ROW)) { - return Optional.empty(); - } - } else { - return Optional.empty(); - } - + public Plan pushPartitionLimitThroughWindow(WindowExpression windowFunc, + long partitionLimit, boolean hasGlobalLimit) { LogicalWindow window = (LogicalWindow) withChildren(new LogicalPartitionTopN<>(windowFunc, hasGlobalLimit, partitionLimit, child(0))); + return window; + } - return Optional.ofNullable(window); + private Set extractRelatedConjuncts(Set conjuncts, ExprId slotRefID) { + Predicate condition = conjunct -> { + if (!(conjunct instanceof BinaryOperator)) { + return false; + } + BinaryOperator op = (BinaryOperator) conjunct; + Expression leftChild = op.children().get(0); + Expression rightChild = op.children().get(1); + + if (!(conjunct instanceof LessThan || conjunct instanceof LessThanEqual || conjunct instanceof EqualTo)) { + return false; + } + + // TODO: Now, we only support the column on the left side. + if (!(leftChild instanceof SlotReference) || !(rightChild instanceof IntegerLikeLiteral)) { + return false; + } + return ((SlotReference) leftChild).getExprId() == slotRefID; + }; + + return conjuncts.stream() + .filter(condition) + .collect(ImmutableSet.toImmutableSet()); } private boolean isUnique(NamedExpression namedExpression) { diff --git a/regression-test/suites/nereids_rules_p0/push_down_filter_through_window/push_down_multi_filter_through_window.groovy b/regression-test/suites/nereids_rules_p0/push_down_filter_through_window/push_down_multi_filter_through_window.groovy new file mode 100644 index 0000000000..d808d30f8e --- /dev/null +++ b/regression-test/suites/nereids_rules_p0/push_down_filter_through_window/push_down_multi_filter_through_window.groovy @@ -0,0 +1,160 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +suite("push_down_multi_filter_through_window") { + sql "SET enable_nereids_planner=true" + sql "SET enable_fallback_to_original_planner=false" + sql "set ignore_shape_nodes='PhysicalDistribute'" + sql "drop table if exists push_down_multi_predicate_through_window_t" + multi_sql """ + CREATE TABLE push_down_multi_predicate_through_window_t (c1 INT, c2 INT, c3 VARCHAR(50)) properties("replication_num"="1"); + INSERT INTO push_down_multi_predicate_through_window_t (c1, c2, c3) VALUES(1, 10, 'A'),(2, 20, 'B'),(3, 30, 'C'),(4, 40, 'D'); + """ + explain { + sql ("select * from (select row_number() over(partition by c1, c2 order by c3) as rn from push_down_multi_predicate_through_window_t) t where rn <= 1;") + contains "VPartitionTopN" + contains "functions: row_number" + contains "partition limit: 1" + } + + explain { + sql ("select * from (select rank() over(partition by c1, c2 order by c3) as rk from push_down_multi_predicate_through_window_t) t where rk <= 1;") + contains "VPartitionTopN" + contains "functions: rank" + contains "partition limit: 1" + } + + explain { + sql ("select * from (select rank() over(partition by c1, c2 order by c3) as rk from push_down_multi_predicate_through_window_t) t where rk > 1;") + notContains "VPartitionTopN" + } + + explain { + sql ("select * from (select row_number() over(partition by c1, c2 order by c3) as rn from push_down_multi_predicate_through_window_t) t where rn > 1;") + notContains "VPartitionTopN" + } + + explain { + sql ("select * from (select row_number() over(partition by c1, c2 order by c3) as rn, rank() over(partition by c1 order by c3) as rk from push_down_multi_predicate_through_window_t) t where rn <= 1 and rk <= 1;") + contains "VPartitionTopN" + contains "functions: row_number" + contains "partition limit: 1" + } + + explain { + sql ("select * from (select rank() over(partition by c1 order by c3) as rk, row_number() over(partition by c1, c2 order by c3) as rn from push_down_multi_predicate_through_window_t) t where rn <= 10 and rk <= 1;") + contains "VPartitionTopN" + contains "functions: row_number" + contains "partition limit: 10" + } + + explain { + sql ("select * from (select rank() over(partition by c1 order by c3) as rk, row_number() over(partition by c1, c2 order by c3) as rn from push_down_multi_predicate_through_window_t) t where rk <= 1;") + contains "VPartitionTopN" + contains "functions: rank" + contains "partition limit: 1" + } + + explain { + sql ("select * from (select rank() over(partition by c1 order by c3) as rk, row_number() over(partition by c1, c2 order by c3) as rn from push_down_multi_predicate_through_window_t) t where rn <= 10;") + contains "VPartitionTopN" + contains "functions: row_number" + contains "partition limit: 10" + } + + explain { + sql ("select * from (select rank() over(partition by c1 order by c3) as rk, rank() over(partition by c1, c2 order by c3) as rn from push_down_multi_predicate_through_window_t) t where rn <= 1 and rk <= 10;") + contains "VPartitionTopN" + contains "functions: rank" + contains "partition limit: 1" + } + + explain { + sql ("select * from (select rank() over(partition by c1 order by c3) as rk, rank() over(partition by c1, c2 order by c3) as rn from push_down_multi_predicate_through_window_t) t where rn <= 10 and rk <= 1;") + contains "VPartitionTopN" + contains "functions: rank" + contains "partition limit: 1" + } + + explain { + sql ("select * from (select rank() over(partition by c1 order by c3) as rk, rank() over(partition by c1, c2 order by c3) as rn from push_down_multi_predicate_through_window_t) t where rn > 1 and rk <= 1;") + contains "VPartitionTopN" + contains "functions: rank" + contains "partition limit: 1" + } + + explain { + sql ("select * from (select rank() over(partition by c1 order by c3) as rk, rank() over(partition by c1, c2 order by c3) as rn from push_down_multi_predicate_through_window_t) t where rn <= 1 and rk > 1;") + contains "VPartitionTopN" + contains "functions: rank" + contains "partition limit: 1" + } + + explain { + sql ("select * from (select row_number() over(partition by c1, c2 order by c3) as rn, rank() over(partition by c1 order by c3) as rk from push_down_multi_predicate_through_window_t) t limit 10;") + contains "VPartitionTopN" + contains "functions: row_number" + contains "partition limit: 10" + } + + explain { + sql ("select * from (select rank() over(partition by c1, c2 order by c3) as rn, rank() over(partition by c1 order by c3) as rk from push_down_multi_predicate_through_window_t) t limit 10;") + contains "VPartitionTopN" + contains "functions: rank" + contains "partition limit: 10" + } + + explain { + sql ("select * from (select row_number() over(partition by c1, c2 order by c3) as rn, row_number() over(partition by c1 order by c3) as rk from push_down_multi_predicate_through_window_t) t where rn <= 10 and rk <= 1;") + contains "VPartitionTopN" + contains "functions: row_number" + contains "partition limit: 1" + } + + explain { + sql ("select * from (select row_number() over(partition by c1, c2 order by c3) as rn, row_number() over(partition by c1 order by c3) as rk from push_down_multi_predicate_through_window_t) t where rn <= 1 and rk <= 10;") + contains "VPartitionTopN" + contains "functions: row_number" + contains "partition limit: 1" + } + + explain { + sql ("select * from (select row_number() over(partition by c1, c2 order by c3) as rn, rank() over(partition by c1 order by c3) as rk1, rank() over(partition by c2 order by c3) as rk2 from push_down_multi_predicate_through_window_t) t where rn <= 1 and rk1 <= 10 and rk2 <= 100;") + contains "VPartitionTopN" + contains "functions: row_number" + contains "partition limit: 1" + } + + explain { + sql ("select * from (select row_number() over(partition by c1 order by c3) as rn1, row_number() over(partition by c2 order by c3) as rn2, rank() over(partition by c1, c2 order by c3) as rk from push_down_multi_predicate_through_window_t) t where rn1 <= 10 and rn2 <= 1 and rk <= 100;") + contains "VPartitionTopN" + contains "functions: row_number" + contains "partition limit: 1" + } + + explain { + sql ("select * from (select rank() over(partition by c1, c2 order by c3) as rk, row_number() over(partition by c1 order by c3) as rn1, row_number() over(partition by c2 order by c3) as rn2 from push_down_multi_predicate_through_window_t) t where rn1 <= 1 and rn2 <= 10 and rk <= 100;") + contains "VPartitionTopN" + contains "functions: row_number" + contains "partition limit: 1" + } + + explain { + sql ("select * from (select row_number() over(partition by c1, c2 order by c3) as rn, rank() over(partition by c1 order by c3) as rk from push_down_multi_predicate_through_window_t) t where rn <= 1 or rk <= 1;") + notContains "VPartitionTopN" + } +}