Author: thomasm
Date: Fri Oct  6 14:15:37 2017
New Revision: 1811334

URL: http://svn.apache.org/viewvc?rev=1811334&view=rev
Log:
OAK-6776 Correctly use IndexPlan.supportsPathRestrictions

Modified:
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/TraversalAvoidanceTest.java

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java?rev=1811334&r1=1811333&r2=1811334&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java
 Fri Oct  6 14:15:37 2017
@@ -36,6 +36,7 @@ import org.apache.jackrabbit.oak.api.Res
 import org.apache.jackrabbit.oak.api.Tree;
 import org.apache.jackrabbit.oak.namepath.JcrPathParser;
 import org.apache.jackrabbit.oak.namepath.NamePathMapper;
+import org.apache.jackrabbit.oak.plugins.index.counter.jmx.NodeCounter;
 import org.apache.jackrabbit.oak.plugins.memory.PropertyValues;
 import org.apache.jackrabbit.oak.query.QueryOptions.Traversal;
 import org.apache.jackrabbit.oak.query.ast.AndImpl;
@@ -82,6 +83,7 @@ import org.apache.jackrabbit.oak.query.p
 import org.apache.jackrabbit.oak.query.plan.SelectorExecutionPlan;
 import 
org.apache.jackrabbit.oak.query.stats.QueryStatsData.QueryExecutionStats;
 import org.apache.jackrabbit.oak.spi.query.Filter;
+import org.apache.jackrabbit.oak.spi.query.Filter.PathRestriction;
 import org.apache.jackrabbit.oak.spi.query.QueryConstants;
 import org.apache.jackrabbit.oak.spi.query.QueryIndex;
 import org.apache.jackrabbit.oak.spi.query.QueryIndex.AdvancedQueryIndex;
@@ -997,8 +999,13 @@ public class QueryImpl implements Query
                         filter, sortOrder, rootState);
                 cost = Double.POSITIVE_INFINITY;
                 for (IndexPlan p : ipList) {
-                    // TODO limit is after all conditions
-                    long entryCount = Math.min(maxEntryCount, 
p.getEstimatedEntryCount());
+                    
+                    long entryCount = p.getEstimatedEntryCount();
+                    if (p.getSupportsPathRestriction()) {
+                        entryCount = scaleEntryCount(rootState, filter, 
entryCount);
+                    }
+                    
+                    entryCount = Math.min(maxEntryCount, entryCount);
                     double c = p.getCostPerExecution() + entryCount * 
p.getCostPerEntry();
 
                     if (LOG.isDebugEnabled()) {
@@ -1032,34 +1039,58 @@ public class QueryImpl implements Query
             }
         }
         potentiallySlowTraversalQuery = bestIndex == null;
