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 2020/11/28 05:45:28 UTC

[groovy] branch master updated: Support sub-query in `select` clause

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 7202117  Support sub-query in `select` clause
7202117 is described below

commit 7202117754d219bb17e30bef5b39df58824f3440
Author: Daniel Sun <su...@apache.org>
AuthorDate: Sat Nov 28 13:44:42 2020 +0800

    Support sub-query in `select` clause
---
 .../codehaus/groovy/ast/expr/CastExpression.java   |   6 +-
 .../org/apache/groovy/ginq/dsl/GinqAstBuilder.java |  30 ++++++
 .../ginq/provider/collection/GinqAstWalker.groovy  |   8 ++
 .../collection/runtime/AsciiTableMaker.groovy      |   2 +-
 .../collection/runtime/QueryableCollection.java    |   2 +-
 .../collection/runtime/QueryableHelper.groovy      |  21 ++++
 .../collection/runtime/TooManyValuesException.java |  34 +++++++
 .../test/org/apache/groovy/ginq/GinqTest.groovy    | 108 +++++++++++++++++++++
 .../org/apache/groovy/ginq/GinqErrorTest.groovy    |  16 +++
 9 files changed, 224 insertions(+), 3 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/ast/expr/CastExpression.java b/src/main/java/org/codehaus/groovy/ast/expr/CastExpression.java
index 7d5592b..b760573 100644
--- a/src/main/java/org/codehaus/groovy/ast/expr/CastExpression.java
+++ b/src/main/java/org/codehaus/groovy/ast/expr/CastExpression.java
@@ -28,7 +28,7 @@ import java.util.Objects;
  */
 public class CastExpression extends Expression {
 
-    private final Expression expression;
+    private Expression expression;
     private final boolean ignoreAutoboxing;
 
     private boolean coerce;
@@ -54,6 +54,10 @@ public class CastExpression extends Expression {
         return expression;
     }
 
+    public void setExpression(Expression expression) {
+        this.expression = expression;
+    }
+
     public boolean isIgnoringAutoboxing() {
         return ignoreAutoboxing;
     }
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 88567ee..5ab1856 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
@@ -35,6 +35,7 @@ import org.apache.groovy.ginq.dsl.expression.WhereExpression;
 import org.codehaus.groovy.ast.CodeVisitorSupport;
 import org.codehaus.groovy.ast.expr.ArgumentListExpression;
 import org.codehaus.groovy.ast.expr.BinaryExpression;
+import org.codehaus.groovy.ast.expr.CastExpression;
 import org.codehaus.groovy.ast.expr.DeclarationExpression;
 import org.codehaus.groovy.ast.expr.Expression;
 import org.codehaus.groovy.ast.expr.MethodCallExpression;
@@ -46,6 +47,7 @@ import java.util.ArrayDeque;
 import java.util.Arrays;
 import java.util.Deque;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Set;
 
 /**
@@ -316,6 +318,34 @@ public class GinqAstBuilder extends CodeVisitorSupport implements SyntaxErrorRep
         super.visitDeclarationExpression(expression);
     }
 
+    @Override
+    public void visitCastExpression(CastExpression expression) {
+        super.visitCastExpression(expression);
+
+        if (null != latestGinqExpression && isSelectMethodCallExpression(expression.getExpression())) {
+            // use the nested ginq and clear it
+            expression.setExpression(latestGinqExpression);
+            latestGinqExpression = null;
+        }
+    }
+
+    @Override
+    public void visitArgumentlistExpression(ArgumentListExpression expression) {
+        List<Expression> list = expression.getExpressions();
+        if (list != null) {
+            for (int i = 0, n = list.size(); i < n; i++) {
+                Expression expr = list.get(i);
+                expr.visit(this);
+
+                if (null != latestGinqExpression && isSelectMethodCallExpression(expr)) {
+                    // use the nested ginq and clear it
+                    list.set(i, latestGinqExpression);
+                    latestGinqExpression = null;
+                }
+            }
+        }
+    }
+
     private static boolean isSelectMethodCallExpression(Expression expression) {
         return expression instanceof MethodCallExpression && KW_SELECT.equals(((MethodCallExpression) expression).getMethodAsString());
     }
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 01ded49..27cad73 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
@@ -18,6 +18,7 @@
  */
 package org.apache.groovy.ginq.provider.collection
 
+
 import groovy.transform.CompileStatic
 import org.apache.groovy.ginq.dsl.GinqAstVisitor
 import org.apache.groovy.ginq.dsl.GinqSyntaxError
@@ -422,6 +423,13 @@ class GinqAstWalker implements GinqAstVisitor<Expression>, SyntaxErrorReportable
                     }
                 }
 
+                if (expression instanceof AbstractGinqExpression) {
+                    return callX(
+                            new ClassExpression(QUERYABLE_HELPER_TYPE), "singleValue",
+                            callX(GinqAstWalker.this.visit((AbstractGinqExpression) expression),"toList")
+                    )
+                }
+
                 return expression.transformExpression(this)
             }
         })).getExpression(0)
diff --git a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/AsciiTableMaker.groovy b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/AsciiTableMaker.groovy
index 4d8236c..dd379f6 100644
--- a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/AsciiTableMaker.groovy
+++ b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/AsciiTableMaker.groovy
@@ -55,7 +55,7 @@ class AsciiTableMaker {
                     }
                 })
 
