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 ac922484de3b94feb6af2115d3ef69f03948b74c Author: Daniel Sun <[email protected]> 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 + } + } +}