-        if (bestPlan != null &&
-                (filter.getPathRestriction() == 
Filter.PathRestriction.NO_RESTRICTION ||
-                        bestPlan.getSupportsPathRestriction())) {
+        if (traversalEnabled) {
+            TraversingIndex traversal = new TraversingIndex();
+            double cost = traversal.getCost(filter, rootState);
             if (LOG.isDebugEnabled()) {
-                logDebug("Ignoring traversal. Params:: best index:" + 
bestIndex + ";" +
-                        " property restriction: " + 
filter.getPathRestriction() + ";" +
-                        " best plans supports path restriction: " + 
bestPlan.getSupportsPathRestriction());
+                logDebug("cost for " + traversal.getIndexName() + " is " + 
cost);
             }
-        } else {
-            if (traversalEnabled) {
-                TraversingIndex traversal = new TraversingIndex();
-                double cost = traversal.getCost(filter, rootState);
-                if (LOG.isDebugEnabled()) {
-                    logDebug("cost for " + traversal.getIndexName() + " is " + 
cost);
-                }
-                if (cost < bestCost || bestCost == Double.POSITIVE_INFINITY) {
-                    bestCost = cost;
-                    bestPlan = null;
-                    bestIndex = traversal;
-                    if (potentiallySlowTraversalQuery) {
-                        potentiallySlowTraversalQuery = 
traversal.isPotentiallySlow(filter, rootState);
-                    }
+            if (cost < bestCost || bestCost == Double.POSITIVE_INFINITY) {
+                bestCost = cost;
+                bestPlan = null;
+                bestIndex = traversal;
+                if (potentiallySlowTraversalQuery) {
+                    potentiallySlowTraversalQuery = 
traversal.isPotentiallySlow(filter, rootState);
                 }
             }
         }
         return new SelectorExecutionPlan(filter.getSelector(), bestIndex, 
                 bestPlan, bestCost);
     }
+    
+    private long scaleEntryCount(NodeState rootState, FilterImpl filter, long 
count) {
+        PathRestriction r = filter.getPathRestriction();
+        if (r != PathRestriction.ALL_CHILDREN) {
+            return count;
+        }
+        String path = filter.getPath();
+        if (path.startsWith(JoinConditionImpl.SPECIAL_PATH_PREFIX)) {
+            // don't know the path currently, could be root
+            return count;
+        }
+        long filterPathCount = NodeCounter.getEstimatedNodeCount(rootState, 
path, true);
+        if (filterPathCount < 0) {
+            // don't know
+            return count;
+        }
+        long totalNodesCount = NodeCounter.getEstimatedNodeCount(rootState, 
"/", true);
+        if (totalNodesCount <= 0) {
+            totalNodesCount = 1;
+        }
+        // same logic as for the property index (see 
ContentMirrorStoreStrategy):
+        
+        // assume nodes in the index are evenly distributed in the repository 
(old idea)
+        long countScaledDown = (long) ((double) count / totalNodesCount * 
filterPathCount);
+        // assume 80% of the indexed nodes are in this subtree
+        long mostNodesFromThisSubtree = (long) (filterPathCount * 0.8);
+        // count can at most be the assumed subtree size
+        count = Math.min(count, mostNodesFromThisSubtree);
+        // this in theory should not have any effect, 
+        // except if the above estimates are incorrect,
+        // so this is just for safety feature
+        count = Math.max(count, countScaledDown);
+        return count;
+    }
 
     @Override
     public boolean isPotentiallySlow() {

Modified: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/TraversalAvoidanceTest.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/TraversalAvoidanceTest.java?rev=1811334&r1=1811333&r2=1811334&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/TraversalAvoidanceTest.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/TraversalAvoidanceTest.java
 Fri Oct  6 14:15:37 2017
@@ -13,12 +13,22 @@
  */
 package org.apache.jackrabbit.oak.query;
 
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
+import static org.junit.Assert.assertNotNull;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
 import org.apache.jackrabbit.oak.InitialContent;
 import org.apache.jackrabbit.oak.Oak;
 import org.apache.jackrabbit.oak.api.ContentRepository;
 import org.apache.jackrabbit.oak.api.Result;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.plugins.index.AsyncIndexUpdate;
+import 
org.apache.jackrabbit.oak.plugins.index.counter.NodeCounterEditorProvider;
+import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
 import org.apache.jackrabbit.oak.spi.query.Cursor;
 import org.apache.jackrabbit.oak.spi.query.Filter;
 import org.apache.jackrabbit.oak.spi.query.IndexRow;
@@ -26,27 +36,81 @@ import org.apache.jackrabbit.oak.spi.que
 import org.apache.jackrabbit.oak.spi.query.QueryIndexProvider;
 import org.apache.jackrabbit.oak.spi.security.OpenSecurityProvider;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
+import org.apache.jackrabbit.oak.spi.whiteboard.WhiteboardUtils;
 import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Test;
 
-import javax.annotation.Nonnull;
-import java.util.List;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
 
 public class TraversalAvoidanceTest extends AbstractQueryTest {
+    
+    Whiteboard wb;
+    NodeStore nodeStore;
+
     private static final String QUERY = "SELECT * FROM [nt:base]";
-    private static final String PATH_RESTRICTED_QUERY = "SELECT * FROM 
[nt:base] WHERE ISDESCENDANTNODE('/content')";
-    private static final String PATH_RESTRICTED_SLOW_TRAVERSAL_QUERY = "SELECT 
* FROM [nt:base] WHERE ISDESCENDANTNODE('/jcr:system')";
+    
+    private static final String PATH_RESTRICTED_QUERY = 
+            "SELECT * FROM [nt:base] WHERE ISDESCENDANTNODE('/content/test0')";
+    
+    private static final String PATH_RESTRICTED_SLOW_TRAVERSAL_QUERY = 
+            "SELECT * FROM [nt:base] WHERE ISDESCENDANTNODE('/content')";
 
     private TestQueryIndexProvider testIndexProvider = new 
TestQueryIndexProvider();
     @Override
     protected ContentRepository createRepository() {
-        return new Oak()
+        nodeStore = new MemoryNodeStore();
+        Oak oak = new Oak(nodeStore)
                 .with(new OpenSecurityProvider())
                 .with(new InitialContent())
+                .with(new NodeCounterEditorProvider())
                 .with(testIndexProvider)
-                .createContentRepository();
+                //Effectively disable async indexing auto run
+                //such that we can control run timing as per test requirement
+                .withAsyncIndexing("async", TimeUnit.DAYS.toSeconds(1));
+                
+            wb = oak.getWhiteboard();
+            return oak.createContentRepository();      
+    }
+    
+    @Before
+    public void before() throws Exception {
+        session = createRepository().login(null, null);
+        root = session.getLatestRoot();
+        qe = root.getQueryEngine();
+        
+        root.getTree("/oak:index/counter").setProperty("resolution", 100);
+        root.getTree("/oak:index/counter").setProperty("seed", 1);
+        
+        Tree content = root.getTree("/").addChild("content");
+        // add 200'000 nodes under /content
+        for (int i = 0; i < 2000; i++) {
+            Tree t = content.addChild("test" + i);
+            for (int j = 0; j < 100; j++) {
+                t.addChild("n" + j);
+            }
+        }
+        root.commit();
+        
+        runAsyncIndex();
     }
 
+    private void runAsyncIndex() {
+        Runnable async = WhiteboardUtils.getService(wb, Runnable.class, new 
Predicate<Runnable>() {
+            @Override
+            public boolean apply(@Nullable Runnable input) {
+                return input instanceof AsyncIndexUpdate;
+            }
+        });
+        assertNotNull(async);
+        async.run();
+        root.refresh();
+    }    
+
     @Test
     public void noPlansLetTraversalWin() {
         assertPlanSelection(QUERY, "traverse", "Traversal must be used if 
nothing else participates");
@@ -58,20 +122,20 @@ public class TraversalAvoidanceTest exte
 
     @Test
     public void singlePlanWithoutPathRestrictionWins() {
-        testIndexProvider.addPlan("plan1", 1000000, false);
+        testIndexProvider.addPlan("plan1", 10000, false);
 
         assertPlanSelection(QUERY, "plan1", "Valid plan without path 
restriction must win");
     }
 
     @Test
     public void singlePlanWithPathRestriction() {
-        testIndexProvider.addPlan("plan1", 1000000, true);
+        testIndexProvider.addPlan("plan1", 10000, true);
 
         assertPlanSelection(PATH_RESTRICTED_QUERY, "plan1", "Valid plan which 
evaluate path" +
                 " restrictions wins with query having path restriction");
 
         testIndexProvider.restPlans();
-        testIndexProvider.addPlan("plan1", 1000000, false);
+        testIndexProvider.addPlan("plan1", 10000, false);
 
         assertPlanSelection(PATH_RESTRICTED_QUERY, "traverse", "Valid plan 
which evaluate path" +
                 " restrictions wins with query having path restriction");
@@ -83,7 +147,7 @@ public class TraversalAvoidanceTest exte
 
     @Test
     public void competingPlans() {
-        testIndexProvider.addPlan("plan1", 1000000, true);
+        testIndexProvider.addPlan("plan1", 100000, true);
         testIndexProvider.addPlan("plan2", 100, true);
 
         assertPlanSelection(QUERY, "plan2", "Low cost must win");
@@ -91,7 +155,7 @@ public class TraversalAvoidanceTest exte
         assertPlanSelection(PATH_RESTRICTED_SLOW_TRAVERSAL_QUERY, "plan2", 
"Low cost must win");
 
         testIndexProvider.restPlans();
-        testIndexProvider.addPlan("plan1", 1000000, false);
+        testIndexProvider.addPlan("plan1", 100000, false);
         testIndexProvider.addPlan("plan2", 100, true);
 
         assertPlanSelection(QUERY, "plan2", "Low cost must win");
@@ -99,16 +163,16 @@ public class TraversalAvoidanceTest exte
         assertPlanSelection(PATH_RESTRICTED_SLOW_TRAVERSAL_QUERY, "plan2", 
"Low cost must win");
 
         testIndexProvider.restPlans();
-        testIndexProvider.addPlan("plan1", 1000000, true);
-        testIndexProvider.addPlan("plan2", 100, false);
+        testIndexProvider.addPlan("plan1", 200000, true);
+        testIndexProvider.addPlan("plan2", 10000, false);
 
         assertPlanSelection(QUERY, "plan2", "Low cost must win");
         assertPlanSelection(PATH_RESTRICTED_QUERY, "traverse", "Low cost must 
win");
         assertPlanSelection(PATH_RESTRICTED_SLOW_TRAVERSAL_QUERY, "plan2", 
"Low cost must win");
 
         testIndexProvider.restPlans();
-        testIndexProvider.addPlan("plan1", 1000000, false);
-        testIndexProvider.addPlan("plan2", 100, false);
+        testIndexProvider.addPlan("plan1", 200000, false);
+        testIndexProvider.addPlan("plan2", 1000, false);
 
         assertPlanSelection(QUERY, "plan2", "Low cost must win");
         assertPlanSelection(PATH_RESTRICTED_QUERY, "traverse", "Low cost must 
win");
@@ -190,7 +254,7 @@ public class TraversalAvoidanceTest exte
 
         @Override
         public String getPlanDescription(IndexPlan plan, NodeState root) {
-            return "unimportant plan description (" + plan.getPlanName() + ")";
+            return "plan=" + plan.getPlanName();
         }
 
         void addPlan(String name, long cost, boolean supportsPathRestriction) {
@@ -233,6 +297,6 @@ public class TraversalAvoidanceTest exte
 
     private void assertPlanSelection(String query, String expectedPlan, String 
message) {
         String explain = explain(query);
-        Assert.assertTrue(message, explain.contains(expectedPlan));
+        Assert.assertTrue(message + ", but got: " + explain, 
explain.contains(expectedPlan));
     }
 }


Reply via email to