You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2021/07/19 11:07:05 UTC

[groovy] branch master updated: Support `distinct` in GINQ

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

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


The following commit(s) were added to refs/heads/master by this push:
     new b8131c4  Support `distinct` in GINQ
b8131c4 is described below

commit b8131c4939d33b34ad145a9b227efa8706c979f4
Author: Daniel Sun <su...@apache.org>
AuthorDate: Mon Jul 19 19:06:51 2021 +0800

    Support `distinct` in GINQ
---
 .../org/apache/groovy/ginq/dsl/GinqAstBuilder.java | 36 ++++++++++++++++---
 .../ginq/provider/collection/GinqAstWalker.groovy  |  5 +++
 .../groovy-ginq/src/spec/doc/ginq-userguide.adoc   | 20 ++++++-----
 .../test/org/apache/groovy/ginq/GinqTest.groovy    | 40 ++++++++++++++++++++++
 .../org/apache/groovy/ginq/GinqErrorTest.groovy    | 11 ++++++
 5 files changed, 100 insertions(+), 12 deletions(-)

diff --git a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/dsl/GinqAstBuilder.java b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/dsl/GinqAstBuilder.java
index 379472a..9c8b1b4 100644
--- a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/dsl/GinqAstBuilder.java
+++ b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/dsl/GinqAstBuilder.java
@@ -42,6 +42,7 @@ import org.codehaus.groovy.ast.expr.DeclarationExpression;
 import org.codehaus.groovy.ast.expr.Expression;
 import org.codehaus.groovy.ast.expr.MethodCallExpression;
 import org.codehaus.groovy.ast.expr.PropertyExpression;
+import org.codehaus.groovy.ast.expr.TupleExpression;
 import org.codehaus.groovy.ast.expr.VariableExpression;
 import org.codehaus.groovy.ast.stmt.BlockStatement;
 import org.codehaus.groovy.ast.stmt.ExpressionStatement;
@@ -64,6 +65,7 @@ import java.util.Set;
  */
 public class GinqAstBuilder extends CodeVisitorSupport implements SyntaxErrorReportable {
     public static final String ROOT_GINQ_EXPRESSION = "__ROOT_GINQ_EXPRESSION";
+    public static final String GINQ_SELECT_DISTINCT = "__GINQ_SELECT_DISTINCT";
     private final Deque<GinqExpression> ginqExpressionStack = new ArrayDeque<>();
     private GinqExpression latestGinqExpression;
     private final SourceUnit sourceUnit;
@@ -151,11 +153,11 @@ public class GinqAstBuilder extends CodeVisitorSupport implements SyntaxErrorRep
     @Override
     public void visitMethodCallExpression(MethodCallExpression call) {
         final String methodName = call.getMethodAsString();
-        if ("over".equals(methodName)) {
+        if (KW_OVER.equals(methodName)) {
             visitingOverClause = true;
         }
         super.visitMethodCallExpression(call);
-        if ("over".equals(methodName)) {
+        if (KW_OVER.equals(methodName)) {
             visitingOverClause = false;
         }
 
@@ -329,7 +331,31 @@ public class GinqAstBuilder extends CodeVisitorSupport implements SyntaxErrorRep
         }
 
         if (KW_SELECT.equals(methodName)) {
-            SelectExpression selectExpression = new SelectExpression(call.getArguments());
+            TupleExpression tupleExpression = (TupleExpression) call.getArguments();
+            if (1 == tupleExpression.getExpressions().size()) {
+                Expression firstExpression = tupleExpression.getExpressions().get(0);
+                if (firstExpression instanceof MethodCallExpression) {
+                    MethodCallExpression mce = (MethodCallExpression) firstExpression;
+                    if (KW_DISTINCT.equals(mce.getMethodAsString())) {
+                        tupleExpression = (TupleExpression) mce.getArguments();
+                        currentGinqExpression.putNodeMetaData(GINQ_SELECT_DISTINCT, true);
+                    }
+                }
+            } else {
+                for (Expression expression : tupleExpression.getExpressions()) {
+                    if (expression instanceof MethodCallExpression) {
+                        MethodCallExpression mce = (MethodCallExpression) expression;
+                        if (KW_DISTINCT.equals(mce.getMethodAsString())) {
+                            this.collectSyntaxError(new GinqSyntaxError(
+                                    "Invalid usage of `distinct`",
+                                    mce.getLineNumber(), mce.getColumnNumber()
+                            ));
+                        }
+                    }
+                }
+            }
+
+            SelectExpression selectExpression = new SelectExpression(tupleExpression);
             selectExpression.setSourcePosition(call.getMethod());
 
             currentGinqExpression.setSelectExpression(selectExpression);
@@ -450,9 +476,11 @@ public class GinqAstBuilder extends CodeVisitorSupport implements SyntaxErrorRep
     private static final String KW_LIMIT = "limit";
     private static final String KW_SELECT = "select";
     private static final String KW_SHUTDOWN = "shutdown";
+    private static final String KW_DISTINCT = "distinct";
+    private static final String KW_OVER = "over";
     private static final Set<String> KEYWORD_SET = new HashSet<>();
     static {
-        KEYWORD_SET.addAll(Arrays.asList(KW_FROM, KW_WHERE, KW_ON, KW_HAVING, KW_EXISTS, KW_GROUPBY, KW_ORDERBY, KW_LIMIT, KW_SELECT, KW_SHUTDOWN));
+        KEYWORD_SET.addAll(Arrays.asList(KW_FROM, KW_WHERE, KW_ON, KW_HAVING, KW_EXISTS, KW_GROUPBY, KW_ORDERBY, KW_LIMIT, KW_SELECT, KW_DISTINCT, KW_OVER, KW_SHUTDOWN));
         KEYWORD_SET.addAll(JoinExpression.JOIN_NAME_LIST);
     }
 }
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 819f979..3333790 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
@@ -202,6 +202,11 @@ class GinqAstWalker implements GinqAstVisitor<Expression>, SyntaxErrorReportable
         }
 
         final resultName = "__r${System.nanoTime()}"
+
+        Boolean distinct = ginqExpression.getNodeMetaData(GinqAstBuilder.GINQ_SELECT_DISTINCT)
+        if (distinct) {
+            selectMethodCallExpression = callX(selectMethodCallExpression, "distinct")
+        }
         statementList << declS(localVarX(resultName).tap {it.modifiers |= Opcodes.ACC_FINAL}, selectMethodCallExpression)
 
         if (parallelEnabled) {
diff --git a/subprojects/groovy-ginq/src/spec/doc/ginq-userguide.adoc b/subprojects/groovy-ginq/src/spec/doc/ginq-userguide.adoc
index 7c19105..cc5c708 100644
--- a/subprojects/groovy-ginq/src/spec/doc/ginq-userguide.adoc
+++ b/subprojects/groovy-ginq/src/spec/doc/ginq-userguide.adoc
@@ -141,6 +141,18 @@ Construct new objects as column values:
 include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_projection_02,indent=0]
 ----
 
+===== Distinct
+`distinct` is equivalent to SQL's `DISTINCT`
+[source, groovy]
+----
+include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_distinct_1,indent=0]
+----
+
+[source, groovy]
+----
+include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_distinct_2,indent=0]
+----
+
 ==== Filtering
 `where` is equivalent to SQL's `WHERE`
 
