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 b2b36c2  Support `range` of window in GINQ
b2b36c2 is described below

commit b2b36c203de0f44a6084a0746dacd5a5563a31f0
Author: Daniel Sun <[email protected]>
AuthorDate: Fri Jan 1 21:35:49 2021 +0800

    Support `range` of window in GINQ
---
 .../ginq/provider/collection/GinqAstWalker.groovy  |  16 +-
 .../runtime/{ValueBound.java => ReversedList.java} |  30 ++--
 .../provider/collection/runtime/ValueBound.java    |   4 +-
 .../collection/runtime/WindowDefinition.java       |  18 +-
 .../collection/runtime/WindowDefinitionImpl.java   |  13 +-
 .../provider/collection/runtime/WindowImpl.java    | 114 +++++++++++--
 .../groovy-ginq/src/spec/doc/ginq-userguide.adoc   |  51 ++++--
 .../test/org/apache/groovy/ginq/GinqTest.groovy    | 190 +++++++++++++++++++++
 8 files changed, 389 insertions(+), 47 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 8f71520..25f0e73 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
@@ -41,6 +41,7 @@ import 
org.apache.groovy.ginq.provider.collection.runtime.NamedRecord
 import org.apache.groovy.ginq.provider.collection.runtime.Queryable
 import org.apache.groovy.ginq.provider.collection.runtime.QueryableHelper
 import org.apache.groovy.ginq.provider.collection.runtime.RowBound
+import org.apache.groovy.ginq.provider.collection.runtime.ValueBound
 import org.apache.groovy.ginq.provider.collection.runtime.WindowDefinition
 import org.apache.groovy.util.Maps
 import org.codehaus.groovy.GroovyBugError
@@ -791,6 +792,7 @@ class GinqAstWalker implements GinqAstVisitor<Expression>, 
SyntaxErrorReportable
         Expression classifierExpr = null
         Expression orderExpr = null
         Expression rowsExpr = null
+        Expression rangeExpr = null
         ArgumentListExpression argumentListExpression = 
(ArgumentListExpression) methodCallExpression.arguments
         if (1 == argumentListExpression.getExpressions().size()) {
             final List<MethodCallExpression> ignoredMethodCallExpressionList = 
[]
@@ -805,13 +807,15 @@ class GinqAstWalker implements 
GinqAstVisitor<Expression>, SyntaxErrorReportable
                         orderExpr = call.arguments
                     } else if ('rows' == call.methodAsString) {
                         rowsExpr = call.arguments
+                    } else if ('range' == call.methodAsString) {
+                        rangeExpr = call.arguments
                     } else {
                         ignoredMethodCallExpressionList << call
                     }
                 }
             })
 
-            validateWindowClause(classifierExpr, orderExpr, rowsExpr, 
ignoredMethodCallExpressionList)
+            validateWindowClause(classifierExpr, orderExpr, rowsExpr, 
rangeExpr, ignoredMethodCallExpressionList)
         }
 
         def argumentExpressionList = []
@@ -832,6 +836,11 @@ class GinqAstWalker implements GinqAstVisitor<Expression>, 
SyntaxErrorReportable
             argumentExpressionList << rowBoundCtorCallExpression
         }
 
+        if (rangeExpr) {
+            def valueBoundCtorCallExpression = ctorX(VALUEBOUND_TYPE, 
rangeExpr)
+            argumentExpressionList << valueBoundCtorCallExpression
+        }
+
         callX(
                 callX(new ClassExpression(WINDOW_DEFINITION_TYPE), 'of', 
args(argumentExpressionList)),
                 'setId',
@@ -839,8 +848,8 @@ class GinqAstWalker implements GinqAstVisitor<Expression>, 
SyntaxErrorReportable
         )
     }
 
