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]