@@ -875,15 +887,7 @@ include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_winfunction_38
 include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_tips_05,indent=0]
 ----
 
-==== Distinct
-GINQ does not support `distinct` of SQL for now, but we could invoke `distinct()` method to workaround:
-[source, groovy]
-----
-include::../test/org/apache/groovy/ginq/GinqTest.groovy[tags=ginq_tips_14,indent=0]
-----
-
 ==== List Comprehension
-
 List comprehension is an elegant way to define and create lists based on existing lists:
 [source, groovy]
 ----
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 91c0d21..8220c90 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
@@ -235,6 +235,46 @@ class GinqTest {
     }
 
     @Test
+    void "testGinq - from select distinct - 1"() {
+        assertGinqScript '''
+// tag::ginq_distinct_1[]
+            def result = GQ {
+                from n in [1, 2, 2, 3, 3, 3]
+                select distinct(n)
+            }
+            assert [1, 2, 3] == result.toList()
+// end::ginq_distinct_1[]
+        '''
+    }
+
+    @Test
+    void "testGinq - from select distinct - 2"() {
+        assertGinqScript '''
+// tag::ginq_distinct_2[]
+            def result = GQ {
+                from n in [1, 2, 2, 3, 3, 3]
+                select distinct(n, n + 1)
+            }
+            assert [[1, 2], [2, 3], [3, 4]] == result.toList()
+// end::ginq_distinct_2[]
+        '''
+    }
+
+    @Test
+    void "testGinq - from select distinct - 3"() {
+        assertGinqScript '''
+            def result = GQ {
+                from v in (
+                    from n in [1, 2, 2, 3, 3, 3]
+                    select distinct(n)
+                )
+                select v
+            }
+            assert [1, 2, 3] == result.toList()
+        '''
+    }
+
+    @Test
     void "testGinq - from where select - 1"() {
         assertGinqScript '''
             def numbers = [0, 1, 2, 3, 4, 5]
diff --git a/subprojects/groovy-ginq/src/test/groovy/org/apache/groovy/ginq/GinqErrorTest.groovy b/subprojects/groovy-ginq/src/test/groovy/org/apache/groovy/ginq/GinqErrorTest.groovy
index 0beefad..bea17c9 100644
--- a/subprojects/groovy-ginq/src/test/groovy/org/apache/groovy/ginq/GinqErrorTest.groovy
+++ b/subprojects/groovy-ginq/src/test/groovy/org/apache/groovy/ginq/GinqErrorTest.groovy
@@ -49,6 +49,17 @@ class GinqErrorTest {
     }
 
     @Test
+    void "testGinq - from select distinct - 1"() {
+        def err = shouldFail '''\
+            GQ {
+                from n in [1, 2, 2, 3, 3, 3]
+                select n, distinct(n + 1)
+            }
+        '''
+        assert err.toString().contains('Invalid usage of `distinct` @ line 3, column 27.')
+    }
+
+    @Test
     void "testGinq - select from - 1"() {
         def err = shouldFail '''\
             GQ {