[
https://issues.apache.org/jira/browse/GROOVY-12724?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18143769#comment-18143769
]
ASF GitHub Bot commented on GROOVY-12724:
-----------------------------------------
daniellansun commented on code in PR #3259:
URL: https://github.com/apache/groovy/pull/3259#discussion_r4550576103
##########
subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/QueryableHelper.groovy:
##########
@@ -111,6 +111,36 @@ class QueryableHelper {
return CompletableFuture.supplyAsync(() -> { function.apply(param) },
ThreadPoolHolder.THREAD_POOL)
}
+ /**
+ * Runs one clause body on the shared pool. The declared type is {@code
Object}
+ * so static inference never sees {@code CompletableFuture}: a parallel row
+ * was being typed as that future, and the generated bridge then cast the
row.
+ * The value returned to the runtime is still the future {@code select}
unwraps.
+ *
+ * @param supplier the clause body
+ * @return the asynchronous result
+ * @since 7.0.0
+ */
+ @GroovyABI(since = "7.0.0")
+ static Object supplyClause(Supplier<Object> supplier) {
+ return CompletableFuture.supplyAsync(supplier,
ThreadPoolHolder.THREAD_POOL)
Review Comment:
`supplyClause` stays `CompletableFuture.supplyAsync` on the GINQ pool.
`async` uses the runtime async executor, and `executeAsync(supplier, pool)`
returns an `Awaitable`, not a `CompletableFuture`. `select` unwraps with
`CompletableFuture.get()`. The declared type remains `Object` so a parallel row
is not inferred as the future.
##########
subprojects/groovy-ginq/src/test/groovy/org/apache/groovy/ginq/GinqCompileStaticTest.groovy:
##########
@@ -0,0 +1,237 @@
+/*
+ * 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.ginq
+
+import groovy.ginq.transform.GQ
+import groovy.transform.CompileStatic
+import org.apache.groovy.ginq.provider.collection.runtime.Queryable
+import org.junit.jupiter.api.Test
+
+import static groovy.test.GroovyAssert.assertScript
+
+/**
+ * GINQ SQL-like queries compiled with {@code @CompileStatic}.
+ */
+@CompileStatic
+final class GinqCompileStaticTest {
+
+ private final int minimum = 18
+
+ @Test
+ void macroFiltersAndProjects() {
+ List<Integer> numbers = [1, 2, 3, 4]
+ assert [4, 6, 8] == GQ {
+ from n in numbers
+ where n > 1
+ select n * 2
+ }.toList()
+ }
+
+ @Test
+ void macroAsList() {
+ assert [2, 3] == GQL {
+ from n in [1, 2, 3]
+ where n > 1
+ select n
+ }
+ }
+
+ @Test
+ void readsFieldsAndLocals() {
+ int extra = 1
+ assert [18, 21] == GQ {
+ from n in [15, 18, 21]
+ where n >= minimum - extra
+ select n
+ }.toList()
+ }
+
+ @Test
+ void localIsReadWhenTheQueryIsBuilt() {
+ int extra = 1
+ Queryable query = GQ {
+ from n in [1, 2, 3, 4]
+ where n > extra
+ select n
+ }
+ extra = 3
+ // Locals are copied when the expression is evaluated. The copy is
what runs later.
+ assert [2, 3, 4] == query.toList()
+ }
+
+ @Test
+ void joinsProjectsAndGroups() {
+ List<Item> items = [new Item(1, 'Orange'), new Item(2, 'Apple')]
+ List<Sale> sales = [new Sale(1), new Sale(1), new Sale(2)]
+ assert ['Orange', 'Orange', 'Apple'] == GQ {
+ from s in sales
+ join i in items on i.id == s.item
+ select i.name
+ }.toList()
+
+ assert [[0, 6.0], [1, 4.0]] == GQ {
+ from n in [1, 2, 3, 4]
+ groupby n % 2 as parity
+ orderby parity
+ select parity, sum(n)
+ }.toList()
+ }
+
+ @Test
+ void windowRowNumber() {
+ assert [[1, 0], [2, 1], [3, 2]] == GQ {
+ from n in [1, 2, 3]
+ select n, (rowNumber() over(orderby n))
+ }.toList()
+ }
+
+ @Test
+ void nestedQueryAndMapRows() {
+ assert [3] == GQ {
+ from n in [1, 2, 3]
+ where n > (from m in [1, 2] select max(m))
+ select n
+ }.toList()
+
+ List<Map<String, Integer>> rows = [[a: 1, b: 10], [a: 3, b: 30]]
+ assert [30] == GQ {
+ from r in rows
+ where r.a > 1
+ select r.b
+ }.toList()
+ }
+
+ @Test
+ void queryInsideClosure() {
+ List<Integer> numbers = [1, 2, 3]
+ List found = []
+ numbers.each { Integer n ->
+ if (n == 1) {
+ found.addAll(GQ {
+ from m in numbers
+ where m >= n
+ select m
+ }.toList())
+ }
+ }
+ assert [1, 2, 3] == found
+ }
+
+ @Test
+ void orderByDescendingAndMembership() {
+ assert [3, 2, 1] == GQ {
+ from n in [1, 2, 3]
+ orderby n in desc
+ select n
+ }.toList()
+
+ assert [1, 3] == GQ {
+ from n in [1, 2, 3]
+ where n in [1, 3]
+ select n
+ }.toList()
+ }
+
+ @Test
+ void parallelClauseKeepsRowValues() {
+ assert [3, 2, 1] == GQ(parallel: true) {
+ from n in [1, 2, 3]
+ orderby n in desc
+ select n
+ }.toList()
+ }
+
+ @Test
+ void staticMethodAndAnnotation() {
+ assert [1, 2, 3] == staticNumbers()
+ assert [2, 3] == above([1, 2, 3], 2).toList()
+ assert [3, 4] == listed(2)
+ }
+
+ @Test
+ void compiledClassInAScript() {
+ assertScript '''
+ import groovy.transform.CompileStatic
+
+ @CompileStatic
+ class Shop {
+ List names(List<String> items) {
+ GQ {
+ from item in items
+ where item.size() > 3
+ select item.toUpperCase()
+ }.toList()
+ }
+ }
+
+ assert ['PEAR'] == new Shop().names(['pear', 'fig'])
+ '''
+ }
+
+ @Test
+ void customAggregateFromTheDsl() {
+ assertScript '''
+ def result = GQ {
+ from n in [1, 2, 3]
+ groupby n % 2 as k
+ select total(n)
+ }.toList()
+ assert result.toSet() == [new BigDecimal('4'), new
BigDecimal('2')].toSet()
Review Comment:
`total()` sums with `BigDecimal`, including for integral inputs, so the
values are BigDecimals. The assertion is now `[4.0, 2.0].toSet()`.
> GINQ: support static compilation and a fluent query API
> -------------------------------------------------------
>
> Key: GROOVY-12724
> URL: https://issues.apache.org/jira/browse/GROOVY-12724
> Project: Groovy
> Issue Type: Improvement
> Components: GINQ
> Reporter: Daniel Sun
> Assignee: Daniel Sun
> Priority: Major
>
> Under @CompileStatic / @TypeChecked, GQ, GQL, and @GQ currently rely on
> dynamic lowering. This improvement makes those entry points usable from a
> statically checked scope while keeping the SQL-like syntax lowered to the
> in-memory collection runtime.
> h3. Goals
> * Allow GQ / GQL / @GQ from @CompileStatic and @TypeChecked without forcing
> the lowered tree through static checking at the expression site.
> * Defer pipeline construction with LazyQueryable until a terminal read
> (toList, count, etc.), copying locals at the expression site (same binding as
> dynamic GQ).
> * Add Ginq.from(...) as a deferred, generic fluent chain on the same runtime,
> with @ClosureParams so where/select/join/aggregate closures can infer element
> types where inference is sound.
> * Preserve default lazy/deferred evaluation and dynamic GQ behavior for map
> rows and other intentionally dynamic cases.
> h3. Non-goals / known limits
> * Stored untyped Closure variables still need a SAM target type for parameter
> inference (same as Java/C#).
> * Raw Query parameters/fields cannot recover a safe element type.
> * Names bound by from inside GQ / GQL / @GQ stay dynamic by design; callers
> that need checked element types use Ginq.from.
> h3. Related work
> GitHub PR: https://github.com/apache/groovy/pull/3259
> Stacked on GEP-26 SQL window work (PR #3167 / gep26-sql-window).
--
This message was sent by Atlassian Jira
(v10.3.26#10030026)