Repository: cassandra
Updated Branches:
  refs/heads/trunk 06f626acd -> a604b14bf


BTree updates may call provided update function twice

patch by Benjamin; reviewed by Benedict for CASSANDRA-8018


Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo
Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/5ab1d95b
Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/5ab1d95b
Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/5ab1d95b

Branch: refs/heads/trunk
Commit: 5ab1d95b29509ee5a061eddda39d7f4189abbe37
Parents: d15c918
Author: Benedict Elliott Smith <bened...@apache.org>
Authored: Tue Dec 2 17:17:35 2014 +0000
Committer: Benedict Elliott Smith <bened...@apache.org>
Committed: Tue Dec 2 17:18:51 2014 +0000

----------------------------------------------------------------------
 CHANGES.txt                                     |   1 +
 .../apache/cassandra/utils/btree/Builder.java   |   2 +-
 .../cassandra/utils/btree/NodeBuilder.java      |   6 +-
 .../org/apache/cassandra/utils/BTreeTest.java   | 120 +++++++++++++++++--
 4 files changed, 117 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cassandra/blob/5ab1d95b/CHANGES.txt
----------------------------------------------------------------------
diff --git a/CHANGES.txt b/CHANGES.txt
index 7df396d..c5ac66c 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 2.1.3
+ * BTree updates may call provided update function twice (CASSANDRA-8018)
  * Release sstable references after anticompaction (CASSANDRA-8386)
  * Handle abort() in SSTableRewriter properly (CASSANDRA-8320)
  * Fix high size calculations for prepared statements (CASSANDRA-8231)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5ab1d95b/src/java/org/apache/cassandra/utils/btree/Builder.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/utils/btree/Builder.java 
b/src/java/org/apache/cassandra/utils/btree/Builder.java
index f6677d4..0f2fd5b 100644
--- a/src/java/org/apache/cassandra/utils/btree/Builder.java
+++ b/src/java/org/apache/cassandra/utils/btree/Builder.java
@@ -109,7 +109,7 @@ final class Builder
 
         current.reset(EMPTY_LEAF, POSITIVE_INFINITY, updateF, null);
         for (V key : source)
-            current.addNewKey(key);
+            current.addNewKey(updateF.apply(key));
 
         current = current.ascendToRoot();
 

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5ab1d95b/src/java/org/apache/cassandra/utils/btree/NodeBuilder.java
----------------------------------------------------------------------
diff --git a/src/java/org/apache/cassandra/utils/btree/NodeBuilder.java 
b/src/java/org/apache/cassandra/utils/btree/NodeBuilder.java
index 9d57182..c715873 100644
--- a/src/java/org/apache/cassandra/utils/btree/NodeBuilder.java
+++ b/src/java/org/apache/cassandra/utils/btree/NodeBuilder.java
@@ -133,7 +133,7 @@ final class NodeBuilder
 
         int i = copyFromKeyPosition;
         boolean found; // exact key match?
-        boolean owns = true; // true iff this node (or a child) should contain 
the key
+        boolean owns = true; // true if this node (or a child) should contain 
the key
         if (i == copyFromKeyEnd)
         {
             found = false;
@@ -185,7 +185,7 @@ final class NodeBuilder
                 }
                 else
                 {
-                    // if not found, we need to apply updateFunction still
+                    // if not found, we still need to apply the update function
                     key = updateFunction.apply(key);
                     addNewKey(key); // handles splitting parent if necessary 
via ensureRoom
                 }
@@ -319,7 +319,7 @@ final class NodeBuilder
     void addNewKey(Object key)
     {
         ensureRoom(buildKeyPosition + 1);
-        buildKeys[buildKeyPosition++] = updateFunction.apply(key);
+        buildKeys[buildKeyPosition++] = key;
     }
 
     // copies children from copyf to the builder, up to the provided index in 
copyf (exclusive)

http://git-wip-us.apache.org/repos/asf/cassandra/blob/5ab1d95b/test/unit/org/apache/cassandra/utils/BTreeTest.java
----------------------------------------------------------------------
diff --git a/test/unit/org/apache/cassandra/utils/BTreeTest.java 
b/test/unit/org/apache/cassandra/utils/BTreeTest.java
index a6d4528..e1bf388 100644
--- a/test/unit/org/apache/cassandra/utils/BTreeTest.java
+++ b/test/unit/org/apache/cassandra/utils/BTreeTest.java
@@ -17,22 +17,21 @@
  */
 package org.apache.cassandra.utils;
 
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.List;
-import java.util.Random;
+import java.util.*;
 import java.util.concurrent.ThreadLocalRandom;
 
 import org.junit.Test;
 
-import junit.framework.Assert;
 import org.apache.cassandra.utils.btree.BTree;
 import org.apache.cassandra.utils.btree.BTreeSet;
 import org.apache.cassandra.utils.btree.UpdateFunction;
 
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertTrue;
+
 public class BTreeTest
 {
-
     static Integer[] ints = new Integer[20];
     static
     {
@@ -114,13 +113,78 @@ public class BTreeTest
         }
     }
 
