You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2020/12/27 12:53:55 UTC

[groovy] branch master updated: Support window function `rank` and `denseRank`

This is an automated email from the ASF dual-hosted git repository.

sunlan pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git


The following commit(s) were added to refs/heads/master by this push:
     new a81acc1  Support window function `rank` and `denseRank`
a81acc1 is described below

commit a81acc1ac1d4e45bd8f61ccf081e9c23ff7c0ad0
Author: Daniel Sun <su...@apache.org>
AuthorDate: Sun Dec 27 20:52:10 2020 +0800

    Support window function `rank` and `denseRank`
---
 .../ginq/provider/collection/GinqAstWalker.groovy  |  6 ++--
 .../ginq/provider/collection/runtime/Window.java   | 16 +++++++++
 .../provider/collection/runtime/WindowImpl.java    | 39 ++++++++++++++++++++--
 .../groovy-ginq/src/spec/doc/ginq-userguide.adoc   | 19 +++++++++--
 .../test/org/apache/groovy/ginq/GinqTest.groovy    | 16 +++++++++
 5 files changed, 90 insertions(+), 6 deletions(-)

diff --git a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/GinqAstWalker.groovy b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/GinqAstWalker.groovy
index 30347b5..ca309a5 100644
--- a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/GinqAstWalker.groovy
+++ b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/GinqAstWalker.groovy
@@ -678,7 +678,7 @@ class GinqAstWalker implements GinqAstVisitor<Expression>, SyntaxErrorReportable
                             if (windowFunctionMethodCallExpression.methodAsString in WINDOW_FUNCTION_LIST) {
                                 def argumentListExpression = (ArgumentListExpression) windowFunctionMethodCallExpression.arguments
                                 List<Expression> argumentExpressionList = []
-                                if (windowFunctionMethodCallExpression.methodAsString !in [FUNCTION_ROW_NUMBER]) {
+                                if (windowFunctionMethodCallExpression.methodAsString !in [FUNCTION_ROW_NUMBER, FUNCTION_RANK, FUNCTION_DENSE_RANK]) {
                                     def windowFunctionLambdaCode = argumentListExpression.getExpression(0)
                                     def windowFunctionLambdaName = '__wfp'
                                     def rootObjectExpression = findRootObjectExpression(windowFunctionLambdaCode)
@@ -1349,8 +1349,10 @@ class GinqAstWalker implements GinqAstVisitor<Expression>, SyntaxErrorReportable
     private static final String FUNCTION_LAG = 'lag'
     private static final String FUNCTION_FIRST_VALUE = 'firstValue'
     private static final String FUNCTION_LAST_VALUE = 'lastValue'
+    private static final String FUNCTION_RANK = 'rank'
+    private static final String FUNCTION_DENSE_RANK = 'denseRank'
     private static final List<String> WINDOW_FUNCTION_LIST = [FUNCTION_COUNT, FUNCTION_MIN, FUNCTION_MAX, FUNCTION_SUM, FUNCTION_AVG, FUNCTION_MEDIAN,
-                                                              FUNCTION_ROW_NUMBER, FUNCTION_LEAD, FUNCTION_LAG, FUNCTION_FIRST_VALUE, FUNCTION_LAST_VALUE]
+                                                              FUNCTION_ROW_NUMBER, FUNCTION_LEAD, FUNCTION_LAG, FUNCTION_FIRST_VALUE, FUNCTION_LAST_VALUE, FUNCTION_RANK, FUNCTION_DENSE_RANK]
 
     private static final String NAMEDRECORD_CLASS_NAME = NamedRecord.class.name
 
diff --git a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/Window.java b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/Window.java
index 7df13e9..3e683f2 100644
--- a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/Window.java
+++ b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/Window.java
@@ -128,4 +128,20 @@ public interface Window<T> extends Queryable<T> {
      * @since 4.0.0
      */
     <V> V lastValue(Function<? super T, ? extends V> extractor);
+
+    /**
+     * Returns the rank in the window
+     *
+     * @return the rank
+     * @since 4.0.0
+     */
+    long rank();
+
+    /**
+     * Returns the dense rank in the window
+     *
+     * @return the dense rank
+     * @since 4.0.0
+     */
+    long denseRank();
 }
diff --git a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/WindowImpl.java b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/WindowImpl.java
index 536e06f..27afed2 100644
--- a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/WindowImpl.java
+++ b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/WindowImpl.java
@@ -32,8 +32,9 @@ class WindowImpl<T, U extends Comparable<? super U>> extends QueryableCollection
     private static final long serialVersionUID = -3458969297047398621L;
     private final T currentRecord;
     private final long index;
-    private final U value;
     private final WindowDefinition<T, U> windowDefinition;
+    private final U value;
+    private final Function<? super T, ? extends U> keyExtractor;
 
     WindowImpl(T currentRecord, Queryable<T> partition, WindowDefinition<T, U> windowDefinition) {
         super(partition.orderBy(windowDefinition.orderBy().toArray(Order.EMPTY_ARRAY)).toList());
@@ -43,8 +44,10 @@ class WindowImpl<T, U extends Comparable<? super U>> extends QueryableCollection
         List<T> sortedList = this.toList();
         final List<Order<? super T, ? extends U>> order = windowDefinition.orderBy();
         if (null != order && 1 == order.size()) {
-            this.value = order.get(0).getKeyExtractor().apply(currentRecord);
+            this.keyExtractor = order.get(0).getKeyExtractor();
+            this.value = keyExtractor.apply(currentRecord);
         } else {
+            this.keyExtractor = null;
             this.value = null;
         }
 
@@ -111,6 +114,38 @@ class WindowImpl<T, U extends Comparable<? super U>> extends QueryableCollection
         return extractor.apply(this.toList().get(resultIndex));
     }
 
+    @Override
+    public long rank() {
+        long result = 1L;
+        if (null == value || null == keyExtractor) {
+            return -1;
+        }
+        for (T t : this.toList()) {
+            U v = keyExtractor.apply(t);
+            if (value.compareTo(v) > 0) {
+                result++;
+            }
+        }
+        return result;
+    }
+
+    @Override
+    public long denseRank() {
+        long result = 1L;
+        if (null == value || null == keyExtractor) {
+            return -1;
+        }
+        U latestV = null;
+        for (T t : this.toList()) {
+            U v = keyExtractor.apply(t);
+            if (null != v && value.compareTo(v) > 0 && (null == latestV || v.compareTo(latestV) != 0)) {
+                result++;
+            }
+            latestV = v;
+        }
+        return result;
+    }
+
     private long getFirstIndex() {
         RowBound rowBound = windowDefinition.rows();
         long firstRowIndex;
diff --git a/subprojects/groovy-ginq/src/spec/doc/ginq-userguide.adoc b/subprojects/groovy-ginq/src/spec/doc/ginq-userguide.adoc
index 46631cf..8e5fba1 100644
--- a/subprojects/groovy-ginq/src/spec/doc/ginq-userguide.adoc
+++ b/subprojects/groovy-ginq/src/spec/doc/ginq-userguide.adoc
@@ -410,8 +410,17 @@ include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_nested_04,inde
 ----
 
 ==== Window Functions
-GINQ supports some built-in window functions, e.g.
-`rowNumber`, `lead`, `lag`, `firstValue`, `lastValue`, `min`, `max`, `count`, `sum`, `avg`, `median`, etc.
+
+Window can be defined by `partitionby`, `orderby` and `rows`:
+```sql
+over(
+    [partitionby <expression> (, <expression>)*]
+    [orderby <expression> (, <expression>)*]
+    [rows <expression>, <expression>]
+)
+```
+Also, GINQ supports some built-in window functions, e.g.
+`rowNumber`, `rank`, `denseRank`, `lead`, `lag`, `firstValue`, `lastValue`, `min`, `max`, `count`, `sum`, `avg`, `median`, etc.
 
 ===== `rowNumber`
 [source, groovy]
@@ -421,6 +430,12 @@ include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_winfunction_24
 [NOTE]
 The parentheses around the window function is required.
 
+===== `rank` and `denseRank`
+[source, groovy]
+----
+include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_winfunction_25,indent=0]
+----
+
 ===== `lead` and `lag`
 [source, groovy]
 ----
diff --git a/subprojects/groovy-ginq/src/spec/test/org/apache/groovy/ginq/GinqTest.groovy b/subprojects/groovy-ginq/src/spec/test/org/apache/groovy/ginq/GinqTest.groovy
index 3e6f7f98..2b44b17 100644
--- a/subprojects/groovy-ginq/src/spec/test/org/apache/groovy/ginq/GinqTest.groovy
+++ b/subprojects/groovy-ginq/src/spec/test/org/apache/groovy/ginq/GinqTest.groovy
@@ -5159,6 +5159,22 @@ class GinqTest {
         '''
     }
 
+    @Test
+    void "testGinq - window - 44"() {
+        assertGinqScript '''
+// tag::ginq_winfunction_25[]
+            assert [['a', 1, 1], ['b', 2, 2], ['b', 2, 2], 
+                    ['c', 4, 3], ['c', 4, 3], ['d', 6, 4], 
+                    ['e', 7, 5]] == GQ {
+                from s in ['a', 'b', 'b', 'c', 'c', 'd', 'e']
+                select s, 
+                    (rank() over(orderby s)),
+                    (denseRank() over(orderby s))
+            }.toList()
+// end::ginq_winfunction_25[]
+        '''
+    }
+
     private static void assertGinqScript(String script) {
         String deoptimizedScript = script.replaceAll(/\bGQ\s*[{]/, 'GQ(optimize:false) {')
         List<String> scriptList = [deoptimizedScript, script]