Index: src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexPlan.java =================================================================== --- src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexPlan.java (revision 1763455) +++ src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexPlan.java (working copy) @@ -54,7 +54,7 @@ /** * The cost overhead to use the index in number of read operations. */ - static final double COST_OVERHEAD = 2; + public static final double COST_OVERHEAD = 2; /** * The maximum cost when the index can be used. @@ -83,6 +83,8 @@ private final PathFilter pathFilter; + private final boolean unique; + PropertyIndexPlan(String name, NodeState root, NodeState definition, Filter filter){ this(name, root, definition, filter, Mounts.defaultMountInfoProvider()); @@ -91,6 +93,7 @@ PropertyIndexPlan(String name, NodeState root, NodeState definition, Filter filter, MountInfoProvider mountInfoProvider) { this.name = name; + this.unique = definition.getBoolean(IndexConstants.UNIQUE_PROPERTY_NAME); this.definition = definition; this.properties = newHashSet(definition.getNames(PROPERTY_NAMES)); pathFilter = PathFilter.from(definition.builder()); @@ -107,7 +110,7 @@ Set bestValues = emptySet(); int bestDepth = 1; - if (matchesNodeTypes && + if (matchesNodeTypes && pathFilter.areAllDescendantsIncluded(filter.getPath())) { for (String property : properties) { PropertyRestriction restriction = @@ -145,10 +148,20 @@ cost += strategy.count(filter, root, definition, values, MAX_COST); } + if (unique && cost <= 1) { + // for unique index, for the normal case + // (that is, for a regular lookup) + // no further reads are needed + cost = 0; + } if (cost < bestCost) { bestDepth = depth; bestValues = values; bestCost = cost; + if (bestCost == 0) { + // shortcut: not possible to top this + break; + } } } } @@ -207,8 +220,6 @@ Set getStrategies(NodeState definition, MountInfoProvider mountInfoProvider) { - boolean unique = definition - .getBoolean(IndexConstants.UNIQUE_PROPERTY_NAME); return Multiplexers.getStrategies(unique, mountInfoProvider, definition, INDEX_CONTENT_NODE_NAME); } Index: src/test/java/org/apache/jackrabbit/oak/query/index/IndexSelectionTest.java =================================================================== --- src/test/java/org/apache/jackrabbit/oak/query/index/IndexSelectionTest.java (revision 0) +++ src/test/java/org/apache/jackrabbit/oak/query/index/IndexSelectionTest.java (working copy) @@ -0,0 +1,137 @@ +/* + * 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.index; + +import java.util.List; +import java.util.UUID; + +import javax.annotation.Nonnull; + +import com.google.common.collect.ImmutableList; +import org.apache.jackrabbit.JcrConstants; +import org.apache.jackrabbit.oak.Oak; +import org.apache.jackrabbit.oak.api.ContentRepository; +import org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexEditorProvider; +import org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexPlan; +import org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexProvider; +import org.apache.jackrabbit.oak.plugins.nodetype.write.InitialContent; +import org.apache.jackrabbit.oak.query.AbstractQueryTest; +import org.apache.jackrabbit.oak.spi.query.Cursor; +import org.apache.jackrabbit.oak.spi.query.Filter; +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.apache.jackrabbit.oak.util.NodeUtil; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class IndexSelectionTest extends AbstractQueryTest { + private TestIndexProvider testIndexProvider = new TestIndexProvider(); + @Override + protected ContentRepository createRepository() { + return new Oak() + .with(new OpenSecurityProvider()) + .with(new InitialContent()) + .with(new PropertyIndexEditorProvider()) + .with(new PropertyIndexProvider()) + .with(testIndexProvider) + .createContentRepository(); + } + + @Test + public void uuidIndexQuery() throws Exception{ + NodeUtil node = new NodeUtil(root.getTree("/")); + String uuid = UUID.randomUUID().toString(); + node.setString(JcrConstants.JCR_UUID, uuid); + root.commit(); + + assertQuery("SELECT * FROM [nt:base] WHERE [jcr:uuid] = '"+uuid+"' ", + ImmutableList.of("/")); + assertEquals("Test index plan should not be invoked", + 0, testIndexProvider.index.invocationCount); + } + + @Test + public void uuidIndexNotNullQuery() throws Exception{ + NodeUtil node = new NodeUtil(root.getTree("/")); + String uuid = UUID.randomUUID().toString(); + node.setString(JcrConstants.JCR_UUID, uuid); + root.commit(); + + assertQuery("SELECT * FROM [nt:base] WHERE [jcr:uuid] is not null", + ImmutableList.of("/")); + assertEquals("Test index plan should be invoked", + 1, testIndexProvider.index.invocationCount); + } + + @Test + public void uuidIndexInListQuery() throws Exception{ + NodeUtil node = new NodeUtil(root.getTree("/")); + String uuid = UUID.randomUUID().toString(); + String uuid2 = UUID.randomUUID().toString(); + node.setString(JcrConstants.JCR_UUID, uuid); + root.commit(); + + assertQuery("SELECT * FROM [nt:base] WHERE [jcr:uuid] in('" + uuid + "', '" + uuid2 + "')", + ImmutableList.of("/")); + assertEquals("Test index plan should be invoked", + 1, testIndexProvider.index.invocationCount); + } + + private static class TestIndexProvider implements QueryIndexProvider { + TestIndex index = new TestIndex(); + @Nonnull + @Override + public List getQueryIndexes(NodeState nodeState) { + return ImmutableList.of(index); + } + } + + private static class TestIndex implements QueryIndex { + int invocationCount = 0; + @Override + public double getMinimumCost() { + return PropertyIndexPlan.COST_OVERHEAD + 0.1; + } + + @Override + public double getCost(Filter filter, NodeState rootState) { + invocationCount++; + return Double.POSITIVE_INFINITY; + } + + @Override + public Cursor query(Filter filter, NodeState rootState) { + return null; + } + + @Override + public String getPlan(Filter filter, NodeState rootState) { + return null; + } + + @Override + public String getIndexName() { + return "test-index"; + } + } +} \ No newline at end of file