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/10/04 15:59:38 UTC
[groovy] 01/01: GROOVY-8258: Implement the built-in collection LINQ
provider
This is an automated email from the ASF dual-hosted git repository.
sunlan pushed a commit to branch GROOVY-8258
in repository https://gitbox.apache.org/repos/asf/groovy.git
commit fed55e09986dbab92f269e598622d90d01e54d49
Author: Daniel Sun <su...@apache.org>
AuthorDate: Sun Oct 4 23:54:31 2020 +0800
GROOVY-8258: Implement the built-in collection LINQ provider
---
settings.gradle | 1 +
subprojects/groovy-linq/build.gradle | 24 ++
.../java/org/apache/groovy/linq/Queryable.java | 124 +++++++
.../groovy/linq/provider/QueryableCollection.java | 244 +++++++++++++
.../linq/provider/QueryableCollectionTest.groovy | 403 +++++++++++++++++++++
5 files changed, 796 insertions(+)
diff --git a/settings.gradle b/settings.gradle
index b8a3622..3aca670 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -45,6 +45,7 @@ def subprojects = ['groovy-ant',
'groovy-jmx',
'groovy-json',
'groovy-jsr223',
+ 'groovy-linq',
'groovy-macro',
'groovy-macro-library',
'groovy-nio',
diff --git a/subprojects/groovy-linq/build.gradle b/subprojects/groovy-linq/build.gradle
new file mode 100644
index 0000000..8e249dd
--- /dev/null
+++ b/subprojects/groovy-linq/build.gradle
@@ -0,0 +1,24 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+dependencies {
+ api rootProject
+ testImplementation project(':groovy-test')
+}
diff --git a/subprojects/groovy-linq/src/main/java/org/apache/groovy/linq/Queryable.java b/subprojects/groovy-linq/src/main/java/org/apache/groovy/linq/Queryable.java
new file mode 100644
index 0000000..95d67f9
--- /dev/null
+++ b/subprojects/groovy-linq/src/main/java/org/apache/groovy/linq/Queryable.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.linq;
+
+import groovy.lang.Tuple2;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.BiPredicate;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+/**
+ * Represents the queryable objects, e.g. Java collections
+ *
+ * @param <T> the type of Queryable element
+ * @since 4.0.0
+ */
+public interface Queryable<T> {
+ <U> Queryable<Tuple2<T, U>> innerJoin(Queryable<? extends U> queryable, BiPredicate<? super T, ? super U> joiner);
+
+ <U> Queryable<Tuple2<T, U>> leftJoin(Queryable<? extends U> queryable, BiPredicate<? super T, ? super U> joiner);
+
+ <U> Queryable<Tuple2<T, U>> rightJoin(Queryable<? extends U> queryable, BiPredicate<? super T, ? super U> joiner);
+
+ default <U> Queryable<Tuple2<T, U>> fullJoin(Queryable<? extends U> queryable, BiPredicate<? super T, ? super U> joiner) {
+ Queryable<Tuple2<T, U>> lj = this.leftJoin(queryable, joiner);
+ Queryable<Tuple2<T, U>> rj = this.rightJoin(queryable, joiner);
+ return lj.union(rj);
+ }
+
+ <U> Queryable<Tuple2<T, U>> crossJoin(Queryable<? extends U> queryable);
+
+ Queryable<T> where(Predicate<? super T> filter);
+
+ <K> Queryable<Tuple2<K, Queryable<T>>> groupBy(Function<? super T, ? extends K> classifier, BiPredicate<? super K, ? super Queryable<? extends T>> having);
+
+ default <K> Queryable<Tuple2<K, Queryable<T>>> groupBy(Function<? super T, ? extends K> classifier) {
+ return groupBy(classifier, (k, l) -> true);
+ }
+
+ <U extends Comparable<? super U>> Queryable<T> orderBy(Order<? super T, ? extends U>... orders);
+
+ Queryable<T> limit(int offset, int size);
+
+ default Queryable<T> limit(int size) {
+ return limit(0, size);
+ }
+
+ <U> Queryable<U> select(Function<? super T, ? extends U> mapper);
+
+ Queryable<T> distinct();
+
+ default Queryable<T> union(Queryable<? extends T> queryable) {
+ return this.unionAll(queryable).distinct();
+ }
+
+ Queryable<T> unionAll(Queryable<? extends T> queryable);
+
+ Queryable<T> intersect(Queryable<? extends T> queryable);
+
+ Queryable<T> minus(Queryable<? extends T> queryable);
+
+ List<T> toList();
+
+ default Stream<T> stream() {
+ return toList().stream();
+ }
+
+ // Built-in aggregate functions {
+ int count();
+ BigDecimal sum(Function<? super T, BigDecimal> mapper);
+ // } Built-in aggregate functions
+
+ class Order<T, U extends Comparable<? super U>> {
+ private final Function<? super T, ? extends U> keyExtractor;
+ private final boolean asc;
+
+ public Order(Function<? super T, ? extends U> keyExtractor, boolean asc) {
+ this.keyExtractor = keyExtractor;
+ this.asc = asc;
+ }
+
+ public Function<? super T, ? extends U> getKeyExtractor() {
+ return keyExtractor;
+ }
+
+ public boolean isAsc() {
+ return asc;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof Order)) return false;
+ Order<?, ?> order = (Order<?, ?>) o;
+ return asc == order.asc &&
+ keyExtractor.equals(order.keyExtractor);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(keyExtractor, asc);
+ }
+ }
+}
diff --git a/subprojects/groovy-linq/src/main/java/org/apache/groovy/linq/provider/QueryableCollection.java b/subprojects/groovy-linq/src/main/java/org/apache/groovy/linq/provider/QueryableCollection.java
new file mode 100644
index 0000000..282e68b
--- /dev/null
+++ b/subprojects/groovy-linq/src/main/java/org/apache/groovy/linq/provider/QueryableCollection.java
@@ -0,0 +1,244 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.linq.provider;
+
+import groovy.lang.Tuple;
+import groovy.lang.Tuple2;
+import org.apache.groovy.linq.Queryable;
+
+import java.math.BigDecimal;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.function.BiPredicate;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+/**
+ * Represents the queryable collections
+ *
+ * @param <T> the type of Queryable element
+ * @since 4.0.0
+ */
+public class QueryableCollection<T> implements Queryable<T>, Iterable<T> {
+ private final Iterable<T> sourceIterable;
+ private Stream<T> sourceStream;
+
+ public static <T> Queryable<T> from(Iterable<T> sourceIterable) {
+ return new QueryableCollection<>(sourceIterable);
+ }
+
+ @SuppressWarnings("unchecked")
+ public static <T> Queryable<T> from(Stream<? extends T> sourceStream) {
+ Iterable<T> sourceIterable = (Iterable<T>) toIterable(sourceStream);
+ return from(sourceIterable);
+ }
+
+ private QueryableCollection(Iterable<T> sourceIterable) {
+ this.sourceIterable = sourceIterable;
+ this.sourceStream = toStream(sourceIterable);
+ }
+
+ @Override
+ public Iterator<T> iterator() {
+ return sourceIterable.iterator();
+ }
+
+ @Override
+ public <U> Queryable<Tuple2<T, U>> innerJoin(Queryable<? extends U> queryable, BiPredicate<? super T, ? super U> joiner) {
+ Stream<Tuple2<T, U>> stream =
+ this.stream()
+ .flatMap(p ->
+ queryable.stream()
+ .filter(c -> joiner.test(p, c))
+ .map(c -> Tuple.tuple(p, c)));
+
+ return from(stream);
+ }
+
+ @Override
+ public <U> Queryable<Tuple2<T, U>> leftJoin(Queryable<? extends U> queryable, BiPredicate<? super T, ? super U> joiner) {
+ return outerJoin(this, queryable, joiner);
+ }
+
+ @Override
+ public <U> Queryable<Tuple2<T, U>> rightJoin(Queryable<? extends U> queryable, BiPredicate<? super T, ? super U> joiner) {
+ return outerJoin(queryable, this, (a, b) -> joiner.test(b, a)).select(e -> Tuple.tuple(e.getV2(), e.getV1()));
+ }
+
+ @Override
+ public <U> Queryable<Tuple2<T, U>> crossJoin(Queryable<? extends U> queryable) {
+ Stream<Tuple2<T, U>> stream =
+ this.stream()
+ .flatMap(p ->
+ queryable.stream()
+ .map(c -> Tuple.tuple(p, c)));
+
+ return from(stream);
+ }
+
+ @Override
+ public Queryable<T> where(Predicate<? super T> filter) {
+ Stream<T> stream = this.stream().filter(filter::test);
+
+ return from(stream);
+ }
+
+ @Override
+ public <K> Queryable<Tuple2<K, Queryable<T>>> groupBy(Function<? super T, ? extends K> classifier, BiPredicate<? super K, ? super Queryable<? extends T>> having) {
+ Stream<Tuple2<K, Queryable<T>>> stream =
+ this.stream()
+ .collect(Collectors.groupingBy(classifier, Collectors.toList()))
+ .entrySet().stream()
+ .filter(m -> having.test(m.getKey(), from(m.getValue())))
+ .map(m -> Tuple.tuple(m.getKey(), from(m.getValue())));
+
+ return from(stream);
+ }
+
+ @Override
+ public <U extends Comparable<? super U>> Queryable<T> orderBy(Order<? super T, ? extends U>... orders) {
+ Comparator<T> comparator = null;
+ for (int i = 0, n = orders.length; i < n; i++) {
+ Order<? super T, ? extends U> order = orders[i];
+ Comparator<U> ascOrDesc = order.isAsc() ? Comparator.naturalOrder() : Comparator.reverseOrder();
+ comparator =
+ 0 == i
+ ? Comparator.comparing(order.getKeyExtractor(), ascOrDesc)
+ : comparator.thenComparing(order.getKeyExtractor(), ascOrDesc);
+ }
+
+ if (null == comparator) {
+ return this;
+ }
+
+ return from(this.stream().sorted(comparator));
+ }
+
+ @Override
+ public Queryable<T> limit(int offset, int size) {
+ Stream<T> stream = this.stream().skip(offset).limit(size);
+
+ return from(stream);
+ }
+
+ @Override
+ public <U> Queryable<U> select(Function<? super T, ? extends U> mapper) {
+ Stream<U> stream = this.stream().map(mapper);
+
+ return from(stream);
+ }
+
+ @Override
+ public Queryable<T> distinct() {
+ Stream<? extends T> stream = this.stream().distinct();
+
+ return from(stream);
+ }
+
+ @Override
+ public Queryable<T> unionAll(Queryable<? extends T> queryable) {
+ Stream<T> stream = Stream.concat(this.stream(), queryable.stream());
+
+ return from(stream);
+ }
+
+ @Override
+ public Queryable<T> intersect(Queryable<? extends T> queryable) {
+ Stream<T> stream = this.stream().filter(a -> queryable.stream().anyMatch(b -> b.equals(a))).distinct();
+
+ return from(stream);
+ }
+
+ @Override
+ public Queryable<T> minus(Queryable<? extends T> queryable) {
+ Stream<T> stream = this.stream().filter(a -> queryable.stream().noneMatch(b -> b.equals(a))).distinct();
+
+ return from(stream);
+ }
+
+ @Override
+ public List<T> toList() {
+ return stream().collect(Collectors.toList());
+ }
+
+ @Override
+ public Stream<T> stream() {
+ try {
+ sourceStream = sourceStream.peek(e -> {}); // check whether the stream is usable
+ } catch (IllegalStateException ex) {
+ sourceStream = toStream(sourceIterable); // we have to create new stream every time because Java stream can not be reused
+ }
+
+ return sourceStream;
+ }
+
+ @Override
+ public int count() {
+ return toList().size();
+ }
+
+ @Override
+ public BigDecimal sum(Function<? super T, BigDecimal> mapper) {
+ return this.stream().map(mapper).reduce(BigDecimal.ZERO, BigDecimal::add);
+ }
+
+ private static <T, U> Queryable<Tuple2<T, U>> outerJoin(Queryable<? extends T> queryable1, Queryable<? extends U> queryable2, BiPredicate<? super T, ? super U> joiner) {
+ Stream<Tuple2<T, U>> stream =
+ queryable1.stream()
+ .flatMap(p ->
+ queryable2.stream()
+ .map(c -> joiner.test(p, c) ? c : null)
+ .reduce(new LinkedList<U>(), (r, e) -> {
+ int size = r.size();
+ if (0 == size) {
+ r.add(e);
+ return r;
+ }
+
+ int lastIndex = size - 1;
+ Object lastElement = r.get(lastIndex);
+
+ if (null != e) {
+ if (null == lastElement) {
+ r.set(lastIndex, e);
+ } else {
+ r.add(e);
+ }
+ }
+
+ return r;
+ }, (i, o) -> o).stream()
+ .map(c -> null == c ? Tuple.tuple(p, null) : Tuple.tuple(p, c)));
+
+ return from(stream);
+ }
+
+ private static <T> Stream<T> toStream(Iterable<T> sourceIterable) {
+ return StreamSupport.stream(sourceIterable.spliterator(), false);
+ }
+
+ private static <T> Iterable<T> toIterable(Stream<T> sourceStream) {
+ return sourceStream::iterator;
+ }
+}
diff --git a/subprojects/groovy-linq/src/test/groovy/org/apache/groovy/linq/provider/QueryableCollectionTest.groovy b/subprojects/groovy-linq/src/test/groovy/org/apache/groovy/linq/provider/QueryableCollectionTest.groovy
new file mode 100644
index 0000000..be20ea6
--- /dev/null
+++ b/subprojects/groovy-linq/src/test/groovy/org/apache/groovy/linq/provider/QueryableCollectionTest.groovy
@@ -0,0 +1,403 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.groovy.linq.provider
+
+
+import groovy.transform.CompileDynamic
+import groovy.transform.CompileStatic
+import groovy.transform.EqualsAndHashCode
+import groovy.transform.ToString
+import org.apache.groovy.linq.Queryable
+import org.junit.Test
+
+import java.util.stream.Stream
+
+@CompileStatic
+class QueryableCollectionTest {
+ @Test
+ void testFrom() {
+ assert [1, 2, 3] == QueryableCollection.from(Stream.of(1, 2, 3)).toList()
+ assert [1, 2, 3] == QueryableCollection.from(Arrays.asList(1, 2, 3)).toList()
+ }
+
+ @Test
+ void testInnerJoin0() {
+ def nums1 = [1, 2, 3]
+ def nums2 = [1, 2, 3]
+ def result = QueryableCollection.from(nums1).innerJoin(QueryableCollection.from(nums2), (a, b) -> a == b).toList()
+ assert [[1, 1], [2, 2], [3, 3]] == result
+ }
+
+ @Test
+ void testInnerJoin1() {
+ def nums1 = [1, 2, 3]
+ def nums2 = [2, 3, 4]
+ def result = QueryableCollection.from(nums1).innerJoin(QueryableCollection.from(nums2), (a, b) -> a == b).toList()
+ assert [[2, 2], [3, 3]] == result
+ }
+
+ @Test
+ void testLeftJoin0() {
+ def nums1 = [1, 2, 3]
+ def nums2 = [1, 2, 3]
+ def result = QueryableCollection.from(nums1).leftJoin(QueryableCollection.from(nums2), (a, b) -> a == b).toList()
+ assert [[1, 1], [2, 2], [3, 3]] == result
+ }
+
+ @Test
+ void testRightJoin0() {
+ def nums2 = [1, 2, 3]
+ def nums1 = [1, 2, 3]
+ def result = QueryableCollection.from(nums1).rightJoin(QueryableCollection.from(nums2), (a, b) -> a == b).toList()
+ assert [[1, 1], [2, 2], [3, 3]] == result
+ }
+
+ @Test
+ void testLeftJoin1() {
+ def nums1 = [1, 2, 3]
+ def nums2 = [2, 3, 4]
+ def result = QueryableCollection.from(nums1).leftJoin(QueryableCollection.from(nums2), (a, b) -> a == b).toList()
+ assert [[1, null], [2, 2], [3, 3]] == result
+ }
+
+ @Test
+ void testRightJoin1() {
+ def nums2 = [1, 2, 3]
+ def nums1 = [2, 3, 4]
+ def result = QueryableCollection.from(nums1).rightJoin(QueryableCollection.from(nums2), (a, b) -> a == b).toList()
+ assert [[null, 1], [2, 2], [3, 3]] == result
+ }
+
+ @Test
+ void testLeftJoin2() {
+ def nums1 = [1, 2, 3, null]
+ def nums2 = [2, 3, 4]
+ def result = QueryableCollection.from(nums1).leftJoin(QueryableCollection.from(nums2), (a, b) -> a == b).toList()
+ assert [[1, null], [2, 2], [3, 3], [null, null]] == result
+ }
+
+ @Test
+ void testRightJoin2() {
+ def nums2 = [1, 2, 3, null]
+ def nums1 = [2, 3, 4]
+ def result = QueryableCollection.from(nums1).rightJoin(QueryableCollection.from(nums2), (a, b) -> a == b).toList()
+ assert [[null, 1], [2, 2], [3, 3], [null, null]] == result
+ }
+
+ @Test
+ void testLeftJoin3() {
+ def nums1 = [1, 2, 3, null]
+ def nums2 = [2, 3, 4, null]
+ def result = QueryableCollection.from(nums1).leftJoin(QueryableCollection.from(nums2), (a, b) -> a == b).toList()
+ assert [[1, null], [2, 2], [3, 3], [null, null]] == result
+ }
+
+ @Test
+ void testRightJoin3() {
+ def nums2 = [1, 2, 3, null]
+ def nums1 = [2, 3, 4, null]
+ def result = QueryableCollection.from(nums1).rightJoin(QueryableCollection.from(nums2), (a, b) -> a == b).toList()
+ assert [[null, 1], [2, 2], [3, 3], [null, null]] == result
+ }
+
+ @Test
+ void testLeftJoin4() {
+ def nums1 = [1, 2, 3]
+ def nums2 = [2, 3, 4, null]
+ def result = QueryableCollection.from(nums1).leftJoin(QueryableCollection.from(nums2), (a, b) -> a == b).toList()
+ assert [[1, null], [2, 2], [3, 3]] == result
+ }
+
+ @Test
+ void testRightJoin4() {
+ def nums2 = [1, 2, 3]
+ def nums1 = [2, 3, 4, null]
+ def result = QueryableCollection.from(nums1).rightJoin(QueryableCollection.from(nums2), (a, b) -> a == b).toList()
+ assert [[null, 1], [2, 2], [3, 3]] == result
+ }
+
+ @Test
+ void testLeftJoin5() {
+ def nums1 = [1, 2, 3, null, null]
+ def nums2 = [2, 3, 4]
+ def result = QueryableCollection.from(nums1).leftJoin(QueryableCollection.from(nums2), (a, b) -> a == b).toList()
+ assert [[1, null], [2, 2], [3, 3], [null, null], [null, null]] == result
+ }
+
+ @Test
+ void testRightJoin5() {
+ def nums2 = [1, 2, 3, null, null]
+ def nums1 = [2, 3, 4]
+ def result = QueryableCollection.from(nums1).rightJoin(QueryableCollection.from(nums2), (a, b) -> a == b).toList()
+ assert [[null, 1], [2, 2], [3, 3], [null, null], [null, null]] == result
+ }
+
+ @Test
+ void testLeftJoin6() {
+ def nums1 = [1, 2, 3, null, null]
+ def nums2 = [2, 3, 4, null]
+ def result = QueryableCollection.from(nums1).leftJoin(QueryableCollection.from(nums2), (a, b) -> a == b).toList()
+ assert [[1, null], [2, 2], [3, 3], [null, null], [null, null]] == result
+ }
+
+ @Test
+ void testRightJoin6() {
+ def nums2 = [1, 2, 3, null, null]
+ def nums1 = [2, 3, 4, null]
+ def result = QueryableCollection.from(nums1).rightJoin(QueryableCollection.from(nums2), (a, b) -> a == b).toList()
+ assert [[null, 1], [2, 2], [3, 3], [null, null], [null, null]] == result
+ }
+
+ @Test
+ void testLeftJoin7() {
+ def nums1 = [1, 2, 3, null, null]
+ def nums2 = [2, 3, 4, null, null]
+ def result = QueryableCollection.from(nums1).leftJoin(QueryableCollection.from(nums2), (a, b) -> a == b).toList()
+ assert [[1, null], [2, 2], [3, 3], [null, null], [null, null]] == result
+ }
+
+ @Test
+ void testRightJoin7() {
+ def nums2 = [1, 2, 3, null, null]
+ def nums1 = [2, 3, 4, null, null]
+ def result = QueryableCollection.from(nums1).rightJoin(QueryableCollection.from(nums2), (a, b) -> a == b).toList()
+ assert [[null, 1], [2, 2], [3, 3], [null, null], [null, null]] == result
+ }
+
+ @Test
+ void testLeftJoin8() {
+ def nums1 = [1, 2, 3, null]
+ def nums2 = [2, 3, 4, null, null]
+ def result = QueryableCollection.from(nums1).leftJoin(QueryableCollection.from(nums2), (a, b) -> a == b).toList()
+ assert [[1, null], [2, 2], [3, 3], [null, null]] == result
+ }
+
+ @Test
+ void testRightJoin8() {
+ def nums2 = [1, 2, 3, null]
+ def nums1 = [2, 3, 4, null, null]
+ def result = QueryableCollection.from(nums1).rightJoin(QueryableCollection.from(nums2), (a, b) -> a == b).toList()
+ assert [[null, 1], [2, 2], [3, 3], [null, null]] == result
+ }
+
+ @Test
+ void testLeftJoin9() {
+ def nums1 = [1, 2, 3]
+ def nums2 = [2, 3, 4, null, null]
+ def result = QueryableCollection.from(nums1).leftJoin(QueryableCollection.from(nums2), (a, b) -> a == b).toList()
+ assert [[1, null], [2, 2], [3, 3]] == result
+ }
+
+ @Test
+ void testRightJoin9() {
+ def nums2 = [1, 2, 3]
+ def nums1 = [2, 3, 4, null, null]
+ def result = QueryableCollection.from(nums1).rightJoin(QueryableCollection.from(nums2), (a, b) -> a == b).toList()
+ assert [[null, 1], [2, 2], [3, 3]] == result
+ }
+
+ @Test
+ void testFullJoin() {
+ def nums1 = [1, 2, 3]
+ def nums2 = [2, 3, 4]
+ def result = QueryableCollection.from(nums1).fullJoin(QueryableCollection.from(nums2), (a, b) -> a == b).toList()
+ assert [[1, null], [2, 2], [3, 3], [null, 4]] == result
+ }
+
+ @Test
+ void testCrossJoin() {
+ def nums1 = [1, 2, 3]
+ def nums2 = [3, 4, 5]
+ def result = QueryableCollection.from(nums1).crossJoin(QueryableCollection.from(nums2)).toList()
+ assert [[1, 3], [1, 4], [1, 5], [2, 3], [2, 4], [2, 5], [3, 3], [3, 4], [3, 5]] == result
+ }
+
+ @Test
+ void testWhere() {
+ def nums = [1, 2, 3, 4, 5]
+ def result = QueryableCollection.from(nums).where(e -> e > 3).toList()
+ assert [4, 5] == result
+ }
+
+ @Test
+ void testGroupBySelect0() {
+ def nums = [1, 2, 2, 3, 3, 4, 4, 5]
+ def result = QueryableCollection.from(nums).groupBy(e -> e).select(e -> Tuple.tuple(e.v1, e.v2.toList())).toList()
+ assert [[1, [1]], [2, [2, 2]], [3, [3, 3]], [4, [4, 4]], [5, [5]]] == result
+ }
+
+ @Test
+ void testGroupBySelect1() {
+ def nums = [1, 2, 2, 3, 3, 4, 4, 5]
+ def result = QueryableCollection.from(nums).groupBy(e -> e).select(e -> Tuple.tuple(e.v1, e.v2.count())).toList()
+ assert [[1, 1], [2, 2], [3, 2], [4, 2], [5, 1]] == result
+ }
+
+ @Test
+ void testGroupBySelect2() {
+ def nums = [1, 2, 2, 3, 3, 4, 4, 5]
+ def result =
+ QueryableCollection.from(nums).groupBy(e -> e)
+ .select(e ->
+ Tuple.tuple(
+ e.v1,
+ e.v2.count(),
+ e.v2.sum(n -> new BigDecimal(n))
+ )
+ ).toList()
+ assert [[1, 1, 1], [2, 2, 4], [3, 2, 6], [4, 2, 8], [5, 1, 5]] == result
+ }
+
+ @Test
+ @CompileDynamic
+ void testGroupBySelect3() {
+ def nums = [1, 2, 2, 3, 3, 4, 4, 5]
+ def result =
+ QueryableCollection.from(nums).groupBy(e -> e, (k, q) -> k > 2)
+ .select(e ->
+ Tuple.tuple(
+ e.v1,
+ e.v2.count(),
+ e.v2.sum(n -> new BigDecimal(n))
+ )
+ ).toList()
+ assert [[3, 2, 6], [4, 2, 8], [5, 1, 5]] == result
+ }
+
+ @Test
+ void testOrderBy() {
+ Person daniel = new Person('Daniel', 35)
+ Person peter = new Person('Peter', 10)
+ Person alice = new Person('Alice', 22)
+ Person john = new Person('John', 10)
+
+ def persons = [daniel, peter, alice, john]
+ def result = QueryableCollection.from(persons).orderBy(
+ new Queryable.Order<Person, Comparable>((Person e) -> e.age, true),
+ new Queryable.Order<Person, Comparable>((Person e) -> e.name, true)
+ ).toList()
+ assert [john, peter, alice, daniel] == result
+
+ result = QueryableCollection.from(persons).orderBy(
+ new Queryable.Order<Person, Comparable>((Person e) -> e.age, false),
+ new Queryable.Order<Person, Comparable>((Person e) -> e.name, true)
+ ).toList()
+ assert [daniel, alice, john, peter] == result
+
+ result = QueryableCollection.from(persons).orderBy(
+ new Queryable.Order<Person, Comparable>((Person e) -> e.age, true),
+ new Queryable.Order<Person, Comparable>((Person e) -> e.name, false)
+ ).toList()
+ assert [peter, john, alice, daniel] == result
+
+ result = QueryableCollection.from(persons).orderBy(
+ new Queryable.Order<Person, Comparable>((Person e) -> e.age, false),
+ new Queryable.Order<Person, Comparable>((Person e) -> e.name, false)
+ ).toList()
+ assert [daniel, alice, peter, john] == result
+ }
+
+ @Test
+ void testLimit() {
+ def nums = [1, 2, 3, 4, 5]
+ def result = QueryableCollection.from(nums).limit(1, 2).toList()
+ assert [2, 3] == result
+
+ result = QueryableCollection.from(nums).limit(2).toList()
+ assert [1, 2] == result
+ }
+
+ @Test
+ void testSelect() {
+ def nums = [1, 2, 3, 4, 5]
+ def result = QueryableCollection.from(nums).select(e -> e + 1).toList()
+ assert [2, 3, 4, 5, 6] == result
+ }
+
+ @Test
+ void testDistinct() {
+ def nums = [1, 2, 2, 3, 3, 2, 3, 4, 5, 5]
+ def result = QueryableCollection.from(nums).distinct().toList()
+ assert [1, 2, 3, 4, 5] == result
+ }
+
+ @Test
+ void testUnion() {
+ def nums1 = [1, 2, 3]
+ def nums2 = [2, 3, 4]
+ def result = QueryableCollection.from(nums1).union(QueryableCollection.from(nums2)).toList()
+ assert [1, 2, 3, 4] == result
+ }
+
+ @Test
+ void testUnionAll() {
+ def nums1 = [1, 2, 3]
+ def nums2 = [2, 3, 4]
+ def result = QueryableCollection.from(nums1).unionAll(QueryableCollection.from(nums2)).toList()
+ assert [1, 2, 3, 2, 3, 4] == result
+ }
+
+ @Test
+ void testIntersect() {
+ def nums1 = [1, 2, 2, 3]
+ def nums2 = [2, 3, 3, 4]
+ def result = QueryableCollection.from(nums1).intersect(QueryableCollection.from(nums2)).toList()
+ assert [2, 3] == result
+ }
+
+ @Test
+ void testMinus() {
+ def nums1 = [1, 1, 2, 3]
+ def nums2 = [2, 3, 4]
+ def result = QueryableCollection.from(nums1).minus(QueryableCollection.from(nums2)).toList()
+ assert [1] == result
+ }
+
+ @Test
+ void testFromWhereLimitSelect() {
+ def nums1 = [1, 2, 3, 4, 5]
+ def nums2 = [0, 1, 2, 3, 4, 5, 6]
+ def result =
+ QueryableCollection.from(nums1)
+ .innerJoin(QueryableCollection.from(nums2), (a, b) -> a == b)
+ .where(t -> t.v1 > 1)
+ .limit(1, 2)
+ .select(t -> t.v1 + 1)
+ .toList()
+ assert [4, 5] == result
+ }
+
+// @Test void testIterator() {
+// def nums = [1, 2, 3]
+// def result = from(nums).iterator().toList()
+// assert nums == result
+// }
+
+ @ToString
+ @EqualsAndHashCode
+ static class Person {
+ String name
+ int age
+
+ Person(String name, int age) {
+ this.name = name
+ this.age = age
+ }
+ }
+}