-                return makeAsciiTable(list, DEFAULT_MAX_WIDTH, true)
+                return "\n" + makeAsciiTable(list, DEFAULT_MAX_WIDTH, true)
             }
         }
 
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 5ec3010..5aa9aaf 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
@@ -371,6 +371,6 @@ class QueryableCollection<T> implements Queryable<T>, Serializable {
 
     @Override
     public String toString() {
-        return "\n" + AsciiTableMaker.makeAsciiTable(this);
+        return AsciiTableMaker.makeAsciiTable(this);
     }
 }
diff --git a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/QueryableHelper.groovy b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/QueryableHelper.groovy
index 23616d0..66b90d3 100644
--- a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/QueryableHelper.groovy
+++ b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/QueryableHelper.groovy
@@ -48,5 +48,26 @@ class QueryableHelper {
         return from(sourceRecordList)
     }
 
+    /**
+     * Returns single value of list
+     *
+     * @param list the list
+     * @return the single value
+     * @throws TooManyValuesException if list contains more than one values
+     * @since 4.0.0
+     */
+    static <T> T singleValue(final List<? extends T> list) {
+        int size = list.size()
+
+        if (0 == size) {
+            return null
+        }
+        if (1 == size) {
+            return list.get(0)
+        }
+
+        throw new TooManyValuesException("subquery returns more than one value: $list")
+    }
+
     private QueryableHelper() {}
 }
diff --git a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/TooManyValuesException.java b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/TooManyValuesException.java
new file mode 100644
index 0000000..415bf44
--- /dev/null
+++ b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/runtime/TooManyValuesException.java
@@ -0,0 +1,34 @@
+/*
+ *  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.provider.collection.runtime;
+
+import groovy.lang.GroovyRuntimeException;
+
+/**
+ * Thrown when too many values returned by sub-query in the {@code select} clause
+ *
+ * @since 4.0.0
+ */
+public class TooManyValuesException extends GroovyRuntimeException {
+    private static final long serialVersionUID = -2479599910868287387L;
+
+    public TooManyValuesException(String msg) {
+        super(msg);
+    }
+}
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 e93987b..7698b74 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
@@ -2672,6 +2672,114 @@ class GinqTest {
     }
 
     @Test
+    void "testGinq - subQuery - 0"() {
+        assertScript '''
+            assert [[null], [2], [3]] == GQ {
+                from n in [1, 2, 3]
+                select (
+                    from m in [2, 3, 4]
+                    where m == n
+                    select m   
+                ) as sq
+            }.toList()
+        '''
+    }
+
+    @Test
+    void "testGinq - subQuery - 1"() {
+        assertScript '''
+            assert [[1, null], [2, 2], [3, 3]] == GQ {
+                from n in [1, 2, 3]
+                select n, (
+                    from m in [2, 3, 4]
+                    where m == n
+                    select m   
+                ) as sq
+            }.toList()
+        '''
+    }
+
+    @Test
+    void "testGinq - subQuery - 2"() {
+        assertScript '''
+            assert [[1, null], [2, 2], [3, 3]] == GQ {
+                from n in [1, 2, 3]
+                innerjoin k in [1, 2, 3] on k == n
+                select n, (
+                    from m in [2, 3, 4]
+                    where m == n && m == k
+                    select m   
+                ) as sq
+            }.toList()
+        '''
+    }
+
+    @Test
+    void "testGinq - subQuery - 3"() {
+        assertScript '''
+            assert [null, 2, 3] == GQ {
+                from n in [1, 2, 3]
+                innerjoin k in [1, 2, 3] on k == n
+                select (
+                    from m in [2, 3, 4]
+                    where m == n && m == k
+                    select m   
+                )
+            }.toList()
+        '''
+    }
+
+    @Test
+    void "testGinq - subQuery - 4"() {
+        assertScript '''
+            assert [[1, null], [2, 2], [3, 3]] == GQ {
+                from n in [1, 2, 3]
+                innerjoin k in [1, 2, 3] on k == n
+                select n, (
+                    from m in [2, 3, 4]
+                    where m == n && m == k
+                    select m   
+                )
+            }.toList()
+        '''
+    }
+
+    @Test
+    void "testGinq - subQuery - 5"() {
+        assertScript '''
+            assert [null, 2, 3] == GQ {
+                from n in [1, 2, 3]
+                select (
+                    from m in [2, 3, 4]
+                    where m == n
+                    limit 1
+                    select m   
+                )
+            }.toList()
+        '''
+    }
+
+    @Test
+    void "testGinq - subQuery - 6"() {
+        assertScript '''
+            assert [[null, null], [2, null], [3, 3]] == GQ {
+                from n in [1, 2, 3]
+                select (
+                    from m in [2, 3, 4]
+                    where m == n
+                    limit 1
+                    select m   
+                ) as sq1, (
+                    from m in [3, 4, 5]
+                    where m == n
+                    limit 1
+                    select m   
+                ) as sq2
+            }.toList()
+        '''
+    }
+
+    @Test
     void "testGinq - lazy - 1"() {
         assertScript '''
             int cnt = 0
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 77ceaf3..4ce1eeb 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
@@ -291,4 +291,20 @@ class GinqErrorTest {
 
         assert err.toString().contains('`where` clause cannot contain assignment expression @ line 3, column 17.')
     }
+
+    @Test
+    void "testGinq - subQuery - 1"() {
+        def err = shouldFail '''
+            GQ {
+                from n in [2, 3]
+                select n, (
+                    from m in [2, 3, 4]
+                    where m > n
+                    select m   
+                ) as q
+            }.toList()
+        '''
+
+        assert err.toString().contains('subquery returns more than one value: [3, 4]')
+    }
 }