You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2023/08/18 02:33:58 UTC

[groovy] 02/02: GROOVY-11157: Provide a GINQ aggregate function shorthand for returning a list of the selected items

This is an automated email from the ASF dual-hosted git repository.

paulk pushed a commit to branch GROOVY_4_0_X
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit bbe34976c18221a000d89339934ae49b5ac487cc
Author: Paul King <pa...@asert.com.au>
AuthorDate: Thu Aug 17 15:02:38 2023 +1000

    GROOVY-11157: Provide a GINQ aggregate function shorthand for returning a list of the selected items
---
 .../ginq/provider/collection/GinqAstWalker.groovy  |  3 +-
 .../provider/collection/runtime/Queryable.java     | 11 +++++
 .../collection/runtime/QueryableCollection.java    |  8 ++++
 .../groovy-ginq/src/spec/doc/ginq-userguide.adoc   | 15 ++++++
 .../test/org/apache/groovy/ginq/GinqTest.groovy    | 53 ++++++++++++++++++++--
 5 files changed, 84 insertions(+), 6 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 e7f7520f4c..ca3b30f3a1 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
@@ -1573,13 +1573,14 @@ class GinqAstWalker implements GinqAstVisitor<Expression>, SyntaxErrorReportable
     private static final String FUNCTION_MAX = 'max'
     private static final String FUNCTION_SUM = 'sum'
     private static final String FUNCTION_AVG = 'avg'
+    private static final String FUNCTION_LIST = 'list'
     private static final String FUNCTION_MEDIAN = 'median'
     private static final String FUNCTION_STDEV = 'stdev'
     private static final String FUNCTION_STDEVP = 'stdevp'
     private static final String FUNCTION_VAR = 'var'
     private static final String FUNCTION_VARP = 'varp'
     private static final String FUNCTION_AGG = 'agg'
