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) {