Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/aggregate/AggregateIndex.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/aggregate/AggregateIndex.java (revision 1705070) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/aggregate/AggregateIndex.java (working copy) @@ -55,6 +55,11 @@ } @Override + public double getMinimumCost() { + return baseIndex.getMinimumCost(); + } + + @Override public double getCost(Filter filter, NodeState rootState) { throw new UnsupportedOperationException("Not supported as implementing AdvancedQueryIndex"); } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/aggregate/package-info.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/aggregate/package-info.java (revision 1705070) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/aggregate/package-info.java (working copy) @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@Version("1.1.0") +@Version("1.2.0") @Export(optional = "provide:=true") package org.apache.jackrabbit.oak.plugins.index.aggregate; Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/diffindex/UUIDDiffIndex.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/diffindex/UUIDDiffIndex.java (revision 1705070) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/diffindex/UUIDDiffIndex.java (working copy) @@ -25,6 +25,11 @@ } @Override + public double getMinimumCost() { + return 0; + } + + @Override public String getIndexName() { return "UUIDDiffIndex"; } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/nodetype/NodeTypeIndex.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/nodetype/NodeTypeIndex.java (revision 1705070) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/nodetype/NodeTypeIndex.java (working copy) @@ -36,6 +36,11 @@ class NodeTypeIndex implements QueryIndex, JcrConstants { @Override + public double getMinimumCost() { + return NodeTypeIndexLookup.MINIMUM_COST; + } + + @Override public double getCost(Filter filter, NodeState root) { // TODO don't call getCost for such queries if (filter.getFullTextConstraint() != null) { Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/nodetype/NodeTypeIndexLookup.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/nodetype/NodeTypeIndexLookup.java (revision 1705070) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/nodetype/NodeTypeIndexLookup.java (working copy) @@ -31,6 +31,11 @@ */ class NodeTypeIndexLookup implements JcrConstants { + /** + * Derived from {@link #getCost(Filter)} + */ + static final double MINIMUM_COST = PropertyIndexLookup.COST_OVERHEAD; + private final NodeState root; public NodeTypeIndexLookup(NodeState root) { Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndex.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndex.java (revision 1705070) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndex.java (working copy) @@ -44,13 +44,29 @@ private static final Logger LOG = LoggerFactory.getLogger(OrderedPropertyIndex.class); + /** + * We're local. Low-cost + */ + private static final double COST_PER_EXECUTION = 1; + + private final OrderedPropertyIndexProvider indexProvider; + + public OrderedPropertyIndex(OrderedPropertyIndexProvider indexProvider) { + this.indexProvider = indexProvider; + } + @Override + public double getMinimumCost() { + return COST_PER_EXECUTION; + } + + @Override public String getIndexName() { return TYPE; } OrderedPropertyIndexLookup getLookup(NodeState root) { - return new OrderedPropertyIndexLookup(root); + return new OrderedPropertyIndexLookup(indexProvider, root); } /** @@ -68,7 +84,7 @@ */ static IndexPlan.Builder getIndexPlanBuilder(final Filter filter) { IndexPlan.Builder b = new IndexPlan.Builder(); - b.setCostPerExecution(1); // we're local. Low-cost + b.setCostPerExecution(COST_PER_EXECUTION); // we're local but slightly more expensive than a standard PropertyIndex b.setCostPerEntry(1.3); b.setFulltextIndex(false); // we're never full-text Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexLookup.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexLookup.java (revision 1705070) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexLookup.java (working copy) @@ -76,18 +76,21 @@ */ private static final int MAX_COST = 100; + private final OrderedPropertyIndexProvider indexProvider; + private NodeState root; private String name; private OrderedPropertyIndexLookup parent; - public OrderedPropertyIndexLookup(NodeState root) { - this(root, "", null); + public OrderedPropertyIndexLookup(OrderedPropertyIndexProvider indexProvider, NodeState root) { + this(indexProvider, root, "", null); } - public OrderedPropertyIndexLookup(NodeState root, String name, + public OrderedPropertyIndexLookup(OrderedPropertyIndexProvider indexProvider, NodeState root, String name, OrderedPropertyIndexLookup parent) { + this.indexProvider = indexProvider; this.root = root; this.name = name; this.parent = parent; @@ -106,9 +109,14 @@ */ @Nullable NodeState getIndexNode(NodeState node, String propertyName, Filter filter) { + if (parent == null && !indexProvider.mayHaveRootIndexes()) { + return null; + } + // keep a fallback to a matching index def that has *no* node type constraints // (initially, there is no fallback) NodeState fallback = null; + boolean hasRootIndexes = false; NodeState state = node.getChildNode(INDEX_DEFINITIONS_NAME); for (ChildNodeEntry entry : state.getChildNodeEntries()) { @@ -117,6 +125,8 @@ if (type == null || type.isArray() || !getType().equals(type.getValue(Type.STRING))) { continue; } + hasRootIndexes = true; + if (contains(index.getNames(PROPERTY_NAMES), propertyName)) { NodeState indexContent = index.getChildNode(INDEX_CONTENT_NODE_NAME); if (!indexContent.exists()) { @@ -140,6 +150,10 @@ } } } + + if (parent == null) { + indexProvider.indicateRootIndexes(hasRootIndexes); + } return fallback; } @@ -243,7 +257,7 @@ for (String element : PathUtils.elements(path)) { if (lookup == null) { lookup = new OrderedPropertyIndexLookup( - root.getChildNode(element), element, this); + indexProvider, root.getChildNode(element), element, this); } else { remainder = PathUtils.concat(remainder, element); } @@ -308,7 +322,7 @@ for (String element : PathUtils.elements(path)) { if (lookup == null) { lookup = new OrderedPropertyIndexLookup( - root.getChildNode(element), element, this); + indexProvider, root.getChildNode(element), element, this); } else { remainder = PathUtils.concat(remainder, element); } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexProvider.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexProvider.java (revision 1705070) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexProvider.java (working copy) @@ -18,6 +18,7 @@ package org.apache.jackrabbit.oak.plugins.index.property; import java.util.List; +import java.util.concurrent.TimeUnit; import javax.annotation.Nonnull; @@ -33,9 +34,46 @@ @Service(QueryIndexProvider.class) public class OrderedPropertyIndexProvider implements QueryIndexProvider { + private static final long DEFAULT_NO_INDEX_CACHE_TIMEOUT = TimeUnit.SECONDS.toMillis(30); + + /** + * How often it should check for a new ordered property index. + */ + private static long noIndexCacheTimeout = DEFAULT_NO_INDEX_CACHE_TIMEOUT; + + /** + * The last time when it checked for the existence of an ordered property index AND could not find any. + */ + private volatile long lastNegativeIndexCheck; + @Override @Nonnull public List getQueryIndexes(NodeState nodeState) { - return ImmutableList. of(new OrderedPropertyIndex()); + return ImmutableList. of(new OrderedPropertyIndex(this)); } + + /** + * @return true if there may be any ordered indexes below the root path + */ + boolean mayHaveRootIndexes() { + return System.currentTimeMillis() - lastNegativeIndexCheck > noIndexCacheTimeout; + } + + /** + * Indicates whether or not there are ordered indexes below the root path + * + * @param hasRootIndexes + */ + void indicateRootIndexes(boolean hasRootIndexes) { + lastNegativeIndexCheck = hasRootIndexes ? 0 : System.currentTimeMillis(); + } + + public static void setCacheTimeoutForTesting(long timeout) { + noIndexCacheTimeout = timeout; + } + + public static void resetCacheTimeoutForTesting() { + noIndexCacheTimeout = DEFAULT_NO_INDEX_CACHE_TIMEOUT; + } + } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java (revision 1705070) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java (working copy) @@ -98,6 +98,11 @@ private static final Logger LOG = LoggerFactory.getLogger(PropertyIndex.class); + /** + * Cached property index plan + */ + private PropertyIndexPlan plan; + static Set encode(PropertyValue value) { if (value == null) { return null; @@ -121,7 +126,22 @@ return values; } - private static PropertyIndexPlan plan(NodeState root, Filter filter) { + private PropertyIndexPlan getPlan(NodeState root, Filter filter) { + // Reuse cached plan if the filter is the same (which should always be the case). The filter is compared as a + // string because it would not be possible to use its equals method since the preparing flag would be different + // and creating a separate isSimilar method is not worth the effort since it would not be used anymore once the + // PropertyIndex has been refactored to an AdvancedQueryIndex (which will make the plan cache obsolete). + PropertyIndexPlan plan = this.plan; + if (plan != null && plan.getFilter().toString().equals(filter.toString())) { + return plan; + } else { + plan = createPlan(root, filter); + this.plan = plan; + return plan; + } + } + + private static PropertyIndexPlan createPlan(NodeState root, Filter filter) { PropertyIndexPlan bestPlan = null; // TODO support indexes on a path @@ -138,6 +158,10 @@ plan.getName(), plan.getCost()); if (bestPlan == null || plan.getCost() < bestPlan.getCost()) { bestPlan = plan; + // Stop comparing if the costs are the minimum + if (plan.getCost() == PropertyIndexPlan.COST_OVERHEAD) { + break; + } } } } @@ -149,6 +173,11 @@ //--------------------------------------------------------< QueryIndex >-- @Override + public double getMinimumCost() { + return PropertyIndexPlan.COST_OVERHEAD; + } + + @Override public String getIndexName() { return PROPERTY; } @@ -163,8 +192,12 @@ // not an appropriate index for native search return Double.POSITIVE_INFINITY; } + if (filter.getPropertyRestrictions().isEmpty() && filter.getSelector().getSelectorConstraints().isEmpty()) { + // not an appropriate index for no property restrictions & selector constraints + return Double.POSITIVE_INFINITY; + } - PropertyIndexPlan plan = plan(root, filter); + PropertyIndexPlan plan = getPlan(root, filter); if (plan != null) { return plan.getCost(); } else { @@ -174,7 +207,7 @@ @Override public Cursor query(Filter filter, NodeState root) { - PropertyIndexPlan plan = plan(root, filter); + PropertyIndexPlan plan = getPlan(root, filter); checkState(plan != null, "Property index is used even when no index" + " is available for filter " + filter); @@ -183,7 +216,7 @@ @Override public String getPlan(Filter filter, NodeState root) { - PropertyIndexPlan plan = plan(root, filter); + PropertyIndexPlan plan = getPlan(root, filter); if (plan != null) { return plan.toString(); } else { Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java (revision 1705070) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java (working copy) @@ -63,7 +63,7 @@ /** * The cost overhead to use the index in number of read operations. */ - private static final double COST_OVERHEAD = 2; + public static final double COST_OVERHEAD = 2; /** * The maximum cost when the index can be used. Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexPlan.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexPlan.java (revision 1705070) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexPlan.java (working copy) @@ -31,7 +31,6 @@ import org.apache.jackrabbit.oak.api.PropertyValue; import org.apache.jackrabbit.oak.commons.PathUtils; import org.apache.jackrabbit.oak.plugins.index.PathFilter; -import org.apache.jackrabbit.oak.plugins.index.PathFilter.Result; import org.apache.jackrabbit.oak.plugins.index.property.strategy.ContentMirrorStoreStrategy; import org.apache.jackrabbit.oak.plugins.index.property.strategy.IndexStoreStrategy; import org.apache.jackrabbit.oak.plugins.index.property.strategy.UniqueEntryStoreStrategy; @@ -58,7 +57,7 @@ /** * The cost overhead to use the index in number of read operations. */ - private static final double COST_OVERHEAD = 2; + static final double COST_OVERHEAD = 2; /** * The maximum cost when the index can be used. @@ -260,6 +259,10 @@ return cursor; } + Filter getFilter() { + return filter; + } + //------------------------------------------------------------< Object >-- @Override Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/package-info.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/package-info.java (revision 1705070) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/package-info.java (working copy) @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@Version("1.0.1") +@Version("2.0.0") @Export(optional = "provide:=true") package org.apache.jackrabbit.oak.plugins.index.property; Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceIndex.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceIndex.java (revision 1705070) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceIndex.java (working copy) @@ -53,7 +53,14 @@ private static final ContentMirrorStoreStrategy STORE = new ContentMirrorStoreStrategy(); + private static final double COST = 1; + @Override + public double getMinimumCost() { + return COST; + } + + @Override public String getIndexName() { return NAME; } @@ -72,7 +79,7 @@ for (PropertyRestriction pr : filter.getPropertyRestrictions()) { if (isEqualityRestrictionOnType(pr, REFERENCE) || isEqualityRestrictionOnType(pr, WEAKREFERENCE)) { - return 1; + return COST; } } // not an appropriate index Index: oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java (revision 1705070) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/query/QueryImpl.java (working copy) @@ -28,6 +28,7 @@ import com.google.common.collect.Lists; import com.google.common.collect.Maps; +import com.google.common.collect.Ordering; import org.apache.jackrabbit.oak.api.PropertyValue; import org.apache.jackrabbit.oak.api.Tree; import org.apache.jackrabbit.oak.api.Type; @@ -122,6 +123,13 @@ private static final Logger LOG = LoggerFactory.getLogger(QueryImpl.class); + private static final Ordering MINIMAL_COST_ORDERING = new Ordering() { + @Override + public int compare(QueryIndex left, QueryIndex right) { + return Double.compare(left.getMinimumCost(), right.getMinimumCost()); + } + }; + SourceImpl source; final String statement; final HashMap bindVariableMap = new HashMap(); @@ -928,7 +936,16 @@ double bestCost = Double.POSITIVE_INFINITY; IndexPlan bestPlan = null; - for (QueryIndex index : indexProvider.getQueryIndexes(rootState)) { + + // Sort the indexes according to their minimum cost to be able to skip the remaining indexes if the cost of the + // current index is below the minimum cost of the next index. + final List queryIndexes = MINIMAL_COST_ORDERING + .sortedCopy(indexProvider.getQueryIndexes(rootState)); + + for (int i = 0; i < queryIndexes.size(); i++) { + final QueryIndex index = queryIndexes.get(i); + final QueryIndex nextIndex = (i < queryIndexes.size()) ? queryIndexes.get(i) : null; + double cost; String indexName = index.getIndexName(); IndexPlan indexPlan = null; @@ -998,6 +1015,10 @@ bestIndex = index; bestPlan = indexPlan; } + // Stop looking for a better index if the current best cost is lower than the next minimum cost + if (nextIndex != null && bestCost <= nextIndex.getMinimumCost()) { + break; + } } if (traversalEnabled) { Index: oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/TraversingIndex.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/TraversingIndex.java (revision 1705070) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/TraversingIndex.java (working copy) @@ -34,6 +34,11 @@ public class TraversingIndex implements QueryIndex { @Override + public double getMinimumCost() { + return 0; + } + + @Override public Cursor query(Filter filter, NodeState rootState) { return Cursors.newTraversingCursor(filter, rootState); } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndex.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndex.java (revision 1705070) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/QueryIndex.java (working copy) @@ -53,8 +53,17 @@ * even thought it will be set in the filter. */ public interface QueryIndex { - + /** + * Returns the minimum cost which {@link #getCost(Filter, NodeState)} would return in the best possible case. + *

+ * The implementation should return a static/cached value because it is called very often. + * + * @return the minimum cost for the index + */ + double getMinimumCost(); + + /** * Estimate the worst-case cost to query with the given filter. The returned * cost is a value between 1 (very fast; lookup of a unique node) and the * estimated number of entries to traverse, if the cursor would be fully @@ -336,7 +345,7 @@ protected PropertyRestriction propRestriction; protected String pathPrefix = "/"; protected Map attributes = Maps.newHashMap(); - protected String planName = null; + protected String planName; public Builder setCostPerExecution(double costPerExecution) { this.costPerExecution = costPerExecution; @@ -543,7 +552,7 @@ } @Override - public String getPlanName(){ + public String getPlanName() { return planName; } }; Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/package-info.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/package-info.java (revision 1705070) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/query/package-info.java (working copy) @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@Version("3.0.0") +@Version("4.0.0") @Export(optional = "provide:=true") package org.apache.jackrabbit.oak.spi.query; Index: oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/LowCostOrderedPropertyIndexProvider.java =================================================================== --- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/LowCostOrderedPropertyIndexProvider.java (revision 1705070) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/LowCostOrderedPropertyIndexProvider.java (working copy) @@ -30,13 +30,22 @@ public class LowCostOrderedPropertyIndexProvider extends OrderedPropertyIndexProvider { @Override public List getQueryIndexes(NodeState nodeState) { - return ImmutableList. of(new LowCostOrderedPropertyIndex()); + return ImmutableList. of(new LowCostOrderedPropertyIndex(this)); } private static class LowCostOrderedPropertyIndex extends OrderedPropertyIndex { + public LowCostOrderedPropertyIndex(LowCostOrderedPropertyIndexProvider indexProvider) { + super(indexProvider); + } + @Override - public double getCost(Filter filter, NodeState root) { + public double getMinimumCost() { return 1e-3; } + + @Override + public double getCost(Filter filter, NodeState root) { + return getMinimumCost(); + } } } Index: oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedIndexCostTest.java =================================================================== --- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedIndexCostTest.java (revision 1705070) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedIndexCostTest.java (working copy) @@ -92,7 +92,7 @@ @Test @Ignore("As of OAK-622 this should no longer be used. Removing later.") public void costFullTextConstraint() { - OrderedPropertyIndex index = new OrderedPropertyIndex(); + OrderedPropertyIndex index = new OrderedPropertyIndex(new OrderedPropertyIndexProvider()); NodeState root = InitialContent.INITIAL_CONTENT; Filter filter = EasyMock.createNiceMock(Filter.class); FullTextExpression fte = EasyMock.createNiceMock(FullTextExpression.class); @@ -106,7 +106,7 @@ @Test @Ignore("As of OAK-622 this should no longer be used. Removing later.") public void costContainsNativeConstraints(){ - OrderedPropertyIndex index = new OrderedPropertyIndex(); + OrderedPropertyIndex index = new OrderedPropertyIndex(new OrderedPropertyIndexProvider()); NodeState root = InitialContent.INITIAL_CONTENT; Filter filter = EasyMock.createNiceMock(Filter.class); EasyMock.expect(filter.containsNativeConstraint()).andReturn(true).anyTimes(); @@ -124,7 +124,7 @@ */ @Test @Ignore("As of OAK-622 this should no longer be used. Removing later.") public void costGreaterThanAscendingDirection() throws Exception { - OrderedPropertyIndex index = new OrderedPropertyIndex(); + OrderedPropertyIndex index = new OrderedPropertyIndex(new OrderedPropertyIndexProvider()); NodeBuilder builder = InitialContent.INITIAL_CONTENT.builder(); defineAscendingIndex(builder); NodeState root = builder.getNodeState(); @@ -148,7 +148,7 @@ */ @Test @Ignore("As of OAK-622 this should no longer be used. Removing later.") public void costGreaterThanEqualAscendingDirection() throws IllegalArgumentException, RepositoryException { - OrderedPropertyIndex index = new OrderedPropertyIndex(); + OrderedPropertyIndex index = new OrderedPropertyIndex(new OrderedPropertyIndexProvider()); NodeBuilder builder = InitialContent.INITIAL_CONTENT.builder(); defineAscendingIndex(builder); NodeState root = builder.getNodeState(); @@ -173,7 +173,7 @@ */ @Test @Ignore("As of OAK-622 this should no longer be used. Removing later.") public void costLessThanAscendingDirection() throws IllegalArgumentException, RepositoryException { - OrderedPropertyIndex index = new OrderedPropertyIndex(); + OrderedPropertyIndex index = new OrderedPropertyIndex(new OrderedPropertyIndexProvider()); NodeBuilder builder = InitialContent.INITIAL_CONTENT.builder(); defineAscendingIndex(builder); NodeState root = builder.getNodeState(); @@ -192,7 +192,7 @@ @Test @Ignore("As of OAK-622 this should no longer be used. Removing later.") public void costLessThanEqualsAscendingDirection() throws IllegalArgumentException, RepositoryException { - OrderedPropertyIndex index = new OrderedPropertyIndex(); + OrderedPropertyIndex index = new OrderedPropertyIndex(new OrderedPropertyIndexProvider()); NodeBuilder builder = InitialContent.INITIAL_CONTENT.builder(); defineAscendingIndex(builder); NodeState root = builder.getNodeState(); @@ -212,7 +212,7 @@ @Test @Ignore("As of OAK-622 this should no longer be used. Removing later.") public void costGreaterThanDescendingDirection() throws IllegalArgumentException, RepositoryException { - OrderedPropertyIndex index = new OrderedPropertyIndex(); + OrderedPropertyIndex index = new OrderedPropertyIndex(new OrderedPropertyIndexProvider()); NodeBuilder builder = InitialContent.INITIAL_CONTENT.builder(); defineDescendingIndex(builder); NodeState root = builder.getNodeState(); @@ -232,7 +232,7 @@ @Test @Ignore("As of OAK-622 this should no longer be used. Removing later.") public void costGreaterEqualThanDescendingDirection() throws IllegalArgumentException, RepositoryException { - OrderedPropertyIndex index = new OrderedPropertyIndex(); + OrderedPropertyIndex index = new OrderedPropertyIndex(new OrderedPropertyIndexProvider()); NodeBuilder builder = InitialContent.INITIAL_CONTENT.builder(); defineDescendingIndex(builder); NodeState root = builder.getNodeState(); @@ -253,7 +253,7 @@ @Test @Ignore("As of OAK-622 this should no longer be used. Removing later.") public void costLessThanDescendingDirection() throws IllegalArgumentException, RepositoryException { - OrderedPropertyIndex index = new OrderedPropertyIndex(); + OrderedPropertyIndex index = new OrderedPropertyIndex(new OrderedPropertyIndexProvider()); NodeBuilder builder = InitialContent.INITIAL_CONTENT.builder(); defineDescendingIndex(builder); NodeState root = builder.getNodeState(); @@ -273,7 +273,7 @@ @Test @Ignore("As of OAK-622 this should no longer be used. Removing later.") public void costLessThanEqualDescendingDirection() throws IllegalArgumentException, RepositoryException { - OrderedPropertyIndex index = new OrderedPropertyIndex(); + OrderedPropertyIndex index = new OrderedPropertyIndex(new OrderedPropertyIndexProvider()); NodeBuilder builder = InitialContent.INITIAL_CONTENT.builder(); defineDescendingIndex(builder); NodeState root = builder.getNodeState(); @@ -294,7 +294,7 @@ @Test @Ignore("As of OAK-622 this should no longer be used. Removing later.") public void costBetweenDescendingDirection() throws IllegalArgumentException, RepositoryException { - OrderedPropertyIndex index = new OrderedPropertyIndex(); + OrderedPropertyIndex index = new OrderedPropertyIndex(new OrderedPropertyIndexProvider()); NodeBuilder builder = InitialContent.INITIAL_CONTENT.builder(); defineDescendingIndex(builder); NodeState root = builder.getNodeState(); @@ -317,7 +317,7 @@ @Test @Ignore("As of OAK-622 this should no longer be used. Removing later.") public void costBetweenAscendingDirection() throws IllegalArgumentException, RepositoryException { - OrderedPropertyIndex index = new OrderedPropertyIndex(); + OrderedPropertyIndex index = new OrderedPropertyIndex(new OrderedPropertyIndexProvider()); NodeBuilder builder = InitialContent.INITIAL_CONTENT.builder(); defineAscendingIndex(builder); NodeState root = builder.getNodeState(); Index: oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexQueryTest.java =================================================================== --- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexQueryTest.java (revision 1705070) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexQueryTest.java (working copy) @@ -507,7 +507,7 @@ NodeState indexed = HOOK.processCommit(before, after, CommitInfo.EMPTY); - final OrderedPropertyIndex index = new OrderedPropertyIndex(); + final OrderedPropertyIndex index = new OrderedPropertyIndex(new OrderedPropertyIndexProvider()); final String nodeTypeName = JcrConstants.NT_BASE; Filter filter = createFilter(indexed, nodeTypeName); @@ -583,7 +583,7 @@ NodeState indexed = HOOK.processCommit(before, after, CommitInfo.EMPTY); - final OrderedPropertyIndex index = new OrderedPropertyIndex(); + final OrderedPropertyIndex index = new OrderedPropertyIndex(new OrderedPropertyIndexProvider()); final String nodeTypeName = JcrConstants.NT_BASE; Filter filter = createFilter(indexed, nodeTypeName); @@ -624,7 +624,7 @@ NodeState indexed = HOOK.processCommit(before, after, CommitInfo.EMPTY); - final OrderedPropertyIndex index = new OrderedPropertyIndex(); + final OrderedPropertyIndex index = new OrderedPropertyIndex(new OrderedPropertyIndexProvider()); final String nodeTypeName = JcrConstants.NT_BASE; FilterImpl filter = createFilter(indexed, nodeTypeName); filter.restrictProperty("somethingNotIndexed", Operator.EQUAL, PropertyValues.newLong(1L)); Index: oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java =================================================================== --- oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java (revision 1705070) +++ oak-jcr/src/test/java/org/apache/jackrabbit/oak/jcr/query/QueryTest.java (working copy) @@ -58,6 +58,9 @@ import org.apache.jackrabbit.oak.commons.json.JsopTokenizer; import org.apache.jackrabbit.oak.jcr.AbstractRepositoryTest; import org.apache.jackrabbit.oak.jcr.NodeStoreFixture; +import org.apache.jackrabbit.oak.plugins.index.property.OrderedPropertyIndexProvider; +import org.junit.After; +import org.junit.Before; import org.junit.Ignore; import org.junit.Test; @@ -69,7 +72,17 @@ public QueryTest(NodeStoreFixture fixture) { super(fixture); } - + + @Before + public void disableCaching() { + OrderedPropertyIndexProvider.setCacheTimeoutForTesting(0); + } + + @After + public void enableCaching() { + OrderedPropertyIndexProvider.resetCacheTimeoutForTesting(); + } + @Test public void join() throws Exception { Session session = getAdminSession(); Index: oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java =================================================================== --- oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java (revision 1705070) +++ oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndex.java (working copy) @@ -154,6 +154,7 @@ private static final Logger LOG = LoggerFactory .getLogger(LuceneIndex.class); public static final String NATIVE_QUERY_FUNCTION = "native*lucene"; + private static double MIN_COST = 2.2; /** * IndexPaln Attribute name which refers to the path of Lucene index to be used @@ -178,6 +179,11 @@ } @Override + public double getMinimumCost() { + return MIN_COST; + } + + @Override public String getIndexName() { return "lucene"; } Index: oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java =================================================================== --- oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java (revision 1705070) +++ oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LucenePropertyIndex.java (working copy) @@ -163,6 +163,8 @@ */ public class LucenePropertyIndex implements AdvancedQueryIndex, QueryIndex, NativeQueryIndex, AdvanceFulltextQueryIndex { + + private static double MIN_COST = 2.1; private static final Logger LOG = LoggerFactory .getLogger(LucenePropertyIndex.class); @@ -191,6 +193,11 @@ } @Override + public double getMinimumCost() { + return MIN_COST; + } + + @Override public String getIndexName() { return "lucene-property"; } Index: oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LowCostLuceneIndexProvider.java =================================================================== --- oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LowCostLuceneIndexProvider.java (revision 1705070) +++ oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LowCostLuceneIndexProvider.java (working copy) @@ -36,19 +36,23 @@ } private static class LowCostLuceneIndex extends LuceneIndex { - public LowCostLuceneIndex(IndexTracker tracker, NodeAggregator aggregator) { super(tracker, aggregator); } @Override + public double getMinimumCost() { + return 1e-3; + } + + @Override public List getPlans(Filter filter, List sortOrder, NodeState rootState) { String indexPath = new LuceneIndexLookup(rootState).getOldFullTextIndexPath(filter, tracker); if (indexPath == null){ return Collections.emptyList(); } return Collections.singletonList(planBuilder(filter) - .setCostPerExecution(1e-3) + .setCostPerExecution(getMinimumCost()) .setAttribute(ATTR_INDEX_PATH, indexPath) .build()); } Index: oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/SolrQueryIndex.java =================================================================== --- oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/SolrQueryIndex.java (revision 1705070) +++ oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/SolrQueryIndex.java (working copy) @@ -77,6 +77,10 @@ static final String NATIVE_LUCENE_QUERY = "native*lucene"; + private static double MIN_COST = 2.3; + + private static double COST_FOR_SINGLE_RESTRICTION = 10; + private final Logger log = LoggerFactory.getLogger(SolrQueryIndex.class); private final String name; @@ -104,6 +108,11 @@ } @Override + public double getMinimumCost() { + return MIN_COST; + } + + @Override public String getIndexName() { return name; } @@ -111,7 +120,7 @@ @Override public double getCost(Filter filter, NodeState root) { // cost is inverse proportional to the number of matching restrictions, infinite if no restriction matches - double cost = 10d / getMatchingFilterRestrictions(filter); + double cost = COST_FOR_SINGLE_RESTRICTION / getMatchingFilterRestrictions(filter); if (log.isDebugEnabled()) { log.debug("Solr: cost for {} is {}", name, cost); } Index: oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/SolrQueryIndexProvider.java =================================================================== --- oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/SolrQueryIndexProvider.java (revision 1705070) +++ oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/query/SolrQueryIndexProvider.java (working copy) @@ -17,9 +17,11 @@ package org.apache.jackrabbit.oak.plugins.index.solr.query; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.WeakHashMap; +import java.util.concurrent.TimeUnit; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -49,6 +51,11 @@ */ public class SolrQueryIndexProvider implements QueryIndexProvider { + /** + * How often it should check for a new Solr index. + */ + private static final long NO_INDEX_CACHE_TIMEOUT = TimeUnit.SECONDS.toMillis(30); + private final Logger log = LoggerFactory.getLogger(getClass()); private final SolrServerProvider solrServerProvider; @@ -59,6 +66,11 @@ private final Map estimators = new WeakHashMap(); + /** + * The last time when it checked for the existence of a Solr index AND could not find any. + */ + private volatile long lastNegativeIndexCheck = 0; + public SolrQueryIndexProvider(@Nonnull SolrServerProvider solrServerProvider, @Nonnull OakSolrConfigurationProvider oakSolrConfigurationProvider, @Nullable NodeAggregator nodeAggregator) { this.oakSolrConfigurationProvider = oakSolrConfigurationProvider; @@ -73,7 +85,19 @@ @Nonnull @Override public List getQueryIndexes(NodeState nodeState) { + List queryIndexes; + // Return immediately if there are not any Solr indexes and the cache timeout has not been exceeded + long currentTime = System.currentTimeMillis(); + if (currentTime - lastNegativeIndexCheck > NO_INDEX_CACHE_TIMEOUT) { + queryIndexes = internalGetQueryIndexes(nodeState); + lastNegativeIndexCheck = queryIndexes.isEmpty() ? currentTime : 0; + } else { + queryIndexes = Collections.emptyList(); + } + return queryIndexes; + } + private List internalGetQueryIndexes(NodeState nodeState) { List tempIndexes = new ArrayList(); NodeState definitions = nodeState.getChildNode(INDEX_DEFINITIONS_NAME); for (ChildNodeEntry entry : definitions.getChildNodeEntries()) {