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)