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 185e3f5 Tweak row number further
185e3f5 is described below
commit 185e3f56162202b433de7bc72b7af4918d0143c7
Author: Daniel Sun <[email protected]>
AuthorDate: Mon Dec 28 12:40:17 2020 +0800
Tweak row number further
---
.../ginq/provider/collection/GinqAstWalker.groovy | 3 ++-
.../provider/collection/runtime/Queryable.java | 14 +++++++++++-
.../collection/runtime/QueryableCollection.java | 23 +++++++++++++++-----
.../provider/collection/runtime/WindowImpl.java | 25 ++++++++++++++++------
.../test/org/apache/groovy/ginq/GinqTest.groovy | 20 +++++++++++++++++
5 files changed, 71 insertions(+), 14 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 aadab92..c2c0d68 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
@@ -737,7 +737,7 @@ class GinqAstWalker implements GinqAstVisitor<Expression>,
SyntaxErrorReportable
def
windowDefinitionFactoryMethodCallExpression =
constructWindowDefinitionFactoryMethodCallExpression(expression,
dataSourceExpression)
Expression newObjectExpression = callX(wqVar,
'over', args(
- currentRecordVar,
+ callX(TUPLE_TYPE, 'tuple',
args(currentRecordVar, callX(varX(rowNumberName), 'get'))),
windowDefinitionFactoryMethodCallExpression
))
result = callX(
@@ -1371,6 +1371,7 @@ class GinqAstWalker implements
GinqAstVisitor<Expression>, SyntaxErrorReportable
private static final ClassNode WINDOW_DEFINITION_TYPE =
makeWithoutCaching(WindowDefinition.class)
private static final ClassNode ROWBOUND_TYPE = makeCached(RowBound.class)
private static final ClassNode ATOMIC_LONG_TYPE =
makeCached(AtomicLong.class)
+ private static final ClassNode TUPLE_TYPE = makeWithoutCaching(Tuple.class)
private static final List<String> ORDER_OPTION_LIST = Arrays.asList('asc',
'desc')
private static final String FUNCTION_COUNT = 'count'
diff --git
a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/Queryable.java
b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/Queryable.java
index 8021955..1279214 100644
---
a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/Queryable.java
+++
b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/Queryable.java
@@ -254,6 +254,18 @@ public interface Queryable<T> {
<U extends Comparable<? super U>> Queryable<T> orderBy(Order<? super T, ?
extends U>... orders);
/**
+ * Sort {@link Queryable} instance, similar to SQL's {@code order by}
+ *
+ * @param orders the order rules for sorting
+ * @param <U> the type of field to sort
+ * @return the result of order by
+ * @since 4.0.0
+ */
+ default <U extends Comparable<? super U>> Queryable<T> orderBy(List<?
extends Order<? super T, ? extends U>> orders) {
+ return orderBy(orders.toArray(Order.EMPTY_ARRAY));
+ }
+
+ /**
* Paginate {@link Queryable} instance, similar to MySQL's {@code limit}
*
* @param offset the start position
@@ -450,7 +462,7 @@ public interface Queryable<T> {
* @param <U> the type of window value
* @return the window
*/
- <U extends Comparable<? super U>> Window<T> over(T currentRecord,
WindowDefinition<T, U> windowDefinition);
+ <U extends Comparable<? super U>> Window<T> over(Tuple2<T, Long>
currentRecord, WindowDefinition<T, U> windowDefinition);
/**
* Represents an order rule
diff --git
a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/QueryableCollection.java
b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/QueryableCollection.java
index 9088ea8..73130d7 100644
---
a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/QueryableCollection.java
+++
b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/QueryableCollection.java
@@ -18,6 +18,7 @@
*/
package org.apache.groovy.ginq.provider.collection.runtime;
+import groovy.lang.Tuple;
import groovy.lang.Tuple2;
import groovy.transform.Internal;
import org.apache.groovy.internal.util.Supplier;
@@ -70,6 +71,12 @@ class QueryableCollection<T> implements Queryable<T>,
Serializable {
this.sourceStream = sourceStream;
}
+ protected List<Tuple2<T, Long>> listWithIndex;
+ QueryableCollection(Queryable<Tuple2<T, Long>> queryableWithIndex) {
+ this(queryableWithIndex.toList().stream().map(e ->
e.getV1()).collect(Collectors.toList()));
+ this.listWithIndex = queryableWithIndex.toList();
+ }
+
public Iterator<T> iterator() {
readLock.lock();
try {
@@ -484,17 +491,23 @@ class QueryableCollection<T> implements Queryable<T>,
Serializable {
}
@Override
- public <U extends Comparable<? super U>> Window<T> over(T currentRecord,
WindowDefinition<T, U> windowDefinition) {
+ public <U extends Comparable<? super U>> Window<T> over(Tuple2<T, Long>
currentRecord, WindowDefinition<T, U> windowDefinition) {
this.makeReusable();
- Queryable<T> partition =
+ Queryable<Tuple2<T, Long>> partition =
partitionCache.computeIfAbsent(windowDefinition, wd -> {
- final Queryable<Tuple2<?, Queryable<T>>> q =
this.groupBy(wd.partitionBy());
+ long[] rn = new long[] { 1L };
+ List<Tuple2<T, Long>> listWithIndex =
+ this.toList().stream()
+ .map(e -> Tuple.tuple(e, rn[0]++))
+ .collect(Collectors.toList());
+
+ final Queryable<Tuple2<?, Queryable<Tuple2<T, Long>>>> q =
from(listWithIndex).groupBy(wd.partitionBy().compose(e -> e.getV1()));
if (q instanceof QueryableCollection) {
((QueryableCollection) q).makeReusable();
}
return q;
})
- .where(e -> Objects.equals(e.getV1(),
windowDefinition.partitionBy().apply(currentRecord)))
+ .where(e -> Objects.equals(e.getV1(),
windowDefinition.partitionBy().apply(currentRecord.getV1())))
.select((e, q) -> e.getV2())
.stream()
.findFirst()
@@ -571,7 +584,7 @@ class QueryableCollection<T> implements Queryable<T>,
Serializable {
return AsciiTableMaker.makeAsciiTable(this);
}
- private final Map<WindowDefinition<T, ?>, Queryable<Tuple2<?,
Queryable<T>>>> partitionCache = new ConcurrentHashMap<>(4);
+ private final Map<WindowDefinition<T, ?>, Queryable<Tuple2<?,
Queryable<Tuple2<T, Long>>>>> partitionCache = new ConcurrentHashMap<>(4);
private Stream<T> sourceStream;
private volatile Iterable<T> sourceIterable;
private final ReadWriteLock rwl = new ReentrantReadWriteLock();
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 27afed2..10b0670 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
@@ -18,8 +18,11 @@
*/
package org.apache.groovy.ginq.provider.collection.runtime;
+import groovy.lang.Tuple2;
+
import java.util.List;
import java.util.function.Function;
+import java.util.stream.Collectors;
/**
* Represents window which stores elements used by window functions
@@ -30,22 +33,22 @@ import java.util.function.Function;
*/
class WindowImpl<T, U extends Comparable<? super U>> extends
QueryableCollection<T> implements Window<T> {
private static final long serialVersionUID = -3458969297047398621L;
- private final T currentRecord;
+ private final Tuple2<T, Long> currentRecord;
private final long index;
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());
+ WindowImpl(Tuple2<T, Long> currentRecord, Queryable<Tuple2<T, Long>>
partition, WindowDefinition<T, U> windowDefinition) {
+ super(partition.orderBy(composeOrders(windowDefinition)));
this.currentRecord = currentRecord;
this.windowDefinition = windowDefinition;
- List<T> sortedList = this.toList();
+ List<Tuple2<T, Long>> sortedList = listWithIndex;
final List<Order<? super T, ? extends U>> order =
windowDefinition.orderBy();
if (null != order && 1 == order.size()) {
this.keyExtractor = order.get(0).getKeyExtractor();
- this.value = keyExtractor.apply(currentRecord);
+ this.value = keyExtractor.apply(currentRecord.getV1());
} else {
this.keyExtractor = null;
this.value = null;
@@ -53,7 +56,8 @@ class WindowImpl<T, U extends Comparable<? super U>> extends
QueryableCollection
int tmpIndex = -1;
for (int i = 0, n = sortedList.size(); i < n; i++) {
- if (currentRecord == sortedList.get(i)) {
+ final Tuple2<T, Long> record = sortedList.get(i);
+ if (currentRecord.getV1() == record.getV1() &&
currentRecord.getV2().equals(record.getV2())) {
tmpIndex = i;
break;
}
@@ -62,6 +66,13 @@ class WindowImpl<T, U extends Comparable<? super U>> extends
QueryableCollection
this.index = tmpIndex;
}
+ private static <T, U extends Comparable<? super U>> List<Order<? super
Tuple2<T, Long>, ? extends U>> composeOrders(WindowDefinition<T, U>
windowDefinition) {
+ List<Order<? super Tuple2<T, Long>, ? extends U>> result =
windowDefinition.orderBy().stream()
+ .map(order -> new Order<Tuple2<T, Long>, U>(t ->
order.getKeyExtractor().apply(t.getV1()), order.isAsc()))
+ .collect(Collectors.toList());
+ return result;
+ }
+
@Override
public long rowNumber() {
return index;
@@ -71,7 +82,7 @@ class WindowImpl<T, U extends Comparable<? super U>> extends
QueryableCollection
public <V> V lead(Function<? super T, ? extends V> extractor, long lead, V
def) {
V field;
if (0 == lead) {
- field = extractor.apply(currentRecord);
+ field = extractor.apply(currentRecord.getV1());
} else if (0 <= index + lead && index + lead < this.size()) {
field = extractor.apply(this.toList().get((int) index + (int)
lead));
} else {
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 b62a14e..1a0b668 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
@@ -5203,6 +5203,26 @@ class GinqTest {
'''
}
+ @Test
+ void "testGinq - window - 47"() {
+ assertGinqScript '''
+ assert [[1, 0], [1, 1], [2, 2], [2, 3]] == GQ {
+ from n in [1, 1, 2, 2]
+ select n, (rowNumber() over(orderby n))
+ }.toList()
+ '''
+ }
+
+ @Test
+ void "testGinq - window - 48"() {
+ assertGinqScript '''
+ assert [['aa', 0], ['aa', 1], ['bb', 2], ['bb', 3]] == GQ {
+ from s in ['aa', 'aa', 'bb', 'bb']
+ select s, (rowNumber() over(orderby s))
+ }.toList()
+ '''
+ }
+
private static void assertGinqScript(String script) {
String deoptimizedScript = script.replaceAll(/\bGQ\s*[{]/,
'GQ(optimize:false) {')
List<String> scriptList = [deoptimizedScript, script]