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:56 UTC
[groovy] branch GROOVY_4_0_X updated (710de13c2b -> bbe34976c1)
This is an automated email from the ASF dual-hosted git repository.
paulk pushed a change to branch GROOVY_4_0_X
in repository https://gitbox.apache.org/repos/asf/groovy.git
from 710de13c2b `groovy-contracts`: fix dsld errors and matching
new c81e217838 GROOVY-11155: Add DGM variants of count(Iterable|Iterator|Map, Closure) with an initial value
new bbe34976c1 GROOVY-11157: Provide a GINQ aggregate function shorthand for returning a list of the selected items
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
.../groovy/runtime/DefaultGroovyMethods.java | 96 ++++++++++++++++++----
.../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 ++++++++++--
6 files changed, 166 insertions(+), 20 deletions(-)
[groovy] 02/02: GROOVY-11157: Provide a GINQ aggregate function shorthand for returning a list of the selected items
Posted by pa...@apache.org.
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)
[groovy] 01/02: GROOVY-11155: Add DGM variants of count(Iterable|Iterator|Map, Closure) with an initial value
Posted by pa...@apache.org.
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 c81e217838e31a643d46d325b5e7ad35f9d7053e
Author: Paul King <pa...@asert.com.au>
AuthorDate: Wed Aug 16 19:46:17 2023 +1000
GROOVY-11155: Add DGM variants of count(Iterable|Iterator|Map, Closure) with an initial value
---
.../groovy/runtime/DefaultGroovyMethods.java | 96 ++++++++++++++++++----
1 file changed, 82 insertions(+), 14 deletions(-)
diff --git a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
index cf884c60fc..397f9a673f 100644
--- a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
@@ -3063,7 +3063,9 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport {
* The iterator will become exhausted of elements after determining the count value.
* <p>
* Example usage:
- * <pre class="groovyTestCase">assert [2,4,2,1,3,5,2,4,3].toSet().iterator().count{ it % 2 == 0 } == 2</pre>
+ * <pre class="groovyTestCase">
+ * assert [2,4,2,1,3,5,2,4,3].toSet().iterator().count{ it % 2 == 0 } == 2
+ * </pre>
*
* @param self the Iterator from which we count the number of matching occurrences
* @param closure a closure condition
@@ -3071,16 +3073,37 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport {
* @since 1.8.0
*/
public static <T> Number count(Iterator<T> self, @ClosureParams(FirstParam.FirstGenericType.class) Closure closure) {
- long answer = 0;
+ long answer = count(self, 0L, closure);
+ // for b/c with Java return an int if we can
+ if (answer <= Integer.MAX_VALUE) return (int) answer;
+ return answer;
+ }
+
+ /**
+ * Counts the number of occurrences which satisfy the given closure from the
+ * items within this Iterator, adding the count to the initial count.
+ * The iterator will become exhausted of elements after determining the count value.
+ * <p>
+ * Example usage:
+ * <pre class="groovyTestCase">
+ * assert [2,4,2,1,3,5,2,4,3].toSet().iterator().count(100){ it % 2 == 0 } == 102
+ * </pre>
+ *
+ * @param self the Iterator from which we count the number of matching occurrences
+ * @param initialCount start counting from this value
+ * @param closure a closure condition
+ * @return the number of occurrences
+ * @since 4.0.14
+ */
+ public static <T, E extends Number> E count(Iterator<T> self, E initialCount, @ClosureParams(FirstParam.FirstGenericType.class) Closure closure) {
+ E count = initialCount;
BooleanClosureWrapper bcw = new BooleanClosureWrapper(closure);
while (self.hasNext()) {
if (bcw.call(self.next())) {
- ++answer;
+ count = (E) NumberNumberPlus.plus(count, 1);
}
}
- // for b/c with Java return an int if we can
- if (answer <= Integer.MAX_VALUE) return (int) answer;
- return answer;
+ return count;
}
/**
@@ -3089,7 +3112,9 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport {
* <code>compareTo(value) == 0</code> or <code>equals(value)</code> ).
* <p>
* Example usage:
- * <pre class="groovyTestCase">assert [2,4,2,1,3,5,2,4,3].count(4) == 2</pre>
+ * <pre class="groovyTestCase">
+ * assert [2,4,2,1,3,5,2,4,3].count(4) == 2
+ * </pre>
*
* @param self the Iterable within which we count the number of occurrences
* @param value the value being searched for
@@ -3104,7 +3129,9 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport {
* Counts the number of occurrences which satisfy the given closure from inside this Iterable.
* <p>
* Example usage:
- * <pre class="groovyTestCase">assert [2,4,2,1,3,5,2,4,3].count{ it % 2 == 0 } == 5</pre>
+ * <pre class="groovyTestCase">
+ * assert [2,4,2,1,3,5,2,4,3].count{ it % 2 == 0 } == 5
+ * </pre>
*
* @param self the Iterable within which we count the number of occurrences
* @param closure a closure condition
@@ -3115,13 +3142,33 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport {
return count(self.iterator(), closure);
}
+ /**
+ * Counts the number of occurrences which satisfy the given closure from inside this Iterable.
+ * <p>
+ * Example usage:
+ * <pre class="groovyTestCase">
+ * assert [2,4,2,1,3,5,2,4,3].count(100L){ it % 2 == 0 } == 105L
+ * </pre>
+ *
+ * @param self the Iterable within which we count the number of occurrences
+ * @param initialCount start counting from this value
+ * @param closure a closure condition
+ * @return the number of occurrences
+ * @since 4.0.14
+ */
+ public static <T, E extends Number> E count(Iterable<T> self, E initialCount, @ClosureParams(FirstParam.FirstGenericType.class) Closure closure) {
+ return count(self.iterator(), initialCount, closure);
+ }
+
/**
* Counts the number of occurrences which satisfy the given closure from inside this map.
* If the closure takes one parameter then it will be passed the Map.Entry.
* Otherwise, the closure should take two parameters and will be passed the key and value.
* <p>
* Example usage:
- * <pre class="groovyTestCase">assert [a:1, b:1, c:2, d:2].count{ k,v {@code ->} k == 'a' {@code ||} v == 2 } == 3</pre>
+ * <pre class="groovyTestCase">
+ * assert [a:1, b:1, c:2, d:2].count{ k,v {@code ->} k == 'a' {@code ||} v == 2 } == 3
+ * </pre>
*
* @param self the map within which we count the number of occurrences
* @param closure a 1 or 2 arg Closure condition applying on the entries
@@ -3129,16 +3176,37 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport {
* @since 1.8.0
*/
public static <K,V> Number count(Map<K,V> self, @ClosureParams(MapEntryOrKeyValue.class) Closure<?> closure) {
- long answer = 0;
+ long answer = count(self, 0L, closure);
+ // for b/c with Java return an int if we can
+ if (answer <= Integer.MAX_VALUE) return (int) answer;
+ return answer;
+ }
+
+ /**
+ * Counts the number of occurrences which satisfy the given closure from inside this map.
+ * If the closure takes one parameter then it will be passed the Map.Entry.
+ * Otherwise, the closure should take two parameters and will be passed the key and value.
+ * <p>
+ * Example usage:
+ * <pre class="groovyTestCase">
+ * assert [a:1, b:1, c:2, d:2].count(100G){ k,v {@code ->} k == 'a' {@code ||} v == 2 } == 103G
+ * </pre>
+ *
+ * @param self the map within which we count the number of occurrences
+ * @param initialCount start counting from this value
+ * @param closure a 1 or 2 arg Closure condition applying on the entries
+ * @return the number of occurrences
+ * @since 4.0.14
+ */
+ public static <K,V, E extends Number> E count(Map<K,V> self, E initialCount, @ClosureParams(MapEntryOrKeyValue.class) Closure<?> closure) {
+ E count = initialCount;
BooleanClosureWrapper bcw = new BooleanClosureWrapper(closure);
for (Map.Entry<K, V> entry : self.entrySet()) {
if (bcw.callForMap(entry)) {
- ++answer;
+ count = (E) NumberNumberPlus.plus(count, 1);
}
}
- // for b/c with Java return an int if we can
- if (answer <= Integer.MAX_VALUE) return (int) answer;
- return answer;
+ return count;
}
/**