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 16:49:13 UTC

svn commit: r1642315 - in /jackrabbit/oak/branches/1.0: ./ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/ oak-core/src/test/java/org/apache/jack...

Author: davide
Date: Fri Nov 28 15:49:13 2014
New Revision: 1642315

URL: http://svn.apache.org/r1642315
Log:
OAK-2077: Improve the resilence of the OrderedIndex for dangling links

merged into 1.0


Added:
    jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/Oak2077QueriesTest.java
      - copied, changed from r1642285, jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/Oak2077QueriesTest.java
Modified:
    jackrabbit/oak/branches/1.0/   (props changed)
    jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java
    jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/BasicOrderedPropertyIndexQueryTest.java
    jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStorageStrategyTest.java

Propchange: jackrabbit/oak/branches/1.0/
------------------------------------------------------------------------------
  Merged /jackrabbit/oak/trunk:r1642285

Modified: jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java?rev=1642315&r1=1642314&r2=1642315&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java (original)
+++ jackrabbit/oak/branches/1.0/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java Fri Nov 28 15:49:13 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()) {
@@ -649,7 +659,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) {
@@ -657,12 +669,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;
         }
@@ -792,7 +811,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());
     }
     
     /**
@@ -806,11 +825,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()) {
@@ -858,6 +880,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;
@@ -884,7 +909,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;
@@ -902,6 +938,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> {
@@ -1103,7 +1169,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);
@@ -1151,14 +1217,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 = "";
@@ -1200,7 +1266,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;
         
@@ -1210,4 +1276,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/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/BasicOrderedPropertyIndexQueryTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/BasicOrderedPropertyIndexQueryTest.java?rev=1642315&r1=1642314&r2=1642315&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/BasicOrderedPropertyIndexQueryTest.java (original)
+++ jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/BasicOrderedPropertyIndexQueryTest.java Fri Nov 28 15:49:13 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++;
+            }
+        }        
     }
 
     /**

Copied: jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/Oak2077QueriesTest.java (from r1642285, 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/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/Oak2077QueriesTest.java?p2=jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/Oak2077QueriesTest.java&p1=jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/Oak2077QueriesTest.java&r1=1642285&r2=1642315&rev=1642315&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/Oak2077QueriesTest.java (original)
+++ jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/Oak2077QueriesTest.java Fri Nov 28 15:49:13 2014
@@ -409,7 +409,7 @@ public class Oak2077QueriesTest extends 
     
     @Test
     public void queryNotNullAscending() throws Exception {
-        setTraversalEnabled(false);
+        setTravesalEnabled(false);
         final int numberOfNodes = 20;
         final OrderDirection direction = ASC;
         final String inexistent  = formatNumber(numberOfNodes + 1);
@@ -428,12 +428,12 @@ public class Oak2077QueriesTest extends 
         // pointing to a non-existent node in lane 0 we expect the result to be truncated
         assertLogAndQuery(statement, expected);
         
-        setTraversalEnabled(true);
+        setTravesalEnabled(true);
     }
     
     @Test
     public void queryNotNullDescending() throws Exception {
-        setTraversalEnabled(false);
+        setTravesalEnabled(false);
         final int numberOfNodes = 20;
         final OrderDirection direction = DESC; //changed
         final String inexistent  = formatNumber(0); //changed
@@ -455,7 +455,7 @@ public class Oak2077QueriesTest extends 
         // 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);
+        setTravesalEnabled(true);
     }
 
     // As of OAK-2202 we don't use the skip list for returning a specific key item, so we're not
@@ -467,7 +467,7 @@ public class Oak2077QueriesTest extends 
 
     @Test
     public void queryGreaterThanAscending() throws Exception {
-        setTraversalEnabled(false);
+        setTravesalEnabled(false);
         final int numberOfNodes = 20;
         final OrderDirection direction = ASC;
         final String inexistent  = formatNumber(numberOfNodes + 1);
@@ -489,7 +489,7 @@ public class Oak2077QueriesTest extends 
         // pointing to a non-existent node in lane 0 we expect the result to be truncated
         assertLogAndQuery(String.format(statement, whereCondition), expected);
         
-        setTraversalEnabled(true);
+        setTravesalEnabled(true);
     }
 
     /*
@@ -498,7 +498,7 @@ public class Oak2077QueriesTest extends 
      */
     @Test
     public void queryGreaterThanAscendingLane1() throws Exception {
-        setTraversalEnabled(false);
+        setTravesalEnabled(false);
         final int numberOfNodes = 20;
         final OrderDirection direction = ASC;
         String inexistent  = formatNumber(numberOfNodes + 1);
@@ -521,12 +521,12 @@ public class Oak2077QueriesTest extends 
         Result result = executeQuery(st, SQL2, null);
         assertRightOrder(expected, result.getRows().iterator());
 
-        setTraversalEnabled(true);
+        setTravesalEnabled(true);
     }
 
     @Test
     public void queryGreaterThenDescending() throws Exception {
-        setTraversalEnabled(false);
+        setTravesalEnabled(false);
         final int numberOfNodes = 20;
         final int offset = 5;
         final OrderDirection direction = DESC;
@@ -548,12 +548,12 @@ public class Oak2077QueriesTest extends 
         // pointing to a non-existent node in lane 0 we expect the result to be truncated
         assertLogAndQuery(String.format(statement, whereCondition), expected);
         
-        setTraversalEnabled(true);
+        setTravesalEnabled(true);
     }
     
     @Test
     public void queryGreaterThanEqualAscending() throws Exception {
-        setTraversalEnabled(false);
+        setTravesalEnabled(false);
         final int numberOfNodes = 20;
         final OrderDirection direction = ASC;
         final String inexistent  = formatNumber(numberOfNodes + 1);
@@ -574,12 +574,12 @@ public class Oak2077QueriesTest extends 
         // pointing to a non-existent node in lane 0 we expect the result to be truncated
         assertLogAndQuery(String.format(statement, whereCondition), expected);
         
-        setTraversalEnabled(true);
+        setTravesalEnabled(true);
     }
     
     @Test
     public void queryGreaterThanEqualDescending() throws Exception {
-        setTraversalEnabled(false);
+        setTravesalEnabled(false);
         final int numberOfNodes = 20;
         final int offset = 5;
         final OrderDirection direction = DESC;
@@ -601,12 +601,12 @@ public class Oak2077QueriesTest extends 
         // pointing to a non-existent node in lane 0 we expect the result to be truncated
         assertLogAndQuery(String.format(statement, whereCondition), expected);
         
-        setTraversalEnabled(true);
+        setTravesalEnabled(true);
     }
     
     @Test
     public void queryLessThanAscending() throws Exception {
-        setTraversalEnabled(false);
+        setTravesalEnabled(false);
         final int numberOfNodes = 20;
         final OrderDirection direction = ASC;
         final String inexistent  = formatNumber(numberOfNodes + 1);
@@ -626,12 +626,12 @@ public class Oak2077QueriesTest extends 
         // pointing to a non-existent node in lane 0 we expect the result to be truncated
         assertLogAndQuery(String.format(statement, whereCondition), expected);
         
-        setTraversalEnabled(true);
+        setTravesalEnabled(true);
     }
     
     @Test
     public void queryLessThanDescending() throws Exception {
-        setTraversalEnabled(false);
+        setTravesalEnabled(false);
         final int numberOfNodes = 20;
         final int offset = 5;
         final OrderDirection direction = DESC;
@@ -652,6 +652,6 @@ public class Oak2077QueriesTest extends 
         // pointing to a non-existent node in lane 0 we expect the result to be truncated
         assertLogAndQuery(String.format(statement, whereCondition), expected);
         
-        setTraversalEnabled(true);
+        setTravesalEnabled(true);
     }
 }

Modified: jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStorageStrategyTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStorageStrategyTest.java?rev=1642315&r1=1642314&r2=1642315&view=diff
==============================================================================
--- jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStorageStrategyTest.java (original)
+++ jackrabbit/oak/branches/1.0/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStorageStrategyTest.java Fri Nov 28 15:49:13 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;
+    }
 }