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]