-    private void validateWindowClause(Expression classifierExpr, Expression 
orderExpr, Expression rowsExpr, List<? extends MethodCallExpression> 
ignoredMethodCallExpressionList) {
-        new ListExpression(Arrays.asList(classifierExpr, orderExpr, 
rowsExpr).grep(e -> null != e)).visit(new GinqAstBaseVisitor() {
+    private void validateWindowClause(Expression classifierExpr, Expression 
orderExpr, Expression rowsExpr, Expression rangeExpr, List<? extends 
MethodCallExpression> ignoredMethodCallExpressionList) {
+        new ListExpression(Arrays.asList(classifierExpr, orderExpr, rowsExpr, 
rangeExpr).grep(e -> null != e)).visit(new GinqAstBaseVisitor() {
             @Override
             void visitMethodCallExpression(MethodCallExpression call) {
                 ignoredMethodCallExpressionList.remove(call)
@@ -1403,6 +1412,7 @@ class GinqAstWalker implements 
GinqAstVisitor<Expression>, SyntaxErrorReportable
     private static final ClassNode QUERYABLE_HELPER_TYPE = 
makeWithoutCaching(QueryableHelper.class)
     private static final ClassNode WINDOW_DEFINITION_TYPE = 
makeWithoutCaching(WindowDefinition.class)
     private static final ClassNode ROWBOUND_TYPE = makeCached(RowBound.class)
+    private static final ClassNode VALUEBOUND_TYPE = 
makeWithoutCaching(ValueBound.class)
     private static final ClassNode ATOMIC_LONG_TYPE = 
makeCached(AtomicLong.class)
     private static final ClassNode TUPLE_TYPE = makeWithoutCaching(Tuple.class)
 
diff --git 
a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/ValueBound.java
 
b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/ReversedList.java
similarity index 64%
copy from 
subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/ValueBound.java
copy to 
subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/ReversedList.java
index 10dfffc..b725a9a 100644
--- 
a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/ValueBound.java
+++ 
b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/ReversedList.java
@@ -18,22 +18,28 @@
  */
 package org.apache.groovy.ginq.provider.collection.runtime;
 
+import java.util.AbstractList;
+import java.util.List;
+
 /**
- * Represents value bounds of window frame
+ * Represents view of reversed list
  *
  * @since 4.0.0
  */
-public class ValueBound<L extends Comparable<? super L>, U extends 
Comparable<? super U>> extends AbstractBound<L, U> {
-    private static final long serialVersionUID = -8240086260919353012L;
+class ReversedList<E> extends AbstractList<E> {
+    private final List<E> delegate;
+
+    ReversedList(List<E> list) {
+        this.delegate = list;
+    }
+
+    @Override
+    public E get(int index) {
+        return delegate.get(delegate.size() - 1 - index);
+    }
 
-    /**
-     * Construct a new ValueBound instance with lower and upper frame bounds
-     *
-     * @param lower the lower frame bound
-     * @param upper the upper frame bound
-     * @since 4.0.0
-     */
-    public ValueBound(L lower, U upper) {
-        super(lower, upper);
+    @Override
+    public int size() {
+        return delegate.size();
     }
 }
diff --git 
a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/ValueBound.java
 
b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/ValueBound.java
index 10dfffc..e981499 100644
--- 
a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/ValueBound.java
+++ 
b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/ValueBound.java
@@ -23,7 +23,7 @@ package org.apache.groovy.ginq.provider.collection.runtime;
  *
  * @since 4.0.0
  */
-public class ValueBound<L extends Comparable<? super L>, U extends 
Comparable<? super U>> extends AbstractBound<L, U> {
+public class ValueBound<U extends Comparable<? super U>> extends 
AbstractBound<U, U> {
     private static final long serialVersionUID = -8240086260919353012L;
 
     /**
@@ -33,7 +33,7 @@ public class ValueBound<L extends Comparable<? super L>, U 
extends Comparable<?
      * @param upper the upper frame bound
      * @since 4.0.0
      */
-    public ValueBound(L lower, U upper) {
+    public ValueBound(U lower, U upper) {
         super(lower, upper);
     }
 }
diff --git 
a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/WindowDefinition.java
 
b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/WindowDefinition.java
index b975629..9d88288 100644
--- 
a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/WindowDefinition.java
+++ 
b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/WindowDefinition.java
@@ -136,11 +136,25 @@ public interface WindowDefinition<T, U extends 
Comparable<? super U>> {
      * @return the {@link WindowDefinition} instance
      * @since 4.0.0
      */
-    static <T, U extends Comparable<? super U>> WindowDefinition<T, U> 
of(Function<? super T, ?> partitionBy, List<Queryable.Order<? super T, ? 
extends U>> orderBy, ValueBound<? extends U, ? extends U> range) {
+    static <T, U extends Comparable<? super U>> WindowDefinition<T, U> 
of(Function<? super T, ?> partitionBy, List<Queryable.Order<? super T, ? 
extends U>> orderBy, ValueBound<? extends U> range) {
         return new WindowDefinitionImpl<>(partitionBy, orderBy, range);
     }
 
     /**
+     * Factory method to create {@link WindowDefinition} instance
+     *
+     * @param orderBy order definition
+     * @param range the window bounds
+     * @param <T> the type of {@link Queryable} element
+     * @param <U> the type of field to sort
+     * @return the {@link WindowDefinition} instance
+     * @since 4.0.0
+     */
+    static <T, U extends Comparable<? super U>> WindowDefinition<T, U> 
of(List<Queryable.Order<? super T, ? extends U>> orderBy, ValueBound<? extends 
U> range) {
+        return new WindowDefinitionImpl<>(orderBy, range);
+    }
+
+    /**
      * Define partition, similar to SQL's {@code partition by} of window 
definition
      *
      * @return partition definition
@@ -175,7 +189,7 @@ public interface WindowDefinition<T, U extends Comparable<? 
super U>> {
      * @return range definition
      * @since 4.0.0
      */
-    default ValueBound<? extends U, ? extends U> range() {
+    default ValueBound<? extends U> range() {
         return null;
     }
 
diff --git 
a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/WindowDefinitionImpl.java
 
b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/WindowDefinitionImpl.java
index 4a767d4..1771e01 100644
--- 
a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/WindowDefinitionImpl.java
+++ 
b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/WindowDefinitionImpl.java
@@ -34,10 +34,10 @@ class WindowDefinitionImpl<T, U extends Comparable<? super 
U>> implements Window
     private final Function<? super T, ?> partitionBy;
     private final List<Queryable.Order<? super T, ? extends U>> orderBy;
     private final RowBound rows;
-    private final ValueBound<? extends U, ? extends U> range;
+    private final ValueBound<? extends U> range;
 
     public WindowDefinitionImpl(Function<? super T, ?> partitionBy, 
List<Queryable.Order<? super T, ? extends U>> orderBy,
-                                RowBound rows, ValueBound<? extends U, ? 
extends U> range) {
+                                RowBound rows, ValueBound<? extends U> range) {
         this.partitionBy = partitionBy;
         this.orderBy = orderBy;
         this.rows = rows;
@@ -71,10 +71,15 @@ class WindowDefinitionImpl<T, U extends Comparable<? super 
U>> implements Window
     }
 
     public WindowDefinitionImpl(Function<? super T, ?> partitionBy, 
List<Queryable.Order<? super T, ? extends U>> orderBy,
-                                ValueBound<? extends U, ? extends U> range) {
+                                ValueBound<? extends U> range) {
         this(partitionBy, orderBy, RowBound.DEFAULT, range);
     }
 
+    public WindowDefinitionImpl(List<Queryable.Order<? super T, ? extends U>> 
orderBy,
+                                ValueBound<? extends U> range) {
+        this((T t) -> Queryable.NULL, orderBy, RowBound.DEFAULT, range);
+    }
+
     @Override
     public Function<? super T, ?> partitionBy() {
         return partitionBy;
@@ -91,7 +96,7 @@ class WindowDefinitionImpl<T, U extends Comparable<? super 
U>> implements Window
     }
 
     @Override
-    public ValueBound<? extends U, ? extends U> range() {
+    public ValueBound<? extends U> range() {
         return range;
     }
 
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 7611006..0b78bd2 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
@@ -19,8 +19,11 @@
 package org.apache.groovy.ginq.provider.collection.runtime;
 
 import groovy.lang.Tuple2;
+import org.codehaus.groovy.runtime.typehandling.NumberMath;
 
+import java.math.BigDecimal;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 import java.util.function.Function;
 import java.util.stream.Collectors;
@@ -28,6 +31,7 @@ import java.util.stream.Collectors;
 import static java.util.Collections.binarySearch;
 import static java.util.Comparator.comparing;
 import static 
org.apache.groovy.ginq.provider.collection.runtime.Queryable.from;
+import static org.codehaus.groovy.runtime.dgmimpl.NumberNumberPlus.plus;
 
 /**
  * Represents window which stores elements used by window functions
@@ -37,12 +41,6 @@ import static 
org.apache.groovy.ginq.provider.collection.runtime.Queryable.from;
  * @since 4.0.0
  */
 class WindowImpl<T, U extends Comparable<? super U>> extends 
QueryableCollection<T> implements Window<T> {
-    private static final long serialVersionUID = -3458969297047398621L;
-    private final Tuple2<T, Long> currentRecord;
-    private final Function<? super T, ? extends U> keyExtractor;
-    private final int index;
-    private final U value;
-    private final List<T> list;
 
     static <T, U extends Comparable<? super U>> Window<T> 
newInstance(Tuple2<T, Long> currentRecord, Queryable<Tuple2<T, Long>> 
partition, WindowDefinition<T, U> windowDefinition) {
         Function<? super T, ? extends U> keyExtractor;
@@ -59,23 +57,23 @@ class WindowImpl<T, U extends Comparable<? super U>> 
extends QueryableCollection
                 ? binarySearch(listWithIndex, currentRecord, 
comparing(Tuple2::getV2))
                 : binarySearch(listWithIndex, currentRecord, 
makeComparator(composeOrders(orderList)).thenComparing(Tuple2::getV2));
         int index = tmpIndex >= 0 ? tmpIndex : -tmpIndex - 1;
+        U value = null == keyExtractor ? null : 
keyExtractor.apply(currentRecord.getV1());
 
-        long size = partition.size();
-        RowBound validRowBound = getValidRowBound(windowDefinition, index, 
size);
+        RowBound validRowBound = getValidRowBound(windowDefinition, index, 
value, listWithIndex);
         List<T> list = null == validRowBound ? Collections.emptyList()
                                   : 
from(listWithIndex.stream().map(Tuple2::getV1).collect(Collectors.toList()))
                                       .limit(validRowBound.getLower(), 
validRowBound.getUpper() - validRowBound.getLower() + 1)
                                       .toList();
 
-        return new WindowImpl<>(currentRecord, index, list, keyExtractor);
+        return new WindowImpl<>(currentRecord, index, value, list, 
keyExtractor);
     }
 
-    private WindowImpl(Tuple2<T, Long> currentRecord, int index, List<T> list, 
Function<? super T, ? extends U> keyExtractor) {
+    private WindowImpl(Tuple2<T, Long> currentRecord, int index, U value, 
List<T> list, Function<? super T, ? extends U> keyExtractor) {
         super(list);
         this.currentRecord = currentRecord;
         this.keyExtractor = keyExtractor;
         this.index = index;
-        this.value = null == keyExtractor ? null : 
keyExtractor.apply(currentRecord.getV1());
+        this.value = value;
         this.list = list;
     }
 
@@ -164,15 +162,94 @@ class WindowImpl<T, U extends Comparable<? super U>> 
extends QueryableCollection
         return null == upper || Long.MAX_VALUE == upper ? size - 1 : index + 
upper;
     }
 
-    private static <T, U extends Comparable<? super U>> RowBound 
getValidRowBound(WindowDefinition<T, U> windowDefinition, int index, long size) 
{
-        long firstIndex = getFirstIndex(windowDefinition, index);
-        long lastIndex = getLastIndex(windowDefinition, index, size);
+    private static <T, U extends Comparable<? super U>> RowBound 
getValidRowBound(WindowDefinition<T, U> windowDefinition, int index, U value, 
List<Tuple2<T, Long>> listWithIndex) {
+        int size = listWithIndex.size();
+        long firstIndex = 0;
+        long lastIndex = size - 1;
+        if (null != windowDefinition.rows() && RowBound.DEFAULT != 
windowDefinition.rows()) {
+            firstIndex = getFirstIndex(windowDefinition, index);
+            lastIndex = getLastIndex(windowDefinition, index, size);
+        } else if (null != windowDefinition.range() && null != 
windowDefinition.orderBy()) {
+            ValueBound<? extends U> valueBound = windowDefinition.range();
+            U lower = valueBound.getLower();
+            U upper = valueBound.getUpper();
+            if (value instanceof Number && (lower instanceof Number || null == 
lower) && (upper instanceof Number || null == upper)) {
+                final List<Order<? super T, ? extends U>> orderList = 
windowDefinition.orderBy();
+                if (orderList.size() == 1) {
+                    Order<? super T, ? extends U> order = orderList.get(0);
+
+                    if (listWithIndex.isEmpty()) {
+                        return null;
+                    }
+                    int flag = order.isAsc() ? 1 : -1;
+                    BigDecimal firstElement = NumberMath.toBigDecimal((Number) 
order.getKeyExtractor().apply(listWithIndex.get(0).getV1()));
+                    BigDecimal lastElement = NumberMath.toBigDecimal((Number) 
order.getKeyExtractor().apply(listWithIndex.get(size - 1).getV1()));
+
+                    BigDecimal lowerValue = null == lower ? MIN_VALUE : 
NumberMath.toBigDecimal(plus((Number) value, (Number) lower));
+                    BigDecimal upperValue = null == upper ? MAX_VALUE : 
NumberMath.toBigDecimal(plus((Number) value, (Number) upper));
+                    if ((flag * lowerValue.compareTo(firstElement) < 0 && flag 
* upperValue.compareTo(firstElement) < 0)
+                            || (flag * lowerValue.compareTo(lastElement) > 0 
&& flag * upperValue.compareTo(lastElement) > 0)) {
+                        return null;
+                    }
+
+                    List<BigDecimal> list =
+                            listWithIndex.stream()
+                                    .map(e -> NumberMath.toBigDecimal((Number) 
order.getKeyExtractor().apply(e.getV1())))
+                                    .collect(Collectors.toList());
+                    if (order.isAsc()) {
+                        firstIndex = getIndexByValue(lowerValue, true, list);
+                        lastIndex = getIndexByValue(upperValue, false, list);
+                    } else {
+                        final List<BigDecimal> reversedList = new 
ReversedList<>(list);
+                        lastIndex = size - 1 - getIndexByValue(lowerValue, 
true, reversedList);
+                        firstIndex = size - 1 - getIndexByValue(upperValue, 
false, reversedList);
+                    }
+                }
+            }
+        }
+
         if ((firstIndex < 0 && lastIndex < 0) || (firstIndex >= size && 
lastIndex >= size)) {
             return null;
         }
         return new RowBound(Math.max(firstIndex, 0), Math.min(lastIndex, size 
- 1));
     }
 
+    private static <T, U extends Comparable<? super U>> long 
getIndexByValue(BigDecimal value, boolean isLower, List<BigDecimal> list) {
+        int tmpIndex = binarySearch(list, value, Comparator.comparing(u -> u));
+        int valueIndex;
+        if (tmpIndex >= 0) {
+            valueIndex = tmpIndex;
+        } else {
+            valueIndex = -tmpIndex - 1;
+            if (!isLower) {
+                valueIndex = valueIndex - 1;
+                if (valueIndex < 0) {
+                    valueIndex = 0;
+                }
+            }
+        }
+
+        if (isLower) {
+            int i = valueIndex - 1;
+            for (; i >= 0; i--) {
+                if (!value.equals(list.get(i))) {
+                    break;
+                }
+            }
+            valueIndex = i + 1;
+        } else {
+            int i = valueIndex + 1;
+            for (int n = list.size(); i < n; i++) {
+                if (!value.equals(list.get(i))) {
+                    break;
+                }
+            }
+            valueIndex = i - 1;
+        }
+
+        return valueIndex;
+    }
+
     private static <T, U extends Comparable<? super U>> List<Order<Tuple2<T, 
Long>, U>> composeOrders(List<Queryable.Order<? super T, ? extends U>> 
orderList) {
         return orderList.stream()
                 .map(order -> new Order<Tuple2<T, Long>, U>(t -> 
order.getKeyExtractor().apply(t.getV1()), order.isAsc()))
@@ -182,4 +259,13 @@ class WindowImpl<T, U extends Comparable<? super U>> 
extends QueryableCollection
     private static <T, U extends Comparable<? super U>> List<Order<Tuple2<T, 
Long>, U>> composeOrders(WindowDefinition<T, U> windowDefinition) {
         return composeOrders(windowDefinition.orderBy());
     }
+
+    private final Tuple2<T, Long> currentRecord;
+    private final Function<? super T, ? extends U> keyExtractor;
+    private final int index;
+    private final U value;
+    private final List<T> list;
+    private static final BigDecimal MIN_VALUE = 
NumberMath.toBigDecimal(Long.MIN_VALUE);
+    private static final BigDecimal MAX_VALUE = 
NumberMath.toBigDecimal(Long.MAX_VALUE);
+    private static final long serialVersionUID = -3458969297047398621L;
 }
diff --git a/subprojects/groovy-ginq/src/spec/doc/ginq-userguide.adoc 
b/subprojects/groovy-ginq/src/spec/doc/ginq-userguide.adoc
index 5260034..27c408b 100644
--- a/subprojects/groovy-ginq/src/spec/doc/ginq-userguide.adoc
+++ b/subprojects/groovy-ginq/src/spec/doc/ginq-userguide.adoc
@@ -411,14 +411,18 @@ 
include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_nested_04,inde
 
 ==== Window Functions
 
-Window can be defined by `partitionby`, `orderby` and `rows`:
+Window can be defined by `partitionby`, `orderby`, `rows` and `range`:
 ```sql
 over(
     [partitionby <expression> (, <expression>)*]
     [orderby <expression> (, <expression>)*
-       [rows <expression>, <expression>]]
+       [rows <expression>, <expression> | range <expression>, <expression>]]
 )
 ```
+* `0` used as bound of `rows` and `range` clause is equivalent to SQL's 
`CURRENT ROW`, and negative means `PRECEDING`, positive means `FOLLOWING`
+* `null` used as the lower bound of `rows` and `range` clause is equivalent to 
SQL's `UNBOUNDED PRECEDING`
+* `null` used as the upper bound of `rows` and `range` clause is equivalent to 
SQL's `UNBOUNDED FOLLOWING`
+
 Also, GINQ provides some built-in window functions, e.g.
 `rowNumber`, `rank`, `denseRank`, `lead`, `lag`, `firstValue`, `lastValue`, 
`min`, `max`, `count`, `sum`, `avg`, `median`, etc.
 
@@ -523,10 +527,6 @@ 
include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_winfunction_18
 ----
 
include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_winfunction_19,indent=0]
 ----
-[NOTE]
-`0` in the `rows` clause is equivalent to SQL's `CURRENT ROW`,
-negative means `PRECEDING`,
-positive means `FOLLOWING`
 
 [source, groovy]
 ----
@@ -537,15 +537,11 @@ 
include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_winfunction_12
 ----
 
include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_winfunction_14,indent=0]
 ----
-[NOTE]
-`null` used as the lower bound of `rows` clause is equivalent to SQL's 
`UNBOUNDED PRECEDING`
 
 [source, groovy]
 ----
 
include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_winfunction_15,indent=0]
 ----
-[NOTE]
-`null` used as the upper bound of `rows` clause is equivalent to SQL's 
`UNBOUNDED FOLLOWING`
 
 [source, groovy]
 ----
@@ -568,6 +564,41 @@ 
include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_winfunction_23
 
include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_winfunction_26,indent=0]
 ----
 
+[source, groovy]
+----
+include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_winfunction_27,indent=0]
+----
+
+[source, groovy]
+----
+include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_winfunction_28,indent=0]
+----
+
+[source, groovy]
+----
+include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_winfunction_33,indent=0]
+----
+
+[source, groovy]
+----
+include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_winfunction_31,indent=0]
+----
+
+[source, groovy]
+----
+include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_winfunction_32,indent=0]
+----
+
+[source, groovy]
+----
+include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_winfunction_29,indent=0]
+----
+
+[source, groovy]
+----
+include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_winfunction_30,indent=0]
+----
+
 === GINQ Tips
 ==== Row Number
 `_rn` is the implicit variable representing row number for each record in the 
result set. It starts with `0`
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 09a6c0d..e5cb322 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
@@ -5249,6 +5249,196 @@ class GinqTest {
         '''
     }
 
+    @Test
+    void "testGinq - window - 51"() {
+        assertGinqScript '''
+// tag::ginq_winfunction_27[]
+            assert [[1, 1, 1], [2, 2, 3], [5, 2, 10], [5, 2, 10]] == GQ {
+                from n in [1, 2, 5, 5]
+                select n, (count() over(orderby n range -2, 0)),
+                          (sum(n) over(orderby n range -2, 0))
+            }.toList()
+// end::ginq_winfunction_27[]
+        '''
+    }
+
+    @Test
+    void "testGinq - window - 52"() {
+        assertGinqScript '''
+            assert [[1, 1, 1], [2, 2, 3], [5, 2, 10], [5, 2, 10]] == GQ {
+                from n in [1, 2, 5, 5]
+                select n, (count() over(orderby n in desc range -2, 0)),
+                          (sum(n) over(orderby n in desc range -2, 0))
+            }.toList()
+        '''
+    }
+
+    @Test
+    void "testGinq - window - 53"() {
+        assertGinqScript '''
+            assert [[1, 0, 0], [2, 1, 1], [5, 0, 0], [5, 0, 0]] == GQ {
+                from n in [1, 2, 5, 5]
+                select n, (count() over(orderby n range -2, -1)),
+                          (sum(n) over(orderby n range -2, -1))
+            }.toList()
+        '''
+    }
+
+    @Test
+    void "testGinq - window - 54"() {
+        assertGinqScript '''
+// tag::ginq_winfunction_32[]
+            assert [[1, 0, 0], [2, 1, 1], [5, 0, 0], [5, 0, 0]] == GQ {
+                from n in [1, 2, 5, 5]
+                select n, (count() over(orderby n in desc range -2, -1)), 
+                          (sum(n) over(orderby n in desc range -2, -1))
+            }.toList()
+// end::ginq_winfunction_32[]
+        '''
+    }
+
+    @Test
+    void "testGinq - window - 55"() {
+        assertGinqScript '''
+            assert [[1, 1, 2], [2, 0, 0], [5, 0, 0], [5, 0, 0]] == GQ {
+                from n in [1, 2, 5, 5]
+                select n, (count() over(orderby n range 1, 2)),
+                          (sum(n) over(orderby n range 1, 2))
+            }.toList()
+        '''
+    }
+
+    @Test
+    void "testGinq - window - 56"() {
+        assertGinqScript '''
+// tag::ginq_winfunction_31[]
+            assert [[1, 1, 2], [2, 0, 0], [5, 0, 0], [5, 0, 0]] == GQ {
+                from n in [1, 2, 5, 5]
+                select n, (count() over(orderby n in desc range 1, 2)), 
+                          (sum(n) over(orderby n in desc range 1, 2))
+            }.toList()
+// end::ginq_winfunction_31[]
+        '''
+    }
+
+    @Test
+    void "testGinq - window - 57"() {
+        assertGinqScript '''
+// tag::ginq_winfunction_29[]
+            assert [[1, 3, 12], [2, 2, 10], [5, 0, 0], [5, 0, 0]] == GQ {
+                from n in [1, 2, 5, 5]
+                select n, (count() over(orderby n range 1, null)), 
+                          (sum(n) over(orderby n range 1, null))
+            }.toList()
+// end::ginq_winfunction_29[]
+        '''
+    }
+
+    @Test
+    void "testGinq - window - 58"() {
+        assertGinqScript '''
+            assert [[1, 3, 12], [2, 2, 10], [5, 0, 0], [5, 0, 0]] == GQ {
+                from n in [1, 2, 5, 5]
+                select n, (count() over(orderby n in desc range 1, null)), 
+                          (sum(n) over(orderby n in desc range 1, null))
+            }.toList()
+        '''
+    }
+
+    @Test
+    void "testGinq - window - 59"() {
+        assertGinqScript '''
+// tag::ginq_winfunction_30[]
+            assert [[1, 2, 3], [2, 2, 3], [5, 4, 13], [5, 4, 13]] == GQ {
+                from n in [1, 2, 5, 5]
+                select n, (count() over(orderby n range null, 1)), 
+                          (sum(n) over(orderby n range null, 1))
+            }.toList()
+// end::ginq_winfunction_30[]
+        '''
+    }
+
+    @Test
+    void "testGinq - window - 60"() {
+        assertGinqScript '''
+            assert [[1, 2, 3], [2, 2, 3], [5, 4, 13], [5, 4, 13]] == GQ {
+                from n in [1, 2, 5, 5]
+                select n, (count() over(orderby n in desc range null, 1)), 
+                          (sum(n) over(orderby n in desc range null, 1))
+            }.toList()
+        '''
+    }
+
+    @Test
+    void "testGinq - window - 61"() {
+        assertGinqScript '''
+            assert [[1, 4, 13], [2, 4, 13], [5, 4, 13], [5, 4, 13]] == GQ {
+                from n in [1, 2, 5, 5]
+                select n, (count() over(orderby n range null, null)), 
+                          (sum(n) over(orderby n range null, null))
+            }.toList()
+        '''
+    }
+
+    @Test
+    void "testGinq - window - 62"() {
+        assertGinqScript '''
+            assert [[1, 4, 13], [2, 4, 13], [5, 4, 13], [5, 4, 13]] == GQ {
+                from n in [1, 2, 5, 5]
+                select n, (count() over(orderby n in desc range null, null)), 
+                          (sum(n) over(orderby n in desc range null, null))
+            }.toList()
+        '''
+    }
+
+    @Test
+    void "testGinq - window - 63"() {
+        assertGinqScript '''
+// tag::ginq_winfunction_28[]
+            assert [[1, 2, 3], [2, 1, 2], [5, 2, 10], [5, 2, 10]] == GQ {
+                from n in [1, 2, 5, 5]
+                select n, (count() over(orderby n range 0, 1)),
+                          (sum(n) over(orderby n range 0, 1))
+            }.toList()
+// end::ginq_winfunction_28[]
+        '''
+    }
+
+    @Test
+    void "testGinq - window - 64"() {
+        assertGinqScript '''
+            assert [[1, 2, 3], [2, 1, 2], [5, 2, 10], [5, 2, 10]] == GQ {
+                from n in [1, 2, 5, 5]
+                select n, (count() over(orderby n in desc range 0, 1)),
+                          (sum(n) over(orderby n in desc range 0, 1))
+            }.toList()
+        '''
+    }
+
+    @Test
+    void "testGinq - window - 65"() {
+        assertGinqScript '''
+// tag::ginq_winfunction_33[]
+            assert [[1, 2, 3], [2, 2, 3], [5, 2, 10], [5, 2, 10]] == GQ {
+                from n in [1, 2, 5, 5]
+                select n, (count() over(orderby n range -1, 1)), 
+                          (sum(n) over(orderby n range -1, 1))
+            }.toList()
+// end::ginq_winfunction_33[]
+        '''
+    }
+
+    @Test
+    void "testGinq - window - 66"() {
+        assertGinqScript '''
+            assert [[1, 2, 3], [2, 2, 3], [5, 2, 10], [5, 2, 10]] == GQ {
+                from n in [1, 2, 5, 5]
+                select n, (count() over(orderby n in desc range -1, 1)), 
+                          (sum(n) over(orderby n in desc range -1, 1))
+            }.toList()
+        '''
+    }
+
     private static void assertGinqScript(String script) {
         String deoptimizedScript = script.replaceAll(/\bGQ\s*[{]/, 
'GQ(optimize:false) {')
         List<String> scriptList = [deoptimizedScript, script]

Reply via email to