Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexUtils.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexUtils.java (revision 1574393) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexUtils.java (working copy) @@ -107,8 +107,29 @@ boolean unique, @Nonnull String[] propertyNames, @Nullable String[] declaringNodeTypeNames) throws RepositoryException { + + createIndexDefinition(indexNode, indexDefName, unique, propertyNames, declaringNodeTypeNames, PropertyIndexEditorProvider.TYPE); + } + + /** + * Create a new property index definition below the given {@code indexNode} of the provided {@code propertyIndexType}. + * + * @param indexNode + * @param indexDefName + * @param unique + * @param propertyNames + * @param declaringNodeTypeNames + * @param propertyIndexType + * @throws RepositoryException + */ + public static void createIndexDefinition(@Nonnull NodeUtil indexNode, + @Nonnull String indexDefName, + boolean unique, + @Nonnull String[] propertyNames, + @Nullable String[] declaringNodeTypeNames, + @Nonnull String propertyIndexType) throws RepositoryException { NodeUtil entry = indexNode.getOrAddChild(indexDefName, INDEX_DEFINITIONS_NODE_TYPE); - entry.setString(TYPE_PROPERTY_NAME, PropertyIndexEditorProvider.TYPE); + entry.setString(TYPE_PROPERTY_NAME, propertyIndexType); entry.setBoolean(REINDEX_PROPERTY_NAME, true); if (unique) { entry.setBoolean(UNIQUE_PROPERTY_NAME, true); Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedIndex.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedIndex.java (revision 0) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedIndex.java (working copy) @@ -0,0 +1,26 @@ +/* + * 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; + +/** + * interface for shared constants around different actors: QueryIndex, IndexEditors, + * IndexEditorProviders, ... + */ +public interface OrderedIndex { + String TYPE = "ordered"; +} 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 0) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndex.java (working copy) @@ -0,0 +1,35 @@ +/* + * 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 org.apache.jackrabbit.oak.plugins.index.property.OrderedIndex.TYPE; + +import org.apache.jackrabbit.oak.spi.state.NodeState; + +public class OrderedPropertyIndex extends PropertyIndex { + + @Override + public String getIndexName() { + return TYPE; + } + + @Override + PropertyIndexLookup getLookup(NodeState root) { + return new OrderedPropertyIndexLookup(root); + } +} Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditor.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditor.java (revision 0) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditor.java (working copy) @@ -0,0 +1,150 @@ +/* + * 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 java.util.Collections; +import java.util.Set; + +import javax.annotation.Nonnull; + +import org.apache.jackrabbit.oak.api.CommitFailedException; +import org.apache.jackrabbit.oak.api.PropertyState; +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.property.strategy.IndexStoreStrategy; +import org.apache.jackrabbit.oak.plugins.index.property.strategy.OrderedContentMirrorStoreStrategy; +import org.apache.jackrabbit.oak.spi.commit.Editor; +import org.apache.jackrabbit.oak.spi.state.NodeBuilder; +import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Strings; + +public class OrderedPropertyIndexEditor extends PropertyIndexEditor { + private static final Logger log = LoggerFactory.getLogger(OrderedPropertyIndexEditor.class); + private static final IndexStoreStrategy ORDERED_MIRROR = new OrderedContentMirrorStoreStrategy(); + + private final Set propertyNames; + + private boolean properlyConfigured; + + public OrderedPropertyIndexEditor(NodeBuilder definition, NodeState root, + IndexUpdateCallback callback) { + super(definition, root, callback); + + Set pns = null; + + PropertyState names = definition.getProperty(IndexConstants.PROPERTY_NAMES); + if (names != null) { + String value = names.getValue(Type.NAME, 0); + if (Strings.isNullOrEmpty(value)) { + log.warn("Empty value passed as propertyNames. Index not properly configured. Ignoring."); + } else { + if (names.isArray()) { + log.warn("Only single value supported. '{}' only will be used.", value); + } + pns = Collections.singleton(value); + this.properlyConfigured = true; + } + } + + this.propertyNames = pns; + } + + OrderedPropertyIndexEditor(OrderedPropertyIndexEditor parent, String name) { + super(parent, name); + this.propertyNames = parent.getPropertyNames(); + } + + /** + * Same as {@link PropertyIndexEditor#getStrategy(boolean)} but ignores the boolean flag. + * + * @return the proper index strategy + */ + @Override + IndexStoreStrategy getStrategy(boolean unique) { + return ORDERED_MIRROR; + } + + public boolean isProperlyConfigured() { + return properlyConfigured; + } + + @Override + Set getPropertyNames() { + return propertyNames; + } + + @Override + PropertyIndexEditor getChildIndexEditor(@Nonnull PropertyIndexEditor parent, + @Nonnull String name) { + return new OrderedPropertyIndexEditor(this, name); + } + + @Override + public void enter(NodeState before, NodeState after) { + log.debug("enter() - before: {} - after: {}", before, after); + super.enter(before, after); + } + + @Override + public void leave(NodeState before, NodeState after) throws CommitFailedException { + log.debug("leave() - before: {} - after: {}", before, after); + super.leave(before, after); + } + + @Override + public void propertyAdded(PropertyState after) { + log.debug("propertyAdded() - after: {}", after); + super.propertyAdded(after); + } + + @Override + public void propertyChanged(PropertyState before, PropertyState after) { + log.debug("propertyChanged() - before: {} - after: {}", before, after); + super.propertyChanged(before, after); + } + + @Override + public void propertyDeleted(PropertyState before) { + log.debug("propertyDeleted() - before: {}", before); + super.propertyDeleted(before); + } + + @Override + public Editor childNodeAdded(String name, NodeState after) { + log.debug("childNodeAdded() - name: {} - after: {}", name, after); + return super.childNodeAdded(name, after); + } + + @Override + public Editor childNodeChanged(String name, NodeState before, NodeState after) { + log.debug("childNodeChanged() - name: {} - before: {} - after: {}", new Object[] { name, + before, + after }); + return super.childNodeChanged(name, before, after); + } + + @Override + public Editor childNodeDeleted(String name, NodeState before) { + log.debug("childNodeDeleted() - name: {} - before: {}", name, before); + return super.childNodeDeleted(name, before); + } +} Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditorProvider.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditorProvider.java (revision 0) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditorProvider.java (working copy) @@ -0,0 +1,42 @@ +/* + * 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 javax.annotation.CheckForNull; +import javax.annotation.Nonnull; + +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Service; +import org.apache.jackrabbit.oak.api.CommitFailedException; +import org.apache.jackrabbit.oak.plugins.index.IndexEditorProvider; +import org.apache.jackrabbit.oak.plugins.index.IndexUpdateCallback; +import org.apache.jackrabbit.oak.spi.commit.Editor; +import org.apache.jackrabbit.oak.spi.state.NodeBuilder; +import org.apache.jackrabbit.oak.spi.state.NodeState; + +@Component +@Service(IndexEditorProvider.class) +public class OrderedPropertyIndexEditorProvider implements IndexEditorProvider, OrderedIndex { + + @Override + @CheckForNull + public Editor getIndexEditor(@Nonnull String type, @Nonnull NodeBuilder definition, @Nonnull NodeState root, @Nonnull IndexUpdateCallback callback) throws CommitFailedException { + Editor editor = (TYPE.equals(type)) ? new OrderedPropertyIndexEditor(definition,root,callback) : null; + return editor; + } +} 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 0) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexLookup.java (working copy) @@ -0,0 +1,44 @@ +/* + * 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 org.apache.jackrabbit.oak.plugins.index.property.strategy.IndexStoreStrategy; +import org.apache.jackrabbit.oak.plugins.index.property.strategy.OrderedContentMirrorStoreStrategy; +import org.apache.jackrabbit.oak.spi.state.NodeState; + +/** + * + */ +public class OrderedPropertyIndexLookup extends PropertyIndexLookup { + + private static final IndexStoreStrategy STORE = new OrderedContentMirrorStoreStrategy(); + + public OrderedPropertyIndexLookup(NodeState root) { + super(root); + } + + @Override + IndexStoreStrategy getStrategy(NodeState indexMeta) { + return STORE; + } + + @Override + String getType() { + return OrderedIndex.TYPE; + } +} 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 0) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexProvider.java (working copy) @@ -0,0 +1,41 @@ +/* + * 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 java.util.List; + +import javax.annotation.Nonnull; + +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Service; +import org.apache.jackrabbit.oak.spi.query.QueryIndex; +import org.apache.jackrabbit.oak.spi.query.QueryIndexProvider; +import org.apache.jackrabbit.oak.spi.state.NodeState; + +import com.google.common.collect.ImmutableList; + +@Component +@Service(QueryIndexProvider.class) +public class OrderedPropertyIndexProvider implements QueryIndexProvider { + + @Override + @Nonnull + public List getQueryIndexes(NodeState nodeState) { + return ImmutableList. of(new OrderedPropertyIndex()); + } +} 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 1574393) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java (working copy) @@ -120,6 +120,16 @@ return "property"; } + /** + * return the proper implementation of the Lookup + * + * @param root + * @return the lookup + */ + PropertyIndexLookup getLookup(NodeState root) { + return new PropertyIndexLookup(root); + } + @Override public double getCost(Filter filter, NodeState root) { if (filter.getFullTextConstraint() != null) { @@ -127,7 +137,7 @@ return Double.POSITIVE_INFINITY; } - PropertyIndexLookup lookup = new PropertyIndexLookup(root); + PropertyIndexLookup lookup = getLookup(root); for (PropertyRestriction pr : filter.getPropertyRestrictions()) { String propertyName = PathUtils.getName(pr.propertyName); // TODO support indexes on a path @@ -157,7 +167,7 @@ public Cursor query(Filter filter, NodeState root) { Iterable paths = null; - PropertyIndexLookup lookup = new PropertyIndexLookup(root); + PropertyIndexLookup lookup = getLookup(root); int depth = 1; for (PropertyRestriction pr : filter.getPropertyRestrictions()) { String propertyName = PathUtils.getName(pr.propertyName); @@ -202,7 +212,7 @@ public String getPlan(Filter filter, NodeState root) { StringBuilder buff = new StringBuilder("property"); StringBuilder notIndexed = new StringBuilder(); - PropertyIndexLookup lookup = new PropertyIndexLookup(root); + PropertyIndexLookup lookup = getLookup(root); for (PropertyRestriction pr : filter.getPropertyRestrictions()) { String propertyName = PathUtils.getName(pr.propertyName); // TODO support indexes on a path Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditor.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditor.java (revision 1574393) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditor.java (working copy) @@ -31,6 +31,7 @@ import java.util.Set; +import javax.annotation.Nonnull; import javax.jcr.PropertyType; import org.apache.jackrabbit.oak.api.CommitFailedException; @@ -111,6 +112,8 @@ this.path = "/"; this.definition = definition; + //initPropertyNames(definition); + // get property names PropertyState names = definition.getProperty(PROPERTY_NAMES); if (names.count() == 1) { @@ -137,17 +140,26 @@ } this.updateCallback = updateCallback; } - - private PropertyIndexEditor(PropertyIndexEditor parent, String name) { + + PropertyIndexEditor(PropertyIndexEditor parent, String name) { this.parent = parent; this.name = name; this.path = null; this.definition = parent.definition; - this.propertyNames = parent.propertyNames; + this.propertyNames = parent.getPropertyNames(); this.typePredicate = parent.typePredicate; this.keysToCheckForUniqueness = parent.keysToCheckForUniqueness; this.updateCallback = parent.updateCallback; } + + /** + * commodity method for allowing extensions + * + * @return the propertyNames + */ + Set getPropertyNames() { + return propertyNames; + } /** * Returns the path of this node, building it lazily when first requested. @@ -193,7 +205,7 @@ return keys; } - private static IndexStoreStrategy getStrategy(boolean unique) { + IndexStoreStrategy getStrategy(boolean unique) { return unique ? UNIQUE : MIRROR; } @@ -214,8 +226,8 @@ if (typeChanged) { // possible type change, so ignore diff results and // just load all matching values from both states - beforeKeys = getMatchingKeys(before, propertyNames); - afterKeys = getMatchingKeys(after, propertyNames); + beforeKeys = getMatchingKeys(before, getPropertyNames()); + afterKeys = getMatchingKeys(after, getPropertyNames()); } if (beforeKeys != null && !typePredicate.apply(before)) { // the before state doesn't match the type, so clear its values @@ -282,7 +294,7 @@ public void propertyAdded(PropertyState after) { String name = after.getName(); typeChanged = typeChanged || isTypeProperty(name); - if (propertyNames.contains(name)) { + if (getPropertyNames().contains(name)) { afterKeys = addValueKeys(afterKeys, after); } } @@ -291,7 +303,7 @@ public void propertyChanged(PropertyState before, PropertyState after) { String name = after.getName(); typeChanged = typeChanged || isTypeProperty(name); - if (propertyNames.contains(name)) { + if (getPropertyNames().contains(name)) { beforeKeys = addValueKeys(beforeKeys, before); afterKeys = addValueKeys(afterKeys, after); } @@ -301,25 +313,36 @@ public void propertyDeleted(PropertyState before) { String name = before.getName(); typeChanged = typeChanged || isTypeProperty(name); - if (propertyNames.contains(name)) { + if (getPropertyNames().contains(name)) { beforeKeys = addValueKeys(beforeKeys, before); } } + /** + * Retrieve a new index editor associated with the child node to process + * + * @param parent the index editor related to the parent node + * @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); + } + @Override public Editor childNodeAdded(String name, NodeState after) { - return new PropertyIndexEditor(this, name); + return getChildIndexEditor(this, name); } @Override public Editor childNodeChanged( String name, NodeState before, NodeState after) { - return new PropertyIndexEditor(this, name); + return getChildIndexEditor(this, name); } @Override public Editor childNodeDeleted(String name, NodeState before) { - return new PropertyIndexEditor(this, name); + return getChildIndexEditor(this, name); } -} +} \ No newline at end of file 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 1574393) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java (working copy) @@ -25,7 +25,6 @@ import static org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexEditorProvider.TYPE; import static org.apache.jackrabbit.oak.plugins.index.property.PropertyIndex.encode; -import java.util.Iterator; import java.util.Set; import javax.annotation.Nullable; @@ -100,12 +99,11 @@ } NodeState node = root; - Iterator it = PathUtils.elements(path).iterator(); - while (it.hasNext()) { + for (String s : PathUtils.elements(path)) { if (getIndexNode(node, propertyName, filter) != null) { return true; } - node = node.getChildNode(it.next()); + node = node.getChildNode(s); } return false; } @@ -117,8 +115,8 @@ } return getStrategy(indexMeta).query(filter, propertyName, indexMeta, encode(value)); } - - private static IndexStoreStrategy getStrategy(NodeState indexMeta) { + + IndexStoreStrategy getStrategy(NodeState indexMeta) { if (indexMeta.getBoolean(IndexConstants.UNIQUE_PROPERTY_NAME)) { return UNIQUE; } @@ -130,7 +128,7 @@ if (indexMeta == null) { return Double.POSITIVE_INFINITY; } - return COST_OVERHEAD + + return COST_OVERHEAD + getStrategy(indexMeta).count(indexMeta, encode(value), MAX_COST); } @@ -146,8 +144,7 @@ * node was found */ @Nullable - private static NodeState getIndexNode( - NodeState node, String propertyName, Filter filter) { + private NodeState getIndexNode(NodeState node, String propertyName, Filter filter) { // keep a fallback to a matching index def that has *no* node type constraints // (initially, there is no fallback) NodeState fallback = null; @@ -156,7 +153,7 @@ for (ChildNodeEntry entry : state.getChildNodeEntries()) { NodeState index = entry.getNodeState(); PropertyState type = index.getProperty(TYPE_PROPERTY_NAME); - if (type == null || type.isArray() || !TYPE.equals(type.getValue(Type.STRING))) { + if (type == null || type.isArray() || !getType().equals(type.getValue(Type.STRING))) { continue; } if (contains(index.getNames(PROPERTY_NAMES), propertyName)) { @@ -184,7 +181,16 @@ } return fallback; } - + + /** + * retrieve the type of the index + * + * @return the type + */ + String getType() { + return TYPE; + } + private static Set getSuperTypes(Filter filter) { if (filter != null && !filter.matchesAllTypes()) { return filter.getSupertypes(); @@ -192,4 +198,4 @@ return null; } -} +} \ No newline at end of file Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/ContentMirrorStoreStrategy.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/ContentMirrorStoreStrategy.java (revision 1574393) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/ContentMirrorStoreStrategy.java (working copy) @@ -24,6 +24,8 @@ import java.util.Iterator; import java.util.Set; +import javax.annotation.Nonnull; + import org.apache.jackrabbit.oak.api.PropertyState; import org.apache.jackrabbit.oak.api.Type; import org.apache.jackrabbit.oak.commons.PathUtils; @@ -49,7 +51,7 @@ *
* For example for a node that is under {@code /test/node}, the index * structure will be {@code /oak:index/index/test/node}: - * + * *
  * {@code
  * /
@@ -79,7 +81,7 @@
         }
     }
 
-    private static void remove(NodeBuilder index, String key, String value) {
+    private void remove(NodeBuilder index, String key, String value) {
         NodeBuilder builder = index.getChildNode(key);
         if (builder.exists()) {
             // Collect all builders along the given path
@@ -98,18 +100,13 @@
             }
 
             // Prune all index nodes that are no longer needed
-            for (NodeBuilder node : builders) {
-                if (node.getBoolean("match") || node.getChildNodeCount(1) > 0) {
-                    return;
-                } else if (node.exists()) {
-                    node.remove();
-                }
-            }
+            prune(index, builders);
         }
     }
 
-    private static void insert(NodeBuilder index, String key, String value) {
-        NodeBuilder builder = index.child(key);
+    private void insert(NodeBuilder index, String key, String value) {
+        // NodeBuilder builder = index.child(key);
+        NodeBuilder builder = fetchKeyNode(index, key);
         for (String name : PathUtils.elements(value)) {
             builder = builder.child(name);
         }
@@ -126,7 +123,7 @@
                 PathIterator it = new PathIterator(filter, indexName);
                 if (values == null) {
                     it.setPathContainsValue(true);
-                    it.enqueue(index.getChildNodeEntries().iterator());
+                    it.enqueue(getChildNodeEntries(index).iterator());
                 } else {
                     for (String p : values) {
                         NodeState property = index.getChildNode(p);
@@ -142,8 +139,14 @@
         };
     }
 
+    @Nonnull
+    Iterable getChildNodeEntries(@Nonnull
+    final NodeState index) {
+        return index.getChildNodeEntries();
+    }
+
     @Override
-    public Iterable query(final Filter filter, final String indexName, 
+    public Iterable query(final Filter filter, final String indexName,
             final NodeState indexMeta, final Iterable values) {
         return query(filter, indexName, indexMeta, INDEX_CONTENT_NODE_NAME, values);
     }
@@ -213,18 +216,18 @@
          * Keep the returned path, to avoid returning duplicate entries.
          */
         private final Set knownPaths = Sets.newHashSet();
-        
+
         PathIterator(Filter filter, String indexName) {
             this.filter = filter;
             this.indexName = indexName;
             parentPath = "";
             currentPath = "/";
         }
-        
+
         void enqueue(Iterator it) {
             nodeIterators.addLast(it);
         }
-        
+
         void setPathContainsValue(boolean pathContainsValue) {
             if (init) {
                 throw new IllegalStateException("This iterator is already initialized");
@@ -260,7 +263,7 @@
                 break;
             }
         }
-        
+
         private void fetchNextPossiblyDuplicate() {
             while (!nodeIterators.isEmpty()) {
                 Iterator iterator = nodeIterators.getLast();
@@ -287,7 +290,7 @@
                     if (node.getBoolean("match")) {
                         return;
                     }
-                    
+
                 } else {
                     nodeIterators.removeLast();
                     parentPath = PathUtils.getParentPath(parentPath);
@@ -310,37 +313,37 @@
             fetchNext();
             return result;
         }
-        
+
         @Override
         public void remove() {
             throw new UnsupportedOperationException();
         }
-        
+
     }
-    
+
     /**
      * A node visitor to recursively traverse a number of nodes.
      */
     interface NodeVisitor {
         void visit(NodeState state);
     }
-    
+
     /**
      * A node visitor that counts the number of matching nodes up to a given
      * maximum, in order to estimate the number of matches.
      */
     static class CountingNodeVisitor implements NodeVisitor {
-        
+
         /**
          * The maximum number of matching nodes to count.
          */
         final int maxCount;
-        
+
         /**
          * The current count of matching nodes.
          */
         int count;
-        
+
         /**
          * The current depth (number of parent nodes).
          */
@@ -351,7 +354,7 @@
          * calculate the average depth.
          */
         long depthTotal;
-        
+
         CountingNodeVisitor(int maxCount) {
             this.maxCount = maxCount;
         }
@@ -373,7 +376,7 @@
                 depth--;
             }
         }
-        
+
         /**
          * The number of matches (at most the maximum count).
          * 
@@ -382,7 +385,7 @@
         int getCount() {
             return count;
         }
-        
+
         /**
          * The number of estimated matches. This value might be higher than the
          * number of counted matches, if the maximum number of matches has been
@@ -402,7 +405,38 @@
             estimatedNodes = Math.min(estimatedNodes, Integer.MAX_VALUE);
             return Math.max(count, (int) estimatedNodes);
         }
-        
+
     }
+    
+    /**
+     * fetch from the index the key node
+     * 
+     * @param index
+     *            the current index root
+     * @param key
+     *            the 'key' to fetch from the repo
+     * @return the node representing the key
+     */
+    NodeBuilder fetchKeyNode(@Nonnull NodeBuilder index, 
+                             @Nonnull String key) {
+        return index.child(key);
+    }
 
-}
+    /**
+     * Physically prune a list of nodes from the index
+     * 
+     * @param index
+     *            the current index
+     * @param builders
+     *            list of nodes to prune
+     */
+    void prune(final NodeBuilder index, final Deque builders) {
+        for (NodeBuilder node : builders) {
+            if (node.getBoolean("match") || node.getChildNodeCount(1) > 0) {
+                return;
+            } else if (node.exists()) {
+                node.remove();
+            }
+        }
+    }
+}
\ No newline at end of file
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java	(revision 0)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java	(working copy)
@@ -0,0 +1,273 @@
+/*
+ * 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.strategy;
+
+import java.util.Collections;
+import java.util.Deque;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState;
+import org.apache.jackrabbit.oak.spi.state.AbstractChildNodeEntry;
+import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Strings;
+
+/**
+ * Same as for {@link ContentMirrorStoreStrategy} but the order of the keys is kept by using the
+ * following structure
+ * 
+ * 
+ *  :index : {
+ *      :start : { :next = n1 },
+ *      n0 : { /content/foo/bar(match=true), :next=n3 },
+ *      n1 : { /content/foo1/bar(match=true), :next=n0 },
+ *      n2 : { /content/foo2/bar(match=true), :next= }, //this is the end of the list
+ *      n3 : { /content/foo3/bar(match=true), :next=n2 }
+ *  }
+ * 
+ */
+public class OrderedContentMirrorStoreStrategy extends ContentMirrorStoreStrategy {
+    private static final Logger log = LoggerFactory.getLogger(OrderedContentMirrorStoreStrategy.class);
+
+    /**
+     * the property linking to the next node
+     */
+    public static final String NEXT = ":next";
+
+    /**
+     * node that works as root of the index (start point or 0 element)
+     */
+    public static final String START = ":start";
+
+    /**
+     * a NodeState used for easy creating of an empty :start
+     */
+    public static final NodeState EMPTY_START_NODE = EmptyNodeState.EMPTY_NODE.builder()
+                                                                              .setProperty(NEXT, "")
+                                                                              .getNodeState();
+
+    @Override
+    NodeBuilder fetchKeyNode(@Nonnull NodeBuilder index, @Nonnull String key) {
+        log.debug("fetchKeyNode() - index: {} - key: {}", index, key);
+        NodeBuilder localkey = null;
+        NodeBuilder start = index.child(START);
+
+        // identifying the right place for insert
+        String n = start.getString(NEXT);
+        if (Strings.isNullOrEmpty(n)) {
+            // new/empty index
+            localkey = index.child(key);
+            localkey.setProperty(NEXT, "");
+            start.setProperty(NEXT, key);
+        } else {
+            // specific use-case where the item has to be added as first of the list
+            String nextKey = n;
+            if (key.compareTo(nextKey) < 0) {
+                localkey = index.child(key);
+                localkey.setProperty(NEXT, nextKey);
+                start.setProperty(NEXT, key);
+            } else {
+                Iterable children = getChildNodeEntries(index.getNodeState());
+                for (ChildNodeEntry child : children) {
+                    nextKey = child.getNodeState().getString(NEXT);
+                    if (Strings.isNullOrEmpty(nextKey)) {
+                        // we're at the last element, therefore our 'key' has to be appended
+                        index.getChildNode(child.getName()).setProperty(NEXT, key);
+                        localkey = index.child(key);
+                        localkey.setProperty(NEXT, "");
+                    } else {
+                        if (key.compareTo(nextKey) < 0) {
+                            index.getChildNode(child.getName()).setProperty(NEXT, key);
+                            localkey = index.child(key);
+                            localkey.setProperty(NEXT, nextKey);
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+
+        return localkey;
+    }
+
+    @Override
+    void prune(final NodeBuilder index, final Deque builders) {
+        for (NodeBuilder node : builders) {
+            if (node.hasProperty("match") || node.getChildNodeCount(1) > 0) {
+                return;
+            } else if (node.exists()) {
+                if (node.hasProperty(NEXT)) {
+                    // it's an index key and we have to relink the list
+                    ChildNodeEntry previous = findPrevious(index.getNodeState(),
+                                                           node.getNodeState()); // (1) find the
+                                                                                 // previous element
+                    log.debug("previous: {}", previous);
+                    String next = node.getString(NEXT); // (2) find the next element
+                    if (next == null) {
+                        next = "";
+                    }
+                    // (3) re-link the previous to the next
+                    index.getChildNode(previous.getName()).setProperty(NEXT, next); 
+                } 
+                node.remove();
+            }
+        }
+    }
+
+    @Nullable
+    ChildNodeEntry findPrevious(@Nonnull final NodeState index, @Nonnull final NodeState node) {
+        ChildNodeEntry previous = null;
+        ChildNodeEntry current = null;
+        boolean found = false;
+        Iterator it = getChildNodeEntries(index, true).iterator();
+
+        while (!found && it.hasNext()) {
+            current = it.next();
+            if (previous == null) {
+                // first iteration
+                previous = current;
+            } else {
+                found = node.equals(current.getNodeState());
+                if (!found) {
+                    previous = current;
+                }
+            }
+        }
+
+        return ((found) ? previous : null);
+    }
+
+    @Override
+    public void update(NodeBuilder index, String path, Set beforeKeys,
+                       Set afterKeys) {
+        log.debug("update() - index     : {}", index);
+        log.debug("update() - path      : {}", path);
+        log.debug("update() - beforeKeys: {}", beforeKeys);
+        log.debug("update() - afterKeys : {}", afterKeys);
+        super.update(index, path, beforeKeys, afterKeys);
+    }
+
+    /**
+     * retrieve an Iterable for going through the index in the right order without the :start node
+     * 
+     * @param index the root of the index (:index)
+     * @return
+     */
+    @Override
+    @Nonnull
+    Iterable getChildNodeEntries(@Nonnull final NodeState index) {
+        return getChildNodeEntries(index, false);
+    }
+
+    /**
+     * Retrieve an Iterable for going through the index in the right order with potentially the
+     * :start node
+     * 
+     * @param index the root of the index (:index)
+     * @param includeStart true if :start should be included as first element
+     * @return
+     */
+    @Nonnull
+    Iterable getChildNodeEntries(@Nonnull final NodeState index,
+                                                           final boolean includeStart) {
+        Iterable cne = null;
+        final NodeState start = index.getChildNode(START);
+
+        if ((!start.exists() || Strings.isNullOrEmpty(start.getString(NEXT))) && !includeStart) {
+            // if the property is not there or is empty it means we're empty
+            cne = Collections.emptyList();
+        } else {
+            cne = new Iterable() {
+                private NodeState localIndex = index;
+                private NodeState localStart = ((includeStart && !start.exists()) ? EMPTY_START_NODE
+                                                                             : start);
+                private NodeState current = localStart;
+                private boolean localIncludeStart = includeStart;
+
+                @Override
+                public Iterator iterator() {
+                    return new Iterator() {
+
+                        @Override
+                        public boolean hasNext() {
+                            return ((localIncludeStart && localStart.equals(current)) || (!localIncludeStart && !Strings.isNullOrEmpty(current.getString(NEXT))));
+                        }
+
+                        @Override
+                        public ChildNodeEntry next() {
+                            ChildNodeEntry localCNE = null;
+                            if (localIncludeStart && localStart.equals(current)) {
+                                localCNE = new OrderedChildNodeEntry(START, current);
+                                // let's set it to false. We just included it.
+                                localIncludeStart = false; 
+                            } else {
+                                if (hasNext()) {
+                                    final String name = current.getString(NEXT);
+                                    current = localIndex.getChildNode(name);
+                                    localCNE = new OrderedChildNodeEntry(name, current);
+                                } else {
+                                    throw new NoSuchElementException();
+                                }
+                            }
+                            return localCNE;
+                        }
+
+                        @Override
+                        public void remove() {
+                            throw new UnsupportedOperationException();
+                        }
+                    };
+                }
+            };
+        }
+        return cne;
+    }
+
+    private static final class OrderedChildNodeEntry extends AbstractChildNodeEntry {
+        private final String name;
+        private final NodeState state;
+
+        public OrderedChildNodeEntry(@Nonnull
+        final String name, @Nonnull
+        final NodeState state) {
+            this.name = name;
+            this.state = state;
+        }
+
+        @Override
+        @Nonnull
+        public String getName() {
+            return name;
+        }
+
+        @Override
+        @Nonnull
+        public NodeState getNodeState() {
+            return state;
+        }
+    }
+}
Index: oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditorTest.java
===================================================================
--- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditorTest.java	(revision 0)
+++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditorTest.java	(working copy)
@@ -0,0 +1,84 @@
+/*
+ * 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 org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+
+import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.junit.Test;
+
+public class OrderedPropertyIndexEditorTest {
+   
+   @Test public void isProperlyConfiguredWithPropertyNames(){
+      NodeBuilder definition = createNiceMock(NodeBuilder.class);
+      PropertyState names = createNiceMock(PropertyState.class);
+      expect(names.count()).andReturn(1);
+      expect(definition.getProperty(IndexConstants.PROPERTY_NAMES)).andReturn(names).anyTimes();
+      replay(names);
+      replay(definition);
+      
+      OrderedPropertyIndexEditor ie = new OrderedPropertyIndexEditor(definition, null, null);
+      assertFalse("With empty or missing property the index should not work.",ie.isProperlyConfigured());
+   }
+   
+   @Test public void isProperlyConfiguredSingleValuePropertyNames(){
+      NodeBuilder definition = createNiceMock(NodeBuilder.class);
+      PropertyState names = createNiceMock(PropertyState.class);
+      expect(names.count()).andReturn(1);
+      expect(names.getValue(Type.NAME,0)).andReturn("jcr:lastModified").anyTimes();
+      expect(definition.getProperty(IndexConstants.PROPERTY_NAMES)).andReturn(names).anyTimes();
+      replay(names);
+      replay(definition);
+      
+      OrderedPropertyIndexEditor ie = new OrderedPropertyIndexEditor(definition, null, null);
+      assertNotNull("With a correct property set 'propertyNames' can't be null",ie.getPropertyNames());
+      assertEquals(1,ie.getPropertyNames().size());
+      assertEquals("jcr:lastModified",ie.getPropertyNames().iterator().next());
+      assertTrue("Expecting a properly configured index",ie.isProperlyConfigured());
+   }
+   
+   @Test public void multiValueProperty(){
+      NodeBuilder definition = createNiceMock(NodeBuilder.class);
+      PropertyState names = createNiceMock(PropertyState.class);
+      expect(names.isArray()).andReturn(true).anyTimes();
+      expect(names.count()).andReturn(2).anyTimes();
+      expect(names.getValue(Type.NAME,0)).andReturn("jcr:lastModified").anyTimes();
+      expect(names.getValue(Type.NAME,1)).andReturn("foo:bar").anyTimes();
+      expect(names.getValue(Type.NAMES)).andReturn(Arrays.asList("jcr:lastModified","foo:bar")).anyTimes();
+      expect(definition.getProperty(IndexConstants.PROPERTY_NAMES)).andReturn(names).anyTimes();
+      replay(names);
+      replay(definition);
+
+      OrderedPropertyIndexEditor ie = new OrderedPropertyIndexEditor(definition, null, null);
+      assertNotNull("With a correct property set 'propertyNames' can't be null",ie.getPropertyNames());
+      assertEquals("When multiple properties are a passed only the first one is taken", 1,ie.getPropertyNames().size());
+      assertEquals("jcr:lastModified",ie.getPropertyNames().iterator().next());
+      assertTrue("Expecting a properly configured index",ie.isProperlyConfigured());
+   }   
+}
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 0)
+++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexQueryTest.java	(working copy)
@@ -0,0 +1,340 @@
+/*
+ * 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 junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.Assert.fail;
+import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
+import static org.apache.jackrabbit.JcrConstants.NT_UNSTRUCTURED;
+
+import java.text.DecimalFormat;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+import javax.annotation.Nonnull;
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.oak.Oak;
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.ContentRepository;
+import org.apache.jackrabbit.oak.api.PropertyValue;
+import org.apache.jackrabbit.oak.api.ResultRow;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.api.Type;
+import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
+import org.apache.jackrabbit.oak.plugins.index.IndexUtils;
+import org.apache.jackrabbit.oak.plugins.nodetype.write.InitialContent;
+import org.apache.jackrabbit.oak.query.AbstractQueryTest;
+import org.apache.jackrabbit.oak.spi.query.PropertyValues;
+import org.apache.jackrabbit.oak.spi.security.OpenSecurityProvider;
+import org.apache.jackrabbit.oak.util.NodeUtil;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableMap;
+
+public class OrderedPropertyIndexQueryTest extends AbstractQueryTest {
+    /**
+     * the property used by the index
+     */
+    public static final String ORDERED_PROPERTY = "foo";
+
+    /**
+     * number of nodes to create for testing.
+     * 
+     * It has been found during development that in some cases the order of the nodes creation within the persistence
+     * where the actual expected order.
+     * 
+     * The higher the value the lower the chance for this to happen.
+     */
+    private static final int NUMBER_OF_NODES = 50;
+
+    /**
+     * convenience orderable object that represents a tuple of values and paths
+     * 
+     * where the values are the indexed keys from the index and the paths are the path which hold the key
+     */
+    private class ValuePathTuple implements Comparable {
+        private final String value;
+        private final String path;
+
+        ValuePathTuple(String value, String path) {
+            this.value = value;
+            this.path = path;
+        }
+
+        @Override
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + getOuterType().hashCode();
+            result = prime * result + ((path == null) ? 0 : path.hashCode());
+            result = prime * result + ((value == null) ? 0 : value.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj){
+                return true;
+            }
+            if (obj == null){
+                return false;
+            }
+            if (getClass() != obj.getClass()){
+                return false;
+            }
+            ValuePathTuple other = (ValuePathTuple) obj;
+            if (!getOuterType().equals(other.getOuterType())){
+                return false;
+            }
+            if (path == null) {
+                if (other.path != null){
+                    return false;
+                }
+            } else if (!path.equals(other.path)){
+                return false;
+            }
+            if (value == null) {
+                if (other.value != null){
+                    return false;
+                }
+            } else if (!value.equals(other.value)){
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public int compareTo(ValuePathTuple o) {
+            if (this.equals(o)){
+                return 0;
+            }
+            if (this.value.compareTo(o.value) < 0){
+                return -1;
+            }
+            if (this.value.compareTo(o.value) > 0){
+                return 1;
+            }
+            if (this.path.compareTo(o.path) < 0){
+                return -1;
+            }
+            if (this.path.compareTo(o.path) > 0){
+                return 1;
+            }
+            return 0;
+        }
+
+        private OrderedPropertyIndexQueryTest getOuterType() {
+            return OrderedPropertyIndexQueryTest.this;
+        }
+
+    }
+
+    /**
+     * testing for asserting the right comparison behaviour of the custom class
+     */
+    @Test
+    public void valuePathTupleComparison() {
+        try {
+            new ValuePathTuple("value", "path").compareTo(null);
+            fail("It should have raised a NPE");
+        } catch (NullPointerException e) {
+            // so far so good
+        }
+        assertEquals(0, (new ValuePathTuple("value", "path")).compareTo(new ValuePathTuple("value", "path")));
+        assertEquals(-1, (new ValuePathTuple("value", "path")).compareTo(new ValuePathTuple("value1", "path")));
+        assertEquals(-1, (new ValuePathTuple("value1", "path")).compareTo(new ValuePathTuple("value1", "path1")));
+        assertEquals(1, (new ValuePathTuple("value1", "path")).compareTo(new ValuePathTuple("value", "path")));
+        assertEquals(1, (new ValuePathTuple("value1", "path1")).compareTo(new ValuePathTuple("value1", "path")));
+
+        assertEquals(-1,
+            (new ValuePathTuple("value000", "/test/n1")).compareTo(new ValuePathTuple("value001", "/test/n0")));
+        assertEquals(1,
+            (new ValuePathTuple("value001", "/test/n0")).compareTo(new ValuePathTuple("value000", "/test/n1")));
+    }
+
+    @Override
+    protected ContentRepository createRepository() {
+        return new Oak().with(new InitialContent())
+            .with(new OpenSecurityProvider())
+            // .with(new PropertyIndexProvider())
+            // .with(new PropertyIndexEditorProvider())
+            .with(new OrderedPropertyIndexProvider()).with(new OrderedPropertyIndexEditorProvider())
+            .createContentRepository();
+    }
+
+    /**
+     * create a child node for the provided father
+     * 
+     * @param father
+     * @param name
+     *            the name of the node to create
+     * @param propName
+     *            the name of the property to assign
+     * @param propValue
+     *            the value of the property to assign
+     * @return
+     */
+    private static Tree child(Tree father, String name, String propName, String propValue) {
+        Tree child = father.addChild(name);
+        child.setProperty(JCR_PRIMARYTYPE, NT_UNSTRUCTURED, Type.NAME);
+        child.setProperty(propName, propValue, Type.STRING);
+        return child;
+    }
+
+    /**
+     * generate a list of values to be used as ordered set. Will return something like
+     * {@code value000, value001, value002, ...}
+     * 
+     * 
+     * @param amount
+     * @return
+     */
+    private static List generateOrderedValues(int amount) {
+        if (amount > 1000){
+            throw new RuntimeException("amount cannot be greater than 100");
+        }
+        List values = new ArrayList(amount);
+        NumberFormat nf = new DecimalFormat("000");
+        for (int i = 0; i < amount; i++){
+            values.add(String.format("value%s", String.valueOf(nf.format(i))));
+        }
+        return values;
+    }
+
+    /**
+     * convenience method that adds a bunch of nodes in random order and return the order in which they should be
+     * presented by the OrderedIndex
+     * 
+     * @param values
+     *            the values of the property that will be indexed
+     * @param father
+     *            the father under which add the nodes
+     * @return
+     */
+    private List addChildNodes(final List values, final Tree father) {
+        List nodes = new ArrayList();
+        Random rnd = new Random();
+        int counter = 0;
+        while (!values.isEmpty()) {
+            String v = values.remove(rnd.nextInt(values.size()));
+            Tree t = child(father, String.format("n%s", counter++), ORDERED_PROPERTY, v);
+            nodes.add(new ValuePathTuple(v, t.getPath()));
+        }
+
+        Collections.sort(nodes);
+        return nodes;
+    }
+
+    @Override
+    protected void createTestIndexNode() throws Exception {
+        Tree index = root.getTree("/");
+        IndexUtils.createIndexDefinition(new NodeUtil(index.getChild(IndexConstants.INDEX_DEFINITIONS_NAME)),
+            TEST_INDEX_NAME, false, new String[] { ORDERED_PROPERTY }, null, OrderedIndex.TYPE);
+        root.commit();
+    }
+
+    /**
+     * assert the right order of the returned resultset
+     * 
+     * @param orderedSequence
+     *            the right order in which the resultset should be returned
+     * @param resultset
+     *            the resultset
+     */
+    private void assertRightOrder(@Nonnull
+    final List orderedSequence, @Nonnull
+    final Iterator resultset) {
+        assertTrue("No results returned", resultset.hasNext());
+        int counter = 0;
+        while (resultset.hasNext() && counter < orderedSequence.size()) {
+            ResultRow row = resultset.next();
+            assertEquals(String.format("Wrong path at the element '%d'", counter), orderedSequence.get(counter).path,
+                row.getPath());
+            counter++;
+        }
+    }
+
+    /**
+     * Query the index for retrieving all the entries
+     * 
+     * @throws CommitFailedException
+     * @throws ParseException
+     * @throws RepositoryException
+     */
+    @Test
+    public void queryAllEntries() throws CommitFailedException, ParseException, RepositoryException {
+        setTravesalEnabled(false);
+
+        // index automatically created by the framework:
+        // {@code createTestIndexNode()}
+
+        Tree rTree = root.getTree("/");
+        Tree test = rTree.addChild("test");
+        List nodes = addChildNodes(generateOrderedValues(NUMBER_OF_NODES), test);
+        root.commit();
+
+        // querying
+        Iterator results;
+        results = executeQuery(String.format("SELECT * from [%s] WHERE foo IS NOT NULL", NT_UNSTRUCTURED), SQL2, null)
+            .getRows().iterator();
+        assertRightOrder(nodes, results);
+
+        setTravesalEnabled(true);
+    }
+
+    /**
+     * test the index for returning the items related to a single key
+     * 
+     * @throws CommitFailedException
+     * @throws ParseException
+     */
+    @Test
+    public void queryOneKey() throws CommitFailedException, ParseException {
+        setTravesalEnabled(false);
+
+        // index automatically created by the framework:
+        // {@code createTestIndexNode()}
+
+        Tree rTree = root.getTree("/");
+        Tree test = rTree.addChild("test");
+        List nodes = addChildNodes(generateOrderedValues(NUMBER_OF_NODES), test);
+        root.commit();
+
+        ValuePathTuple searchfor = nodes.get(NUMBER_OF_NODES / 2); // getting the middle of the random list of
+                                                                         // nodes.
+        Map filter = ImmutableMap
+            .of(ORDERED_PROPERTY, PropertyValues.newString(searchfor.value));
+        String query = "SELECT * FROM [%s] WHERE %s=$%s";
+        Iterator results = executeQuery(
+            String.format(query, NT_UNSTRUCTURED, ORDERED_PROPERTY, ORDERED_PROPERTY), SQL2, filter).getRows()
+            .iterator();
+        assertTrue("one element is expected", results.hasNext());
+        assertEquals("wrong path returned", searchfor.path, results.next().getPath());
+        assertFalse("there should be not any more items", results.hasNext());
+
+        setTravesalEnabled(true);
+    }
+}
Index: oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStorageStrategyTest.java
===================================================================
--- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStorageStrategyTest.java	(revision 0)
+++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStorageStrategyTest.java	(working copy)
@@ -0,0 +1,975 @@
+/*
+ * 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.strategy;
+
+import static com.google.common.collect.Sets.newHashSet;
+import static org.apache.jackrabbit.oak.plugins.index.property.strategy.OrderedContentMirrorStoreStrategy.NEXT;
+import static org.apache.jackrabbit.oak.plugins.index.property.strategy.OrderedContentMirrorStoreStrategy.START;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Iterator;
+import java.util.Set;
+
+import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState;
+import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.junit.Test;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Iterators;
+
+/**
+ *
+ */
+public class OrderedContentMirrorStorageStrategyTest {
+    /**
+     * ascending ordered set of keys. Useful for testing
+     */
+    private static final String[] KEYS = new String[] { "donald", "goofy", "mickey", "minnie" };
+    private static final Set EMPTY_KEY_SET = newHashSet();
+
+    /**
+     * checks that the fist item/key is inserted with an empty property 'next'
+     * 
+     * expected structure:
+     * 
+     * 
+     * :index : {
+     *    :start : { :next=n0 },
+     *    n0 : { 
+     *       :next=,
+     *       foo : {
+     *          bar: { match=true}
+     *       }
+     *    }
+     * }
+     * 
+     */
+    @Test
+    public void firstAndOnlyItem() {
+        final String PATH = "/foo/bar";
+        final String[] PATH_NODES = Iterables.toArray(PathUtils.elements(PATH), String.class);
+        final String N0 = KEYS[0];
+
+        IndexStoreStrategy store = new OrderedContentMirrorStoreStrategy();
+        NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder();
+        NodeBuilder node = null;
+
+        store.update(index, PATH, EMPTY_KEY_SET, newHashSet(N0));
+
+        assertFalse(":index should be left alone with not changes", index.hasProperty(NEXT));
+        node = index.getChildNode(START);
+        assertTrue(":index should have the :start node", node.exists());
+        assertEquals(":start should point to n0", N0, node.getString(NEXT));
+
+        node = index.getChildNode(N0);
+        assertTrue("n0 should exists in the index", node.exists());
+        assertEquals("n0 should point nowhere as it's the last (and only) element", "", node.getString(NEXT));
+
+        // checking content structure below n0
+        node = node.getChildNode(PATH_NODES[0]);
+        assertTrue("n0 should contain 'foo'", node.exists());
+        node = node.getChildNode(PATH_NODES[1]);
+        assertTrue("'foo' should contain 'bar'", node.exists());
+        assertTrue("the 'foo' node should have 'match=true'", node.getBoolean("match"));
+    }
+
+    /**
+     * test the saving of 2 new keys that comes already ordered
+     * 
+     * final state of the index will be
+     * 
+     * 
+     *    :index : {
+     *       :start : { :next=n0 },
+     *       n0 : { :next=n1 },
+     *       n1 : { :next= }       
+     *    }
+     * 
+     */
+    @Test
+    public void first2newKeysAlreadyOrdered() {
+        final String PATH = "/foo/bar";
+        final String N0 = KEYS[0];
+        final String N1 = KEYS[1];
+
+        IndexStoreStrategy store = new OrderedContentMirrorStoreStrategy();
+        NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder();
+        NodeBuilder node = null;
+
+        store.update(index, PATH, EMPTY_KEY_SET, newHashSet(N0)); // first node
+                                                                  // arrives
+
+        node = index.getChildNode(START);
+        assertTrue(":index should have :start", node.exists());
+        assertEquals(":start should point to n0", N0, node.getString(NEXT));
+
+        node = index.getChildNode(N0);
+        assertTrue(":index should have n0", node.exists());
+        assertEquals("n0 should point nowhere at this stage", "", node.getString(NEXT));
+
+        store.update(index, PATH, EMPTY_KEY_SET, newHashSet(N1)); // second node
+                                                                  // arrives
+
+        node = index.getChildNode(START);
+        assertTrue(":index should still have :start", node.exists());
+        assertEquals(":start should still point to n0", N0, node.getString(NEXT));
+
+        node = index.getChildNode(N0);
+        assertTrue("n0 should still exists", node.exists());
+        assertEquals("n0 should point to n1", N1, node.getString(NEXT));
+
+        node = index.getChildNode(N1);
+        assertTrue("n1 should exists", node.exists());
+        assertEquals("n1 should point nowhere", "", node.getString(NEXT));
+    }
+
+    /**
+     * Test the iteration of an empty index
+     */
+    @Test
+    public void childNodeEntriesEmptyIndex() {
+        OrderedContentMirrorStoreStrategy store = new OrderedContentMirrorStoreStrategy();
+        NodeState index = EmptyNodeState.EMPTY_NODE;
+
+        @SuppressWarnings("unchecked")
+        Iterable children = (Iterable) store.getChildNodeEntries(index);
+
+        assertNotNull("A returned Iterable cannot be null", children);
+    }
+
+    /**
+     * test the iteration of the index with 2 shuffled items
+     * 
+     * 
+     *    :index : {
+     *       :start : { :next=n1 },
+     *       n0 : { :next= },
+     *       n1 : { :next=n0 }
+     *    }
+     * 
+     */
+    @SuppressWarnings("unchecked")
+    @Test
+    public void childNodeEntriesACoupleOfMixedItems() {
+        final String N0 = KEYS[1];
+        final String N1 = KEYS[0];
+        final NodeState NODE_0 = EmptyNodeState.EMPTY_NODE.builder().setProperty(NEXT, "").getNodeState();
+        final NodeState NODE_1 = EmptyNodeState.EMPTY_NODE.builder().setProperty(NEXT, N0).getNodeState();
+        OrderedContentMirrorStoreStrategy store = new OrderedContentMirrorStoreStrategy();
+        NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder();
+
+        // setting-up the index structure
+        index.child(START).setProperty(NEXT, N1);
+        index.setChildNode(N0, NODE_0);
+        index.setChildNode(N1, NODE_1);
+
+        NodeState indexState = index.getNodeState();
+
+        Iterable children = (Iterable) store.getChildNodeEntries(indexState);
+        assertNotNull("The iterable cannot be null", children);
+        assertEquals("Expecting 2 items in the index", 2, Iterators.size(children.iterator())); 
+
+        // ensuring the right sequence
+        ChildNodeEntry entry = null;
+        children = (Iterable) store.getChildNodeEntries(indexState);
+        Iterator it = children.iterator();
+        assertTrue("We should have 2 elements left to loop through", it.hasNext());
+        entry = it.next();
+        assertEquals("The first element should be n1", N1, entry.getName());
+        assertEquals("Wrong entry returned", NODE_1, entry.getNodeState());
+        assertTrue("We should have 1 elements left to loop through", it.hasNext());
+        entry = it.next();
+        assertEquals("The second element should be n0", N0, entry.getName());
+        assertEquals("Wrong entry returned", NODE_0, entry.getNodeState());
+        assertFalse("We should have be at the end of the list", it.hasNext());
+    }
+
+    /**
+     * test the iteration of the index with 2 shuffled items without the :start
+     * node
+     * 
+     * 
+     *    :index : {
+     *       :start : { :next=n1 },
+     *       n0 : { :next= },
+     *       n1 : { :next=n0 }
+     *    }
+     * 
+     */
+    @SuppressWarnings("unchecked")
+    @Test
+    public void childNodeEntriesACoupleOfMixedItemsNoStart() {
+        final String N0 = KEYS[1];
+        final String N1 = KEYS[0];
+        final NodeState NODE_0 = EmptyNodeState.EMPTY_NODE.builder().setProperty(NEXT, "").getNodeState();
+        final NodeState NODE_1 = EmptyNodeState.EMPTY_NODE.builder().setProperty(NEXT, N0).getNodeState();
+        OrderedContentMirrorStoreStrategy store = new OrderedContentMirrorStoreStrategy();
+        NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder();
+
+        // setting-up the index structure
+        index.child(START).setProperty(NEXT, N1);
+        index.setChildNode(N0, NODE_0);
+        index.setChildNode(N1, NODE_1);
+
+        NodeState indexState = index.getNodeState();
+
+        Iterable children = (Iterable) store.getChildNodeEntries(indexState, false);
+        assertNotNull("The iterable cannot be null", children);
+        assertEquals("Expecting 2 items in the index", 2, Iterators.size(children.iterator()));
+
+        // ensuring the right sequence
+        ChildNodeEntry entry = null;
+        children = (Iterable) store.getChildNodeEntries(indexState);
+        Iterator it = children.iterator();
+        assertTrue("We should have 2 elements left to loop through", it.hasNext());
+        entry = it.next();
+        assertEquals("The first element should be n1", N1, entry.getName());
+        assertEquals("Wrong entry returned", NODE_1, entry.getNodeState());
+        assertTrue("We should have 1 elements left to loop through", it.hasNext());
+        entry = it.next();
+        assertEquals("The second element should be n0", N0, entry.getName());
+        assertEquals("Wrong entry returned", NODE_0, entry.getNodeState());
+        assertFalse("We should have be at the end of the list", it.hasNext());
+    }
+
+    /**
+     * test the iteration of the index with 2 shuffled items including the
+     * :start node as first
+     * 
+     * 
+     *    :index : {
+     *       :start : { :next=n1 },
+     *       n0 : { :next= },
+     *       n1 : { :next=n0 }
+     *    }
+     * 
+     */
+    @SuppressWarnings("unchecked")
+    @Test
+    public void childNodeEntriesACoupleOfMixedItemsWithStart() {
+        final String N0 = KEYS[1];
+        final String N1 = KEYS[0];
+        final NodeState NODE_START = EmptyNodeState.EMPTY_NODE.builder().setProperty(NEXT, N1).getNodeState();
+        final NodeState NODE_0 = EmptyNodeState.EMPTY_NODE.builder().setProperty(NEXT, "").getNodeState();
+        final NodeState NODE_1 = EmptyNodeState.EMPTY_NODE.builder().setProperty(NEXT, N0).getNodeState();
+        OrderedContentMirrorStoreStrategy store = new OrderedContentMirrorStoreStrategy();
+        NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder();
+
+        // setting-up the index structure
+        index.setChildNode(START, NODE_START);
+        index.setChildNode(N0, NODE_0);
+        index.setChildNode(N1, NODE_1);
+
+        NodeState indexState = index.getNodeState();
+
+        Iterable children = (Iterable) store.getChildNodeEntries(indexState, true);
+        assertNotNull("The iterable cannot be null", children);
+        assertEquals("Expecting 3 items in the index", 3, Iterators.size(children.iterator())); 
+
+        // ensuring the right sequence
+        ChildNodeEntry entry = null;
+        children = (Iterable) store.getChildNodeEntries(indexState, true);
+        Iterator it = children.iterator();
+        assertTrue("We should still have elements left to loop through", it.hasNext());
+        entry = it.next();
+        assertEquals("The first element should be :start", START, entry.getName());
+        assertEquals("Wrong entry returned", NODE_START, entry.getNodeState());
+        assertTrue("We should still have elements left to loop through", it.hasNext());
+        entry = it.next();
+        assertEquals("The second element should be n1", N1, entry.getName());
+        assertEquals("Wrong entry returned", NODE_1, entry.getNodeState());
+        assertTrue("We should still have elements left to loop through", it.hasNext());
+        entry = it.next();
+        assertEquals("The third element should be n0", N0, entry.getName());
+        assertEquals("Wrong entry returned", NODE_0, entry.getNodeState());
+        assertFalse("We should be at the end of the list", it.hasNext());
+    }
+
+    /**
+     * test the iteration over an empty list when the :start is required. In
+     * this case :start should always be returned
+     * 
+     * 
+     *    :index : {
+     *       :start : { :next= }
+     *    }
+     * 
+     */
+    @Test
+    public void childNodeEntriesNoItemsWithStart() {
+        NodeState NODE_START = EmptyNodeState.EMPTY_NODE.builder().setProperty(NEXT, "").getNodeState();
+        NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder();
+        OrderedContentMirrorStoreStrategy store = new OrderedContentMirrorStoreStrategy();
+
+        // setting-up the index
+        index.setChildNode(START, NODE_START);
+
+        Iterable children = store.getChildNodeEntries(index.getNodeState(), true);
+        assertEquals("Wrong size of Iterable", 1, Iterators.size(children.iterator()));
+
+        Iterator it = store.getChildNodeEntries(index.getNodeState(), true).iterator();
+        assertTrue("We should have at least 1 element", it.hasNext());
+        ChildNodeEntry entry = it.next();
+        assertEquals(":start is expected", START, entry.getName());
+        assertEquals("wrong node returned", NODE_START, entry.getNodeState());
+        assertFalse("We should be at the end of the list", it.hasNext());
+    }
+
+    /**
+     * test the case where we want an iterator for the children of a brand new
+     * index. In this case :start doesn't exists but if we ask for it we should
+     * return it.
+     */
+    @Test
+    public void childNodeEntriesNewIndexWithStart() {
+        NodeState NODE_START = EmptyNodeState.EMPTY_NODE.builder().setProperty(NEXT, "").getNodeState();
+        NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder();
+        OrderedContentMirrorStoreStrategy store = new OrderedContentMirrorStoreStrategy();
+
+        Iterator children = store.getChildNodeEntries(index.getNodeState(), true).iterator();
+        assertEquals("Wrong number of children", 1, Iterators.size(children));
+
+        children = store.getChildNodeEntries(index.getNodeState(), true).iterator();
+        assertTrue("at least one item expected", children.hasNext());
+        ChildNodeEntry child = children.next();
+        assertEquals(START, child.getName());
+        assertEquals(NODE_START, child.getNodeState());
+        assertFalse(children.hasNext());
+    }
+
+    /**
+     * test the insert of two shuffled items
+     * 
+     * Building final a structure like
+     * 
+     * 
+     *    :index : {
+     *       :start : { :next=n1 },
+     *       n0 : { :next= },
+     *       n1 : { :next=n0 }
+     *    }
+     * 
+     * 
+     * where:
+     * 
+     * 
+     *    Stage 1
+     *    =======
+     * 
+     *    :index : {
+     *       :start : { :next = n0 },
+     *       n0 : {
+     *          :next = 
+     *       }
+     *    }
+     * 
+     *    Stage 2
+     *    =======
+     * 
+     *    :index : {
+     *       :start : { :next = n1 },
+     *       n0 : {
+     *          :next =
+     *       },
+     *       n1 : {
+     *          :next = n0
+     *       }
+     *    }
+     * 
+     */
+    @Test
+    public void twoShuffledItems() {
+        IndexStoreStrategy store = new OrderedContentMirrorStoreStrategy();
+        NodeState root = EmptyNodeState.EMPTY_NODE;
+        NodeBuilder index = root.builder();
+        String key1st = KEYS[1];
+        String key2nd = KEYS[0];
+        NodeState ns = null;
+
+        // Stage 1
+        store.update(index, "/foo/bar", EMPTY_KEY_SET, newHashSet(key1st));
+        ns = index.getChildNode(START).getNodeState();
+        assertEquals(":start is expected to point to the 1st node", key1st, ns.getString(NEXT));
+        ns = index.getChildNode(key1st).getNodeState();
+        assertTrue("At Stage 1 the first node is expected to point nowhere as it's the last",
+                        Strings.isNullOrEmpty(ns.getString(NEXT)));
+
+        // Stage 2
+        store.update(index, "/foo/bar", EMPTY_KEY_SET, newHashSet(key2nd));
+        ns = index.getChildNode(START).getNodeState();
+        assertEquals(":start is expected to point to the 2nd node", key2nd, ns.getString(NEXT));
+        ns = index.getChildNode(key1st).getNodeState();
+        assertTrue("At stage 2 the first element should point nowhere as it's the last",
+                        Strings.isNullOrEmpty(ns.getString(NEXT)));
+        ns = index.getChildNode(key2nd).getNodeState();
+        assertEquals("At Stage 2 the second element should point to the first one", key1st, ns.getString(NEXT));
+    }
+
+    /**
+     * test the insert of shuffled items
+     * 
+     * Building a final structure like
+     * 
+     * 
+     *    {
+     *       :start : { :next = n1 },
+     *       n0 : {
+     *          :next = ""
+     *       },
+     *       n1 : {
+     *          :next = n2
+     *       },
+     *       n2 : {
+     *          :next = n0
+     *       }
+     *    }
+     * 
+     * 
+     * where:
+     * 
+     * 
+     *    Stage 1
+     *    =======
+     * 
+     *    {
+     *       :start : { :next = n0 },
+     *       n0 : {
+     *          :next = 
+     *       }
+     *    }
+     * 
+     *    Stage 2
+     *    =======
+     * 
+     *    {
+     *       :start : { :next = n1 },
+     *       n0 : { :next = },
+     *       n1 : { :next = n0 }
+     *    }
+     *    
+     *    Stage 3
+     *    =======
+     *    
+     *    {
+     *       :start : { :next = n1 },
+     *       n0 : { :next = },
+     *       n1 : { :next = n2 },
+     *       n2 : { :next = n0 }
+     *    }
+     *    
+     *    Stage 4
+     *    =======
+     * 
+     *    {
+     *       :start : { :next = n1 },
+     *       n0 : { :next = n3 },
+     *       n1 : { :next = n2 },
+     *       n2 : { :next = n0 },
+     *       n3 : { :next = }
+     *    }
+     * 
+     */
+    @Test
+    public void fourShuffledElements() {
+        IndexStoreStrategy store = new OrderedContentMirrorStoreStrategy();
+        NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder();
+        String n0 = KEYS[2];
+        String n1 = KEYS[0];
+        String n2 = KEYS[1];
+        String n3 = KEYS[3];
+
+        // Stage 1
+        store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n0));
+        assertEquals(":start should point to the first node", n0, index.getChildNode(START).getString(NEXT));
+        assertTrue("the first node should point nowhere", Strings.isNullOrEmpty(index.getChildNode(n0).getString(NEXT)));
+
+        // Stage 2
+        store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n1));
+        assertEquals(":start should point to n1", n1, index.getChildNode(START).getString(NEXT));
+        assertEquals("'n1' should point to 'n0'", n0, index.getChildNode(n1).getString(NEXT));
+        assertTrue("n0 should still be point nowhere", Strings.isNullOrEmpty(index.getChildNode(n0).getString(NEXT)));
+
+        // Stage 3
+        store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n2));
+        assertEquals(":start should point to n1", n1, index.getChildNode(START).getString(NEXT));
+        assertEquals("n1 should be pointing to n2", n2, index.getChildNode(n1).getString(NEXT));
+        assertEquals("n2 should be pointing to n0", n0, index.getChildNode(n2).getString(NEXT));
+        assertTrue("n0 should still be the last item of the list",
+                        Strings.isNullOrEmpty(index.getChildNode(n0).getString(NEXT)));
+
+        // Stage 4
+        store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n3));
+        assertEquals(":start should point to n1", n1, index.getChildNode(START).getString(NEXT));
+        assertEquals("n1 should be pointing to n2", n2, index.getChildNode(n1).getString(NEXT));
+        assertEquals("n2 should be pointing to n0", n0, index.getChildNode(n2).getString(NEXT));
+        assertEquals("n0 should be pointing to n3", n3, index.getChildNode(n0).getString(NEXT));
+        assertTrue("n3 should be the last element", Strings.isNullOrEmpty(index.getChildNode(n3).getString(NEXT)));
+    }
+
+    /**
+     * perform a test where the index gets updated if an already existent
+     * node/key gets updated by changing the key and the key contains only 1
+     * item.
+     * 
+     * Where the second key is greater than the first.
+     * 
+     * 
+     *    Stage 1
+     *    =======
+     *    
+     *    :index : {
+     *       :start { :next = n0 },
+     *       n0 : {
+     *          :next =,
+     *          content : {
+     *             foobar : {
+     *                match = true
+     *             }
+     *          }
+     *       }
+     *    }
+     *    
+     *    Stage 2
+     *    =======
+     *    
+     *    :index : {
+     *       :start : { :next = n1 },
+     *       n1 : {
+     *          :next =,
+     *          content : {
+     *             foobar : {
+     *                match = true
+     *             }
+     *          }
+     *       }
+     *    }
+     * 
+     */
+
+    @Test
+    public void singleKeyUpdate() {
+        final String N0 = KEYS[0];
+        final String N1 = KEYS[1];
+        final String PATH = "/content/foobar";
+        final String[] NODES = Iterables.toArray(PathUtils.elements(PATH), String.class);
+        IndexStoreStrategy store = new OrderedContentMirrorStoreStrategy();
+        NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder();
+        NodeBuilder node = null;
+
+        // Stage 1
+        store.update(index, PATH, EMPTY_KEY_SET, newHashSet(N0));
+        node = index.getChildNode(START);
+        assertTrue(":start should exists", node.exists());
+        assertEquals(":start should point to n0", N0, node.getString(NEXT));
+
+        node = index.getChildNode(N0);
+        assertTrue(":index should have n0", node.exists());
+        assertTrue("n0 should point nowhere", Strings.isNullOrEmpty(node.getString(NEXT)));
+
+        node = node.getChildNode(NODES[0]);
+        assertTrue("n0 should have /content", node.exists());
+
+        node = node.getChildNode(NODES[1]);
+        assertTrue("/content should contain /foobar", node.exists());
+        assertTrue("/foobar should have match=true", node.getBoolean("match"));
+
+        // Stage 2
+        store.update(index, PATH, newHashSet(N0), newHashSet(N1));
+        node = index.getChildNode(START);
+        assertEquals(":start should now point to N1", N1, node.getString(NEXT));
+
+        node = index.getChildNode(N1);
+        assertTrue("N1 should exists", node.exists());
+        assertTrue("N1 should point nowhere", Strings.isNullOrEmpty(node.getString(NEXT)));
+
+        node = node.getChildNode(NODES[0]);
+        assertTrue("N1 should have /content", node.exists());
+
+        node = node.getChildNode(NODES[1]);
+        assertTrue("/content should contain /foobar", node.exists());
+        assertTrue("/foobar should have match=true", node.getBoolean("match"));
+    }
+
+    /**
+     * 

+ * find a previous item given a key in an index with 1 element only + *

+ * + *

+ * it relies on the functionality of the store.update() for creating the + * index + *

+ * + * + * :index { + * :start : { :next=n0 }, + * n0 = { :next= } + * } + * + */ + @Test + public void findPrevious1ItemIndex() { + final OrderedContentMirrorStoreStrategy store = new OrderedContentMirrorStoreStrategy(); + final String N0 = KEYS[0]; + final NodeState NODE_START = EmptyNodeState.EMPTY_NODE.builder().setProperty(NEXT, N0).getNodeState(); + final NodeState NODE_0 = EmptyNodeState.EMPTY_NODE.builder().setProperty(NEXT, "").getNodeState(); + final NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder(); + + index.setChildNode(START, NODE_START); + index.setChildNode(N0, NODE_0); + + NodeState indexState = index.getNodeState(); + ChildNodeEntry previous = store.findPrevious(indexState, NODE_0); + assertNotNull(previous); + assertEquals("the :start node is expected", NODE_START, previous.getNodeState()); + } + + /** + * test the use case where a document change the indexed property. For + * example document that change author. + * + *

+ * it relies on the functionality of the store.update() for creating the + * index + *

+ * + * + * Stage 1 + * ======= + * + * :index : { + * :start : { :next = n0 }, + * n0 : { + * :next = , + * content : { + * one { match=true }, + * two { match=true } + * } + * } + * } + * + * Stage 2 + * ======= + * + * :index : { + * :start : { :next = n0 }, + * n0 : { + * :next = n1, + * content : { + * one : { match = true } + * } + * }, + * n1 : { + * :next = , + * content : { + * two : { match = true } + * } + * } + * } + * + */ + @Test + public void documentChangingKey() { + final String PATH0 = "/content/one"; + final String PATH1 = "/content/two"; + final String N0 = KEYS[0]; + final String N1 = KEYS[1]; + NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder(); + IndexStoreStrategy store = new OrderedContentMirrorStoreStrategy(); + + // Stage 1 - initialising the index + store.update(index, PATH0, EMPTY_KEY_SET, newHashSet(N0)); + store.update(index, PATH1, EMPTY_KEY_SET, newHashSet(N0)); + + // ensuring the right structure + assertTrue(index.hasChildNode(START)); + assertTrue(index.hasChildNode(N0)); + assertFalse(index.hasChildNode(N1)); + + NodeBuilder node = index.getChildNode(START); + assertEquals(":start pointing to wrong node", N0, node.getString(NEXT)); + + node = index.getChildNode(N0); + assertTrue("N0 should go nowhere", Strings.isNullOrEmpty(node.getString(NEXT))); + + // checking the first document + String[] path = Iterables.toArray(PathUtils.elements(PATH0), String.class); + node = node.getChildNode(path[0]); + assertTrue(node.exists()); + node = node.getChildNode(path[1]); + assertTrue(node.exists()); + assertTrue(node.getBoolean("match")); + + path = Iterables.toArray(PathUtils.elements(PATH0), String.class); + node = index.getChildNode(N0).getChildNode(path[0]); + assertTrue(node.exists()); + node = node.getChildNode(path[1]); + assertTrue(node.exists()); + assertTrue(node.getBoolean("match")); + + // Stage 2 + store.update(index, PATH1, newHashSet(N0), newHashSet(N1)); + assertTrue(index.hasChildNode(START)); + assertTrue(index.hasChildNode(N0)); + assertTrue(index.hasChildNode(N1)); + + node = index.getChildNode(START); + assertEquals(":start pointing to wrong node", N0, node.getString(NEXT)); + + node = index.getChildNode(N0); + assertEquals(N1, node.getString(NEXT)); + path = Iterables.toArray(PathUtils.elements(PATH0), String.class); + node = node.getChildNode(path[0]); + assertTrue(node.exists()); + node = node.getChildNode(path[1]); + assertTrue(node.exists()); + assertTrue(node.getBoolean("match")); + path = Iterables.toArray(PathUtils.elements(PATH1), String.class); + node = index.getChildNode(N0).getChildNode(path[0]);// we know both the + // documents share + // the same /content + assertFalse("/content/two should no longer be under N0", node.hasChildNode(path[1])); + + node = index.getChildNode(N1); + assertTrue("N1 should point nowhere", Strings.isNullOrEmpty(node.getString(NEXT))); + path = Iterables.toArray(PathUtils.elements(PATH1), String.class); + node = node.getChildNode(path[0]); + assertTrue(node.exists()); + node = node.getChildNode(path[1]); + assertTrue(node.exists()); + assertTrue(node.getBoolean("match")); + } + + /** + * test when a document is deleted and is the only one under the indexed key + * + *

+ * it relies on the functionality of the store.update() for creating the + * index + *

+ * + * + * Stage 1 + * ======= + * + * :index : { + * :start : { :next = n0 }, + * n0 : { + * :next = , + * sampledoc : { match = true } + * } + * } + * + * Stage 2 + * ======= + * + * :index : { + * :start : { :next = } + * } + * + */ + @Test + public void deleteTheOnlyDocument() { + final String N0 = KEYS[0]; + final String PATH = "/sampledoc"; + NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder(); + IndexStoreStrategy store = new OrderedContentMirrorStoreStrategy(); + + // Stage 1 - initialising the index + store.update(index, PATH, EMPTY_KEY_SET, newHashSet(N0)); + + // we assume it works and therefore not checking the status of the index + // let's go straight to Stage 2 + + // Stage 2 + store.update(index, PATH, newHashSet(N0), EMPTY_KEY_SET); + assertFalse("The node should have been removed", index.hasChildNode(N0)); + assertTrue("as the index should be empty, :start should point nowhere", + Strings.isNullOrEmpty(index.getChildNode(START).getString(NEXT))); + } + + /** + * test when the document is deleted but there're still some documents left + * under the indexed key + * + *

+ * it relies on the functionality of the store.update() for creating the + * index + *

+ * + * + * Stage 1 + * ======= + * + * :index : { + * :start : { :next = n0 }, + * n0 : { + * :next = , + * doc1 : { match=true }, + * doc2 : { match=true } + * } + * } + * + * Stage 2 + * ======= + * + * :index : { + * :start : { :next = n0 }, + * n0 : { + * :next =, + * doc2 : { match = true } + * } + * } + * + */ + @Test + public void deleteOneOfTheDocuments() { + final String N0 = KEYS[0]; + final String DOC1 = "doc1"; + final String DOC2 = "doc2"; + final String PATH1 = "/" + DOC1; + final String PATH2 = "/" + DOC2; + NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder(); + IndexStoreStrategy store = new OrderedContentMirrorStoreStrategy(); + + store.update(index, PATH1, EMPTY_KEY_SET, newHashSet(N0)); + store.update(index, PATH2, EMPTY_KEY_SET, newHashSet(N0)); + + // we trust the store at this point and skip a double-check. Let's move + // to Stage 2! + + store.update(index, PATH1, newHashSet(N0), EMPTY_KEY_SET); + + assertTrue(index.hasChildNode(START)); + assertTrue(index.hasChildNode(N0)); + assertEquals(":start should still point to N0", N0, index.getChildNode(START).getString(NEXT)); + assertTrue("n0 should point nowhere", Strings.isNullOrEmpty(index.getChildNode(N0).getString(NEXT))); + + assertFalse(index.getChildNode(N0).hasChildNode(DOC1)); + assertTrue(index.getChildNode(N0).hasChildNode(DOC2)); + assertTrue(index.getChildNode(N0).getChildNode(DOC2).getBoolean("match")); + } + + /** + * test when the only document is deleted from an indexed key but there're + * still some keys left in the index + * + *

+ * it relies on the functionality of the store.update() for creating the + * index + *

+ * + * + * Stage 1 + * ======= + * + * :index : { + * :start : { :next = n1 }, + * n0 : { + * :next = , + * content : { + * doc0 : { match = true } + * } + * }, + * n1 : { + * :next = n2, + * content : { + * doc1 : { match = true } + * } + * } + * n2 : { + * :next = n0, + * content : { + * doc2 : { match = true } + * } + * } + * } + * + * Stage 2 + * ======= + * + * :index : { + * :start : { :next = n1 }, + * n0 : { + * :next = , + * content : { + * doc0 : { match = true } + * } + * }, + * n1 : { + * :next = n0, + * content : { + * doc1 : { match = true } + * } + * } + * } + * + * + */ + @Test + public void deleteTheOnlyDocumentInMultiKeysIndex() { + final String PATH0 = "/content/doc0"; + final String PATH1 = "/content/doc1"; + final String PATH2 = "/content/doc2"; + final String N0 = KEYS[2]; + final String N1 = KEYS[0]; + final String N2 = KEYS[1]; + + NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder(); + IndexStoreStrategy store = new OrderedContentMirrorStoreStrategy(); + + // Stage 1 + store.update(index, PATH0, EMPTY_KEY_SET, newHashSet(N0)); + store.update(index, PATH1, EMPTY_KEY_SET, newHashSet(N1)); + store.update(index, PATH2, EMPTY_KEY_SET, newHashSet(N2)); + + // as we trust the store we skip the check and goes straight to Stage 2. + + // Stage 2 + store.update(index, PATH2, newHashSet(N2), EMPTY_KEY_SET); + + // checking key nodes + assertTrue(index.hasChildNode(START)); + assertTrue(index.hasChildNode(N0)); + assertTrue(index.hasChildNode(N1)); + assertFalse(index.hasChildNode(N2)); + + // checking pointers + assertEquals(N1, index.getChildNode(START).getString(NEXT)); + assertEquals(N0, index.getChildNode(N1).getString(NEXT)); + assertTrue(Strings.isNullOrEmpty(index.getChildNode(N0).getString(NEXT))); + + // checking sub-nodes + String[] subNodes = Iterables.toArray(PathUtils.elements(PATH0), String.class); + assertTrue(index.getChildNode(N0).hasChildNode(subNodes[0])); + assertTrue(index.getChildNode(N0).getChildNode(subNodes[0]).hasChildNode(subNodes[1])); + assertTrue(index.getChildNode(N0).getChildNode(subNodes[0]).getChildNode(subNodes[1]).getBoolean("match")); + + subNodes = Iterables.toArray(PathUtils.elements(PATH1), String.class); + assertTrue(index.getChildNode(N1).hasChildNode(subNodes[0])); + assertTrue(index.getChildNode(N1).getChildNode(subNodes[0]).hasChildNode(subNodes[1])); + assertTrue(index.getChildNode(N1).getChildNode(subNodes[0]).getChildNode(subNodes[1]).getBoolean("match")); + } +} Index: oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/Jcr.java =================================================================== --- oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/Jcr.java (revision 1574393) +++ oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/Jcr.java (working copy) @@ -30,6 +30,7 @@ import org.apache.jackrabbit.oak.plugins.commit.JcrConflictHandler; import org.apache.jackrabbit.oak.plugins.index.IndexEditorProvider; import org.apache.jackrabbit.oak.plugins.index.nodetype.NodeTypeIndexProvider; +import org.apache.jackrabbit.oak.plugins.index.property.OrderedPropertyIndexEditorProvider; import org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexEditorProvider; import org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexProvider; import org.apache.jackrabbit.oak.plugins.index.reference.ReferenceEditorProvider; @@ -79,6 +80,8 @@ with(new PropertyIndexProvider()); with(new NodeTypeIndexProvider()); + + with(new OrderedPropertyIndexEditorProvider()); } public Jcr() { Index: oak-run/pom.xml =================================================================== --- oak-run/pom.xml (revision 1574393) +++ oak-run/pom.xml (working copy) @@ -192,6 +192,9 @@ junit test + + com.google.code.findbugs + jsr305 + - Index: oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/BenchmarkRunner.java =================================================================== --- oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/BenchmarkRunner.java (revision 1574393) +++ oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/BenchmarkRunner.java (working copy) @@ -97,6 +97,13 @@ base.value(options), 256, cacheSize, mmap.value(options)) }; Benchmark[] allBenchmarks = new Benchmark[] { + new OrderedIndexQueryOrderedIndexTest(), + new OrderedIndexQueryStandardIndexTest(), + new OrderedIndexQueryNoIndexTest(), + new OrderedIndexInsertOrderedPropertyTest(), + new OrderedIndexInsertStandardPropertyTest(), + new OrderedIndexInsertNoIndexTest(), + new OrderByQueryTest(), new LoginTest(), new LoginLogoutTest(), new NamespaceTest(), Index: oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/OrderByQueryTest.java =================================================================== --- oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/OrderByQueryTest.java (revision 0) +++ oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/OrderByQueryTest.java (working copy) @@ -0,0 +1,89 @@ +/* + * 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.benchmark; + +import java.util.Random; +import java.util.UUID; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.query.Query; +import javax.jcr.query.QueryManager; +import javax.jcr.query.QueryResult; + +/** + * This benchmark measures the read performance of child nodes using + * an ORDER BY query. + *

+ * This is related to OAK-1263. + * + */ +public class OrderByQueryTest extends AbstractTest { + + private static final String NT = "oak:unstructured"; + + private static final String ROOT_NODE_NAME = "test" + TEST_ID; + private static final int NUM_NODES = 10000; + private static final String PROPERTY_NAME = "testProperty"; + private static final Random random = new Random(); // doesn't have to be very secure, just some randomness + + @Override + protected void beforeSuite() throws Exception { + Session session = loginWriter(); + Node rootNode = session.getRootNode(); + if (rootNode.hasNode(ROOT_NODE_NAME)) { + Node root = rootNode.getNode(ROOT_NODE_NAME); + root.remove(); + } + rootNode = session.getRootNode().addNode(ROOT_NODE_NAME, NT); + + for (int i = 0; i < NUM_NODES; i++) { + if (i%1000==0) { + session.save(); + } + Node newNode = rootNode.addNode(UUID.randomUUID().toString(), NT); + newNode.setProperty(PROPERTY_NAME, random.nextLong()); + } + session.save(); + } + + @Override + public void runTest() throws Exception { + final Session session = loginWriter(); + try { + // run the query + final QueryManager qm = session.getWorkspace().getQueryManager(); + + final Query q = + qm.createQuery("SELECT * FROM [oak:unstructured] AS s WHERE " + + "ISDESCENDANTNODE(s, [/"+ROOT_NODE_NAME+"/]) ORDER BY s."+PROPERTY_NAME+"]", + Query.JCR_SQL2); + final QueryResult res = q.execute(); + + final NodeIterator nit = res.getNodes(); +// while(nit.hasNext()) { +// Node node = nit.nextNode(); +//// System.out.println("node: "+node.getPath()+", prop="+node.getProperty(PROPERTY_NAME).getLong()); +// } + } catch (RepositoryException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } +} Index: oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/OrderedIndexBaseTest.java =================================================================== --- oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/OrderedIndexBaseTest.java (revision 0) +++ oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/OrderedIndexBaseTest.java (working copy) @@ -0,0 +1,112 @@ +/* + * 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.benchmark; + +import java.util.UUID; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.jackrabbit.oak.benchmark.util.OakIndexUtils; +import org.apache.jackrabbit.oak.plugins.index.IndexConstants; +import org.apache.jackrabbit.oak.plugins.index.property.OrderedPropertyIndexEditorProvider; +import org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexEditorProvider; +import org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants; + +/** + * + */ +public abstract class OrderedIndexBaseTest extends AbstractTest { + /** + * the number of nodes created per iteration + */ + static final int NODES_PER_ITERATION = Integer.parseInt(System.getProperty("nodesPerIteration", "100")); + + /** + * number of nodes that has to be added before performing the actual test + */ + static final int PRE_ADDED_NODES = Integer.parseInt(System.getProperty("preAddedNodes", "0")); + + /** + * type of the created node + */ + static final String NODE_TYPE = NodeTypeConstants.NT_OAK_UNSTRUCTURED; + + /** + * property that will be indexed + */ + static final String INDEXED_PROPERTY = "indexedProperty"; + + /** + * node name below which creating the test data + */ + final String DUMP_NODE = this.getClass().getSimpleName() + TEST_ID; + + /** + * session used for operations throughout the test + */ + Session session; + + /** + * node under which all the test data will be filled in + */ + Node dump; + + void insertRandomNodes(int numberOfNodes){ + try{ + for(int i=0; i