From 5ec243c94778316aa7715a86e0c8ea1d4b218e49 Mon Sep 17 00:00:00 2001 From: anusudarsan Date: Thu, 1 Jun 2017 22:56:10 -0400 Subject: [PATCH 1/2] Implement RANGE bounded PRECEDING window frame --- .../operator/window/WindowPartition.java | 44 ++++++- .../sql/analyzer/StatementAnalyzer.java | 3 - .../window/TestAggregateWindowFunction.java | 109 ++++++++++++++++++ .../window/TestFirstValueFunction.java | 14 +++ .../window/TestLastValueFunction.java | 14 +++ .../TestMultipleWindowSpecifications.java | 35 ++++++ .../operator/window/TestNthValueFunction.java | 14 +++ .../presto/sql/analyzer/TestAnalyzer.java | 2 - 8 files changed, 224 insertions(+), 11 deletions(-) diff --git a/presto-main/src/main/java/com/facebook/presto/operator/window/WindowPartition.java b/presto-main/src/main/java/com/facebook/presto/operator/window/WindowPartition.java index c882a5fa9c0a7..68bfd695e75eb 100644 --- a/presto-main/src/main/java/com/facebook/presto/operator/window/WindowPartition.java +++ b/presto-main/src/main/java/com/facebook/presto/operator/window/WindowPartition.java @@ -20,6 +20,7 @@ import com.facebook.presto.sql.tree.FrameBound; import com.google.common.collect.ImmutableList; +import java.util.ArrayList; import java.util.List; import static com.facebook.presto.spi.StandardErrorCode.INVALID_WINDOW_FRAME; @@ -28,6 +29,7 @@ import static com.facebook.presto.sql.tree.FrameBound.Type.UNBOUNDED_FOLLOWING; import static com.facebook.presto.sql.tree.FrameBound.Type.UNBOUNDED_PRECEDING; import static com.facebook.presto.sql.tree.WindowFrame.Type.RANGE; +import static com.facebook.presto.sql.tree.WindowFrame.Type.ROWS; import static com.facebook.presto.util.Failures.checkCondition; import static com.google.common.base.Preconditions.checkState; import static java.lang.Math.toIntExact; @@ -46,6 +48,8 @@ public final class WindowPartition private int peerGroupEnd; private int currentPosition; + private List peerGroupStartIndices = new ArrayList<>(); + private List peerGroupEndIndices = new ArrayList<>(); public WindowPartition(PagesIndex pagesIndex, int partitionStart, @@ -142,6 +146,8 @@ private void updatePeerGroup() while ((peerGroupEnd < partitionEnd) && pagesIndex.positionEqualsPosition(peerGroupHashStrategy, peerGroupStart, peerGroupEnd)) { peerGroupEnd++; } + peerGroupStartIndices.add(peerGroupStart); + peerGroupEndIndices.add(peerGroupEnd); } private Range getFrameRange(FrameInfo frameInfo) @@ -150,7 +156,11 @@ private Range getFrameRange(FrameInfo frameInfo) int endPosition = partitionEnd - partitionStart - 1; // handle empty frame - if (emptyFrame(frameInfo, rowPosition, endPosition)) { + if (frameInfo.getType() == ROWS && emptyFrame(frameInfo, rowPosition, endPosition - rowPosition)) { + return new Range(-1, -1); + } + + if (frameInfo.getType() == RANGE && emptyFrame(frameInfo, peerGroupStart, peerGroupEnd - 1)) { return new Range(-1, -1); } @@ -161,6 +171,9 @@ private Range getFrameRange(FrameInfo frameInfo) if (frameInfo.getStartType() == UNBOUNDED_PRECEDING) { frameStart = 0; } + else if (frameInfo.getType() == RANGE && frameInfo.getStartType() == PRECEDING) { + frameStart = precedingStartRange(getStartValue(frameInfo)); + } else if (frameInfo.getStartType() == PRECEDING) { frameStart = preceding(rowPosition, getStartValue(frameInfo)); } @@ -178,6 +191,9 @@ else if (frameInfo.getType() == RANGE) { if (frameInfo.getEndType() == UNBOUNDED_FOLLOWING) { frameEnd = endPosition; } + else if (frameInfo.getType() == RANGE && frameInfo.getEndType() == PRECEDING) { + frameEnd = precedingEndRange(getEndValue(frameInfo)); + } else if (frameInfo.getEndType() == PRECEDING) { frameEnd = preceding(rowPosition, getEndValue(frameInfo)); } @@ -194,19 +210,35 @@ else if (frameInfo.getType() == RANGE) { return new Range(frameStart, frameEnd); } - private boolean emptyFrame(FrameInfo frameInfo, int rowPosition, int endPosition) + private int precedingEndRange(long endValue) + { + int peerGroupEndIndex = peerGroupEndIndices.indexOf(peerGroupEnd); + if (peerGroupEndIndex < endValue) { + return peerGroupEnd - 1; + } + return peerGroupEndIndices.get(toIntExact(peerGroupEndIndex - endValue)) - 1; + } + + private int precedingStartRange(long startValue) + { + int peerGroupStartIndex = peerGroupStartIndices.indexOf(peerGroupStart); + if (peerGroupStartIndex < startValue) { + return 0; + } + return peerGroupStartIndices.get(toIntExact(peerGroupStartIndex - startValue)); + } + + private boolean emptyFrame(FrameInfo frameInfo, int rowPosition, int position) { FrameBound.Type startType = frameInfo.getStartType(); FrameBound.Type endType = frameInfo.getEndType(); - int positions = endPosition - rowPosition; - if ((startType == UNBOUNDED_PRECEDING) && (endType == PRECEDING)) { return getEndValue(frameInfo) > rowPosition; } if ((startType == FOLLOWING) && (endType == UNBOUNDED_FOLLOWING)) { - return getStartValue(frameInfo) > positions; + return getStartValue(frameInfo) > position; } if (startType != endType) { @@ -225,7 +257,7 @@ private boolean emptyFrame(FrameInfo frameInfo, int rowPosition, int endPosition return (start < end) || ((start > rowPosition) && (end > rowPosition)); } - return (start > end) || ((start > positions) && (end > positions)); + return (start > end) || ((start > position) && (end > position)); } private static int preceding(int rowPosition, long value) diff --git a/presto-main/src/main/java/com/facebook/presto/sql/analyzer/StatementAnalyzer.java b/presto-main/src/main/java/com/facebook/presto/sql/analyzer/StatementAnalyzer.java index dff69f7443e73..c6272c0e76f92 100644 --- a/presto-main/src/main/java/com/facebook/presto/sql/analyzer/StatementAnalyzer.java +++ b/presto-main/src/main/java/com/facebook/presto/sql/analyzer/StatementAnalyzer.java @@ -1260,9 +1260,6 @@ private void analyzeWindowFrame(WindowFrame frame) if ((startType == FOLLOWING) && (endType == CURRENT_ROW)) { throw new SemanticException(INVALID_WINDOW_FRAME, frame, "Window frame starting from FOLLOWING cannot end with CURRENT ROW"); } - if ((frame.getType() == RANGE) && ((startType == PRECEDING) || (endType == PRECEDING))) { - throw new SemanticException(INVALID_WINDOW_FRAME, frame, "Window frame RANGE PRECEDING is only supported with UNBOUNDED"); - } if ((frame.getType() == RANGE) && ((startType == FOLLOWING) || (endType == FOLLOWING))) { throw new SemanticException(INVALID_WINDOW_FRAME, frame, "Window frame RANGE FOLLOWING is only supported with UNBOUNDED"); } diff --git a/presto-main/src/test/java/com/facebook/presto/operator/window/TestAggregateWindowFunction.java b/presto-main/src/test/java/com/facebook/presto/operator/window/TestAggregateWindowFunction.java index 3dcaf6b6dd157..199d2efa3601a 100644 --- a/presto-main/src/test/java/com/facebook/presto/operator/window/TestAggregateWindowFunction.java +++ b/presto-main/src/test/java/com/facebook/presto/operator/window/TestAggregateWindowFunction.java @@ -532,4 +532,113 @@ public void testSumAllNulls() .row(null, null, null) .build()); } + + @Test + public void testSumRangePrecedingBounded() + { + assertWindowQueryWithNulls("sum(orderkey) OVER (ORDER BY orderstatus " + + "RANGE 2 PRECEDING)", + resultBuilder(TEST_SESSION, BIGINT, VARCHAR, BIGINT) + .row(3L, "F", 14L) + .row(5L, "F", 14L) + .row(6L, "F", 14L) + .row(null, "F", 14L) + .row(34L, "O", 48L) + .row(null, "O", 48L) + .row(1L, null, 56L) + .row(7L, null, 56L) + .row(null, null, 56L) + .row(null, null, 56L) + .build()); + + assertWindowQueryWithNulls("sum(orderkey) OVER (PARTITION BY orderstatus ORDER BY orderkey " + + "RANGE 2 PRECEDING)", + resultBuilder(TEST_SESSION, BIGINT, VARCHAR, BIGINT) + .row(3L, "F", 3L) + .row(5L, "F", 8L) + .row(6L, "F", 14L) + .row(null, "F", 11L) + .row(34L, "O", 34L) + .row(null, "O", 34L) + .row(1L, null, 1L) + .row(7L, null, 8L) + .row(null, null, 8L) + .row(null, null, 8L) + .build()); + + assertWindowQueryWithNulls("sum(orderkey) OVER (PARTITION BY orderstatus ORDER BY orderkey " + + "RANGE BETWEEN 2 PRECEDING AND 1 PRECEDING)", + resultBuilder(TEST_SESSION, INTEGER, VARCHAR, BIGINT) + .row(3L, "F", null) + .row(5L, "F", 3L) + .row(6L, "F", 8L) + .row(null, "F", 11L) + .row(34L, "O", null) + .row(null, "O", 34L) + .row(1L, null, null) + .row(7L, null, 1L) + .row(null, null, 8L) + .row(null, null, 8L) + .build()); + + assertWindowQueryWithNulls("sum(orderkey) OVER (PARTITION BY orderstatus ORDER BY orderkey " + + "RANGE BETWEEN UNBOUNDED PRECEDING AND 0 PRECEDING)", + resultBuilder(TEST_SESSION, INTEGER, VARCHAR, BIGINT) + .row(3L, "F", 3L) + .row(5L, "F", 8L) + .row(6L, "F", 14L) + .row(null, "F", 14L) + .row(34L, "O", 34L) + .row(null, "O", 34L) + .row(1L, null, 1L) + .row(7L, null, 8L) + .row(null, null, 8L) + .row(null, null, 8L) + .build()); + + assertWindowQueryWithNulls("sum(orderkey) OVER (PARTITION BY orderstatus ORDER BY orderkey " + + "RANGE BETWEEN UNBOUNDED PRECEDING AND 2 PRECEDING)", + resultBuilder(TEST_SESSION, INTEGER, VARCHAR, BIGINT) + .row(3L, "F", null) + .row(5L, "F", null) + .row(6L, "F", 3L) + .row(null, "F", 8L) + .row(34L, "O", null) + .row(null, "O", null) + .row(1L, null, null) + .row(7L, null, null) + .row(null, null, 1L) + .row(null, null, 1L) + .build()); + + assertWindowQueryWithNulls("sum(orderkey) OVER (PARTITION BY orderstatus ORDER BY orderkey " + + "RANGE BETWEEN UNBOUNDED PRECEDING AND 4 PRECEDING)", + resultBuilder(TEST_SESSION, INTEGER, VARCHAR, BIGINT) + .row(3L, "F", null) + .row(5L, "F", null) + .row(6L, "F", null) + .row(null, "F", null) + .row(34L, "O", null) + .row(null, "O", null) + .row(1L, null, null) + .row(7L, null, null) + .row(null, null, null) + .row(null, null, null) + .build()); + + assertWindowQueryWithNulls("sum(orderkey) OVER (PARTITION BY orderstatus ORDER BY orderkey " + + "RANGE BETWEEN 2 PRECEDING AND 3 PRECEDING)", + resultBuilder(TEST_SESSION, INTEGER, VARCHAR, BIGINT) + .row(3L, "F", null) + .row(5L, "F", null) + .row(6L, "F", null) + .row(null, "F", null) + .row(34L, "O", null) + .row(null, "O", null) + .row(1L, null, null) + .row(7L, null, null) + .row(null, null, null) + .row(null, null, null) + .build()); + } } diff --git a/presto-main/src/test/java/com/facebook/presto/operator/window/TestFirstValueFunction.java b/presto-main/src/test/java/com/facebook/presto/operator/window/TestFirstValueFunction.java index 12d665885a080..64dcd318301ef 100644 --- a/presto-main/src/test/java/com/facebook/presto/operator/window/TestFirstValueFunction.java +++ b/presto-main/src/test/java/com/facebook/presto/operator/window/TestFirstValueFunction.java @@ -128,5 +128,19 @@ public void testFirstValueBounded() .row(null, null, 1L) .row(null, null, 7L) .build()); + assertWindowQueryWithNulls("first_value(orderkey) OVER (PARTITION BY orderstatus ORDER BY orderkey " + + "RANGE BETWEEN 2 PRECEDING AND 1 PRECEDING)", + resultBuilder(TEST_SESSION, BIGINT, VARCHAR, BIGINT) + .row(3L, "F", null) + .row(5L, "F", 3L) + .row(6L, "F", 3L) + .row(null, "F", 5L) + .row(34L, "O", null) + .row(null, "O", 34L) + .row(1L, null, null) + .row(7L, null, 1L) + .row(null, null, 1L) + .row(null, null, 1L) + .build()); } } diff --git a/presto-main/src/test/java/com/facebook/presto/operator/window/TestLastValueFunction.java b/presto-main/src/test/java/com/facebook/presto/operator/window/TestLastValueFunction.java index 8a6818cfae05c..5274d76f12a79 100644 --- a/presto-main/src/test/java/com/facebook/presto/operator/window/TestLastValueFunction.java +++ b/presto-main/src/test/java/com/facebook/presto/operator/window/TestLastValueFunction.java @@ -127,5 +127,19 @@ public void testLastValueBounded() .row(null, null, null) .row(null, null, null) .build()); + assertWindowQueryWithNulls("last_value(orderkey) OVER (PARTITION BY orderstatus ORDER BY orderkey " + + "RANGE BETWEEN 2 PRECEDING AND 1 PRECEDING)", + resultBuilder(TEST_SESSION, BIGINT, VARCHAR, BIGINT) + .row(3L, "F", null) + .row(5L, "F", 3L) + .row(6L, "F", 5L) + .row(null, "F", 6L) + .row(34L, "O", null) + .row(null, "O", 34L) + .row(1L, null, null) + .row(7L, null, 1L) + .row(null, null, 7L) + .row(null, null, 7L) + .build()); } } diff --git a/presto-main/src/test/java/com/facebook/presto/operator/window/TestMultipleWindowSpecifications.java b/presto-main/src/test/java/com/facebook/presto/operator/window/TestMultipleWindowSpecifications.java index c208cd9cff74a..289575cf36ec3 100644 --- a/presto-main/src/test/java/com/facebook/presto/operator/window/TestMultipleWindowSpecifications.java +++ b/presto-main/src/test/java/com/facebook/presto/operator/window/TestMultipleWindowSpecifications.java @@ -217,4 +217,39 @@ public void testDisjointWindowSpecifications() .row(null, null, 2L, null) .build()); } + + @Test + public void testMultipleWindowSpecificationsWithRange() + { + // Intersection previous to current row + assertWindowQueryWithNulls("count(orderkey) OVER (PARTITION BY orderstatus ORDER BY orderkey RANGE BETWEEN 3 PRECEDING AND 2 PRECEDING), " + + "sum(orderkey) OVER (PARTITION BY orderstatus ORDER BY orderkey RANGE BETWEEN 2 PRECEDING AND CURRENT ROW)", + resultBuilder(TEST_SESSION, BIGINT, VARCHAR, BIGINT, BIGINT) + .row(3L, "F", 0L, 3L) + .row(5L, "F", 0L, 8L) + .row(6L, "F", 1L, 14L) + .row(null, "F", 2L, 11L) + .row(34L, "O", 0L, 34L) + .row(null, "O", 0L, 34L) + .row(1L, null, 0L, 1L) + .row(7L, null, 0L, 8L) + .row(null, null, 1L, 8L) + .row(null, null, 1L, 8L) + .build()); + // Disjoint + assertWindowQueryWithNulls("count(orderkey) OVER (PARTITION BY orderstatus ORDER BY orderkey RANGE BETWEEN 3 PRECEDING AND 2 PRECEDING), " + + "sum(orderkey) OVER (PARTITION BY orderstatus ORDER BY orderkey RANGE BETWEEN 1 PRECEDING AND CURRENT ROW)", + resultBuilder(TEST_SESSION, BIGINT, VARCHAR, BIGINT, BIGINT) + .row(3L, "F", 0L, 3L) + .row(5L, "F", 0L, 8L) + .row(6L, "F", 1L, 11L) + .row(null, "F", 2L, 6L) + .row(34L, "O", 0L, 34L) + .row(null, "O", 0L, 34L) + .row(1L, null, 0L, 1L) + .row(7L, null, 0L, 8L) + .row(null, null, 1L, 7L) + .row(null, null, 1L, 7L) + .build()); + } } diff --git a/presto-main/src/test/java/com/facebook/presto/operator/window/TestNthValueFunction.java b/presto-main/src/test/java/com/facebook/presto/operator/window/TestNthValueFunction.java index 748998afc0f1e..81dc6e98553d8 100644 --- a/presto-main/src/test/java/com/facebook/presto/operator/window/TestNthValueFunction.java +++ b/presto-main/src/test/java/com/facebook/presto/operator/window/TestNthValueFunction.java @@ -147,6 +147,20 @@ public void testNthValueBounded() .row(null, null, null) .build()); + assertWindowQueryWithNulls("nth_value(orderkey, 4) OVER (PARTITION BY orderstatus ORDER BY orderkey " + + "RANGE BETWEEN 2 PRECEDING AND 1 PRECEDING)", + resultBuilder(TEST_SESSION, BIGINT, VARCHAR, BIGINT) + .row(3L, "F", null) + .row(5L, "F", null) + .row(6L, "F", null) + .row(null, "F", null) + .row(34L, "O", null) + .row(null, "O", null) + .row(1L, null, null) + .row(7L, null, null) + .row(null, null, null) + .row(null, null, null) + .build()); // Timestamp assertWindowQuery("date_format(nth_value(cast(orderdate as TIMESTAMP), 2) OVER (PARTITION BY orderstatus ORDER BY orderkey), '%Y-%m-%d')", resultBuilder(TEST_SESSION, INTEGER, VARCHAR, VARCHAR) diff --git a/presto-main/src/test/java/com/facebook/presto/sql/analyzer/TestAnalyzer.java b/presto-main/src/test/java/com/facebook/presto/sql/analyzer/TestAnalyzer.java index f738d464cff41..189e21c397d11 100644 --- a/presto-main/src/test/java/com/facebook/presto/sql/analyzer/TestAnalyzer.java +++ b/presto-main/src/test/java/com/facebook/presto/sql/analyzer/TestAnalyzer.java @@ -645,8 +645,6 @@ public void testInvalidWindowFrame() assertFails(INVALID_WINDOW_FRAME, "SELECT rank() OVER (ROWS BETWEEN CURRENT ROW AND 5 PRECEDING)"); assertFails(INVALID_WINDOW_FRAME, "SELECT rank() OVER (ROWS BETWEEN 2 FOLLOWING AND 5 PRECEDING)"); assertFails(INVALID_WINDOW_FRAME, "SELECT rank() OVER (ROWS BETWEEN 2 FOLLOWING AND CURRENT ROW)"); - assertFails(INVALID_WINDOW_FRAME, "SELECT rank() OVER (RANGE 2 PRECEDING)"); - assertFails(INVALID_WINDOW_FRAME, "SELECT rank() OVER (RANGE BETWEEN 2 PRECEDING AND CURRENT ROW)"); assertFails(INVALID_WINDOW_FRAME, "SELECT rank() OVER (RANGE BETWEEN CURRENT ROW AND 5 FOLLOWING)"); assertFails(INVALID_WINDOW_FRAME, "SELECT rank() OVER (RANGE BETWEEN 2 PRECEDING AND 5 FOLLOWING)"); From 671c16d130441e08de7381d1a04432a35d679b22 Mon Sep 17 00:00:00 2001 From: anusudarsan Date: Tue, 6 Jun 2017 17:39:20 -0400 Subject: [PATCH 2/2] Implement RANGE bounded FOLLOWING window frame --- .../operator/window/WindowPartition.java | 35 ++++- .../sql/analyzer/StatementAnalyzer.java | 4 - .../window/TestAggregateWindowFunction.java | 124 ++++++++++++++++++ .../window/TestFirstValueFunction.java | 14 ++ .../window/TestLastValueFunction.java | 14 ++ .../TestMultipleWindowSpecifications.java | 44 +++---- .../operator/window/TestNthValueFunction.java | 8 +- .../presto/sql/analyzer/TestAnalyzer.java | 2 - 8 files changed, 212 insertions(+), 33 deletions(-) diff --git a/presto-main/src/main/java/com/facebook/presto/operator/window/WindowPartition.java b/presto-main/src/main/java/com/facebook/presto/operator/window/WindowPartition.java index 68bfd695e75eb..448faf9633f3b 100644 --- a/presto-main/src/main/java/com/facebook/presto/operator/window/WindowPartition.java +++ b/presto-main/src/main/java/com/facebook/presto/operator/window/WindowPartition.java @@ -160,7 +160,7 @@ private Range getFrameRange(FrameInfo frameInfo) return new Range(-1, -1); } - if (frameInfo.getType() == RANGE && emptyFrame(frameInfo, peerGroupStart, peerGroupEnd - 1)) { + if (frameInfo.getType() == RANGE && emptyFrame(frameInfo, peerGroupStart, endPosition - (peerGroupEnd - 1))) { return new Range(-1, -1); } @@ -174,6 +174,9 @@ private Range getFrameRange(FrameInfo frameInfo) else if (frameInfo.getType() == RANGE && frameInfo.getStartType() == PRECEDING) { frameStart = precedingStartRange(getStartValue(frameInfo)); } + else if (frameInfo.getType() == RANGE && frameInfo.getStartType() == FOLLOWING) { + frameStart = followingRange(rowPosition, endPosition, getStartValue(frameInfo)); + } else if (frameInfo.getStartType() == PRECEDING) { frameStart = preceding(rowPosition, getStartValue(frameInfo)); } @@ -194,6 +197,9 @@ else if (frameInfo.getType() == RANGE) { else if (frameInfo.getType() == RANGE && frameInfo.getEndType() == PRECEDING) { frameEnd = precedingEndRange(getEndValue(frameInfo)); } + else if (frameInfo.getType() == RANGE && frameInfo.getEndType() == FOLLOWING) { + frameEnd = followingRange(peerGroupEnd, endPosition + 1, getEndValue(frameInfo)) - 1; + } else if (frameInfo.getEndType() == PRECEDING) { frameEnd = preceding(rowPosition, getEndValue(frameInfo)); } @@ -228,6 +234,33 @@ private int precedingStartRange(long startValue) return peerGroupStartIndices.get(toIntExact(peerGroupStartIndex - startValue)); } + private int followingRange(int followingPeerGroupStart, int endPosition, long value) + { + if (value == 0) { + return followingPeerGroupStart; + } + // TODO: Optimize this to *not* look for peers often, probably have pageIndex keep the peer groups + int followingPeerGroupEnd = 0; + int currentValue = 0; + while (currentValue < value) { + boolean peerFound = false; + followingPeerGroupEnd = followingPeerGroupStart + 1; + while ((followingPeerGroupEnd < partitionEnd) && pagesIndex.positionEqualsPosition(peerGroupHashStrategy, followingPeerGroupStart, followingPeerGroupEnd)) { + followingPeerGroupEnd++; + peerFound = true; + } + if (followingPeerGroupEnd >= partitionEnd) { + return endPosition; + } + + if (!peerFound) { + currentValue++; + } + followingPeerGroupStart++; + } + return followingPeerGroupEnd; + } + private boolean emptyFrame(FrameInfo frameInfo, int rowPosition, int position) { FrameBound.Type startType = frameInfo.getStartType(); diff --git a/presto-main/src/main/java/com/facebook/presto/sql/analyzer/StatementAnalyzer.java b/presto-main/src/main/java/com/facebook/presto/sql/analyzer/StatementAnalyzer.java index c6272c0e76f92..034a56098ad87 100644 --- a/presto-main/src/main/java/com/facebook/presto/sql/analyzer/StatementAnalyzer.java +++ b/presto-main/src/main/java/com/facebook/presto/sql/analyzer/StatementAnalyzer.java @@ -187,7 +187,6 @@ import static com.facebook.presto.sql.tree.FrameBound.Type.PRECEDING; import static com.facebook.presto.sql.tree.FrameBound.Type.UNBOUNDED_FOLLOWING; import static com.facebook.presto.sql.tree.FrameBound.Type.UNBOUNDED_PRECEDING; -import static com.facebook.presto.sql.tree.WindowFrame.Type.RANGE; import static com.facebook.presto.type.UnknownType.UNKNOWN; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkState; @@ -1260,9 +1259,6 @@ private void analyzeWindowFrame(WindowFrame frame) if ((startType == FOLLOWING) && (endType == CURRENT_ROW)) { throw new SemanticException(INVALID_WINDOW_FRAME, frame, "Window frame starting from FOLLOWING cannot end with CURRENT ROW"); } - if ((frame.getType() == RANGE) && ((startType == FOLLOWING) || (endType == FOLLOWING))) { - throw new SemanticException(INVALID_WINDOW_FRAME, frame, "Window frame RANGE FOLLOWING is only supported with UNBOUNDED"); - } } private void analyzeHaving(QuerySpecification node, Scope scope) diff --git a/presto-main/src/test/java/com/facebook/presto/operator/window/TestAggregateWindowFunction.java b/presto-main/src/test/java/com/facebook/presto/operator/window/TestAggregateWindowFunction.java index 199d2efa3601a..028a34b18eaf0 100644 --- a/presto-main/src/test/java/com/facebook/presto/operator/window/TestAggregateWindowFunction.java +++ b/presto-main/src/test/java/com/facebook/presto/operator/window/TestAggregateWindowFunction.java @@ -641,4 +641,128 @@ public void testSumRangePrecedingBounded() .row(null, null, null) .build()); } + + @Test + public void testSumRangeFollowingBounded() + { + assertWindowQueryWithNulls("sum(orderkey) OVER (ORDER BY orderstatus " + + "RANGE BETWEEN current row AND 1 FOLLOWING)", + resultBuilder(TEST_SESSION, BIGINT, VARCHAR, BIGINT) + .row(3L, "F", 48L) + .row(5L, "F", 48L) + .row(6L, "F", 48L) + .row(null, "F", 48L) + .row(34L, "O", 42L) + .row(null, "O", 42L) + .row(1L, null, 8L) + .row(7L, null, 8L) + .row(null, null, 8L) + .row(null, null, 8L) + .build()); + + assertWindowQueryWithNulls("sum(orderkey) OVER (PARTITION BY orderstatus ORDER BY orderkey " + + "RANGE BETWEEN current row AND 1 FOLLOWING)", + resultBuilder(TEST_SESSION, BIGINT, VARCHAR, BIGINT) + .row(3L, "F", 8L) + .row(5L, "F", 11L) + .row(6L, "F", 6L) + .row(null, "F", null) + .row(34L, "O", 34L) + .row(null, "O", null) + .row(1L, null, 8L) + .row(7L, null, 7L) + .row(null, null, null) + .row(null, null, null) + .build()); + + assertWindowQueryWithNulls("sum(orderkey) OVER (PARTITION BY orderstatus ORDER BY orderkey " + + "RANGE BETWEEN current row AND 0 FOLLOWING)", + resultBuilder(TEST_SESSION, BIGINT, VARCHAR, BIGINT) + .row(3L, "F", 3L) + .row(5L, "F", 5L) + .row(6L, "F", 6L) + .row(null, "F", null) + .row(34L, "O", 34L) + .row(null, "O", null) + .row(1L, null, 1L) + .row(7L, null, 7L) + .row(null, null, null) + .row(null, null, null) + .build()); + + assertWindowQueryWithNulls("sum(orderkey) OVER (PARTITION BY orderstatus ORDER BY orderkey " + + "RANGE BETWEEN 1 FOLLOWING AND 2 FOLLOWING)", + resultBuilder(TEST_SESSION, INTEGER, VARCHAR, BIGINT) + .row(3L, "F", 11L) + .row(5L, "F", 6L) + .row(6L, "F", null) + .row(null, "F", null) + .row(34L, "O", null) + .row(null, "O", null) + .row(1L, null, 7L) + .row(7L, null, null) + .row(null, null, null) + .row(null, null, null) + .build()); + + assertWindowQueryWithNulls("sum(orderkey) OVER (PARTITION BY orderstatus ORDER BY orderkey " + + "RANGE BETWEEN 0 FOLLOWING AND UNBOUNDED FOLLOWING)", + resultBuilder(TEST_SESSION, INTEGER, VARCHAR, BIGINT) + .row(3L, "F", 14L) + .row(5L, "F", 11L) + .row(6L, "F", 6L) + .row(null, "F", null) + .row(34L, "O", 34L) + .row(null, "O", null) + .row(1L, null, 8L) + .row(7L, null, 7L) + .row(null, null, null) + .row(null, null, null) + .build()); + + assertWindowQueryWithNulls("sum(orderkey) OVER (PARTITION BY orderstatus ORDER BY orderkey " + + "RANGE BETWEEN 2 FOLLOWING AND UNBOUNDED FOLLOWING)", + resultBuilder(TEST_SESSION, INTEGER, VARCHAR, BIGINT) + .row(3L, "F", 6L) + .row(5L, "F", null) + .row(6L, "F", null) + .row(null, "F", null) + .row(34L, "O", null) + .row(null, "O", null) + .row(1L, null, null) + .row(7L, null, null) + .row(null, null, null) + .row(null, null, null) + .build()); + + assertWindowQueryWithNulls("sum(orderkey) OVER (PARTITION BY orderstatus ORDER BY orderkey " + + "RANGE BETWEEN 4 FOLLOWING AND UNBOUNDED FOLLOWING)", + resultBuilder(TEST_SESSION, INTEGER, VARCHAR, BIGINT) + .row(3L, "F", null) + .row(5L, "F", null) + .row(6L, "F", null) + .row(null, "F", null) + .row(34L, "O", null) + .row(null, "O", null) + .row(1L, null, null) + .row(7L, null, null) + .row(null, null, null) + .row(null, null, null) + .build()); + + assertWindowQueryWithNulls("sum(orderkey) OVER (PARTITION BY orderstatus ORDER BY orderkey " + + "RANGE BETWEEN 3 FOLLOWING AND 2 FOLLOWING)", + resultBuilder(TEST_SESSION, INTEGER, VARCHAR, BIGINT) + .row(3L, "F", null) + .row(5L, "F", null) + .row(6L, "F", null) + .row(null, "F", null) + .row(34L, "O", null) + .row(null, "O", null) + .row(1L, null, null) + .row(7L, null, null) + .row(null, null, null) + .row(null, null, null) + .build()); + } } diff --git a/presto-main/src/test/java/com/facebook/presto/operator/window/TestFirstValueFunction.java b/presto-main/src/test/java/com/facebook/presto/operator/window/TestFirstValueFunction.java index 64dcd318301ef..2c2ab5bb08f7c 100644 --- a/presto-main/src/test/java/com/facebook/presto/operator/window/TestFirstValueFunction.java +++ b/presto-main/src/test/java/com/facebook/presto/operator/window/TestFirstValueFunction.java @@ -142,5 +142,19 @@ public void testFirstValueBounded() .row(null, null, 1L) .row(null, null, 1L) .build()); + assertWindowQueryWithNulls("first_value(orderkey) OVER (PARTITION BY orderstatus ORDER BY orderkey " + + "RANGE BETWEEN 2 PRECEDING AND 2 FOLLOWING)", + resultBuilder(TEST_SESSION, BIGINT, VARCHAR, BIGINT) + .row(3L, "F", 3L) + .row(5L, "F", 3L) + .row(6L, "F", 3L) + .row(null, "F", 5L) + .row(34L, "O", 34L) + .row(null, "O", 34L) + .row(1L, null, 1L) + .row(7L, null, 1L) + .row(null, null, 1L) + .row(null, null, 1L) + .build()); } } diff --git a/presto-main/src/test/java/com/facebook/presto/operator/window/TestLastValueFunction.java b/presto-main/src/test/java/com/facebook/presto/operator/window/TestLastValueFunction.java index 5274d76f12a79..57cbcdc8b9dd3 100644 --- a/presto-main/src/test/java/com/facebook/presto/operator/window/TestLastValueFunction.java +++ b/presto-main/src/test/java/com/facebook/presto/operator/window/TestLastValueFunction.java @@ -141,5 +141,19 @@ public void testLastValueBounded() .row(null, null, 7L) .row(null, null, 7L) .build()); + assertWindowQueryWithNulls("last_value(orderkey) OVER (PARTITION BY orderstatus ORDER BY orderkey " + + "RANGE BETWEEN 2 PRECEDING AND 1 FOLLOWING)", + resultBuilder(TEST_SESSION, BIGINT, VARCHAR, BIGINT) + .row(3L, "F", 5L) + .row(5L, "F", 6L) + .row(6L, "F", null) + .row(null, "F", null) + .row(34L, "O", null) + .row(null, "O", null) + .row(1L, null, 7L) + .row(7L, null, null) + .row(null, null, null) + .row(null, null, null) + .build()); } } diff --git a/presto-main/src/test/java/com/facebook/presto/operator/window/TestMultipleWindowSpecifications.java b/presto-main/src/test/java/com/facebook/presto/operator/window/TestMultipleWindowSpecifications.java index 289575cf36ec3..fafbad350ada4 100644 --- a/presto-main/src/test/java/com/facebook/presto/operator/window/TestMultipleWindowSpecifications.java +++ b/presto-main/src/test/java/com/facebook/presto/operator/window/TestMultipleWindowSpecifications.java @@ -222,34 +222,34 @@ public void testDisjointWindowSpecifications() public void testMultipleWindowSpecificationsWithRange() { // Intersection previous to current row - assertWindowQueryWithNulls("count(orderkey) OVER (PARTITION BY orderstatus ORDER BY orderkey RANGE BETWEEN 3 PRECEDING AND 2 PRECEDING), " + + assertWindowQueryWithNulls("count(orderkey) OVER (PARTITION BY orderstatus ORDER BY orderkey RANGE BETWEEN 3 PRECEDING AND 1 FOLLOWING), " + "sum(orderkey) OVER (PARTITION BY orderstatus ORDER BY orderkey RANGE BETWEEN 2 PRECEDING AND CURRENT ROW)", resultBuilder(TEST_SESSION, BIGINT, VARCHAR, BIGINT, BIGINT) - .row(3L, "F", 0L, 3L) - .row(5L, "F", 0L, 8L) - .row(6L, "F", 1L, 14L) - .row(null, "F", 2L, 11L) - .row(34L, "O", 0L, 34L) - .row(null, "O", 0L, 34L) - .row(1L, null, 0L, 1L) - .row(7L, null, 0L, 8L) - .row(null, null, 1L, 8L) - .row(null, null, 1L, 8L) + .row(3L, "F", 2L, 3L) + .row(5L, "F", 3L, 8L) + .row(6L, "F", 3L, 14L) + .row(null, "F", 3L, 11L) + .row(34L, "O", 1L, 34L) + .row(null, "O", 1L, 34L) + .row(1L, null, 2L, 1L) + .row(7L, null, 2L, 8L) + .row(null, null, 2L, 8L) + .row(null, null, 2L, 8L) .build()); // Disjoint - assertWindowQueryWithNulls("count(orderkey) OVER (PARTITION BY orderstatus ORDER BY orderkey RANGE BETWEEN 3 PRECEDING AND 2 PRECEDING), " + + assertWindowQueryWithNulls("count(orderkey) OVER (PARTITION BY orderstatus ORDER BY orderkey RANGE BETWEEN 3 PRECEDING AND 2 FOLLOWING), " + "sum(orderkey) OVER (PARTITION BY orderstatus ORDER BY orderkey RANGE BETWEEN 1 PRECEDING AND CURRENT ROW)", resultBuilder(TEST_SESSION, BIGINT, VARCHAR, BIGINT, BIGINT) - .row(3L, "F", 0L, 3L) - .row(5L, "F", 0L, 8L) - .row(6L, "F", 1L, 11L) - .row(null, "F", 2L, 6L) - .row(34L, "O", 0L, 34L) - .row(null, "O", 0L, 34L) - .row(1L, null, 0L, 1L) - .row(7L, null, 0L, 8L) - .row(null, null, 1L, 7L) - .row(null, null, 1L, 7L) + .row(3L, "F", 3L, 3L) + .row(5L, "F", 3L, 8L) + .row(6L, "F", 3L, 11L) + .row(null, "F", 3L, 6L) + .row(34L, "O", 1L, 34L) + .row(null, "O", 1L, 34L) + .row(1L, null, 2L, 1L) + .row(7L, null, 2L, 8L) + .row(null, null, 2L, 7L) + .row(null, null, 2L, 7L) .build()); } } diff --git a/presto-main/src/test/java/com/facebook/presto/operator/window/TestNthValueFunction.java b/presto-main/src/test/java/com/facebook/presto/operator/window/TestNthValueFunction.java index 81dc6e98553d8..90df84df2b702 100644 --- a/presto-main/src/test/java/com/facebook/presto/operator/window/TestNthValueFunction.java +++ b/presto-main/src/test/java/com/facebook/presto/operator/window/TestNthValueFunction.java @@ -147,12 +147,12 @@ public void testNthValueBounded() .row(null, null, null) .build()); - assertWindowQueryWithNulls("nth_value(orderkey, 4) OVER (PARTITION BY orderstatus ORDER BY orderkey " + - "RANGE BETWEEN 2 PRECEDING AND 1 PRECEDING)", + assertWindowQueryWithNulls("nth_value(orderkey, 3) OVER (PARTITION BY orderstatus ORDER BY orderkey " + + "RANGE BETWEEN 2 PRECEDING AND 1 FOLLOWING)", resultBuilder(TEST_SESSION, BIGINT, VARCHAR, BIGINT) .row(3L, "F", null) - .row(5L, "F", null) - .row(6L, "F", null) + .row(5L, "F", 6L) + .row(6L, "F", 6L) .row(null, "F", null) .row(34L, "O", null) .row(null, "O", null) diff --git a/presto-main/src/test/java/com/facebook/presto/sql/analyzer/TestAnalyzer.java b/presto-main/src/test/java/com/facebook/presto/sql/analyzer/TestAnalyzer.java index 189e21c397d11..6b4445d3c72be 100644 --- a/presto-main/src/test/java/com/facebook/presto/sql/analyzer/TestAnalyzer.java +++ b/presto-main/src/test/java/com/facebook/presto/sql/analyzer/TestAnalyzer.java @@ -645,8 +645,6 @@ public void testInvalidWindowFrame() assertFails(INVALID_WINDOW_FRAME, "SELECT rank() OVER (ROWS BETWEEN CURRENT ROW AND 5 PRECEDING)"); assertFails(INVALID_WINDOW_FRAME, "SELECT rank() OVER (ROWS BETWEEN 2 FOLLOWING AND 5 PRECEDING)"); assertFails(INVALID_WINDOW_FRAME, "SELECT rank() OVER (ROWS BETWEEN 2 FOLLOWING AND CURRENT ROW)"); - assertFails(INVALID_WINDOW_FRAME, "SELECT rank() OVER (RANGE BETWEEN CURRENT ROW AND 5 FOLLOWING)"); - assertFails(INVALID_WINDOW_FRAME, "SELECT rank() OVER (RANGE BETWEEN 2 PRECEDING AND 5 FOLLOWING)"); assertFails(TYPE_MISMATCH, "SELECT rank() OVER (ROWS 0.5 PRECEDING)"); assertFails(TYPE_MISMATCH, "SELECT rank() OVER (ROWS 'foo' PRECEDING)");