+    /**
+     * Tests that the apply method of the <code>UpdateFunction</code> is only 
called once with each key update.
+     * (see CASSANDRA-8018).
+     */
+    @Test
+    public void testUpdate_UpdateFunctionCallBack()
+    {
+        Object[] btree = new Object[0];
+        CallsMonitor monitor = new CallsMonitor();
+
+        btree = BTree.update(btree, CMP, Arrays.asList(1), true, monitor);
+        assertArrayEquals(new Object[] {1, null}, btree);
+        assertEquals(1, monitor.getNumberOfCalls(1));
+
+        monitor.clear();
+        btree = BTree.update(btree, CMP, Arrays.asList(2), true, monitor);
+        assertArrayEquals(new Object[] {1, 2}, btree);
+        assertEquals(1, monitor.getNumberOfCalls(2));
+
+        // with existing value
+        monitor.clear();
+        btree = BTree.update(btree, CMP, Arrays.asList(1), true, monitor);
+        assertArrayEquals(new Object[] {1, 2}, btree);
+        assertEquals(1, monitor.getNumberOfCalls(1));
+
+        // with two non-existing values
+        monitor.clear();
+        btree = BTree.update(btree, CMP, Arrays.asList(3, 4), true, monitor);
+        assertArrayEquals(new Object[] {1, 2, 3, 4}, btree);
+        assertEquals(1, monitor.getNumberOfCalls(3));
+        assertEquals(1, monitor.getNumberOfCalls(4));
+
+        // with one existing value and one non existing value in disorder
+        monitor.clear();
+        btree = BTree.update(btree, CMP, Arrays.asList(5, 2), false, monitor);
+        assertArrayEquals(new Object[] {3, new Object[]{1, 2}, new Object[]{4, 
5}}, btree);
+        assertEquals(1, monitor.getNumberOfCalls(2));
+        assertEquals(1, monitor.getNumberOfCalls(5));
+    }
+
+    /**
+     * Tests that the apply method of the <code>UpdateFunction</code> is only 
called once per value with each build call.
+     */
+    @Test
+    public void testBuilding_UpdateFunctionCallBack()
+    {
+        CallsMonitor monitor = new CallsMonitor();
+        Object[] btree = BTree.build(Arrays.asList(1), CMP, true, monitor);
+        assertArrayEquals(new Object[] {1, null}, btree);
+        assertEquals(1, monitor.getNumberOfCalls(1));
+
+        monitor.clear();
+        btree = BTree.build(Arrays.asList(1, 2), CMP, true, monitor);
+        assertArrayEquals(new Object[] {1, 2}, btree);
+        assertEquals(1, monitor.getNumberOfCalls(1));
+        assertEquals(1, monitor.getNumberOfCalls(2));
+
+        monitor.clear();
+        btree = BTree.build(Arrays.asList(3, 1, 2), CMP, false, monitor);
+        assertArrayEquals(new Object[] {1, 2, 3, null}, btree);
+        assertEquals(1, monitor.getNumberOfCalls(1));
+        assertEquals(1, monitor.getNumberOfCalls(2));
+        assertEquals(1, monitor.getNumberOfCalls(3));
+    }
+
     private static void checkResult(int count, Object[] btree)
     {
         BTreeSet<Integer> vs = new BTreeSet<>(btree, CMP);
         assert vs.size() == count;
         int i = 0;
         for (Integer j : vs)
-            Assert.assertEquals(j, ints[i++]);
+            assertEquals(j, ints[i++]);
     }
 
     @Test
@@ -137,7 +201,7 @@ public class BTreeTest
         Object[] btree = BTree.build(ranges(range(0, 8)), cmp, true, 
UpdateFunction.NoOp.<String>instance());
         BTree.update(btree, cmp, ranges(range(0, 94)), false, new 
AbortAfterX(90));
         btree = BTree.update(btree, cmp, ranges(range(0, 94)), false, 
UpdateFunction.NoOp.<String>instance());
-        Assert.assertTrue(BTree.isWellFormed(btree, cmp));
+        assertTrue(BTree.isWellFormed(btree, cmp));
     }
 
     private static final class AbortAfterX implements UpdateFunction<String>
@@ -181,4 +245,44 @@ public class BTreeTest
         }
         return r;
     }
+
+    /**
+     * <code>UpdateFunction</code> that count the number of call made to apply 
for each value.
+     */
+    public static final class CallsMonitor implements UpdateFunction<Integer>
+    {
+        private int[] numberOfCalls = new int[20];
+
+        public Integer apply(Integer replacing, Integer update)
+        {
+            numberOfCalls[update] = numberOfCalls[update] + 1;
+            return update;
+        }
+
+        public boolean abortEarly()
+        {
+            return false;
+        }
+
+        public void allocated(long heapSize)
+        {
+
+        }
+
+        public Integer apply(Integer integer)
+        {
+            numberOfCalls[integer] = numberOfCalls[integer] + 1;
+            return integer;
+        }
+
+        public int getNumberOfCalls(Integer key)
+        {
+            return numberOfCalls[key];
+        }
+
+        public void clear()
+        {
+            Arrays.fill(numberOfCalls, 0);
+        }
+    };
 }

Reply via email to