-    private static final List<String> AGG_FUNCTION_NAME_LIST = [FUNCTION_COUNT, FUNCTION_MIN, FUNCTION_MAX, FUNCTION_SUM, FUNCTION_AVG, FUNCTION_MEDIAN, FUNCTION_STDEV, FUNCTION_STDEVP, FUNCTION_VAR, FUNCTION_VARP, FUNCTION_AGG]
+    private static final List<String> AGG_FUNCTION_NAME_LIST = [FUNCTION_COUNT, FUNCTION_MIN, FUNCTION_MAX, FUNCTION_SUM, FUNCTION_AVG, FUNCTION_MEDIAN, FUNCTION_STDEV, FUNCTION_STDEVP, FUNCTION_VAR, FUNCTION_VARP, FUNCTION_LIST, FUNCTION_AGG]
 
     private static final String FUNCTION_ROW_NUMBER = 'rowNumber'
     private static final String FUNCTION_LEAD = 'lead'
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 84dca68003..dd53faeda1 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
@@ -409,6 +409,17 @@ public interface Queryable<T> {
      */
     <U extends Comparable<? super U>> U max(Function<? super T, ? extends U> mapper);
 
+    /**
+     * Aggregate function {@code list}. There is no equivalent in standard SQL
+     * but various databases support a similar concept named: {@code list}, {@code listagg}, or {@code array_agg}.
+     *
+     * @param mapper choose the field to include in the list
+     * @param <U> the field type
+     * @return aggregate list result
+     * @since 4.0.14
+     */
+    <U> List<U> list(Function<? super T, ? extends U> mapper);
+
     /**
      * Aggregate function {@code median}, similar to SQL's {@code median}
      *
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 739b61c7fb..395013a94d 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
@@ -439,6 +439,14 @@ class QueryableCollection<T> implements Queryable<T>, Serializable {
                 .orElse(null));
     }
 
+    @Override
+    public <U> List<U> list(Function<? super T, ? extends U> mapper) {
+        return agg(q -> q.stream()
+            .map(mapper)
+            .filter(Objects::nonNull)
+            .collect(Collectors.toList()));
+    }
+
     @Override
     public BigDecimal median(Function<? super T, ? extends Number> mapper) {
         List<BigDecimal> sortedNumList = agg(q -> q.stream()
diff --git a/subprojects/groovy-ginq/src/spec/doc/ginq-userguide.adoc b/subprojects/groovy-ginq/src/spec/doc/ginq-userguide.adoc
index 4dfa3546a0..dbaeaa2f1d 100644
--- a/subprojects/groovy-ginq/src/spec/doc/ginq-userguide.adoc
+++ b/subprojects/groovy-ginq/src/spec/doc/ginq-userguide.adoc
@@ -361,6 +361,11 @@ GINQ provides some built-in aggregate functions:
 |java.math.BigDecimal
 |the average (arithmetic mean) of all non-null values
 
+|list(_expression_)
+|any
+|java.util.List
+|the aggregated list of all non-null values
+
 |median(_expression_)
 |java.lang.Number
 |java.math.BigDecimal
@@ -423,6 +428,16 @@ include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_grouping_11,in
 include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_grouping_12,indent=0]
 ----
 
+[source, sql]
+----
+include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_grouping_14,indent=0]
+----
+
+[source, sql]
+----
+include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_grouping_15,indent=0]
+----
+
 [source, sql]
 ----
 include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_grouping_03,indent=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 1a9bbc0cb7..b3ea4bbe05 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
@@ -3018,6 +3018,49 @@ class GinqTest {
         '''
     }
 
+    @Test
+    void "testGinq - from groupby select - 32"() {
+        assertGinqScript '''
+// tag::ginq_grouping_14[]
+            assert [['A', ['APPLE', 'APRICOT']],
+                    ['B', ['BANANA']],
+                    ['C', ['CANTALOUPE']]] == GQL {
+                from fruit in ['Apple', 'Apricot', 'Banana', 'Cantaloupe']
+                groupby fruit[0] as firstChar
+                select firstChar, list(fruit.toUpperCase()) as fruit_list
+            }
+// end::ginq_grouping_14[]
+        '''
+    }
+
+    @Test
+    void "testGinq - from groupby select - 33"() {
+        assertGinqScript '''
+            @groovy.transform.EqualsAndHashCode
+            class Person {
+                String name
+                int weight
+                String gender
+
+                Person(String name, int weight, String gender) {
+                    this.name = name
+                    this.weight = weight
+                    this.gender = gender
+                }
+            }
+// tag::ginq_grouping_15[]
+            def persons = [new Person('Linda', 100, 'Female'),
+                           new Person('Daniel', 135, 'Male'),
+                           new Person('David', 122, 'Male')]
+            assert [['Male', ['Daniel', 'David']], ['Female', ['Linda']]] == GQL {
+                from p in persons
+                groupby p.gender
+                select p.gender, list(p.name)
+            }
+// end::ginq_grouping_15[]
+        '''
+    }
+
     @Test
     void "testGinq - from where groupby select - 1"() {
         assertGinqScript '''
@@ -4462,9 +4505,9 @@ class GinqTest {
         assert '3' == filterExpr.rightExpression.text
 
         assert ginqExpression.fromExpression.dataSourceExpr instanceof GinqExpression
-        BinaryExpression contructedFilterExpr1 = ((GinqExpression) ginqExpression.fromExpression.dataSourceExpr).whereExpression.filterExpr
-        assert Types.COMPARE_GREATER_THAN == contructedFilterExpr1.operation.type
-        assert '1' == contructedFilterExpr1.rightExpression.text
+        BinaryExpression constructedFilterExpr1 = ((GinqExpression) ginqExpression.fromExpression.dataSourceExpr).whereExpression.filterExpr
+        assert Types.COMPARE_GREATER_THAN == constructedFilterExpr1.operation.type
+        assert '1' == constructedFilterExpr1.rightExpression.text
 
         assert ginqExpression.joinExpressionList[0].dataSourceExpr !instanceof GinqExpression
     }
@@ -4611,8 +4654,8 @@ class GinqTest {
         }.ast
 
         MethodNode methodNode = ast.classes[0].methods.grep(e -> e.name == 'hello')[0]
-        ExpressionStatement delcareStatement = ((BlockStatement) methodNode.getCode()).getStatements()[0]
-        DeclarationExpression declarationExpression = delcareStatement.getExpression()
+        ExpressionStatement declareStatement = ((BlockStatement) methodNode.getCode()).getStatements()[0]
+        DeclarationExpression declarationExpression = declareStatement.getExpression()
         ClosureExpression closureException = declarationExpression.rightExpression
 
         GinqAstBuilder ginqAstBuilder = new GinqAstBuilder(sourceUnit)