Author: catholicon
Date: Mon Oct  2 01:19:56 2017
New Revision: 1810289

URL: http://svn.apache.org/viewvc?rev=1810289&view=rev
Log:
OAK-6734: IndexPlan: add supportsPathRestrictions

Implement changes for oak-core (QueryImpl mainly) and tests

Added:
    
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/TraversalAvoidanceTest.java
   (with props)
Modified:
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/aggregate/AggregateIndexPlan.java
    
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java
    
jackrabbit/oak/trunk/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndex.java

Modified: 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/aggregate/AggregateIndexPlan.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/aggregate/AggregateIndexPlan.java?rev=1810289&r1=1810288&r2=1810289&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/aggregate/AggregateIndexPlan.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/aggregate/AggregateIndexPlan.java
 Mon Oct  2 01:19:56 2017
@@ -197,6 +197,11 @@ public class AggregateIndexPlan implemen
     }
 
     @Override
+    public boolean getSupportsPathRestriction() {
+        return false;
+    }
+
+    @Override
     @CheckForNull
     public Object getAttribute(String name) {
         return null;

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=1810289&r1=1810288&r2=1810289&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
 Mon Oct  2 01:19:56 2017
@@ -1032,18 +1032,28 @@ public class QueryImpl implements Query
             }
         }
         potentiallySlowTraversalQuery = bestIndex == null;
-        if (traversalEnabled) {
-            TraversingIndex traversal = new TraversingIndex();
-            double cost = traversal.getCost(filter, rootState);
+        if (bestPlan != null &&
+                (filter.getPathRestriction() == 
Filter.PathRestriction.NO_RESTRICTION ||
+                        bestPlan.getSupportsPathRestriction())) {
             if (LOG.isDebugEnabled()) {
-                logDebug("cost for " + traversal.getIndexName() + " is " + 
cost);
+                logDebug("Ignoring traversal. Params:: best index:" + 
bestIndex + ";" +
+                        " property restriction: " + 
filter.getPathRestriction() + ";" +
+                        " best plans supports path restriction: " + 
bestPlan.getSupportsPathRestriction());
             }
-            if (cost < bestCost || bestCost == Double.POSITIVE_INFINITY) {
-                bestCost = cost;
-                bestPlan = null;
-                bestIndex = traversal;
-                if (potentiallySlowTraversalQuery) {
-                    potentiallySlowTraversalQuery = 
traversal.isPotentiallySlow(filter, rootState);
+        } 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);
+                    }
                 }
             }
         }

Added: 
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=1810289&view=auto
==============================================================================
--- 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/TraversalAvoidanceTest.java
 (added)
+++ 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/TraversalAvoidanceTest.java
 Mon Oct  2 01:19:56 2017
