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/12/05 14:42:33 UTC
[groovy] branch master updated: Tweak optimizer of GINQ to cover
leftjoin cases
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 32648a5 Tweak optimizer of GINQ to cover leftjoin cases
32648a5 is described below
commit 32648a53cb5adb87b238a82af08f9e4d128f2094
Author: Daniel Sun <su...@apache.org>
AuthorDate: Sat Dec 5 21:15:48 2020 +0800
Tweak optimizer of GINQ to cover leftjoin cases
---
.../apache/groovy/ginq/GinqGroovyMethods.groovy | 3 +-
.../collection => dsl}/GinqAstOptimizer.groovy | 111 +++++++++++++--------
.../groovy/ginq/dsl/expression/JoinExpression.java | 8 +-
.../test/org/apache/groovy/ginq/GinqTest.groovy | 59 ++++++++++-
4 files changed, 137 insertions(+), 44 deletions(-)
diff --git a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/GinqGroovyMethods.groovy b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/GinqGroovyMethods.groovy
index a7044cd..f870080 100644
--- a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/GinqGroovyMethods.groovy
+++ b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/GinqGroovyMethods.groovy
@@ -20,9 +20,9 @@ package org.apache.groovy.ginq
import groovy.transform.CompileStatic
import org.apache.groovy.ginq.dsl.GinqAstBuilder
+import org.apache.groovy.ginq.dsl.GinqAstOptimizer
import org.apache.groovy.ginq.dsl.GinqAstVisitor
import org.apache.groovy.ginq.dsl.expression.GinqExpression
-import org.apache.groovy.ginq.provider.collection.GinqAstOptimizer
import org.apache.groovy.ginq.provider.collection.GinqAstWalker
import org.apache.groovy.lang.annotation.Incubating
import org.codehaus.groovy.ast.ClassHelper
@@ -36,6 +36,7 @@ import org.codehaus.groovy.macro.runtime.Macro
import org.codehaus.groovy.macro.runtime.MacroContext
import static org.codehaus.groovy.ast.tools.GeneralUtils.asX
+
/**
* Declare GINQ macro methods
*
diff --git a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/GinqAstOptimizer.groovy b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/dsl/GinqAstOptimizer.groovy
similarity index 66%
rename from subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/GinqAstOptimizer.groovy
rename to subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/dsl/GinqAstOptimizer.groovy
index 0e169bb..e77d6ec 100644
--- a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/provider/collection/GinqAstOptimizer.groovy
+++ b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/dsl/GinqAstOptimizer.groovy
@@ -16,13 +16,13 @@
* specific language governing permissions and limitations
* under the License.
*/
-package org.apache.groovy.ginq.provider.collection
+package org.apache.groovy.ginq.dsl
import groovy.transform.CompileStatic
-import org.apache.groovy.ginq.dsl.GinqAstBaseVisitor
import org.apache.groovy.ginq.dsl.expression.DataSourceExpression
import org.apache.groovy.ginq.dsl.expression.FromExpression
import org.apache.groovy.ginq.dsl.expression.GinqExpression
+import org.apache.groovy.ginq.dsl.expression.JoinExpression
import org.apache.groovy.ginq.dsl.expression.SelectExpression
import org.apache.groovy.ginq.dsl.expression.WhereExpression
import org.codehaus.groovy.ast.expr.ArgumentListExpression
@@ -40,7 +40,7 @@ import java.util.stream.Collectors
/**
* Optimize the execution plan of GINQ through transforming AST.
* <p>
- * Note:The optimizer only optimizes the AST for inner joins for now, e.g.
+ * Note:The optimizer only optimizes the AST for inner/left joins for now, e.g.
*
* <pre>
* from n1 in nums1
@@ -75,16 +75,34 @@ class GinqAstOptimizer extends GinqAstBaseVisitor {
return null
}
- boolean allInnerJoin = ginqExpression.joinExpressionList.every {it.innerJoin}
- if (!allInnerJoin) return
+ List<DataSourceExpression> optimizingDataSourceExpressionList = []
+ optimizingDataSourceExpressionList << ginqExpression.fromExpression
+ for (JoinExpression joinExpression : ginqExpression.joinExpressionList) {
+ if (joinExpression.innerJoin) {
+ optimizingDataSourceExpressionList << joinExpression
+ } else if (joinExpression.leftJoin) {
+ break
+ } else {
+ optimizingDataSourceExpressionList.clear()
+ break
+ }
+ }
+
+ if (!optimizingDataSourceExpressionList) {
+ return null
+ }
+ final List<String> optimizingAliasList =
+ (List<String>) optimizingDataSourceExpressionList.stream()
+ .map((DataSourceExpression e) -> e.aliasExpr.text)
+ .collect(Collectors.toList())
- List<DataSourceExpression> dataSourceExpressionList = [].tap {
+ List<DataSourceExpression> allDataSourceExpressionList = [].tap {
it << ginqExpression.fromExpression
it.addAll(ginqExpression.joinExpressionList)
}
- final List<String> aliasList =
- (List<String>) dataSourceExpressionList.stream()
+ final List<String> allAliasList =
+ (List<String>) allDataSourceExpressionList.stream()
.map((DataSourceExpression e) -> e.aliasExpr.text)
.collect(Collectors.toList())
@@ -129,34 +147,9 @@ class GinqAstOptimizer extends GinqAstBaseVisitor {
})
candidatesToOptimize.stream()
- .forEach(e -> collectConditionsToOptimize(e, aliasList, conditionsToOptimize))
-
- conditionsToOptimize.forEach((String alias, List<Expression> conditions) -> {
- DataSourceExpression dataSourceExpression =
- dataSourceExpressionList.grep { DataSourceExpression e -> e.aliasExpr.text == alias }[0]
-
- if (dataSourceExpression) {
- GinqExpression contructedGinqExpression = new GinqExpression()
- String constructedAlias = "alias${System.nanoTime()}"
-
- List<Expression> transformedConditions =
- conditions.stream()
- .map(e -> correctVars(e, alias, constructedAlias))
- .collect(Collectors.toList())
-
- contructedGinqExpression.fromExpression =
- new FromExpression(new VariableExpression(constructedAlias), dataSourceExpression.dataSourceExpr)
- contructedGinqExpression.whereExpression =
- new WhereExpression(contructFilterExpr(transformedConditions))
- contructedGinqExpression.selectExpression =
- new SelectExpression(
- new ArgumentListExpression(
- Collections.singletonList(
- (Expression) new VariableExpression(constructedAlias))))
-
- dataSourceExpression.dataSourceExpr = contructedGinqExpression
- }
- })
+ .forEach(e -> collectConditionsToOptimize(e, allAliasList, optimizingAliasList, conditionsToOptimize))
+
+ transformFromClause(conditionsToOptimize, optimizingAliasList, optimizingDataSourceExpressionList)
whereExpression.filterExpr =
((ListExpression) new ListExpression(Collections.singletonList(whereExpression.filterExpr))
@@ -175,6 +168,37 @@ class GinqAstOptimizer extends GinqAstBaseVisitor {
return null
}
+ private void transformFromClause(LinkedHashMap<String, List<Expression>> conditionsToOptimize, List<String> optimizingAliasList, List<DataSourceExpression> optimizingDataSourceExpressionList) {
+ conditionsToOptimize.forEach((String alias, List<Expression> conditions) -> {
+ if (!optimizingAliasList.contains(alias)) return
+
+ DataSourceExpression dataSourceExpression =
+ optimizingDataSourceExpressionList.grep { DataSourceExpression e -> e.aliasExpr.text == alias }[0]
+
+ if (dataSourceExpression) {
+ GinqExpression contructedGinqExpression = new GinqExpression()
+ String constructedAlias = "alias${System.nanoTime()}"
+
+ List<Expression> transformedConditions =
+ conditions.stream()
+ .map(e -> correctVars(e, alias, constructedAlias))
+ .collect(Collectors.toList())
+
+ contructedGinqExpression.fromExpression =
+ new FromExpression(new VariableExpression(constructedAlias), dataSourceExpression.dataSourceExpr)
+ contructedGinqExpression.whereExpression =
+ new WhereExpression(contructFilterExpr(transformedConditions))
+ contructedGinqExpression.selectExpression =
+ new SelectExpression(
+ new ArgumentListExpression(
+ Collections.singletonList(
+ (Expression) new VariableExpression(constructedAlias))))
+
+ dataSourceExpression.dataSourceExpr = contructedGinqExpression
+ }
+ })
+ }
+
Expression correctVars(Expression expression, final String alias, final String constructedAlias) {
((ListExpression) new ListExpression(Collections.singletonList(expression)).transformExpression(new ExpressionTransformer() {
@Override
@@ -203,13 +227,13 @@ class GinqAstOptimizer extends GinqAstBaseVisitor {
return new BinaryExpression(condition, new Token(Types.LOGICAL_AND, '&&', -1, -1), remainingCondition)
}
- private void collectConditionsToOptimize(Expression expression, List<String> aliasList, Map<String, List<Expression>> conditionsToOptimize) {
+ private void collectConditionsToOptimize(Expression expression, List<String> allAliasList, List<String> optimizingAliasList, Map<String, List<Expression>> conditionsToOptimize) {
boolean toAdd = true
Set<String> usedAliasSet = new HashSet<>()
expression.visit(new GinqAstBaseVisitor() {
@Override
void visitVariableExpression(VariableExpression variableExpression) {
- if (!aliasList.contains(variableExpression.text)) {
+ if (!allAliasList.contains(variableExpression.text)) {
toAdd = false
return
}
@@ -220,13 +244,18 @@ class GinqAstOptimizer extends GinqAstBaseVisitor {
}
})
- if (usedAliasSet.size() > 1) {
- toAdd = false
+ if (usedAliasSet.size() != 1) {
+ return
+ }
+
+ final alias = usedAliasSet[0]
+ if (!optimizingAliasList.contains(alias)) {
+ return
}
if (toAdd) {
expression.putNodeMetaData(TO_OPTIMIZE, true)
- conditionsToOptimize.computeIfAbsent(usedAliasSet[0], k -> []).add(expression)
+ conditionsToOptimize.computeIfAbsent(alias, k -> []).add(expression)
}
}
diff --git a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/dsl/expression/JoinExpression.java b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/dsl/expression/JoinExpression.java
index 2b68e17..350d7a3 100644
--- a/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/dsl/expression/JoinExpression.java
+++ b/subprojects/groovy-ginq/src/main/groovy/org/apache/groovy/ginq/dsl/expression/JoinExpression.java
@@ -31,8 +31,10 @@ import java.util.List;
*/
public class JoinExpression extends DataSourceExpression implements DataSourceHolder {
private static final String INNER_JOIN = "innerjoin";
+ private static final String LEFT_JOIN = "leftjoin";
private static final String CROSS_JOIN = "crossjoin";
- private static final List<String> JOIN_NAME_LIST = Arrays.asList(INNER_JOIN, "leftjoin", "rightjoin", "fulljoin", CROSS_JOIN);
+ private static final List<String> JOIN_NAME_LIST = Arrays.asList(INNER_JOIN, LEFT_JOIN, "rightjoin", "fulljoin", CROSS_JOIN);
+
private final String joinName;
private OnExpression onExpression;
private DataSourceExpression dataSourceExpression;
@@ -54,6 +56,10 @@ public class JoinExpression extends DataSourceExpression implements DataSourceHo
return INNER_JOIN.equals(joinName);
}
+ public boolean isLeftJoin() {
+ return LEFT_JOIN.equals(joinName);
+ }
+
@Override
public <R> R accept(GinqAstVisitor<R> visitor) {
return visitor.visitJoinExpression(this);
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 049376d..2ab140b 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
@@ -21,8 +21,8 @@ package org.apache.groovy.ginq
import groovy.transform.CompileDynamic
import groovy.transform.CompileStatic
import org.apache.groovy.ginq.dsl.GinqAstBuilder
+import org.apache.groovy.ginq.dsl.GinqAstOptimizer
import org.apache.groovy.ginq.dsl.expression.GinqExpression
-import org.apache.groovy.ginq.provider.collection.GinqAstOptimizer
import org.codehaus.groovy.ast.MethodNode
import org.codehaus.groovy.ast.expr.BinaryExpression
import org.codehaus.groovy.ast.expr.ClosureExpression
@@ -1181,6 +1181,20 @@ class GinqTest {
}
@Test
+ void "testGinq - from leftjoin where select - 2"() {
+ assertScript '''
+ def nums1 = [1, 2, 3, null]
+ def nums2 = [2, 3, 4, null]
+ assert [[2, 2], [3, 3]] == GQ {
+ from n1 in nums1
+ leftjoin n2 in nums2 on n1 == n2
+ where n1 != null && n2 != null
+ select n1, n2
+ }.toList()
+ '''
+ }
+
+ @Test
void "testGinq - from rightjoin select - 1"() {
assertScript '''
def nums1 = [1, 2, 3]
@@ -3449,7 +3463,50 @@ class GinqTest {
}
@Test
+ @CompileDynamic
void "testGinq - optimize - 2"() {
+ def code = '''
+ def hello() {
+ def c = {
+ from n1 in nums1
+ leftjoin n2 in nums2 on n1 == n2
+ where n1 > 1 && n2 <= 3
+ select n1, n2
+ }
+ return
+ }
+ '''
+ def sourceUnit
+ def ast = new CompilationUnit().tap {
+ sourceUnit = addSource 'hello.groovy', code
+ compile Phases.CONVERSION
+ }.ast
+
+ MethodNode methodNode = ast.classes[0].methods.grep(e -> e.name == 'hello')[0]
+ ExpressionStatement delcareStatement = ((BlockStatement) methodNode.getCode()).getStatements()[0]
+ DeclarationExpression declarationExpression = delcareStatement.getExpression()
+ ClosureExpression closureException = declarationExpression.rightExpression
+
+ GinqAstBuilder ginqAstBuilder = new GinqAstBuilder(sourceUnit)
+ closureException.code.visit(ginqAstBuilder)
+ GinqExpression ginqExpression = ginqAstBuilder.getGinqExpression()
+
+ GinqAstOptimizer ginqAstOptimizer = new GinqAstOptimizer()
+ ginqAstOptimizer.visitGinqExpression(ginqExpression)
+ BinaryExpression filterExpr = (BinaryExpression) ginqExpression.whereExpression.filterExpr
+ assert 'true' == filterExpr.leftExpression.text
+ assert 'true' != 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
+
+ assert ginqExpression.joinExpressionList[0].dataSourceExpr !instanceof GinqExpression
+ }
+
+ @Test
+ void "testGinq - optimize - 3"() {
assertScript '''
// tag::ginq_optimize_01[]
assert [[1, 1], [2, 2], [3, 3]] == GQ(optimize:false) {