Index: src/main/java/org/apache/jackrabbit/oak/plugins/index/PathFilter.java =================================================================== --- src/main/java/org/apache/jackrabbit/oak/plugins/index/PathFilter.java (revision 1697870) +++ src/main/java/org/apache/jackrabbit/oak/plugins/index/PathFilter.java (working copy) @@ -54,14 +54,17 @@ public static final String PROP_EXCLUDED_PATHS = "excludedPaths"; public enum Result { + /** * Include the path for processing */ INCLUDE, + /** * Exclude the path and subtree for processing */ EXCLUDE, + /** * Do not process the path but just perform traversal to * child nodes. For IndexEditor it means that such nodes @@ -72,7 +75,7 @@ private static final PathFilter ALL = new PathFilter(INCLUDE_ROOT, Collections.emptyList()) { @Override - public Result doFiler(@Nonnull String path) { + public Result filter(@Nonnull String path) { return Result.INCLUDE; } }; @@ -81,21 +84,23 @@ private final String[] excludedPaths; /** - * Constructs the predicate based on given definition state. It looks - * for multi value property with names {@link PathFilter#PROP_INCLUDED_PATHS} - * and {@link PathFilter#PROP_EXCLUDED_PATHS}. Both the properties - * are optional. - * - * @param defn nodestate representing the configuration. Generally it would be the nodestate - * representing the index definition + * Constructs the predicate based on given definition state. It looks for + * multi value property with names {@link PathFilter#PROP_INCLUDED_PATHS} + * and {@link PathFilter#PROP_EXCLUDED_PATHS}. Both the properties are + * optional. + * + * @param defn nodestate representing the configuration. Generally it would + * be the nodestate representing the index definition * @return predicate based on the passed definition state */ - public static PathFilter from(@Nonnull NodeBuilder defn){ - if (!defn.hasProperty(PROP_EXCLUDED_PATHS) && !defn.hasProperty(PROP_INCLUDED_PATHS)){ + public static PathFilter from(@Nonnull NodeBuilder defn) { + if (!defn.hasProperty(PROP_EXCLUDED_PATHS) && + !defn.hasProperty(PROP_INCLUDED_PATHS)) { return ALL; } - return new PathFilter(getStrings(defn, PROP_INCLUDED_PATHS, INCLUDE_ROOT), - getStrings(defn, PROP_EXCLUDED_PATHS, Collections.emptyList())); + return new PathFilter(getStrings(defn, PROP_INCLUDED_PATHS, + INCLUDE_ROOT), getStrings(defn, PROP_EXCLUDED_PATHS, + Collections. emptyList())); } /** @@ -123,21 +128,21 @@ * @param path path to check * @return result indicating if the path needs to be included, excluded or just traversed */ - public Result doFiler(@Nonnull String path) { - for (String excludedPath : excludedPaths){ - if (excludedPath.equals(path) || isAncestor(excludedPath, path)){ + public Result filter(@Nonnull String path) { + for (String excludedPath : excludedPaths) { + if (excludedPath.equals(path) || isAncestor(excludedPath, path)) { return Result.EXCLUDE; } } - for (String includedPath : includedPaths){ - if (includedPath.equals(path) || isAncestor(includedPath, path)){ + for (String includedPath : includedPaths) { + if (includedPath.equals(path) || isAncestor(includedPath, path)) { return Result.INCLUDE; } } - for (String includedPath : includedPaths){ - if (includedPath.startsWith(path)){ + for (String includedPath : includedPaths) { + if (isAncestor(path, includedPath)) { return Result.TRAVERSE; } } @@ -153,12 +158,35 @@ '}'; } - private static Iterable getStrings(NodeBuilder builder, String name, Collection defaultVal) { - PropertyState property = builder.getProperty(name); + private static Iterable getStrings(NodeBuilder builder, String propertyName, + Collection defaultVal) { + PropertyState property = builder.getProperty(propertyName); if (property != null && property.getType() == Type.STRINGS) { return property.getValue(Type.STRINGS); } else { return defaultVal; } } + + /** + * Check whether this node and all descendants are included in this filter. + * + * @param path the path + * @return true if this and all descendants of this path are included in the filter + */ + public boolean areAllDescendantsIncluded(String path) { + for (String excludedPath : excludedPaths) { + if (excludedPath.equals(path) || isAncestor(excludedPath, path) || + isAncestor(path, excludedPath)) { + return false; + } + } + for (String includedPath : includedPaths) { + if (includedPath.equals(path) || isAncestor(includedPath, path)) { + return true; + } + } + return false; + } + } Index: src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditor.java =================================================================== --- src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditor.java (revision 1697870) +++ src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditor.java (working copy) @@ -27,6 +27,7 @@ import org.apache.jackrabbit.oak.api.Type; import org.apache.jackrabbit.oak.plugins.index.IndexConstants; import org.apache.jackrabbit.oak.plugins.index.IndexUpdateCallback; +import org.apache.jackrabbit.oak.plugins.index.PathFilter; import org.apache.jackrabbit.oak.plugins.index.property.OrderedIndex.OrderDirection; import org.apache.jackrabbit.oak.plugins.index.property.strategy.IndexStoreStrategy; import org.apache.jackrabbit.oak.plugins.index.property.strategy.OrderedContentMirrorStoreStrategy; @@ -101,8 +102,8 @@ swl = new StopwatchLogger(OrderedPropertyIndexEditor.class); } - OrderedPropertyIndexEditor(OrderedPropertyIndexEditor parent, String name) { - super(parent, name); + OrderedPropertyIndexEditor(OrderedPropertyIndexEditor parent, String name, PathFilter.Result pathFilterResult) { + super(parent, name, pathFilterResult); this.propertyNames = parent.getPropertyNames(); this.direction = parent.getDirection(); this.swl = parent.swl; @@ -133,8 +134,8 @@ @Override PropertyIndexEditor getChildIndexEditor(@Nonnull PropertyIndexEditor parent, - @Nonnull String name) { - return new OrderedPropertyIndexEditor(this, name); + @Nonnull String name, PathFilter.Result pathFilterResult) { + return new OrderedPropertyIndexEditor(this, name, pathFilterResult); } /** Index: src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditor.java =================================================================== --- src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditor.java (revision 1697870) +++ src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditor.java (working copy) @@ -40,6 +40,7 @@ import org.apache.jackrabbit.oak.plugins.index.IndexConstants; import org.apache.jackrabbit.oak.plugins.index.IndexEditor; import org.apache.jackrabbit.oak.plugins.index.IndexUpdateCallback; +import org.apache.jackrabbit.oak.plugins.index.PathFilter; 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; @@ -110,6 +111,10 @@ private final IndexUpdateCallback updateCallback; + private final PathFilter pathFilter; + + private final PathFilter.Result pathFilterResult; + public PropertyIndexEditor(NodeBuilder definition, NodeState root, IndexUpdateCallback updateCallback) { this.parent = null; @@ -117,6 +122,8 @@ this.path = "/"; this.definition = definition; this.root = root; + pathFilter = PathFilter.from(definition); + pathFilterResult = getPathFilterResult(); //initPropertyNames(definition); @@ -147,7 +154,7 @@ this.updateCallback = updateCallback; } - PropertyIndexEditor(PropertyIndexEditor parent, String name) { + PropertyIndexEditor(PropertyIndexEditor parent, String name, PathFilter.Result pathFilterResult) { this.parent = parent; this.name = name; this.path = null; @@ -157,6 +164,8 @@ this.typePredicate = parent.typePredicate; this.keysToCheckForUniqueness = parent.keysToCheckForUniqueness; this.updateCallback = parent.updateCallback; + this.pathFilter = parent.pathFilter; + this.pathFilterResult = pathFilterResult; } /** @@ -228,6 +237,16 @@ @Override public void leave(NodeState before, NodeState after) throws CommitFailedException { + + if (pathFilterResult == PathFilter.Result.INCLUDE) { + applyTypeRestrictions(before, after); + updateIndex(before, after); + } + checkUniquenessConstraints(); + + } + + private void applyTypeRestrictions(NodeState before, NodeState after) { // apply the type restrictions if (typePredicate != null) { if (typeChanged) { @@ -245,7 +264,9 @@ afterKeys = null; } } - + } + + private void updateIndex(NodeState before, NodeState after) throws CommitFailedException { // if any changes were detected, update the index accordingly if (beforeKeys != null || afterKeys != null) { // first make sure that both the before and after sets are non-null @@ -276,13 +297,17 @@ } } + checkUniquenessConstraints(); + } + + private void checkUniquenessConstraints() throws CommitFailedException { if (parent == null) { // make sure that the index node exist, even with no content definition.child(INDEX_CONTENT_NODE_NAME); boolean uniqueIndex = keysToCheckForUniqueness != null; // check uniqueness constraints when leaving the root - if (uniqueIndex && + if (uniqueIndex && !keysToCheckForUniqueness.isEmpty()) { NodeState indexMeta = definition.getNodeState(); String failed = getFirstDuplicate( @@ -292,12 +317,12 @@ "Uniqueness constraint violated at path [%s] for one of the " + "property in %s having value %s", getPath(), propertyNames, failed); - throw new CommitFailedException(CONSTRAINT, 30, msg); + throw new CommitFailedException(CONSTRAINT, 30, msg); } } } } - + /** * From a set of keys, get those that already exist in the index. * @@ -378,24 +403,43 @@ * @param name the name of the child node * @return an instance of the PropertyIndexEditor */ - PropertyIndexEditor getChildIndexEditor(@Nonnull PropertyIndexEditor parent, @Nonnull String name) { - return new PropertyIndexEditor(parent, name); + PropertyIndexEditor getChildIndexEditor(@Nonnull PropertyIndexEditor parent, @Nonnull String name, PathFilter.Result filterResult) { + return new PropertyIndexEditor(parent, name, filterResult); } @Override public Editor childNodeAdded(String name, NodeState after) { - return getChildIndexEditor(this, name); + PathFilter.Result filterResult = getPathFilterResult(name); + if (filterResult == PathFilter.Result.EXCLUDE) { + return null; + } + return getChildIndexEditor(this, name, filterResult); } @Override public Editor childNodeChanged( String name, NodeState before, NodeState after) { - return getChildIndexEditor(this, name); + PathFilter.Result filterResult = getPathFilterResult(name); + if (filterResult == PathFilter.Result.EXCLUDE) { + return null; + } + return getChildIndexEditor(this, name, filterResult); } @Override public Editor childNodeDeleted(String name, NodeState before) { - return getChildIndexEditor(this, name); + PathFilter.Result filterResult = getPathFilterResult(name); + if (filterResult == PathFilter.Result.EXCLUDE) { + return null; + } + return getChildIndexEditor(this, name, filterResult); } + private PathFilter.Result getPathFilterResult() { + return pathFilter.filter(getPath()); + } + + private PathFilter.Result getPathFilterResult(String childNodeName) { + return pathFilter.filter(concat(getPath(), childNodeName)); + } } \ No newline at end of file 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 1697870) +++ src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexPlan.java (working copy) @@ -18,7 +18,6 @@ import static com.google.common.base.Predicates.in; import static com.google.common.collect.Iterables.any; -import static com.google.common.collect.Iterables.isEmpty; import static com.google.common.collect.Sets.newHashSet; import static com.google.common.collect.Sets.newLinkedHashSet; import static java.util.Collections.emptySet; @@ -31,6 +30,8 @@ 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; @@ -94,11 +95,14 @@ private final int depth; + private final PathFilter pathFilter; + PropertyIndexPlan(String name, NodeState root, NodeState definition, Filter filter) { this.name = name; this.root = root; this.definition = definition; this.properties = newHashSet(definition.getNames(PROPERTY_NAMES)); + pathFilter = PathFilter.from(definition.builder()); if (definition.getBoolean(UNIQUE_PROPERTY_NAME)) { this.strategy = UNIQUE; @@ -118,7 +122,8 @@ Set bestValues = emptySet(); int bestDepth = 1; - if (matchesNodeTypes) { + if (matchesNodeTypes && + pathFilter.areAllDescendantsIncluded(filter.getPath())) { for (String property : properties) { PropertyRestriction restriction = filter.getPropertyRestriction(property); Index: src/test/java/org/apache/jackrabbit/oak/plugins/index/PathFilterTest.java =================================================================== --- src/test/java/org/apache/jackrabbit/oak/plugins/index/PathFilterTest.java (revision 1697870) +++ src/test/java/org/apache/jackrabbit/oak/plugins/index/PathFilterTest.java (working copy) @@ -36,81 +36,84 @@ public class PathFilterTest { @Test - public void exclude() throws Exception{ + public void exclude() throws Exception { PathFilter p = new PathFilter(of("/"), of("/etc")); - assertEquals(PathFilter.Result.INCLUDE, p.doFiler("/")); - assertEquals(PathFilter.Result.INCLUDE, p.doFiler("/a")); - assertEquals(PathFilter.Result.EXCLUDE, p.doFiler("/etc")); - assertEquals(PathFilter.Result.EXCLUDE, p.doFiler("/etc/workflow")); + assertEquals(PathFilter.Result.INCLUDE, p.filter("/")); + assertEquals(PathFilter.Result.INCLUDE, p.filter("/a")); + assertEquals(PathFilter.Result.EXCLUDE, p.filter("/etc")); + assertEquals(PathFilter.Result.EXCLUDE, p.filter("/etc/workflow")); } @Test - public void include() throws Exception{ + public void include() throws Exception { PathFilter p = new PathFilter(of("/content", "/etc"), of("/etc/workflow/instance")); - assertEquals(PathFilter.Result.TRAVERSE, p.doFiler("/")); - assertEquals(PathFilter.Result.EXCLUDE, p.doFiler("/var")); - assertEquals(PathFilter.Result.INCLUDE, p.doFiler("/content")); - assertEquals(PathFilter.Result.INCLUDE, p.doFiler("/content/example")); - assertEquals(PathFilter.Result.INCLUDE, p.doFiler("/etc")); - assertEquals(PathFilter.Result.INCLUDE, p.doFiler("/etc/workflow")); - assertEquals(PathFilter.Result.EXCLUDE, p.doFiler("/etc/workflow/instance")); - assertEquals(PathFilter.Result.EXCLUDE, p.doFiler("/etc/workflow/instance/1")); + assertEquals(PathFilter.Result.TRAVERSE, p.filter("/")); + assertEquals(PathFilter.Result.EXCLUDE, p.filter("/var")); + assertEquals(PathFilter.Result.INCLUDE, p.filter("/content")); + assertEquals(PathFilter.Result.INCLUDE, p.filter("/content/example")); + assertEquals(PathFilter.Result.INCLUDE, p.filter("/etc")); + assertEquals(PathFilter.Result.INCLUDE, p.filter("/etc/workflow")); + assertEquals(PathFilter.Result.EXCLUDE, p.filter("/etc/workflow/instance")); + assertEquals(PathFilter.Result.EXCLUDE, p.filter("/etc/workflow/instance/1")); + assertEquals(PathFilter.Result.EXCLUDE, p.filter("/x")); + assertEquals(PathFilter.Result.EXCLUDE, p.filter("/e")); + assertEquals(PathFilter.Result.EXCLUDE, p.filter("/etcx")); } @Test - public void emptyConfig() throws Exception{ + public void emptyConfig() throws Exception { NodeBuilder root = EMPTY_NODE.builder(); PathFilter p = PathFilter.from(root); - assertEquals(PathFilter.Result.INCLUDE, p.doFiler("/")); - assertEquals(PathFilter.Result.INCLUDE, p.doFiler("/a")); + assertEquals(PathFilter.Result.INCLUDE, p.filter("/")); + assertEquals(PathFilter.Result.INCLUDE, p.filter("/a")); } @Test - public void config() throws Exception{ + public void config() throws Exception { NodeBuilder root = EMPTY_NODE.builder(); root.setProperty(createProperty(PROP_INCLUDED_PATHS, of("/etc"), Type.STRINGS)); root.setProperty(createProperty(PROP_EXCLUDED_PATHS, of("/etc/workflow"), Type.STRINGS)); PathFilter p = PathFilter.from(root); - assertEquals(PathFilter.Result.TRAVERSE, p.doFiler("/")); - assertEquals(PathFilter.Result.INCLUDE, p.doFiler("/etc")); - assertEquals(PathFilter.Result.INCLUDE, p.doFiler("/etc/a")); - assertEquals(PathFilter.Result.EXCLUDE, p.doFiler("/etc/workflow")); - assertEquals(PathFilter.Result.EXCLUDE, p.doFiler("/etc/workflow/1")); + assertEquals(PathFilter.Result.TRAVERSE, p.filter("/")); + assertEquals(PathFilter.Result.INCLUDE, p.filter("/etc")); + assertEquals(PathFilter.Result.INCLUDE, p.filter("/etc/a")); + assertEquals(PathFilter.Result.EXCLUDE, p.filter("/etc/workflow")); + assertEquals(PathFilter.Result.EXCLUDE, p.filter("/etc/workflow/1")); } @Test - public void configOnlyExclude() throws Exception{ + public void configOnlyExclude() throws Exception { NodeBuilder root = EMPTY_NODE.builder(); root.setProperty(createProperty(PROP_EXCLUDED_PATHS, of("/etc/workflow"), Type.STRINGS)); PathFilter p = PathFilter.from(root); - assertEquals(PathFilter.Result.INCLUDE, p.doFiler("/")); - assertEquals(PathFilter.Result.INCLUDE, p.doFiler("/etc")); - assertEquals(PathFilter.Result.INCLUDE, p.doFiler("/etc/a")); - assertEquals(PathFilter.Result.EXCLUDE, p.doFiler("/etc/workflow")); - assertEquals(PathFilter.Result.EXCLUDE, p.doFiler("/etc/workflow/1")); + assertEquals(PathFilter.Result.INCLUDE, p.filter("/")); + assertEquals(PathFilter.Result.INCLUDE, p.filter("/etc")); + assertEquals(PathFilter.Result.INCLUDE, p.filter("/etc/a")); + assertEquals(PathFilter.Result.EXCLUDE, p.filter("/etc/workflow")); + assertEquals(PathFilter.Result.EXCLUDE, p.filter("/etc/workflow/1")); } @Test - public void invalid() throws Exception{ + public void invalid() throws Exception { try { new PathFilter(Collections.emptyList(), of("/etc")); fail(); - } catch (IllegalStateException ignore){ - + } catch (IllegalStateException ignore) { + // expected } try { new PathFilter(of("/etc/workflow"), of("/etc")); fail(); - } catch (IllegalStateException ignore){ - + } catch (IllegalStateException ignore) { + // expected } try { new PathFilter(Collections.emptyList(), Collections.emptyList()); fail(); - } catch (IllegalStateException ignore){ - + } catch (IllegalStateException ignore) { + // expected } } } Index: src/test/java/org/apache/jackrabbit/oak/plugins/index/property/Oak2077QueriesTest.java =================================================================== --- src/test/java/org/apache/jackrabbit/oak/plugins/index/property/Oak2077QueriesTest.java (revision 1697870) +++ src/test/java/org/apache/jackrabbit/oak/plugins/index/property/Oak2077QueriesTest.java (working copy) @@ -49,6 +49,7 @@ import org.apache.jackrabbit.oak.api.Tree; import org.apache.jackrabbit.oak.plugins.index.IndexUpdateCallback; import org.apache.jackrabbit.oak.plugins.index.IndexUtils; +import org.apache.jackrabbit.oak.plugins.index.PathFilter; import org.apache.jackrabbit.oak.plugins.index.property.OrderedIndex.OrderDirection; import org.apache.jackrabbit.oak.plugins.index.property.strategy.IndexStoreStrategy; import org.apache.jackrabbit.oak.plugins.index.property.strategy.OrderedContentMirrorStoreStrategy; @@ -169,8 +170,8 @@ this.rnd = rnd; } - public SeededPropertyIndexEditor(SeededPropertyIndexEditor parent, String name) { - super(parent, name); + public SeededPropertyIndexEditor(SeededPropertyIndexEditor parent, String name, PathFilter.Result pathFilterResult) { + super(parent, name, pathFilterResult); this.rnd = parent.rnd; } @@ -185,8 +186,8 @@ } @Override - PropertyIndexEditor getChildIndexEditor(PropertyIndexEditor parent, String name) { - return new SeededPropertyIndexEditor(this, name); + PropertyIndexEditor getChildIndexEditor(PropertyIndexEditor parent, String name, PathFilter.Result pathFilterResult) { + return new SeededPropertyIndexEditor(this, name, pathFilterResult); } } @@ -314,7 +315,7 @@ /** * truncate the {@link AbstractQueryTest#TEST_INDEX_NAME} index at the 4th element of the * provided lane returning the previous value - * + * * @param lane the desired lane. Must be 0 <= {@code lane} < {@link OrderedIndex#LANES} * @param inexistent the derired value to be injected * @return the value before the change Index: src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexTest.java =================================================================== --- src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexTest.java (revision 1697870) +++ src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexTest.java (working copy) @@ -16,6 +16,8 @@ */ package org.apache.jackrabbit.oak.plugins.index.property; +import static com.google.common.collect.ImmutableSet.of; +import static java.util.Arrays.asList; import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE; import static org.apache.jackrabbit.JcrConstants.JCR_SYSTEM; import static org.apache.jackrabbit.JcrConstants.NT_BASE; @@ -23,14 +25,19 @@ import static org.apache.jackrabbit.JcrConstants.NT_UNSTRUCTURED; import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME; import static org.apache.jackrabbit.oak.plugins.index.IndexUtils.createIndexDefinition; +import static org.apache.jackrabbit.oak.plugins.index.PathFilter.PROP_EXCLUDED_PATHS; +import static org.apache.jackrabbit.oak.plugins.index.PathFilter.PROP_INCLUDED_PATHS; import static org.apache.jackrabbit.oak.plugins.index.counter.NodeCounterEditor.COUNT_PROPERTY_NAME; import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE; +import static org.apache.jackrabbit.oak.plugins.memory.PropertyStates.createProperty; import static org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants.JCR_NODE_TYPES; import static org.apache.jackrabbit.oak.plugins.nodetype.write.InitialContent.INITIAL_CONTENT; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.junit.matchers.JUnitMatchers.containsString; import java.util.Arrays; import java.util.Set; @@ -43,11 +50,13 @@ import ch.qos.logback.core.spi.FilterReply; import com.google.common.collect.Iterables; import org.apache.jackrabbit.oak.api.CommitFailedException; +import org.apache.jackrabbit.oak.api.Tree; import org.apache.jackrabbit.oak.api.Type; import org.apache.jackrabbit.oak.plugins.index.IndexUpdateProvider; import org.apache.jackrabbit.oak.plugins.index.property.strategy.ContentMirrorStoreStrategy; import org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState; import org.apache.jackrabbit.oak.query.QueryEngineSettings; +import org.apache.jackrabbit.oak.query.ast.Operator; import org.apache.jackrabbit.oak.query.ast.SelectorImpl; import org.apache.jackrabbit.oak.query.index.FilterImpl; import org.apache.jackrabbit.oak.query.index.TraversingIndex; @@ -565,6 +574,165 @@ deregisterAppender(appender); } + @Test + public void testPathInclude() throws Exception { + NodeState root = INITIAL_CONTENT; + + // Add index definition + NodeBuilder builder = root.builder(); + NodeBuilder index = createIndexDefinition(builder.child(INDEX_DEFINITIONS_NAME), "foo", + true, false, ImmutableSet.of("foo"), null); + index.setProperty(createProperty(PROP_INCLUDED_PATHS, of("/test/a"), Type.STRINGS)); + NodeState before = builder.getNodeState(); + + // Add some content and process it through the property index hook + builder.child("test").child("a").setProperty("foo", "abc"); + builder.child("test").child("b").setProperty("foo", "abc"); + NodeState after = builder.getNodeState(); + + NodeState indexed = HOOK.processCommit(before, after, CommitInfo.EMPTY); + + FilterImpl f = createFilter(indexed, NT_BASE); + + // Query the index + PropertyIndexLookup lookup = new PropertyIndexLookup(indexed); + assertEquals(ImmutableSet.of("test/a"), find(lookup, "foo", "abc", f)); + } + + @Test + public void testPathExclude() throws Exception { + NodeState root = INITIAL_CONTENT; + + // Add index definition + NodeBuilder builder = root.builder(); + NodeBuilder index = createIndexDefinition(builder.child(INDEX_DEFINITIONS_NAME), "foo", + true, false, ImmutableSet.of("foo"), null); + index.setProperty(createProperty(PROP_EXCLUDED_PATHS, of("/test/a"), Type.STRINGS)); + NodeState before = builder.getNodeState(); + + // Add some content and process it through the property index hook + builder.child("test").child("a").setProperty("foo", "abc"); + builder.child("test").child("b").setProperty("foo", "abc"); + NodeState after = builder.getNodeState(); + + NodeState indexed = HOOK.processCommit(before, after, CommitInfo.EMPTY); + + FilterImpl f = createFilter(indexed, NT_BASE); + f.restrictProperty("foo", Operator.EQUAL, PropertyValues.newString("abc")); + + // Query the index + PropertyIndexLookup lookup = new PropertyIndexLookup(indexed); + assertEquals(ImmutableSet.of("test/b"), find(lookup, "foo", "abc", f)); + + //no path restriction, opt out + PropertyIndexPlan plan = new PropertyIndexPlan("plan", root, index.getNodeState(), f); + assertTrue(Double.POSITIVE_INFINITY == plan.getCost()); + + //path restriction is not an ancestor of excluded path, index may be used + f.setPath("/test2"); + plan = new PropertyIndexPlan("plan", root, index.getNodeState(), f); + assertTrue(Double.POSITIVE_INFINITY != plan.getCost()); + + //path restriction is an ancestor of excluded path, opt out + f.setPath("/test"); + plan = new PropertyIndexPlan("plan", root, index.getNodeState(), f); + assertTrue(Double.POSITIVE_INFINITY == plan.getCost()); + } + + @Test + public void testPathIncludeExclude() throws Exception { + NodeState root = INITIAL_CONTENT; + + // Add index definition + NodeBuilder builder = root.builder(); + NodeBuilder index = createIndexDefinition(builder.child(INDEX_DEFINITIONS_NAME), "foo", + true, false, ImmutableSet.of("foo"), null); + index.setProperty(createProperty(PROP_INCLUDED_PATHS, of("/test/a"), Type.STRINGS)); + index.setProperty(createProperty(PROP_EXCLUDED_PATHS, of("/test/a/b"), Type.STRINGS)); + NodeState before = builder.getNodeState(); + + // Add some content and process it through the property index hook + builder.child("test").child("a").setProperty("foo", "abc"); + builder.child("test").child("a").child("b").setProperty("foo", "abc"); + NodeState after = builder.getNodeState(); + + NodeState indexed = HOOK.processCommit(before, after, CommitInfo.EMPTY); + + FilterImpl f = createFilter(indexed, NT_BASE); + f.restrictProperty("foo", Operator.EQUAL, PropertyValues.newString("abc")); + + // Query the index + PropertyIndexLookup lookup = new PropertyIndexLookup(indexed); + assertEquals(ImmutableSet.of("test/a"), find(lookup, "foo", "abc", f)); + + //no path restriction, opt out + PropertyIndexPlan plan = new PropertyIndexPlan("plan", root, index.getNodeState(), f); + assertTrue(Double.POSITIVE_INFINITY == plan.getCost()); + + //path restriction is not an ancestor of excluded path, index may be used + f.setPath("/test/a/x"); + plan = new PropertyIndexPlan("plan", root, index.getNodeState(), f); + assertTrue(Double.POSITIVE_INFINITY != plan.getCost()); + + //path restriction is an ancestor of excluded path but no included path, opt out + f.setPath("/test/a/b"); + plan = new PropertyIndexPlan("plan", root, index.getNodeState(), f); + assertTrue(Double.POSITIVE_INFINITY == plan.getCost()); + + //path restriction is an ancestor of excluded path, opt out + f.setPath("/test/a"); + plan = new PropertyIndexPlan("plan", root, index.getNodeState(), f); + assertTrue(Double.POSITIVE_INFINITY == plan.getCost()); +} + + @Test + public void testPathExcludeInclude() throws Exception{ + NodeState root = INITIAL_CONTENT; + + // Add index definition + NodeBuilder builder = root.builder(); + NodeBuilder index = createIndexDefinition(builder.child(INDEX_DEFINITIONS_NAME), "foo", + true, false, ImmutableSet.of("foo"), null); + index.setProperty(createProperty(PROP_INCLUDED_PATHS, of("/test/a/b"), Type.STRINGS)); + index.setProperty(createProperty(PROP_EXCLUDED_PATHS, of("/test/a"), Type.STRINGS)); + NodeState before = builder.getNodeState(); + + // Add some content and process it through the property index hook + builder.child("test").child("a").setProperty("foo", "abc"); + builder.child("test").child("a").child("b").setProperty("foo", "abc"); + NodeState after = builder.getNodeState(); + + try { + HOOK.processCommit(before, after, CommitInfo.EMPTY); + assertTrue(false); + } catch (IllegalStateException expected) {} + } + + @Test + public void testPathMismatch() throws Exception { + NodeState root = INITIAL_CONTENT; + + // Add index definition + NodeBuilder builder = root.builder(); + NodeBuilder index = createIndexDefinition(builder.child(INDEX_DEFINITIONS_NAME), "foo", + true, false, ImmutableSet.of("foo"), null); + index.setProperty(createProperty(PROP_INCLUDED_PATHS, of("/test/a"), Type.STRINGS)); + index.setProperty(createProperty(PROP_EXCLUDED_PATHS, of("/test/a/b"), Type.STRINGS)); + NodeState before = builder.getNodeState(); + + // Add some content and process it through the property index hook + builder.child("test").child("a").setProperty("foo", "abc"); + builder.child("test").child("a").child("b").setProperty("foo", "abc"); + NodeState after = builder.getNodeState(); + + NodeState indexed = HOOK.processCommit(before, after, CommitInfo.EMPTY); + + FilterImpl f = createFilter(indexed, NT_BASE); + f.restrictPath("/test2", Filter.PathRestriction.ALL_CHILDREN); + PropertyIndexPlan plan = new PropertyIndexPlan("plan", root, index.getNodeState(), f); + assertTrue(Double.POSITIVE_INFINITY == plan.getCost()); + } + private int getResultSize(NodeState indexed, String name, String value){ FilterImpl f = createFilter(indexed, NT_BASE);