You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by da...@apache.org on 2014/11/28 13:01:47 UTC
svn commit: r1642285 - in /jackrabbit/oak/trunk/oak-core/src:
main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/
test/java/org/apache/jackrabbit/oak/plugins/index/property/
test/java/org/apache/jackrabbit/oak/plugins/index/property/st...
Author: davide
Date: Fri Nov 28 12:01:46 2014
New Revision: 1642285
URL: http://svn.apache.org/r1642285
Log:
OAK-2077: Improve the resilence of the OrderedIndex for dangling links
tracked a warning when a dangling link is enountered and in case it
happens during an insert it attempts to clean it up.
Added:
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/Oak2077QueriesTest.java
Modified:
jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/BasicOrderedPropertyIndexQueryTest.java
jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStorageStrategyTest.java
Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java?rev=1642285&r1=1642284&r2=1642285&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java Fri Nov 28 12:01:46 2014
@@ -17,10 +17,12 @@
package org.apache.jackrabbit.oak.plugins.index.property.strategy;
+import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Iterators.singletonIterator;
import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.ENTRY_COUNT_PROPERTY_NAME;
import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_CONTENT_NODE_NAME;
+import static org.apache.jackrabbit.oak.plugins.index.property.OrderedIndex.LANES;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
@@ -104,9 +106,15 @@ public class OrderedContentMirrorStoreSt
private static final Random RND = new Random(System.currentTimeMillis());
/**
+ * maximum number of attempt for potential recursive processes like seek()
+ */
+ private static final int MAX_RETRIES = LANES+1;
+
+ /**
* the direction of the index.
*/
private OrderDirection direction = OrderedIndex.DEFAULT_DIRECTION;
+
public OrderedContentMirrorStoreStrategy() {
super();
@@ -146,7 +154,7 @@ public class OrderedContentMirrorStoreSt
// we use the seek for seeking the right spot. The walkedLanes will have all our
// predecessors
- String entry = seek(index, condition, walked);
+ String entry = seek(index, condition, walked, 0, new FixingDanglingLinkCallback(index));
if (LOG.isDebugEnabled()) {
LOG.debug("fetchKeyNode() - entry: {} ", entry);
printWalkedLanes("fetchKeyNode() - ", walked);
@@ -199,7 +207,9 @@ public class OrderedContentMirrorStoreSt
do {
entry = seek(index,
new PredicateEquals(key),
- walkedLanes
+ walkedLanes,
+ 0,
+ new LoggingDanglinLinkCallback()
);
lane0Next = getPropertyNext(index.getChildNode(walkedLanes[0]));
if (LOG.isDebugEnabled()) {
@@ -666,7 +676,9 @@ public class OrderedContentMirrorStoreSt
private NodeState start;
NodeState current;
private NodeState index;
+ private NodeBuilder builder;
String currentName;
+ private DanglingLinkCallback dlc = new LoggingDanglinLinkCallback();
public FullIterator(NodeState index, NodeState start, boolean includeStart,
NodeState current) {
@@ -674,12 +686,19 @@ public class OrderedContentMirrorStoreSt
this.start = start;
this.current = current;
this.index = index;
+ this.builder = new ReadOnlyBuilder(index);
}
@Override
public boolean hasNext() {
+ String next = getPropertyNext(current);
boolean hasNext = (includeStart && start.equals(current))
- || (!includeStart && !Strings.isNullOrEmpty(getPropertyNext(current)));
+ || (!includeStart && !Strings.isNullOrEmpty(next)
+ && ensureAndCleanNode(
+ builder, next,
+ currentName == null ? "" : currentName,
+ 0,
+ dlc));
return hasNext;
}
@@ -809,7 +828,7 @@ public class OrderedContentMirrorStoreSt
* last argument
*/
String seek(@Nonnull NodeBuilder index, @Nonnull Predicate<String> condition) {
- return seek(index, condition, null);
+ return seek(index, condition, null, 0, new LoggingDanglinLinkCallback());
}
/**
@@ -823,11 +842,14 @@ public class OrderedContentMirrorStoreSt
* lane represented by the corresponding position in the array. <b>You have</b> to
* pass in an array already sized as {@link OrderedIndex#LANES} or an
* {@link IllegalArgumentException} will be raised
+ * @param retries the number of retries
* @return the entry or null if not found
*/
String seek(@Nonnull final NodeBuilder index,
@Nonnull final Predicate<String> condition,
- @Nullable final String[] walkedLanes) {
+ @Nullable final String[] walkedLanes,
+ int retries,
+ @Nullable DanglingLinkCallback callback) {
boolean keepWalked = false;
String searchfor = condition.getSearchFor();
if (LOG.isDebugEnabled()) {
@@ -875,6 +897,9 @@ public class OrderedContentMirrorStoreSt
lane++;
} else {
if (condition.apply(nextkey)) {
+ // this branch is used so far only for range queries.
+ // while figuring out how to correctly reproduce the issue is less risky
+ // to leave this untouched.
found = nextkey;
} else {
currentKey = nextkey;
@@ -901,7 +926,18 @@ public class OrderedContentMirrorStoreSt
lane--;
} else {
if (condition.apply(nextkey)) {
- found = nextkey;
+ if (ensureAndCleanNode(index, nextkey, currentKey, lane, callback)) {
+ found = nextkey;
+ } else {
+ if (retries < MAX_RETRIES) {
+ return seek(index, condition, walkedLanes, ++retries, callback);
+ } else {
+ LOG.debug(
+ "Attempted a lookup and fix for {} times. Leaving it be and returning null",
+ retries);
+ return null;
+ }
+ }
} else {
currentKey = nextkey;
currentNode = null;
@@ -919,6 +955,36 @@ public class OrderedContentMirrorStoreSt
}
/**
+ * ensure that the provided {@code next} actually exists as node. Attempt to clean it up
+ * otherwise.
+ *
+ * @param index the {@code :index} node
+ * @param next the {@code :next} retrieved for the provided lane
+ * @param current the current node from which {@code :next} has been retrieved
+ * @param lane the lane on which we're looking into
+ * @return true if the node exists, false otherwise
+ */
+ private static boolean ensureAndCleanNode(@Nonnull final NodeBuilder index,
+ @Nonnull final String next,
+ @Nonnull final String current,
+ final int lane,
+ @Nullable DanglingLinkCallback callback) {
+ checkNotNull(index);
+ checkNotNull(next);
+ checkNotNull(current);
+ checkArgument(lane < LANES && lane >= 0, "The lane must be between 0 and LANES");
+
+ if (index.getChildNode(next).exists()) {
+ return true;
+ } else {
+ if (callback != null) {
+ callback.perform(current, next, lane);
+ }
+ return false;
+ }
+ }
+
+ /**
* predicate for evaluating 'key' equality across index
*/
static class PredicateEquals implements Predicate<String> {
@@ -1120,7 +1186,7 @@ public class OrderedContentMirrorStoreSt
* @param value
* @param lane
*/
- static void setPropertyNext(@Nonnull final NodeBuilder node,
+ public static void setPropertyNext(@Nonnull final NodeBuilder node,
final String value, final int lane) {
if (node != null && value != null && lane >= 0 && lane < OrderedIndex.LANES) {
PropertyState next = node.getProperty(NEXT);
@@ -1168,14 +1234,14 @@ public class OrderedContentMirrorStoreSt
/**
* short-cut for using NodeBuilder. See {@code getNext(NodeState)}
*/
- static String getPropertyNext(@Nonnull final NodeBuilder node) {
+ public static String getPropertyNext(@Nonnull final NodeBuilder node) {
return getPropertyNext(node, 0);
}
/**
* short-cut for using NodeBuilder. See {@code getNext(NodeState)}
*/
- static String getPropertyNext(@Nonnull final NodeBuilder node, final int lane) {
+ public static String getPropertyNext(@Nonnull final NodeBuilder node, final int lane) {
checkNotNull(node);
String next = "";
@@ -1217,7 +1283,7 @@ public class OrderedContentMirrorStoreSt
* @param rnd the Random generator to be used for probability
* @return the lane to be updated.
*/
- int getLane(@Nonnull final Random rnd) {
+ protected int getLane(@Nonnull final Random rnd) {
final int maxLanes = OrderedIndex.LANES - 1;
int lane = 0;
@@ -1227,4 +1293,64 @@ public class OrderedContentMirrorStoreSt
return lane;
}
+
+ /**
+ * implementors of this interface will deal with the dangling link cases along the list
+ * (OAK-2077)
+ */
+ interface DanglingLinkCallback {
+ /**
+ * perform the required operation on the provided {@code current} node for the {@code next}
+ * value on {@code lane}
+ *
+ * @param current the current node with the dangling link
+ * @param next the value pointing to the missing node
+ * @param lane the lane on which the link is on
+ */
+ void perform(String current, String next, int lane);
+ }
+
+ /**
+ * implements a "Read-only" version for managing the dangling links which will simply track down
+ * in logs the presence of it
+ */
+ static class LoggingDanglinLinkCallback implements DanglingLinkCallback {
+ private boolean alreadyLogged;
+
+ @Override
+ public void perform(@Nonnull final String current,
+ @Nonnull final String next,
+ int lane) {
+ checkNotNull(next);
+ checkNotNull(current);
+ checkArgument(lane < LANES && lane >= 0, "The lane must be between 0 and LANES");
+
+ if (!alreadyLogged) {
+ LOG.warn(
+ "Dangling link to '{}' found on lane '{}' for key '{}'. Trying to clean it up. You may consider a reindex",
+ new Object[] { next, lane, current });
+ alreadyLogged = true;
+ }
+ }
+ }
+
+ static class FixingDanglingLinkCallback extends LoggingDanglinLinkCallback {
+ private final NodeBuilder indexContent;
+
+ public FixingDanglingLinkCallback(@Nonnull final NodeBuilder indexContent) {
+ this.indexContent = checkNotNull(indexContent);
+ }
+
+ @Override
+ public void perform(String current, String next, int lane) {
+ super.perform(current, next, lane);
+ // as we're already pointing to nowhere it's safe to truncate here and avoid
+ // future errors. We'll fix all the lanes from slowest to fastest starting from the lane
+ // with the error. This should keep the list a bit more consistent with what is
+ // expected.
+ for (int l = lane; l < LANES; l++) {
+ setPropertyNext(indexContent.getChildNode(current), "", lane);
+ }
+ }
+ }
}
\ No newline at end of file
Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/BasicOrderedPropertyIndexQueryTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/BasicOrderedPropertyIndexQueryTest.java?rev=1642285&r1=1642284&r2=1642285&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/BasicOrderedPropertyIndexQueryTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/BasicOrderedPropertyIndexQueryTest.java Fri Nov 28 12:01:46 2014
@@ -20,6 +20,8 @@ import static junit.framework.Assert.ass
import static junit.framework.Assert.assertTrue;
import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
import static org.apache.jackrabbit.JcrConstants.NT_UNSTRUCTURED;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.fail;
import java.text.DecimalFormat;
import java.text.NumberFormat;
@@ -66,35 +68,61 @@ public abstract class BasicOrderedProper
protected static final String ISO_8601_2000 = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
/**
+ * same as {@link #generateOrderedValues(int, int, OrderDirection)} by passing {@code 0} as
+ * {@code offset}
+ *
+ * @param amount
+ * @param direction
+ * @return
+ */
+ protected static List<String> generateOrderedValues(int amount, OrderDirection direction) {
+ return generateOrderedValues(amount, 0, direction);
+ }
+
+ /**
+ * <p>
* generate a list of values to be used as ordered set. Will return something like
* {@code value000, value001, value002, ...}
+ * </p>
*
- *
- * @param amount
+ * @param amount the values to be generated
+ * @param offset move the current counter by this provided amount.
* @param direction the direction of the sorting
* @return a list of {@code amount} values ordered as specified by {@code direction}
*/
- protected static List<String> generateOrderedValues(int amount, OrderDirection direction) {
+ protected static List<String> generateOrderedValues(int amount, int offset , OrderDirection direction) {
if (amount > 1000) {
throw new RuntimeException("amount cannot be greater than 1000");
}
List<String> values = new ArrayList<String>(amount);
- NumberFormat nf = new DecimalFormat("000");
+
if (OrderDirection.DESC.equals(direction)) {
for (int i = amount; i > 0; i--) {
- values.add(String.format("value%s", String.valueOf(nf.format(i))));
+ values.add(formatNumber(i + offset));
}
} else {
for (int i = 0; i < amount; i++) {
- values.add(String.format("value%s", String.valueOf(nf.format(i))));
+ values.add(formatNumber(i + offset));
}
}
return values;
}
+
+ /**
+ * formats the provided number for being used by the
+ * {@link #generateOrderedValues(int, OrderDirection)}
+ *
+ * @param number
+ * @return something in the format {@code value000}
+ */
+ public static String formatNumber(int number) {
+ NumberFormat nf = new DecimalFormat("0000");
+ return String.format("value%s", String.valueOf(nf.format(number)));
+ }
/**
- * as {@code generateOrderedValues(int, OrderDirection)} by forcing OrderDirection.ASC
+ * as {@link #generateOrderedValues(int, OrderDirection)} by forcing {@link OrderDirection.ASC}
*
* @param amount
* @return
@@ -121,9 +149,13 @@ public abstract class BasicOrderedProper
}
/**
+ * <p>
* convenience method that adds a bunch of nodes in random order and return the order in which
- * they should be presented by the OrderedIndex
- *
+ * they should be presented by the OrderedIndex.
+ * </p>
+ * <p>
+ * The nodes will be created using the {@link #ORDERED_PROPERTY} as property for indexing
+ * </p>
* @param values the values of the property that will be indexed
* @param father the father under which add the nodes
* @param direction the direction of the items to be added.
@@ -154,22 +186,27 @@ public abstract class BasicOrderedProper
/**
* assert the right order of the returned resultset
*
- * @param orderedSequence the right order in which the resultset should be returned
+ * @param expected the right order in which the resultset should be returned
* @param resultset the resultset
*/
- protected void assertRightOrder(@Nonnull final List<ValuePathTuple> orderedSequence,
+ protected void assertRightOrder(@Nonnull final List<ValuePathTuple> expected,
@Nonnull final Iterator<? extends ResultRow> 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).getPath(),
- row.getPath()
- );
- counter++;
- }
+ if (expected.isEmpty()) {
+ assertFalse("An empty resultset is expected but something has been returned.",
+ resultset.hasNext());
+ } else {
+ assertTrue("No results returned", resultset.hasNext());
+ int counter = 0;
+ while (resultset.hasNext() && counter < expected.size()) {
+ ResultRow row = resultset.next();
+ assertEquals(
+ String.format("Wrong path at the element '%d'", counter),
+ expected.get(counter).getPath(),
+ row.getPath()
+ );
+ counter++;
+ }
+ }
}
/**
Added: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/Oak2077QueriesTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/Oak2077QueriesTest.java?rev=1642285&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/Oak2077QueriesTest.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/Oak2077QueriesTest.java Fri Nov 28 12:01:46 2014
@@ -0,0 +1,657 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.index.property;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.apache.jackrabbit.oak.api.Type.STRING;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_CONTENT_NODE_NAME;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME;
+import static org.apache.jackrabbit.oak.plugins.index.property.OrderedIndex.OrderDirection.ASC;
+import static org.apache.jackrabbit.oak.plugins.index.property.OrderedIndex.OrderDirection.DESC;
+import static org.apache.jackrabbit.oak.plugins.index.property.strategy.OrderedContentMirrorStoreStrategy.START;
+import static org.apache.jackrabbit.oak.plugins.index.property.strategy.OrderedContentMirrorStoreStrategy.getPropertyNext;
+import static org.apache.jackrabbit.oak.plugins.index.property.strategy.OrderedContentMirrorStoreStrategy.setPropertyNext;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.List;
+import java.util.Random;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.jcr.NoSuchWorkspaceException;
+import javax.jcr.RepositoryException;
+import javax.security.auth.login.LoginException;
+
+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.Result;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.plugins.index.IndexUpdateCallback;
+import org.apache.jackrabbit.oak.plugins.index.IndexUtils;
+import org.apache.jackrabbit.oak.plugins.index.property.OrderedIndex.OrderDirection;
+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.plugins.memory.MemoryNodeStore;
+import org.apache.jackrabbit.oak.plugins.nodetype.write.InitialContent;
+import org.apache.jackrabbit.oak.spi.commit.CommitInfo;
+import org.apache.jackrabbit.oak.spi.commit.Editor;
+import org.apache.jackrabbit.oak.spi.commit.EmptyHook;
+import org.apache.jackrabbit.oak.spi.security.OpenSecurityProvider;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.apache.jackrabbit.oak.util.NodeUtil;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.LoggerContext;
+import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.OutputStreamAppender;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+
+public class Oak2077QueriesTest extends BasicOrderedPropertyIndexQueryTest {
+ private static final LoggingTracker<ILoggingEvent> LOGGING_TRACKER;
+ private NodeStore nodestore;
+ private ContentRepository repository;
+
+ static {
+
+ LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
+
+ PatternLayoutEncoder encoder = new PatternLayoutEncoder();
+ encoder.setContext(lc);
+ encoder.setPattern("%msg%n");
+ encoder.start();
+
+ LOGGING_TRACKER = new LoggingTracker<ILoggingEvent>();
+ LOGGING_TRACKER.setContext(lc);
+ LOGGING_TRACKER.setEncoder(encoder);
+ LOGGING_TRACKER.start();
+
+ // adding the new appender to the root logger
+ ((ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME))
+ .addAppender(LOGGING_TRACKER);
+
+ //configuring the logging level to desired value
+ ((ch.qos.logback.classic.Logger) LoggerFactory.getLogger(LOGGING_TRACKER.getName()))
+ .setLevel(Level.WARN);
+ }
+
+ // ------------------------------------------------------------------------ < utility classes >
+ private static class LoggingTracker<E> extends OutputStreamAppender<E> {
+ private ByteArrayOutputStream baos = new ByteArrayOutputStream();
+
+ @Override
+ public void start() {
+ setOutputStream(baos);
+ super.start();
+ }
+
+ /**
+ * reset the inner OutputStream.
+ */
+ public void reset() {
+ baos.reset();
+ }
+
+ public BufferedReader toBufferedReader() {
+ return new BufferedReader(new StringReader(baos.toString()));
+ }
+
+ public int countLinesTracked() throws IOException {
+ int lines = 0;
+ BufferedReader br = toBufferedReader();
+ while (br.readLine() != null) {
+ lines++;
+ }
+ return lines;
+ }
+
+ @Override
+ public String getName() {
+ return LoggingTracker.class.getName();
+ }
+ }
+
+ /**
+ * used to return an instance of IndexEditor with a defined Random for a better reproducible
+ * unit testing
+ */
+ private class SeededOrderedPropertyIndexEditorProvider extends OrderedPropertyIndexEditorProvider {
+ private Random rnd = new Random(1);
+
+ @Override
+ public Editor getIndexEditor(String type, NodeBuilder definition, NodeState root,
+ IndexUpdateCallback callback) throws CommitFailedException {
+ Editor editor = (TYPE.equals(type)) ? new SeededPropertyIndexEditor(definition, root,
+ callback, rnd) : null;
+ return editor;
+ }
+ }
+
+ /**
+ * index editor that will return a content strategy with
+ */
+ private class SeededPropertyIndexEditor extends OrderedPropertyIndexEditor {
+ private Random rnd;
+
+ public SeededPropertyIndexEditor(NodeBuilder definition, NodeState root,
+ IndexUpdateCallback callback, Random rnd) {
+ super(definition, root, callback);
+ this.rnd = rnd;
+ }
+
+ public SeededPropertyIndexEditor(SeededPropertyIndexEditor parent, String name) {
+ super(parent, name);
+ this.rnd = parent.rnd;
+ }
+
+ @Override
+ IndexStoreStrategy getStrategy(boolean unique) {
+ SeededOrderedMirrorStore store = new SeededOrderedMirrorStore();
+ if (!OrderedIndex.DEFAULT_DIRECTION.equals(getDirection())) {
+ store = new SeededOrderedMirrorStore(DESC);
+ }
+ store.setRandom(rnd);
+ return store;
+ }
+
+ @Override
+ PropertyIndexEditor getChildIndexEditor(PropertyIndexEditor parent, String name) {
+ return new SeededPropertyIndexEditor(this, name);
+ }
+ }
+
+ /**
+ * mocking class that makes use of the provided {@link Random} instance for generating the lanes
+ */
+ private class SeededOrderedMirrorStore extends OrderedContentMirrorStoreStrategy {
+ private Random rnd = new Random();
+
+ public SeededOrderedMirrorStore() {
+ super();
+ }
+
+ public SeededOrderedMirrorStore(OrderDirection direction) {
+ super(direction);
+ }
+
+ @Override
+ public int getLane() {
+ return getLane(rnd);
+ }
+
+ public void setRandom(Random rnd) {
+ this.rnd = rnd;
+ }
+ }
+
+ /**
+ * enum used for injecting the filter condition in the {@code filter()}
+ */
+ private enum FilterCondition {
+ GREATER_THAN, GREATER_THEN_EQUAL, LESS_THAN
+ };
+
+ // ---------------------------------------------------------------------------------- < tests >
+ @Override
+ protected ContentRepository createRepository() {
+ nodestore = new MemoryNodeStore();
+ repository = new Oak(nodestore).with(new InitialContent())
+ .with(new OpenSecurityProvider())
+ .with(new SeededOrderedPropertyIndexEditorProvider())
+ .with(new OrderedPropertyIndexProvider())
+ .createContentRepository();
+ return repository;
+ }
+
+ @Override
+ protected void createTestIndexNode() throws Exception {
+ // leaving it empty. Prefer to create the index definition in each method
+ }
+
+ private void defineIndex(@Nonnull final OrderDirection direction)
+ throws IllegalArgumentException, RepositoryException, CommitFailedException {
+ checkNotNull(direction);
+
+ Tree index = root.getTree("/");
+
+ // removing any previously defined index definition for a complete reset
+ index = index.getChild(INDEX_DEFINITIONS_NAME);
+ if (index.exists()) {
+ index = index.getChild(TEST_INDEX_NAME);
+ if (index.exists()) {
+ index.remove();
+ }
+ }
+ index = root.getTree("/");
+
+ // ensuring we have a clear reset of the environment
+ assertFalse("the index definition should not be here yet",
+ index.getChild(INDEX_DEFINITIONS_NAME).getChild(TEST_INDEX_NAME).exists());
+
+ IndexUtils.createIndexDefinition(
+ new NodeUtil(index.getChild(INDEX_DEFINITIONS_NAME)),
+ TEST_INDEX_NAME,
+ false,
+ new String[] { ORDERED_PROPERTY },
+ null,
+ OrderedIndex.TYPE,
+ ImmutableMap.of(
+ OrderedIndex.DIRECTION, direction.getDirection()
+ )
+ );
+ root.commit();
+ }
+
+ /**
+ * <p>
+ * reset the environment variables to be sure to use the latest root. {@code session, root, qe}
+ * <p>
+ *
+ * @throws IOException
+ * @throws LoginException
+ * @throws NoSuchWorkspaceException
+ */
+ private void resetEnvVariables() throws IOException, LoginException, NoSuchWorkspaceException {
+ session.close();
+ session = repository.login(null, null);
+ root = session.getLatestRoot();
+ qe = root.getQueryEngine();
+ }
+
+ /**
+ * create the test content by the provided attributes
+ *
+ * @param numberOfNodes the number of nodes to be created
+ * @param offset if starting by 0 or by {@code offset}
+ * @param direction the direction of the value
+ * @return the list of ValiePathTuple for later assertions
+ * @throws CommitFailedException
+ */
+ private List<ValuePathTuple> createContent(final int numberOfNodes,
+ final int offset,
+ @Nonnull final OrderDirection direction)
+ throws CommitFailedException {
+ checkNotNull(direction);
+
+ Tree content = root.getTree("/").addChild("content").addChild("nodes");
+ List<String> values = generateOrderedValues(numberOfNodes, offset, direction);
+ List<ValuePathTuple> nodes = addChildNodes(values, content, direction, STRING);
+ root.commit();
+
+ return nodes;
+ }
+
+ /**
+ * truncate the {@link AbstractQueryTest#TEST_INDEX_NAME} index at the 4th element of the
+ * provided lane returning the previous value
+ *
+ * @param lane the desired lane. Must be 0 <= {@code lane} < {@link OrderedIndex#LANES}
+ * @param inexistent the derired value to be injected
+ * @return the value before the change
+ * @throws Exception
+ */
+ @Nullable
+ private String truncate(final int lane, @Nonnull final String inexistent) throws Exception {
+ checkNotNull(inexistent);
+ checkArgument(lane >= 0 && lane < OrderedIndex.LANES);
+
+ String previousValue;
+ NodeBuilder rootBuilder = nodestore.getRoot().builder();
+ NodeBuilder builder = rootBuilder.getChildNode(INDEX_DEFINITIONS_NAME);
+ builder = builder.getChildNode(TEST_INDEX_NAME);
+ builder = builder.getChildNode(INDEX_CONTENT_NODE_NAME);
+
+ NodeBuilder truncated = builder.getChildNode(START);
+ String truncatedName;
+
+ for (int i = 0; i < 4; i++) {
+ // changing the 4th element. No particular reasons on why the 4th.
+ truncatedName = getPropertyNext(truncated, lane);
+ truncated = builder.getChildNode(truncatedName);
+ }
+ previousValue = getPropertyNext(truncated, lane);
+ setPropertyNext(truncated, inexistent, lane);
+
+ nodestore.merge(rootBuilder, EmptyHook.INSTANCE, CommitInfo.EMPTY);
+ resetEnvVariables();
+
+ return previousValue;
+ }
+
+ private void assertLogAndQuery(@Nonnull final String statement,
+ @Nonnull final List<ValuePathTuple> expected) throws Exception {
+ LOGGING_TRACKER.reset();
+ Result result = executeQuery(statement, SQL2, null);
+ assertRightOrder(expected, result.getRows().iterator());
+ assertTrue("We expect at least 1 warning message to be tracked",
+ LOGGING_TRACKER.countLinesTracked() >= 1);
+ }
+
+ /**
+ * filter out the provided list for later assertions
+ *
+ * @param nodes the original list to be filtered
+ * @param inexistent the previously injected inexistent node
+ * @param condition the condition applied in the query to assert. if {@code null} it will behave
+ * as a {@code NOT NULL} query.
+ * @param whereCondition if {@condition} is provided CANNOT BE null. it's the where clause
+ * provided in the query to assert.
+ * @return the filtered list to be expected
+ */
+ @Nonnull
+ private List<ValuePathTuple> filter(@Nonnull final List<ValuePathTuple> nodes,
+ @Nonnull final String inexistent,
+ @Nullable final FilterCondition condition,
+ @Nullable final String whereCondition) {
+ checkNotNull(nodes);
+ checkNotNull(inexistent);
+ checkArgument(condition != null ? whereCondition != null : true,
+ "if 'condition' is not null'whereCondition' MUST be provided");
+
+ return Lists.newArrayList(Iterables.filter(nodes, new Predicate<ValuePathTuple>() {
+ boolean stopHere;
+
+ @Override
+ public boolean apply(ValuePathTuple input) {
+ if (!stopHere) {
+ stopHere = inexistent.equals(input.getValue());
+ }
+ boolean filter = true;
+ if (condition != null) {
+ switch (condition) {
+ case GREATER_THAN:
+ filter = input.getValue().compareTo(whereCondition) > 0;
+ break;
+ case GREATER_THEN_EQUAL:
+ filter = input.getValue().compareTo(whereCondition) >= 0;
+ break;
+ case LESS_THAN:
+ filter = input.getValue().compareTo(whereCondition) < 0;
+ break;
+ default:
+ break;
+ }
+ }
+ return !stopHere && filter;
+ }
+ }));
+ }
+
+ @Test
+ public void queryNotNullAscending() throws Exception {
+ setTraversalEnabled(false);
+ final int numberOfNodes = 20;
+ final OrderDirection direction = ASC;
+ final String inexistent = formatNumber(numberOfNodes + 1);
+ final String statement = "SELECT * FROM [nt:base] WHERE " + ORDERED_PROPERTY
+ + " IS NOT NULL";
+ defineIndex(direction);
+
+ List<ValuePathTuple> nodes = createContent(numberOfNodes, 0, direction);
+
+ // truncating the list on lane 0
+ truncate(0, inexistent);
+
+ //filtering out the part that should not be returned by the resultset.
+ List<ValuePathTuple> expected = filter(nodes, inexistent, null, null);
+
+ // pointing to a non-existent node in lane 0 we expect the result to be truncated
+ assertLogAndQuery(statement, expected);
+
+ setTraversalEnabled(true);
+ }
+
+ @Test
+ public void queryNotNullDescending() throws Exception {
+ setTraversalEnabled(false);
+ final int numberOfNodes = 20;
+ final OrderDirection direction = DESC; //changed
+ final String inexistent = formatNumber(0); //changed
+ final String statement = "SELECT * FROM [nt:base] WHERE " + ORDERED_PROPERTY
+ + " IS NOT NULL";
+ defineIndex(direction);
+
+ List<ValuePathTuple> nodes = createContent(numberOfNodes, 1, direction);
+
+ // truncating the list on lane 0
+ truncate(0, inexistent);
+
+ //filtering out the part that should not be returned by the resultset.
+ List<ValuePathTuple> expected = filter(nodes, inexistent, null, null);
+
+ // pointing to a non-existent node in lane 0 we expect the result to be truncated
+ assertLogAndQuery(statement, expected);
+
+ // as the full iterable used in `property IS NOT NULL` cases walk the index on lane 0, any
+ // other lanes doesn't matter.
+
+ setTraversalEnabled(true);
+ }
+
+ // As of OAK-2202 we don't use the skip list for returning a specific key item, so we're not
+ // affected by OAK-2077
+ // public void queryEqualsAscending() throws Exception {
+ // }
+ // public void queryEqualsDescending() {
+ // }
+
+ @Test
+ public void queryGreaterThanAscending() throws Exception {
+ setTraversalEnabled(false);
+ final int numberOfNodes = 20;
+ final OrderDirection direction = ASC;
+ final String inexistent = formatNumber(numberOfNodes + 1);
+ // as 'values' will start from 0, we're excluding first entry(ies)
+ final String whereCondition = formatNumber(1);
+ final String statement = "SELECT * FROM [nt:base] WHERE " + ORDERED_PROPERTY
+ + " > '%s'";
+ defineIndex(direction);
+
+ List<ValuePathTuple> nodes = createContent(numberOfNodes, 0, direction);
+
+ // truncating the list on lane 0
+ truncate(0, inexistent);
+
+ //filtering out the part that should not be returned by the resultset.
+ List<ValuePathTuple> expected = filter(nodes, inexistent, FilterCondition.GREATER_THAN,
+ whereCondition);
+
+ // pointing to a non-existent node in lane 0 we expect the result to be truncated
+ assertLogAndQuery(String.format(statement, whereCondition), expected);
+
+ setTraversalEnabled(true);
+ }
+
+ /*
+ * for sake of simplicity we check the just the second lane but it should be the same for all
+ * other higher ones.
+ */
+ @Test
+ public void queryGreaterThanAscendingLane1() throws Exception {
+ setTraversalEnabled(false);
+ final int numberOfNodes = 20;
+ final OrderDirection direction = ASC;
+ String inexistent = formatNumber(numberOfNodes + 1);
+ String whereCondition;
+ final String statement = "SELECT * FROM [nt:base] WHERE " + ORDERED_PROPERTY
+ + " > '%s'";
+ defineIndex(direction);
+
+ List<ValuePathTuple> nodes = createContent(numberOfNodes, 0, direction);
+
+ whereCondition = truncate(1, inexistent);
+
+ //filtering out the part that should not be returned by the resultset.
+ List<ValuePathTuple> expected = filter(nodes, inexistent, FilterCondition.GREATER_THAN,
+ whereCondition);
+
+ // no logging should be applied as the missing item does not match the seek condition
+ // we don't care about the logging then.
+ String st = String.format(statement, whereCondition);
+ Result result = executeQuery(st, SQL2, null);
+ assertRightOrder(expected, result.getRows().iterator());
+
+ setTraversalEnabled(true);
+ }
+
+ @Test
+ public void queryGreaterThenDescending() throws Exception {
+ setTraversalEnabled(false);
+ final int numberOfNodes = 20;
+ final int offset = 5;
+ final OrderDirection direction = DESC;
+ final String whereCondition = formatNumber(1);
+ final String inexistent = formatNumber(3);
+ final String statement = "SELECT * FROM [nt:base] WHERE " + ORDERED_PROPERTY
+ + " > '%s'";
+ defineIndex(direction);
+
+ List<ValuePathTuple> nodes = createContent(numberOfNodes, offset, direction);
+
+ // truncating the list on lane 0
+ truncate(0, inexistent);
+
+ //filtering out the part that should not be returned by the resultset.
+ List<ValuePathTuple> expected = filter(nodes, inexistent, FilterCondition.GREATER_THAN,
+ whereCondition);
+
+ // pointing to a non-existent node in lane 0 we expect the result to be truncated
+ assertLogAndQuery(String.format(statement, whereCondition), expected);
+
+ setTraversalEnabled(true);
+ }
+
+ @Test
+ public void queryGreaterThanEqualAscending() throws Exception {
+ setTraversalEnabled(false);
+ final int numberOfNodes = 20;
+ final OrderDirection direction = ASC;
+ final String inexistent = formatNumber(numberOfNodes + 1);
+ // as 'values' will start from 0, we're excluding first entry(ies)
+ final String whereCondition = formatNumber(1);
+ final String statement = "SELECT * FROM [nt:base] WHERE " + ORDERED_PROPERTY
+ + " >= '%s'";
+ defineIndex(direction);
+
+ List<ValuePathTuple> nodes = createContent(numberOfNodes, 0, direction);
+
+ truncate(0, inexistent);
+
+ //filtering out the part that should not be returned by the resultset.
+ List<ValuePathTuple> expected = filter(nodes, inexistent,
+ FilterCondition.GREATER_THEN_EQUAL, whereCondition);
+
+ // pointing to a non-existent node in lane 0 we expect the result to be truncated
+ assertLogAndQuery(String.format(statement, whereCondition), expected);
+
+ setTraversalEnabled(true);
+ }
+
+ @Test
+ public void queryGreaterThanEqualDescending() throws Exception {
+ setTraversalEnabled(false);
+ final int numberOfNodes = 20;
+ final int offset = 5;
+ final OrderDirection direction = DESC;
+ final String whereCondition = formatNumber(1);
+ final String inexistent = formatNumber(3);
+ final String statement = "SELECT * FROM [nt:base] WHERE " + ORDERED_PROPERTY
+ + " >= '%s'";
+ defineIndex(direction);
+
+ List<ValuePathTuple> nodes = createContent(numberOfNodes, offset, direction);
+
+ // truncating the list on lane 0
+ truncate(0, inexistent);
+
+ //filtering out the part that should not be returned by the resultset.
+ List<ValuePathTuple> expected = filter(nodes, inexistent,
+ FilterCondition.GREATER_THEN_EQUAL, whereCondition);
+
+ // pointing to a non-existent node in lane 0 we expect the result to be truncated
+ assertLogAndQuery(String.format(statement, whereCondition), expected);
+
+ setTraversalEnabled(true);
+ }
+
+ @Test
+ public void queryLessThanAscending() throws Exception {
+ setTraversalEnabled(false);
+ final int numberOfNodes = 20;
+ final OrderDirection direction = ASC;
+ final String inexistent = formatNumber(numberOfNodes + 1);
+ final String whereCondition = formatNumber(numberOfNodes + 2);
+ final String statement = "SELECT * FROM [nt:base] WHERE " + ORDERED_PROPERTY
+ + " < '%s'";
+ defineIndex(direction);
+
+ List<ValuePathTuple> nodes = createContent(numberOfNodes, 0, direction);
+
+ truncate(0, inexistent);
+
+ //filtering out the part that should not be returned by the resultset.
+ List<ValuePathTuple> expected = filter(nodes, inexistent, FilterCondition.LESS_THAN,
+ whereCondition);
+
+ // pointing to a non-existent node in lane 0 we expect the result to be truncated
+ assertLogAndQuery(String.format(statement, whereCondition), expected);
+
+ setTraversalEnabled(true);
+ }
+
+ @Test
+ public void queryLessThanDescending() throws Exception {
+ setTraversalEnabled(false);
+ final int numberOfNodes = 20;
+ final int offset = 5;
+ final OrderDirection direction = DESC;
+ final String whereCondition = formatNumber(1);
+ final String inexistent = formatNumber(3);
+ final String statement = "SELECT * FROM [nt:base] WHERE " + ORDERED_PROPERTY
+ + " < '%s'";
+ defineIndex(direction);
+
+ List<ValuePathTuple> nodes = createContent(numberOfNodes, offset, direction);
+
+ truncate(0, inexistent);
+
+ //filtering out the part that should not be returned by the resultset.
+ List<ValuePathTuple> expected = filter(nodes, inexistent, FilterCondition.LESS_THAN,
+ whereCondition);
+
+ // pointing to a non-existent node in lane 0 we expect the result to be truncated
+ assertLogAndQuery(String.format(statement, whereCondition), expected);
+
+ setTraversalEnabled(true);
+ }
+}
Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStorageStrategyTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStorageStrategyTest.java?rev=1642285&r1=1642284&r2=1642285&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStorageStrategyTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStorageStrategyTest.java Fri Nov 28 12:01:46 2014
@@ -17,9 +17,17 @@
package org.apache.jackrabbit.oak.plugins.index.property.strategy;
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Sets.newHashSet;
+import static org.apache.jackrabbit.oak.plugins.index.property.OrderedIndex.LANES;
+import static org.apache.jackrabbit.oak.plugins.index.property.OrderedIndex.OrderDirection.DESC;
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.apache.jackrabbit.oak.plugins.index.property.strategy.OrderedContentMirrorStoreStrategy.getPropertyNext;
+import static org.apache.jackrabbit.oak.plugins.index.property.strategy.OrderedContentMirrorStoreStrategy.setPropertyNext;
+import static org.apache.jackrabbit.oak.plugins.index.property.strategy.OrderedContentMirrorStoreStrategy.FixingDanglingLinkCallback;
+import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
import static org.easymock.EasyMock.createNiceMock;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
@@ -47,6 +55,7 @@ import org.apache.jackrabbit.oak.plugins
import org.apache.jackrabbit.oak.plugins.index.IndexUtils;
import org.apache.jackrabbit.oak.plugins.index.property.OrderedIndex;
import org.apache.jackrabbit.oak.plugins.index.property.OrderedIndex.OrderDirection;
+import org.apache.jackrabbit.oak.plugins.index.property.strategy.OrderedContentMirrorStoreStrategy.PredicateGreaterThan;
import org.apache.jackrabbit.oak.plugins.index.property.strategy.OrderedContentMirrorStoreStrategy.PredicateLessThan;
import org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState;
import org.apache.jackrabbit.oak.query.ast.Operator;
@@ -56,6 +65,7 @@ import org.apache.jackrabbit.oak.spi.que
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.apache.jackrabbit.oak.spi.state.ReadOnlyBuilder;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -3123,7 +3133,7 @@ public class OrderedContentMirrorStorage
try {
item = store.seek(builder,
- new OrderedContentMirrorStoreStrategy.PredicateEquals(searchFor), wl);
+ new OrderedContentMirrorStoreStrategy.PredicateEquals(searchFor), wl, 0, null);
fail("With a wrong size for the lane it should have raised an exception");
} catch (IllegalArgumentException e) {
// so far so good. It was expected
@@ -3138,7 +3148,7 @@ public class OrderedContentMirrorStorage
entry = searchFor;
wl = new String[OrderedIndex.LANES];
item = store.seek(builder,
- new OrderedContentMirrorStoreStrategy.PredicateEquals(searchFor), wl);
+ new OrderedContentMirrorStoreStrategy.PredicateEquals(searchFor), wl, 0, null);
assertNotNull(wl);
assertEquals(OrderedIndex.LANES, wl.length);
assertEquals("Wrong lane", lane0, wl[0]);
@@ -3155,7 +3165,7 @@ public class OrderedContentMirrorStorage
entry = searchFor;
wl = new String[OrderedIndex.LANES];
item = store.seek(builder,
- new OrderedContentMirrorStoreStrategy.PredicateEquals(searchFor), wl);
+ new OrderedContentMirrorStoreStrategy.PredicateEquals(searchFor), wl, 0, null);
assertNotNull(wl);
assertEquals(OrderedIndex.LANES, wl.length);
assertEquals("Wrong lane", lane0, wl[0]);
@@ -3172,7 +3182,7 @@ public class OrderedContentMirrorStorage
entry = searchFor;
wl = new String[OrderedIndex.LANES];
item = store.seek(builder,
- new OrderedContentMirrorStoreStrategy.PredicateEquals(searchFor), wl);
+ new OrderedContentMirrorStoreStrategy.PredicateEquals(searchFor), wl, 0, null);
assertNotNull(wl);
assertEquals(OrderedIndex.LANES, wl.length);
assertEquals("Wrong lane", lane0, wl[0]);
@@ -3245,7 +3255,7 @@ public class OrderedContentMirrorStorage
try {
item = store.seek(builder,
- new OrderedContentMirrorStoreStrategy.PredicateEquals(searchFor), wl);
+ new OrderedContentMirrorStoreStrategy.PredicateEquals(searchFor), wl, 0, null);
fail("With a wrong size for the lane it should have raised an exception");
} catch (IllegalArgumentException e) {
// so far so good. It was expected
@@ -3260,7 +3270,7 @@ public class OrderedContentMirrorStorage
entry = searchFor;
wl = new String[OrderedIndex.LANES];
item = store.seek(builder,
- new OrderedContentMirrorStoreStrategy.PredicateEquals(searchFor), wl);
+ new OrderedContentMirrorStoreStrategy.PredicateEquals(searchFor), wl, 0, null);
assertNotNull(wl);
assertEquals(OrderedIndex.LANES, wl.length);
assertEquals("Wrong lane", lane0, wl[0]);
@@ -3277,7 +3287,7 @@ public class OrderedContentMirrorStorage
entry = searchFor;
wl = new String[OrderedIndex.LANES];
item = store.seek(builder,
- new OrderedContentMirrorStoreStrategy.PredicateEquals(searchFor), wl);
+ new OrderedContentMirrorStoreStrategy.PredicateEquals(searchFor), wl, 0, null);
assertNotNull(wl);
assertEquals(OrderedIndex.LANES, wl.length);
assertEquals("Wrong lane", lane0, wl[0]);
@@ -3294,7 +3304,7 @@ public class OrderedContentMirrorStorage
entry = searchFor;
wl = new String[OrderedIndex.LANES];
item = store.seek(builder,
- new OrderedContentMirrorStoreStrategy.PredicateEquals(searchFor), wl);
+ new OrderedContentMirrorStoreStrategy.PredicateEquals(searchFor), wl, 0, null);
assertNotNull(wl);
assertEquals(OrderedIndex.LANES, wl.length);
assertEquals("Wrong lane", lane0, wl[0]);
@@ -3309,7 +3319,7 @@ public class OrderedContentMirrorStorage
*
* @param index
*/
- private static void printSkipList(NodeState index) {
+ public static void printSkipList(NodeState index) {
final String marker = "->o-";
final String filler = "----";
StringBuffer sb = new StringBuffer();
@@ -3659,4 +3669,100 @@ public class OrderedContentMirrorStorage
assertEquals("path/f", resultset.next());
assertFalse("We should have not any results left", resultset.hasNext());
}
+
+ @Test
+ public void oak2077() {
+ NodeBuilder index;
+ MockOrderedContentMirrorStoreStrategy ascending = new MockOrderedContentMirrorStoreStrategy();
+ MockOrderedContentMirrorStoreStrategy descending = new MockOrderedContentMirrorStoreStrategy(DESC);
+ MockOrderedContentMirrorStoreStrategy strategy;
+ OrderedIndex.Predicate<String> condition;
+ String missingEntry, node;
+
+
+ // creating a dangling link on each lane one at time.
+ for (int lane = 0; lane < LANES; lane++) {
+
+ // ---------------------------------------------------< ascending, plain/inserts case >
+ missingEntry = KEYS[5];
+ strategy = ascending;
+ condition = new PredicateGreaterThan(missingEntry, true);
+ index = EMPTY_NODE.builder();
+ node = oak2077CreateStructure(index, lane, strategy, missingEntry);
+
+ assertOak2077(condition, strategy, index, lane, node);
+
+ // ------------------------------------------------- < descending, plain/inserts case >
+ missingEntry = KEYS[0];
+ strategy = descending;
+ index = EMPTY_NODE.builder();
+ condition = new PredicateLessThan(missingEntry, true);
+ node = oak2077CreateStructure(index, lane, strategy, missingEntry);
+
+ assertOak2077(condition, strategy, index, lane, node);
+ }
+ }
+
+ private static void assertOak2077(@Nonnull final OrderedIndex.Predicate<String> condition,
+ @Nonnull final OrderedContentMirrorStoreStrategy strategy,
+ @Nonnull NodeBuilder index,
+ final int lane,
+ @Nonnull final String node) {
+
+ checkNotNull(condition);
+ checkNotNull(strategy);
+ checkNotNull(index);
+ checkArgument(lane >= 0 && lane < LANES);
+ checkNotNull(node);
+
+ NodeState indexState = index.getNodeState();
+ String[] wl = new String[LANES];
+ String entry;
+
+ entry = strategy.seek(index, condition, wl, 0, new FixingDanglingLinkCallback(index));
+ assertNull("the seeked node does not exist and should have been null. lane: " + lane, entry);
+ assertEquals(
+ "As the index is a NodeBuilder we expect the entry to be fixed. lane: " + lane, "",
+ getPropertyNext(index.getChildNode(node), lane));
+
+ index = new ReadOnlyBuilder(indexState);
+ entry = strategy.seek(index, condition);
+ assertNull("the seeked node does not exist and should have been null. lane: " + lane, entry);
+ }
+
+ /**
+ * <p>
+ * utility method to create the structure for the {@link #oak2077()} test.
+ * </p>
+ * <p>
+ * Create an index according to strategy with nodes from {@code 001} to {@code 004}.
+ * </p>
+ *
+ * @param lane
+ * @param strategy
+ * @param missingEntry
+ * @return the node name with the wrong lane for testing on it later on.
+ */
+ private static String oak2077CreateStructure(@Nonnull final NodeBuilder index,
+ final int lane,
+ @Nonnull final MockOrderedContentMirrorStoreStrategy strategy,
+ @Nonnull final String missingEntry) {
+ checkNotNull(index);
+ checkNotNull(strategy);
+ checkArgument(lane >= 0 && lane < LANES);
+ checkNotNull(missingEntry);
+
+ String node;
+
+ strategy.setLane(0);
+ strategy.update(index, "/we/dont/care", EMPTY_KEY_SET, newHashSet(KEYS[1]));
+ strategy.update(index, "/we/dont/care", EMPTY_KEY_SET, newHashSet(KEYS[2]));
+ strategy.setLane(lane);
+ strategy.update(index, "/we/dont/care", EMPTY_KEY_SET, newHashSet(KEYS[3]));
+ strategy.update(index, "/we/dont/care", EMPTY_KEY_SET, newHashSet(KEYS[4]));
+ node = KEYS[3];
+ setPropertyNext(index.getChildNode(node), missingEntry, lane);
+
+ return node;
+ }
}