You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by th...@apache.org on 2018/09/06 13:30:07 UTC
svn commit: r1840221 - in /jackrabbit/oak/trunk/oak-core/src:
main/java/org/apache/jackrabbit/oak/plugins/index/
main/java/org/apache/jackrabbit/oak/plugins/index/property/
test/java/org/apache/jackrabbit/oak/plugins/index/property/
Author: thomasm
Date: Thu Sep 6 13:30:07 2018
New Revision: 1840221
URL: http://svn.apache.org/viewvc?rev=1840221&view=rev
Log:
OAK-7739: Use an index only if a certain node or property exists
Added:
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexDisabledTest.java
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexConstants.java
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexConstants.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexConstants.java?rev=1840221&r1=1840220&r2=1840221&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexConstants.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexConstants.java Thu Sep 6 13:30:07 2018
@@ -139,5 +139,12 @@ public interface IndexConstants {
* Boolean flag indicating that old indexes need to be disabled
*/
String DISABLE_INDEXES_ON_NEXT_CYCLE = ":disableIndexesOnNextCycle";
+
+ /**
+ * The property of an index. If the given node or property exists, then the
+ * index is used for queries; otherwise, it is not used (returns infinite
+ * cost). The value is: nodes, the path. For properties, the path of the node, then '@' property.
+ */
+ String USE_IF_EXISTS = "useIfExists";
}
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java?rev=1840221&r1=1840220&r2=1840221&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java Thu Sep 6 13:30:07 2018
@@ -21,14 +21,9 @@ import static org.apache.jackrabbit.oak.
import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME;
import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.TYPE_PROPERTY_NAME;
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.util.HashSet;
-import java.util.Set;
-
import org.apache.jackrabbit.oak.api.PropertyState;
-import org.apache.jackrabbit.oak.api.PropertyValue;
import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider;
import org.apache.jackrabbit.oak.spi.query.Cursor;
@@ -40,7 +35,6 @@ import org.apache.jackrabbit.oak.spi.sta
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.google.common.base.Charsets;
import com.google.common.collect.Iterables;
/**
@@ -100,7 +94,7 @@ class PropertyIndex implements QueryInde
/**
* Cached property index plan
*/
- private PropertyIndexPlan plan;
+ private PropertyIndexPlan cachedPlan;
PropertyIndex(MountInfoProvider mountInfoProvider) {
this.mountInfoProvider = mountInfoProvider;
@@ -111,12 +105,12 @@ class PropertyIndex implements QueryInde
// 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;
+ PropertyIndexPlan plan = this.cachedPlan;
if (plan != null && plan.getFilter().toString().equals(filter.toString())) {
return plan;
} else {
plan = createPlan(root, filter, mountInfoProvider);
- this.plan = plan;
+ this.cachedPlan = plan;
return plan;
}
}
@@ -130,7 +124,7 @@ class PropertyIndex implements QueryInde
NodeState state = root.getChildNode(INDEX_DEFINITIONS_NAME);
for (ChildNodeEntry entry : state.getChildNodeEntries()) {
NodeState definition = entry.getNodeState();
- if (wrongIndex(entry, filter)) {
+ if (wrongIndex(entry, filter, root)) {
continue;
}
if (PROPERTY.equals(definition.getString(TYPE_PROPERTY_NAME))
@@ -154,9 +148,13 @@ class PropertyIndex implements QueryInde
return bestPlan;
}
- private static boolean wrongIndex(ChildNodeEntry entry, Filter filter) {
+ private static boolean wrongIndex(ChildNodeEntry entry, Filter filter, NodeState root) {
// REMARK: similar code is used in oak-lucene, IndexPlanner
// skip index if "option(index ...)" doesn't match
+ NodeState definition = entry.getNodeState();
+ if (!isEnabled(definition, root)) {
+ return true;
+ }
PropertyRestriction indexName = filter.getPropertyRestriction(IndexConstants.INDEX_NAME_OPTION);
boolean wrong = false;
if (indexName != null && indexName.first != null) {
@@ -171,7 +169,6 @@ class PropertyIndex implements QueryInde
PropertyRestriction indexTag = filter.getPropertyRestriction(IndexConstants.INDEX_TAG_OPTION);
if (indexTag != null && indexTag.first != null) {
// index tag specified
- NodeState definition = entry.getNodeState();
String[] tags = getOptionalStrings(definition, IndexConstants.INDEX_TAGS);
if (tags == null) {
// no tag
@@ -191,6 +188,24 @@ class PropertyIndex implements QueryInde
return wrong;
}
+ private static boolean isEnabled(NodeState definition, NodeState root) {
+ String useIfExists = definition.getString(IndexConstants.USE_IF_EXISTS);
+ if (useIfExists == null) {
+ return true;
+ }
+ NodeState nodeState = root;
+ for (String element : PathUtils.elements(useIfExists)) {
+ if (element.startsWith("@")) {
+ return nodeState.hasProperty(element.substring(1));
+ }
+ nodeState = nodeState.getChildNode(element);
+ if (!nodeState.exists()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
private static String[] getOptionalStrings(NodeState defn, String propertyName) {
PropertyState ps = defn.getProperty(propertyName);
if (ps != null) {
@@ -225,7 +240,6 @@ class PropertyIndex implements QueryInde
// not an appropriate index for no property restrictions & selector constraints
return Double.POSITIVE_INFINITY;
}
-
PropertyIndexPlan plan = getPlan(root, filter);
if (plan != null) {
return plan.getCost();
Added: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexDisabledTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexDisabledTest.java?rev=1840221&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexDisabledTest.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexDisabledTest.java Thu Sep 6 13:30:07 2018
@@ -0,0 +1,168 @@
+/*
+ * 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.plugins.index.property;
+
+import static com.google.common.collect.ImmutableSet.of;
+import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
+import static org.apache.jackrabbit.JcrConstants.NT_BASE;
+import static org.apache.jackrabbit.JcrConstants.NT_FILE;
+import static org.apache.jackrabbit.JcrConstants.NT_UNSTRUCTURED;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.DECLARING_NODE_TYPES;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_CONTENT_NODE_NAME;
+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.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.InitialContentHelper.INITIAL_CONTENT;
+import static org.apache.jackrabbit.oak.api.Type.NAMES;
+import static org.apache.jackrabbit.oak.spi.commit.CommitInfo.EMPTY;
+import static org.apache.jackrabbit.oak.spi.filter.PathFilter.PROP_EXCLUDED_PATHS;
+import static org.apache.jackrabbit.oak.spi.filter.PathFilter.PROP_INCLUDED_PATHS;
+import static org.apache.jackrabbit.oak.spi.state.NodeStateUtils.getNode;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Set;
+
+import org.apache.jackrabbit.oak.InitialContentHelper;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.PropertyValue;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
+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.plugins.memory.PropertyStates;
+import org.apache.jackrabbit.oak.plugins.memory.PropertyValues;
+import org.apache.jackrabbit.oak.query.NodeStateNodeTypeInfoProvider;
+import org.apache.jackrabbit.oak.query.QueryEngineSettings;
+import org.apache.jackrabbit.oak.query.ast.NodeTypeInfo;
+import org.apache.jackrabbit.oak.query.ast.NodeTypeInfoProvider;
+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;
+import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
+import org.apache.jackrabbit.oak.spi.commit.CompositeHook;
+import org.apache.jackrabbit.oak.spi.commit.DefaultValidator;
+import org.apache.jackrabbit.oak.spi.commit.EditorHook;
+import org.apache.jackrabbit.oak.spi.commit.Validator;
+import org.apache.jackrabbit.oak.spi.commit.ValidatorProvider;
+import org.apache.jackrabbit.oak.spi.mount.Mount;
+import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider;
+import org.apache.jackrabbit.oak.spi.mount.Mounts;
+import org.apache.jackrabbit.oak.spi.query.Filter;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.LoggerFactory;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.Appender;
+import ch.qos.logback.core.read.ListAppender;
+import ch.qos.logback.core.spi.FilterReply;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Sets;
+
+/**
+ * Test the Property2 index mechanism.
+ */
+public class PropertyIndexDisabledTest {
+
+ private static final int MANY = 100;
+
+ private NodeState root;
+ private NodeBuilder rootBuilder;
+ private static final EditorHook HOOK = new EditorHook(
+ new IndexUpdateProvider(new PropertyIndexEditorProvider()));
+
+ @Before
+ public void setup() throws Exception {
+ root = EmptyNodeState.EMPTY_NODE;
+ rootBuilder = InitialContentHelper.INITIAL_CONTENT.builder();
+ commit();
+ }
+
+ @Test
+ public void disabled() throws Exception {
+ NodeBuilder index = createIndexDefinition(rootBuilder.child(INDEX_DEFINITIONS_NAME),
+ "foo", true, false, ImmutableSet.of("foo"), null);
+ index.setProperty(IndexConstants.USE_IF_EXISTS, "/");
+ commit();
+ for (int i = 0; i < MANY; i++) {
+ rootBuilder.child("test").child("n" + i).setProperty("foo", "x" + i % 20);
+ }
+ commit();
+ FilterImpl f = createFilter(root, NT_BASE);
+ f.restrictProperty("foo", Operator.EQUAL, PropertyValues.newString("x10"));
+ PropertyIndex propertyIndex = new PropertyIndex(Mounts.defaultMountInfoProvider());
+ assertTrue(propertyIndex.getCost(f, root) != Double.POSITIVE_INFINITY);
+ assertEquals("property foo = x10", propertyIndex.getPlan(f, root));
+
+ // now test with a node that doesn't exist
+ index = rootBuilder.child(INDEX_DEFINITIONS_NAME).child("foo");
+ index.setProperty(IndexConstants.USE_IF_EXISTS, "/doesNotExist");
+ commit();
+ // need to create a new one - otherwise the cached plan is used
+ propertyIndex = new PropertyIndex(Mounts.defaultMountInfoProvider());
+ assertTrue(propertyIndex.getCost(f, root) == Double.POSITIVE_INFINITY);
+ assertEquals("property index not applicable", propertyIndex.getPlan(f, root));
+
+ // test with a property that does exist
+ index = rootBuilder.child(INDEX_DEFINITIONS_NAME).child("foo");
+ index.setProperty(IndexConstants.USE_IF_EXISTS, "/oak:index/@jcr:primaryType");
+ commit();
+ // need to create a new one - otherwise the cached plan is used
+ propertyIndex = new PropertyIndex(Mounts.defaultMountInfoProvider());
+ assertTrue(propertyIndex.getCost(f, root) != Double.POSITIVE_INFINITY);
+ assertEquals("property foo = x10", propertyIndex.getPlan(f, root));
+
+ // test with a property that does not exist
+ index = rootBuilder.child(INDEX_DEFINITIONS_NAME).child("foo");
+ index.setProperty(IndexConstants.USE_IF_EXISTS, "/oak:index/@unknownProperty");
+ commit();
+ // need to create a new one - otherwise the cached plan is used
+ propertyIndex = new PropertyIndex(Mounts.defaultMountInfoProvider());
+ assertTrue(propertyIndex.getCost(f, root) == Double.POSITIVE_INFINITY);
+ assertEquals("property index not applicable", propertyIndex.getPlan(f, root));
+ }
+
+ private void commit() throws Exception {
+ root = HOOK.processCommit(rootBuilder.getBaseState(), rootBuilder.getNodeState(), EMPTY);
+ rootBuilder = root.builder();
+ }
+
+ private static FilterImpl createFilter(NodeState root, String nodeTypeName) {
+ NodeTypeInfoProvider nodeTypes = new NodeStateNodeTypeInfoProvider(root);
+ NodeTypeInfo type = nodeTypes.getNodeTypeInfo(nodeTypeName);
+ SelectorImpl selector = new SelectorImpl(type, nodeTypeName);
+ return new FilterImpl(selector, "SELECT * FROM [" + nodeTypeName + "]", new QueryEngineSettings());
+ }
+
+}