@@ -0,0 +1,238 @@
+/*
+ * 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.jackrabbit.oak.query;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+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.spi.query.Cursor;
+import org.apache.jackrabbit.oak.spi.query.Filter;
+import org.apache.jackrabbit.oak.spi.query.IndexRow;
+import org.apache.jackrabbit.oak.spi.query.QueryIndex;
+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.junit.Assert;
+import org.junit.Test;
+
+import javax.annotation.Nonnull;
+import java.util.List;
+
+public class TraversalAvoidanceTest extends AbstractQueryTest {
+    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 TestQueryIndexProvider testIndexProvider = new 
TestQueryIndexProvider();
+    @Override
+    protected ContentRepository createRepository() {
+        return new Oak()
+                .with(new OpenSecurityProvider())
+                .with(new InitialContent())
+                .with(testIndexProvider)
+                .createContentRepository();
+    }
+
+    @Test
+    public void noPlansLetTraversalWin() {
+        assertPlanSelection(QUERY, "traverse", "Traversal must be used if 
nothing else participates");
+        assertPlanSelection(PATH_RESTRICTED_QUERY, "traverse", "Traversal must 
be used if nothing" +
+                " else participates");
+        assertPlanSelection(PATH_RESTRICTED_SLOW_TRAVERSAL_QUERY, "traverse", 
"Traversal must be" +
+                " used if nothing else participates");
+    }
+
+    @Test
+    public void singlePlanWithoutPathRestrictionWins() {
+        testIndexProvider.addPlan("plan1", 1000000, false);
+
+        assertPlanSelection(QUERY, "plan1", "Valid plan without path 
restriction must win");
+    }
+
+    @Test
+    public void singlePlanWithPathRestriction() {
+        testIndexProvider.addPlan("plan1", 1000000, 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);
+
+        assertPlanSelection(PATH_RESTRICTED_QUERY, "traverse", "Valid plan 
which evaluate path" +
+                " restrictions wins with query having path restriction");
+
+        testIndexProvider.restPlans();
+        testIndexProvider.addPlan("plan1", 10, false);
+        assertPlanSelection(PATH_RESTRICTED_SLOW_TRAVERSAL_QUERY, "plan1", 
"cost wars still prevail");
+    }
+
+    @Test
+    public void competingPlans() {
+        testIndexProvider.addPlan("plan1", 1000000, true);
+        testIndexProvider.addPlan("plan2", 100, true);
+
+        assertPlanSelection(QUERY, "plan2", "Low cost must win");
+        assertPlanSelection(PATH_RESTRICTED_QUERY, "plan2", "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, true);
+
+        assertPlanSelection(QUERY, "plan2", "Low cost must win");
+        assertPlanSelection(PATH_RESTRICTED_QUERY, "plan2", "Low cost must 
win");
+        assertPlanSelection(PATH_RESTRICTED_SLOW_TRAVERSAL_QUERY, "plan2", 
"Low cost must win");
+
+        testIndexProvider.restPlans();
+        testIndexProvider.addPlan("plan1", 1000000, true);
+        testIndexProvider.addPlan("plan2", 100, 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);
+
+        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");
+    }
+
+    class TestQueryIndexProvider implements QueryIndexProvider {
+        private final TestQueryIndex queryIndex = new TestQueryIndex();
+
+        void addPlan(String name, long cost, boolean supportsPathRestriction) {
+            queryIndex.addPlan(name, cost, supportsPathRestriction);
+        }
+
+        void restPlans() {
+            queryIndex.resetPlans();
+        }
+
+        @Nonnull
+        @Override
+        public List<? extends QueryIndex> getQueryIndexes(NodeState nodeState) 
{
+            return ImmutableList.of(queryIndex);
+        }
+    }
+
+    class TestQueryIndex implements QueryIndex, QueryIndex.AdvancedQueryIndex {
+        final String name;
+
+        final List<IndexPlan> plans;
+
+        TestQueryIndex() {
+            this("EmptyName");
+        }
+
+        TestQueryIndex(String name) {
+            this.name = name;
+            plans = Lists.newArrayListWithCapacity(5);
+        }
+
+        @Override
+        public double getMinimumCost() {
+            return 1000;//some high number
+        }
+
+        @Override
+        public double getCost(Filter filter, NodeState rootState) {
+            return getCost();
+        }
+        private double getCost() {
+            return 500;//arbitrary cost - useless as we are AdvanceQueryIndex
+        }
+
+        @Override
+        public Cursor query(Filter filter, NodeState rootState) {
+            return query();
+        }
+
+        @Override
+        public Cursor query(IndexPlan plan, NodeState rootState) {
+            return query();
+        }
+        private Cursor query() {
+            return new TestEmptyCursor();
+        }
+
+        @Override
+        public String getPlan(Filter filter, NodeState rootState) {
+            return "Unimportant plan";
+        }
+
+        @Override
+        public String getIndexName() {
+            return "test index";
+        }
+
+        @Override
+        public List<IndexPlan> getPlans(Filter filter, List<OrderEntry> 
sortOrder, NodeState rootState) {
+            return ImmutableList.copyOf(plans);
+        }
+
+        @Override
+        public String getPlanDescription(IndexPlan plan, NodeState root) {
+            return "unimportant plan description (" + plan.getPlanName() + ")";
+        }
+
+        void addPlan(String name, long cost, boolean supportsPathRestriction) {
+            plans.add(new IndexPlan.Builder()
+                    .setCostPerEntry(1)
+                    .setCostPerExecution(1)
+                    .setEstimatedEntryCount(cost)
+                    .setSupportsPathRestriction(supportsPathRestriction)
+                    .setPlanName(name)
+                    .build());
+        }
+
+        void resetPlans() {
+            plans.clear();
+        }
+    }
+
+    class TestEmptyCursor implements Cursor {
+
+        @Override
+        public boolean hasNext() {
+            return false;
+        }
+
+        @Override
+        public IndexRow next() {
+            return null;
+        }
+
+        @Override
+        public long getSize(Result.SizePrecision precision, long max) {
+            return 0;
+        }
+    }
+
+    private String explain(String query){
+        String explain = "explain " + query;
+        return executeQuery(explain, SQL2).get(0);
+    }
+
+    private void assertPlanSelection(String query, String expectedPlan, String 
message) {
+        String explain = explain(query);
+        Assert.assertTrue(message, explain.contains(expectedPlan));
+    }
+}

Propchange: 
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/query/TraversalAvoidanceTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: 
jackrabbit/oak/trunk/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndex.java
URL: 
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndex.java?rev=1810289&r1=1810288&r2=1810289&view=diff
==============================================================================
--- 
jackrabbit/oak/trunk/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndex.java
 (original)
+++ 
jackrabbit/oak/trunk/oak-query-spi/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndex.java
 Mon Oct  2 01:19:56 2017
@@ -290,6 +290,11 @@ public interface QueryIndex {
         String getPathPrefix();
 
         /**
+         * Whether index for this plan supports handling path restrictions 
natively.
+         */
+        boolean getSupportsPathRestriction();
+
+        /**
          * The property restriction for this index plan or <code>null</code> if
          * this index plan isn't base on a property restriction. E.g. a plan
          * based on an order by clause in the query.
@@ -344,6 +349,7 @@ public interface QueryIndex {
             protected NodeState definition;
             protected PropertyRestriction propRestriction;
             protected String pathPrefix = "/";
+            protected boolean supportsPathRestriction = false;
             protected Map<String, Object> attributes = Maps.newHashMap();
             protected String planName;
 
@@ -402,6 +408,11 @@ public interface QueryIndex {
                 return this;
             }
 
+            public Builder setSupportsPathRestriction(boolean 
supportsPathRestriction) {
+                this.supportsPathRestriction = supportsPathRestriction;
+                return this;
+            }
+
             public Builder setAttribute(String key, Object value) {
                this.attributes.put(key, value);
                return this;
@@ -440,6 +451,8 @@ public interface QueryIndex {
                             Builder.this.propRestriction;
                     private final String pathPrefix =
                             Builder.this.pathPrefix;
+                    private final boolean supportsPathRestriction =
+                            Builder.this.supportsPathRestriction;
                     private final Map<String, Object> attributes =
                             Builder.this.attributes;
                     private final String planName = Builder.this.planName;
@@ -457,7 +470,8 @@ public interface QueryIndex {
                             + " sortOrder : %s,"
                             + " definition : %s,"
                             + " propertyRestriction : %s,"
-                            + " pathPrefix : %s }",
+                            + " pathPrefix : %s,"
+                            + " supportsPathRestriction : %s }",
                             costPerExecution,
                             costPerEntry,
                             estimatedEntryCount,
@@ -468,7 +482,8 @@ public interface QueryIndex {
                             sortOrder,
                             definition,
                             propRestriction,
-                            pathPrefix
+                            pathPrefix,
+                            supportsPathRestriction
                             );
                     }
 
@@ -533,6 +548,11 @@ public interface QueryIndex {
                     }
 
                     @Override
+                    public boolean getSupportsPathRestriction() {
+                        return supportsPathRestriction;
+                    }
+
+                    @Override
                     protected Object clone() throws CloneNotSupportedException 
{
                         return super.clone();
                     }


Reply via email to