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/25 15:31:33 UTC

[groovy] branch master updated: Cache the result of `partitionby`

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 ed88dab  Cache the result of `partitionby`
ed88dab is described below

commit ed88dabbf1592d4ed6d3e0db3a6cb1f3d1389394
Author: Daniel Sun <su...@apache.org>
AuthorDate: Fri Dec 25 23:31:05 2020 +0800

    Cache the result of `partitionby`
---
 .../ginq/provider/collection/GinqAstWalker.groovy  |  6 ++++-
 .../provider/collection/runtime/Queryable.java     | 21 +++++++++++++----
 .../collection/runtime/QueryableCollection.java    | 19 +++++++++++----
 .../collection/runtime/WindowDefinition.java       | 14 +++++++++++
 .../collection/runtime/WindowDefinitionImpl.java   | 27 ++++++++++++++++++++++
 .../test/org/apache/groovy/ginq/GinqTest.groovy    | 10 ++++++++
 .../runtime/QueryableCollectionTest.groovy         | 25 ++++++--------------
 7 files changed, 94 insertions(+), 28 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 8516e37..5ad13c2 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
@@ -764,7 +764,11 @@ class GinqAstWalker implements GinqAstVisitor<Expression>, SyntaxErrorReportable
             argumentExpressionList << orderCtorCallExpression
         }
 
