diff --git a/src/java/org/apache/cassandra/dht/CollatingOrderPreservingPartitioner.java b/src/java/org/apache/cassandra/dht/CollatingOrderPreservingPartitioner.java index af45923578..a0d8116881 100644 --- a/src/java/org/apache/cassandra/dht/CollatingOrderPreservingPartitioner.java +++ b/src/java/org/apache/cassandra/dht/CollatingOrderPreservingPartitioner.java @@ -71,6 +71,84 @@ public class CollatingOrderPreservingPartitioner implements IPartitioner= 0; i--) + { + // initialize the lsbit if we're carrying + int sum = carrying ? 1 : 0; + + // remove the sign bit, and sum left and right + sum += (lbytes[i] & 0xFF) + (rbytes[i] & 0xFF); + + // see if we'll need to carry + carrying = sum > 0xFF; + + // set to the sum (truncating the msbit) + result[i] = (byte)sum; + } + // the carried bit from addition will be shifted in as the msbit + + // perform the division (as a right shift) + for (int i = 0; i < inlength; i++) + { + // initialize the msbit if we're carrying + byte shifted = (byte)(carrying ? 0x80 : 0x00); + + // check the lsbit to see if we'll need to continue carrying + carrying = (result[i] & 0x01) == 0x01; + + // OR the right shifted value into the result byte + result[i] = (byte)(shifted | ((result[i] & 0xFF) >>> 1)); + } + + if (carrying) + // the last byte in the result array + result[inlength] |= 0x80; + return result; + } + + public BytesToken midpoint(BytesToken ltoken, BytesToken rtoken) + { + return new BytesToken(midpoint(ltoken.token, rtoken.token)); + } + public BytesToken getMinimumToken() { return MINIMUM; diff --git a/src/java/org/apache/cassandra/dht/IPartitioner.java b/src/java/org/apache/cassandra/dht/IPartitioner.java index 3be2a8484f..94a7d5aad4 100644 --- a/src/java/org/apache/cassandra/dht/IPartitioner.java +++ b/src/java/org/apache/cassandra/dht/IPartitioner.java @@ -37,6 +37,18 @@ public interface IPartitioner public Comparator getReverseDecoratedKeyComparator(); + /** + * Calculate a Token representing the approximate "middle" of the given + * range. + * + * The Tokens must have been generated by previous calls to midpoint, + * or be equal to this.getMinimumToken(). The range may not wrap unless it + * involves this.getMinimumToken(). + * + * @return The approximate midpoint between left and right. + */ + public T midpoint(T left, T right); + /** * @return The minimum possible Token in the range that is being partitioned. */ diff --git a/src/java/org/apache/cassandra/dht/OrderPreservingPartitioner.java b/src/java/org/apache/cassandra/dht/OrderPreservingPartitioner.java index cca54ce138..3e979b6535 100644 --- a/src/java/org/apache/cassandra/dht/OrderPreservingPartitioner.java +++ b/src/java/org/apache/cassandra/dht/OrderPreservingPartitioner.java @@ -65,6 +65,104 @@ public class OrderPreservingPartitioner implements IPartitioner return reverseComparator; } + /** + * Copies the given string into a char array, padding the end + * with empty chars up to length. + */ + private static char[] getChars(String str, int length) + { + char[] chars; + if (str.length() < length) + { + chars = new char[length]; + str.getChars(0, str.length(), chars, 0); + } + else if (str.length() == length) + { + chars = str.toCharArray(); + } + else + throw new RuntimeException("Cannot truncate string of length " + str.length() + " to length " + length); + return chars; + } + + /** + * @return A new String array that will compare + * approximately halfway between the parameters. + */ + private static String midpoint(String left, String right) + { + int inlength; + char[] lchars; + char[] rchars; + int comparison = left.compareTo(right); + if (comparison < 0) + { + inlength = Math.max(left.length(), right.length()); + lchars = getChars(left, inlength); + rchars = getChars(right, inlength); + } + else + { + // wrapping range must involve the minimum token + assert MINIMUM.token.equals(right); + + inlength = Math.max(left.length(), 1); + lchars = getChars(left, inlength); + rchars = new char[inlength]; + Arrays.fill(rchars, (char)0xFFFF); + } + + + // if the lsbits of the two inputs are not equal we have to extend + // the result array to make room for a carried bit during the right shift + int outlength = (((int)lchars[inlength-1] & 0x0001) == ((int)rchars[inlength-1] & 0x0001)) + ? inlength + : inlength+1; + char[] result = new char[outlength]; + boolean carrying = false; + + // perform the addition + for (int i = inlength-1; i >= 0; i--) + { + // initialize the lsbit if we're carrying + int sum = carrying ? 0x0001 : 0x0000; + + // remove the sign bit, and sum left and right + sum += (lchars[i] & 0xFFFF) + (rchars[i] & 0xFFFF); + + // see if we'll need to carry + carrying = sum > 0xFFFF; + + // set to the sum (truncating the msbit) + result[i] = (char)sum; + } + // the carried bit from addition will be shifted in as the msbit + + // perform the division (as a right shift) + for (int i = 0; i < inlength; i++) + { + // initialize the msbit if we're carrying + char shifted = (char)(carrying ? 0x8000 : 0x0000); + + // check the lsbit to see if we'll need to continue carrying + carrying = (result[i] & 0x0001) == 0x0001; + + // OR the right shifted value into the result char + result[i] = (char)(shifted | ((result[i] & 0xFFFF) >>> 1)); + } + + if (carrying) + // the last char in the result array + result[inlength] |= 0x8000; + return new String(result); + } + + public StringToken midpoint(StringToken ltoken, StringToken rtoken) + { + return new StringToken(midpoint(ltoken.token, rtoken.token)); + } + public StringToken getMinimumToken() { return MINIMUM; diff --git a/src/java/org/apache/cassandra/dht/RandomPartitioner.java b/src/java/org/apache/cassandra/dht/RandomPartitioner.java index 7e3b3424d3..37133c78d1 100644 --- a/src/java/org/apache/cassandra/dht/RandomPartitioner.java +++ b/src/java/org/apache/cassandra/dht/RandomPartitioner.java @@ -31,6 +31,9 @@ import org.apache.cassandra.utils.GuidGenerator; */ public class RandomPartitioner implements IPartitioner { + public static final BigInteger TWO = new BigInteger("2"); + public static final BigInteger MD5_MAX = TWO.pow(127); + public static final BigIntegerToken MINIMUM = new BigIntegerToken("0"); private static final Comparator comparator = new Comparator() @@ -81,6 +84,26 @@ public class RandomPartitioner implements IPartitioner return rcomparator; } + public BigIntegerToken midpoint(BigIntegerToken ltoken, BigIntegerToken rtoken) + { + BigInteger left = ltoken.token; + BigInteger right = rtoken.token; + + BigInteger midpoint; + if (left.compareTo(right) < 0) + { + midpoint = left.add(right).divide(TWO); + } + else + { + // wrapping case + BigInteger distance = MD5_MAX.add(right).subtract(left); + BigInteger unchecked = distance.divide(TWO).add(left); + midpoint = (unchecked.compareTo(MD5_MAX) > 0) ? unchecked.subtract(MD5_MAX) : unchecked; + } + return new BigIntegerToken(midpoint); + } + public BigIntegerToken getMinimumToken() { return MINIMUM; diff --git a/test/conf/storage-conf.xml b/test/conf/storage-conf.xml index f014113771..20f33778cd 100644 --- a/test/conf/storage-conf.xml +++ b/test/conf/storage-conf.xml @@ -22,7 +22,7 @@ 0.1 batch 1.0 - org.apache.cassandra.dht.OrderPreservingPartitioner + org.apache.cassandra.dht.CollatingOrderPreservingPartitioner org.apache.cassandra.locator.EndPointSnitch org.apache.cassandra.locator.RackUnawareStrategy 1 diff --git a/test/unit/org/apache/cassandra/dht/PartitionerTestCase.java b/test/unit/org/apache/cassandra/dht/PartitionerTestCase.java index 0eacd9b0c6..3acd53cd0c 100644 --- a/test/unit/org/apache/cassandra/dht/PartitionerTestCase.java +++ b/test/unit/org/apache/cassandra/dht/PartitionerTestCase.java @@ -50,6 +50,38 @@ public abstract class PartitionerTestCase { assert tok("asdz").compareTo(tok("asdf")) > 0; } + public void assertMidpoint(T left, T right, int depth) + { + T mid = this.part.midpoint(left, right); + assert new Range(left, right).contains(mid) + : "For " + tos(left) + "," + tos(right) + ": range did not contain mid:" + tos(mid); + if (depth > 0) + assertMidpoint(left, mid, depth-1); + if (depth > 0) + assertMidpoint(mid, right, depth-1); + } + + @Test + public void testMidpoint() + { + assertMidpoint(tok("a"), tok("b"), 16); + assertMidpoint(tok("a"), tok("bbb"), 16); + } + + @Test + public void testMidpointMinimum() + { + assertMidpoint(tok(""), tok("a"), 16); + assertMidpoint(tok(""), tok("aaa"), 16); + } + + @Test + public void testMidpointWrapping() + { + assertMidpoint(tok(""), tok(""), 16); + assertMidpoint(tok("a"), tok(""), 16); + } + @Test public void testTokenFactoryBytes() {