Author: jbellis
Date: Tue Aug 30 05:50:30 2011
New Revision: 1163090

URL: http://svn.apache.org/viewvc?rev=1163090&view=rev
Log:
fix corner cases in Range.differenceToFetch
patch by Tyler Hobbs; reviewed by Stu Hood for CASSANDRA-3084

Modified:
    cassandra/branches/cassandra-0.8/CHANGES.txt
    
cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/dht/Range.java
    
cassandra/branches/cassandra-0.8/test/unit/org/apache/cassandra/dht/RangeTest.java

Modified: cassandra/branches/cassandra-0.8/CHANGES.txt
URL: 
http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/CHANGES.txt?rev=1163090&r1=1163089&r2=1163090&view=diff
==============================================================================
--- cassandra/branches/cassandra-0.8/CHANGES.txt (original)
+++ cassandra/branches/cassandra-0.8/CHANGES.txt Tue Aug 30 05:50:30 2011
@@ -39,6 +39,7 @@
    and string representations in CLI (CASSANDRA-3075)
  * always hint counters (CASSANDRA-3099)
  * fix log4j initialization in EmbeddedCassandraService (CASSANDRA-2857)
+ * fix corner cases in Range.differenceToFetch (CASSANDRA-3084)
 
 
 0.8.4

Modified: 
cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/dht/Range.java
URL: 
http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/dht/Range.java?rev=1163090&r1=1163089&r2=1163090&view=diff
==============================================================================
--- 
cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/dht/Range.java 
(original)
+++ 
cassandra/branches/cassandra-0.8/src/java/org/apache/cassandra/dht/Range.java 
Tue Aug 30 05:50:30 2011
@@ -261,6 +261,24 @@ public class Range extends AbstractBound
     }
 
     /**
+     * Subtracts a portion of this range.
+     * @param contained The range to subtract from this. It must be totally
+     * contained by this range.
+     * @return An ArrayList of the Ranges left after subtracting contained
+     * from this.
+     */
+    private ArrayList<Range> subtractContained(Range contained)
+    {
+        ArrayList<Range> difference = new ArrayList<Range>();
+
+        if (!left.equals(contained.left))
+            difference.add(new Range(left, contained.left));
+        if (!right.equals(contained.right))
+            difference.add(new Range(contained.right, right));
+        return difference;
+    }
+
+    /**
      * Calculate set of the difference ranges of given two ranges
      * (as current (A, B] and rhs is (C, D])
      * which node will need to fetch when moving to a given new token
@@ -270,33 +288,35 @@ public class Range extends AbstractBound
      */
     public Set<Range> differenceToFetch(Range rhs)
     {
-        Set<Range> difference = new HashSet<Range>();
-
-        int comparisonAC = Range.compare(left, rhs.left);
-
-        if (comparisonAC == 0) // (A, B] & (A, C]
+        Set<Range> result;
+        Set<Range> intersectionSet = this.intersectionWith(rhs);
+        if (intersectionSet.isEmpty())
         {
-            if (Range.compare(right, rhs.right) < 0) // B < C
-            {
-                difference.add(new Range(right, rhs.right));
-            }
+            result = new HashSet<Range>();
+            result.add(rhs);
         }
-        else if (comparisonAC > 0) // (A, B] & (C, D]  where C < A (A > C)
+        else
         {
-            difference.add(new Range(rhs.left, left)); // first interval will 
be (C, A]
-
-            if (Range.compare(rhs.right, right) > 0) // D > B
+            Range[] intersections = new Range[intersectionSet.size()];
+            intersectionSet.toArray(intersections);
+            if (intersections.length == 1)
             {
-                difference.add(new Range(rhs.right, right)); // (D, B]
+                result = new 
HashSet<Range>(rhs.subtractContained(intersections[0]));
+            }
+            else
+            {
+                // intersections.length must be 2
+                Range first = intersections[0];
+                Range second = intersections[1];
+                ArrayList<Range> temp = rhs.subtractContained(first);
+
+                // Because there are two intersections, subtracting only one 
of them
+                // will yield a single Range.
+                Range single = temp.get(0);
+                result = new HashSet<Range>(single.subtractContained(second));
             }
         }
-        else // (A, B] & (C, D] where C > A (mean that comparisonAC < 0)
-        {
-            Token newLeft = (Range.compare(rhs.left, right) > 0) ? rhs.left : 
right; // C > B ? (C, D] : (B, D]
-            difference.add(new Range(newLeft, rhs.right));
-        }
-
-        return difference;
+        return result;
     }
 
     public static boolean isTokenInRanges(Token token, Iterable<Range> ranges)