-        callX(new ClassExpression(WINDOW_DEFINITION_TYPE), 'of', args(argumentExpressionList))
+        callX(
+                callX(new ClassExpression(WINDOW_DEFINITION_TYPE), 'of', args(argumentExpressionList)),
+                'setId',
+                new ConstantExpression(argumentListExpression.text)
+        )
     }
 
     private int windowQueryableNameSeq = 0
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 10c0618..202494f 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
@@ -46,6 +46,21 @@ public interface Queryable<T> {
     Object NULL = new Object();
 
     /**
+     * Represents the empty Queryable instance
+     */
+    Queryable EMPTY_QUERYABLE = from(new Object[0]);
+
+    /**
+     * Returns the empty Queryable instance
+     *
+     * @param <T> the type of element
+     * @return the empty Queryable instance
+     */
+    static <T> Queryable<T> emptyQueryable() {
+        return (Queryable<T>) EMPTY_QUERYABLE;
+    }
+
+    /**
      * Factory method to create {@link Queryable} instance
      *
      * @param iterable iterable object, e.g. {@link List}
@@ -210,21 +225,19 @@ public interface Queryable<T> {
      *
      * @param classifier the classifier for group by
      * @param having the filter condition
-     * @param <K> the type of group key
      * @return the result of group by
      * @since 4.0.0
      */
-    <K> Queryable<Tuple2<K, Queryable<T>>> groupBy(Function<? super T, ? extends K> classifier, Predicate<? super Tuple2<? extends K, Queryable<? extends T>>> having);
+    Queryable<Tuple2<?, Queryable<T>>> groupBy(Function<? super T, ?> classifier, Predicate<? super Tuple2<?, Queryable<? extends T>>> having);
 
     /**
      * Group by {@link Queryable} instance without {@code having} clause, similar to SQL's {@code group by}
      *
      * @param classifier the classifier for group by
-     * @param <K> the type of group key
      * @return the result of group by
      * @since 4.0.0
      */
-    default <K> Queryable<Tuple2<K, Queryable<T>>> groupBy(Function<? super T, ? extends K> classifier) {
+    default Queryable<Tuple2<?, Queryable<T>>> groupBy(Function<? super T, ?> classifier) {
         return groupBy(classifier, null);
     }
 
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 62df46b..c06eff1 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
@@ -38,6 +38,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
@@ -207,8 +208,8 @@ class QueryableCollection<T> implements Queryable<T>, Serializable {
     }
 
     @Override
-    public <K> Queryable<Tuple2<K, Queryable<T>>> groupBy(Function<? super T, ? extends K> classifier, Predicate<? super Tuple2<? extends K, Queryable<? extends T>>> having) {
-        Stream<Tuple2<K, Queryable<T>>> stream =
+    public Queryable<Tuple2<?, Queryable<T>>> groupBy(Function<? super T, ?> classifier, Predicate<? super Tuple2<?, Queryable<? extends T>>> having) {
+        Stream<Tuple2<?, Queryable<T>>> stream =
                 this.stream()
                         .collect(Collectors.groupingBy(classifier, Collectors.toList()))
                         .entrySet().stream()
@@ -486,11 +487,18 @@ class QueryableCollection<T> implements Queryable<T>, Serializable {
     public <U extends Comparable<? super U>> Window<T> over(T currentRecord, WindowDefinition<T, U> windowDefinition) {
         this.makeReusable();
         Queryable<T> partition =
-                this.groupBy(windowDefinition.partitionBy()) // TODO cache the group result
+                partitionCache.computeIfAbsent(windowDefinition, wd -> {
+                    final Queryable<Tuple2<?, Queryable<T>>> q = this.groupBy(wd.partitionBy());
+                    if (q instanceof QueryableCollection) {
+                        ((QueryableCollection) q).makeReusable();
+                    }
+                    return q;
+                })
                         .where(e -> QueryableHelper.isEqual(e.getV1(), windowDefinition.partitionBy().apply(currentRecord)))
                         .select((e, q) -> e.getV2())
-                        .toList()
-                        .get(0);
+                        .stream()
+                        .findFirst()
+                        .orElse(Queryable.emptyQueryable());
 
         return new WindowImpl<>(currentRecord, partition, windowDefinition);
     }
@@ -563,6 +571,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 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/WindowDefinition.java b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/WindowDefinition.java
index 96162d4..f05939f 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
@@ -124,4 +124,18 @@ public interface WindowDefinition<T, U extends Comparable<? super U>> {
     default Tuple2<? extends U, ? extends U> range() {
         return null;
     }
+
+    /**
+     * Get the id of window definition
+     *
+     * @return the id of window definition
+     */
+    String getId();
+
+    /**
+     * Set the id of window definition
+     *
+     * @param id the id of window definition
+     */
+    WindowDefinition<T, U> setId(String id);
 }
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 5fdf373..7d1c2fd 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
@@ -20,6 +20,7 @@ package org.apache.groovy.ginq.provider.collection.runtime;
 
 import groovy.lang.Tuple2;
 
+import java.util.Objects;
 import java.util.function.Function;
 
 /**
@@ -79,4 +80,30 @@ class WindowDefinitionImpl<T, U extends Comparable<? super U>> implements Window
     public Tuple2<? extends U, ? extends U> range() {
         return range;
     }
+
+    @Override
+    public String getId() {
+        return id;
+    }
+
+    @Override
+    public WindowDefinition<T, U> setId(String id) {
+        this.id = id;
+        return this;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof WindowDefinitionImpl)) return false;
+        WindowDefinitionImpl<?, ?> that = (WindowDefinitionImpl<?, ?>) o;
+        return Objects.equals(id, that.id);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id);
+    }
+
+    private String id;
 }
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 26aba07..cfccb5a 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
@@ -4895,6 +4895,16 @@ class GinqTest {
         '''
     }
 
+    @Test
+    void "testGinq - window - 23"() {
+        assertGinqScript '''
+            assert [[2, 3, 1], [1, 2, null], [3, null, 2]] == GQ {
+                from n in [2, 1, 3]
+                select n, (lead(n) over(orderby n)), (lag(n) over(orderby n))
+            }.toList()
+        '''
+    }
+
     private static void assertGinqScript(String script) {
         String deoptimizedScript = script.replaceAll(/\bGQ\s*[{]/, 'GQ(optimize:false) {')
         List<String> scriptList = [deoptimizedScript, script]
diff --git a/subprojects/groovy-ginq/src/test/groovy/org/apache/groovy/ginq/provider/collection/runtime/QueryableCollectionTest.groovy b/subprojects/groovy-ginq/src/test/groovy/org/apache/groovy/ginq/provider/collection/runtime/QueryableCollectionTest.groovy
index 716c99a..1a3a7b1 100644
--- a/subprojects/groovy-ginq/src/test/groovy/org/apache/groovy/ginq/provider/collection/runtime/QueryableCollectionTest.groovy
+++ b/subprojects/groovy-ginq/src/test/groovy/org/apache/groovy/ginq/provider/collection/runtime/QueryableCollectionTest.groovy
@@ -1010,33 +1010,25 @@ class QueryableCollectionTest {
     }
 
     @Test
+    @CompileDynamic
     void testWindow1() {
         def nums = [9, 3, 6]
-        def windowDefinition = new WindowDefinition<Integer, Integer>() {
-            @Override
-            Queryable.Order<? super Integer, ? extends Integer> orderBy() {
-                return new Queryable.Order<Integer, Integer>(e -> e, true)
-            }
-        }
 
+        def windowDefinition = WindowDefinition.of(new Queryable.Order<Integer, Integer>(e -> e, true))
         def result = from(nums).over(6, windowDefinition).rowNumber()
 
         assert 1 == result
     }
 
     @Test
+    @CompileDynamic
     void testWindow2() {
         def n9 = new Integer(9)
         def n31 = new Integer(3)
         def n32 = new Integer(3)
         def n6 = new Integer(6)
         def nums = [n9, n31, n32, n6]
-        def windowDefinition = new WindowDefinition<Integer, Integer>() {
-            @Override
-            Queryable.Order<? super Integer, ? extends Integer> orderBy() {
-                return new Queryable.Order<Integer, Integer>(e -> new NamedTuple<>([e, e + 1], ['e', 'e + 1']), true)
-            }
-        }
+        def windowDefinition = WindowDefinition.of(new Queryable.Order<Integer, Integer>(e -> new NamedTuple<>([e, e + 1], ['e', 'e + 1']), true))
 
         assert 0 == from(nums).over(n31, windowDefinition).rowNumber()
         assert 1 == from(nums).over(n32, windowDefinition).rowNumber()
@@ -1048,18 +1040,14 @@ class QueryableCollectionTest {
     }
 
     @Test
+    @CompileDynamic
     void testWindow3() {
         def n9 = new Integer(9)
         def n31 = new Integer(3)
         def n32 = new Integer(3)
         def n6 = new Integer(6)
         def nums = [n9, n31, n32, n6]
-        def windowDefinition = new WindowDefinition<Integer, Integer>() {
-            @Override
-            Queryable.Order<? super Integer, ? extends Integer> orderBy() {
-                return new Queryable.Order<Integer, Integer>(e -> new NamedTuple<>([e, e + 1], ['e', 'e + 1']), true)
-            }
-        }
+        def windowDefinition = WindowDefinition.of(new Queryable.Order<Integer, Integer>(e -> new NamedTuple<>([e, e + 1], ['e', 'e + 1']), true))
 
         assert !from(nums).over(n31, windowDefinition).lag(e -> e)
         assert 3 == from(nums).over(n32, windowDefinition).lag(e -> e)
@@ -1073,6 +1061,7 @@ class QueryableCollectionTest {
     }
 
     @Test
+    @CompileDynamic
     void testWindow4() {
         def n9 = new Integer(9)
         def n31 = new Integer(3)