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]')
+ }
}