Modified: 
cassandra/branches/cassandra-0.8/test/unit/org/apache/cassandra/dht/RangeTest.java
URL: 
http://svn.apache.org/viewvc/cassandra/branches/cassandra-0.8/test/unit/org/apache/cassandra/dht/RangeTest.java?rev=1163090&r1=1163089&r2=1163090&view=diff
==============================================================================
--- 
cassandra/branches/cassandra-0.8/test/unit/org/apache/cassandra/dht/RangeTest.java
 (original)
+++ 
cassandra/branches/cassandra-0.8/test/unit/org/apache/cassandra/dht/RangeTest.java
 Tue Aug 30 05:50:30 2011
@@ -19,12 +19,10 @@
 package org.apache.cassandra.dht;
 
 import java.nio.ByteBuffer;
-import java.util.Arrays;
-import java.util.List;
+import java.util.HashSet;
 import java.util.Set;
 
 import org.apache.commons.lang.StringUtils;
-
 import org.junit.Test;
 
 public class RangeTest
@@ -314,4 +312,151 @@ public class RangeTest
         assert Range.compare(t5, t4) == 1;
         assert Range.compare(t1, t4) == 0;
     }
+
+    private Range makeRange(String token1, String token2)
+    {
+        return new Range(new BigIntegerToken(token1), new 
BigIntegerToken(token2));
+    }
+
+    private Set<Range> makeRanges(String[][] tokenPairs)
+    {
+        Set<Range> ranges = new HashSet<Range>();
+        for (int i = 0; i < tokenPairs.length; ++i)
+            ranges.add(makeRange(tokenPairs[i][0], tokenPairs[i][1]));
+        return ranges;
+    }
+
+    private void checkDifference(Range oldRange, String[][] newTokens, 
String[][] expected)
+    {
+        Set<Range> ranges = makeRanges(newTokens);
+        for (Range newRange : ranges)
+        {
+            Set<Range> diff = oldRange.differenceToFetch(newRange);
+            assert diff.equals(makeRanges(expected)) : "\n" +
+                                                       "Old range: " + 
oldRange.toString() + "\n" +
+                                                       "New range: " + 
newRange.toString() + "\n" +
+                                                       "Difference: (result) " 
+ diff.toString() + " != " + makeRanges(expected) + " (expected)";
+        }
+    }
+
+    @Test
+    public void testDifferenceToFetchNoWrap()
+    {
+        Range oldRange = makeRange("10", "40");
+
+        // New range is entirely contained
+        String[][] newTokens1 = { { "20", "30" }, { "10", "20" }, { "10", "40" 
}, { "20", "40" } };
+        String[][] expected1 = { };
+        checkDifference(oldRange, newTokens1, expected1);
+
+        // Right half of the new range is needed
+        String[][] newTokens2 = { { "10", "50" }, { "20", "50" }, { "40", "50" 
} };
+        String[][] expected2 = { { "40", "50" } };
+        checkDifference(oldRange, newTokens2, expected2);
+
+        // Left half of the new range is needed
+        String[][] newTokens3 = { { "0", "10" }, { "0", "20" }, { "0", "40" } 
};
+        String[][] expected3 = { { "0", "10" } };
+        checkDifference(oldRange, newTokens3, expected3);
+
+        // Parts on both ends of the new range are needed
+        String[][] newTokens4 = { { "0", "50" } };
+        String[][] expected4 = { { "0", "10" }, { "40", "50" } };
+        checkDifference(oldRange, newTokens4, expected4);
+    }
+
+    @Test
+    public void testDifferenceToFetchBothWrap()
+    {
+        Range oldRange = makeRange("1010", "40");
+
+        // New range is entirely contained
+        String[][] newTokens1 = { { "1020", "30" }, { "1010", "20" }, { 
"1010", "40" }, { "1020", "40" } };
+        String[][] expected1 = { };
+        checkDifference(oldRange, newTokens1, expected1);
+
+        // Right half of the new range is needed
+        String[][] newTokens2 = { { "1010", "50" }, { "1020", "50" }, { 
"1040", "50" } };
+        String[][] expected2 = { { "40", "50" } };
+        checkDifference(oldRange, newTokens2, expected2);
+
+        // Left half of the new range is needed
+        String[][] newTokens3 = { { "1000", "10" }, { "1000", "20" }, { 
"1000", "40" } };
+        String[][] expected3 = { { "1000", "1010" } };
+        checkDifference(oldRange, newTokens3, expected3);
+
+        // Parts on both ends of the new range are needed
+        String[][] newTokens4 = { { "1000", "50" } };
+        String[][] expected4 = { { "1000", "1010" }, { "40", "50" } };
+        checkDifference(oldRange, newTokens4, expected4);
+    }
+
+    @Test
+    public void testDifferenceToFetchOldWraps()
+    {
+        Range oldRange = makeRange("1010", "40");
+
+        // New range is entirely contained
+        String[][] newTokens1 = { { "0", "30" }, { "0", "40" }, { "10", "40" } 
};
+        String[][] expected1 = { };
+        checkDifference(oldRange, newTokens1, expected1);
+
+        // Right half of the new range is needed
+        String[][] newTokens2 = { { "0", "50" }, { "10", "50" }, { "40", "50" 
} };
+        String[][] expected2 = { { "40", "50" } };
+        checkDifference(oldRange, newTokens2, expected2);
+
+        // Whole range is needed
+        String[][] newTokens3 = { { "50", "90" } };
+        String[][] expected3 = { { "50", "90" } };
+        checkDifference(oldRange, newTokens3, expected3);
+
+        // Both ends of the new range overlaps the old range
+        String[][] newTokens4 = { { "10", "1010" }, { "40", "1010" }, { "10", 
"1030" }, { "40", "1030" } };
+        String[][] expected4 = { { "40", "1010" } };
+        checkDifference(oldRange, newTokens4, expected4);
+
+        // Only RHS of the new range overlaps the old range
+        String[][] newTokens5 = { { "60", "1010" }, { "60", "1030" } };
+        String[][] expected5 = { { "60", "1010" } };
+        checkDifference(oldRange, newTokens5, expected5);
+    }
+
+    @Test
+    public void testDifferenceToFetchNewWraps()
+    {
+        Range oldRange = makeRange("0", "40");
+
+        // Only the LHS of the new range is needed
+        String[][] newTokens1 = { { "1010", "0" }, { "1010", "10" }, { "1010", 
"40" } };
+        String[][] expected1 = { { "1010", "0" } };
+        checkDifference(oldRange, newTokens1, expected1);
+
+        // Both ends of the new range are needed
+        String[][] newTokens2 = { { "1010", "50" } };
+        String[][] expected2 = { { "1010", "0" }, { "40", "50" } };
+        checkDifference(oldRange, newTokens2, expected2);
+
+        oldRange = makeRange("20", "40");
+
+        // Whole new range is needed
+        String[][] newTokens3 = { { "1010", "0" } };
+        String[][] expected3 = { { "1010", "0" } };
+        checkDifference(oldRange, newTokens3, expected3);
+
+        // Whole new range is needed (matching endpoints)
+        String[][] newTokens4 = { { "1010", "20" } };
+        String[][] expected4 = { { "1010", "20" } };
+        checkDifference(oldRange, newTokens4, expected4);
+
+        // Only RHS of new range is needed
+        String[][] newTokens5 = { { "30", "0" }, { "40", "0" } };
+        String[][] expected5 = { { "40", "0" } };
+        checkDifference(oldRange, newTokens5, expected5);
+
+        // Only RHS of new range is needed (matching endpoints)
+        String[][] newTokens6 = { { "30", "20" }, { "40", "20" } };
+        String[][] expected6 = { { "40", "20" } };
+        checkDifference(oldRange, newTokens6, expected6);
+    }
 }


Reply via email to