/** Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved. Contact: SYSTAP, LLC DBA Blazegraph 2501 Calvert ST NW #106 Washington, DC 20008 licenses@blazegraph.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* * Created on Nov 27, 2006 */ package com.bigdata.btree; import java.util.Arrays; import org.apache.log4j.Level; import com.bigdata.btree.keys.TestKeyBuilder; /** * Test suite using {@link BTree#insert(Object, Object)} to split a tree to * height two (2) (three levels) and then using {@link BTree#remove(Object)} to * reduce the tree back to a single, empty root leaf. This test suite is focused * on m := 3 since we are capable of exercising all split() and join() code * paths with that branching factor. * <p> * Note: This also tests the {@link AbstractNode#isLeftMostNode()} and * {@link AbstractNode#isRightMostNode()} methods. In order to test those * methods as applied to track the #of head splits and tail splits we need a * btree with at least 2 levels of nodes above a layer of leaves. This is * because the methods are tested on a leaf, which checks its parent, which, * really, needs to test a non-root parent before we can say that this is * working as it should. * * @see src/architecture/btree.xls for the examples used in this test suite. * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id$ */ public class TestSplitJoinThreeLevels extends AbstractBTreeTestCase { /** * */ public TestSplitJoinThreeLevels() { } /** * @param name */ public TestSplitJoinThreeLevels(String name) { super(name); } /** * Test ability to split and join a tree of order m == 3 driven by the * insertion and then the removal of a known sequence of keys. This test * checks the state of the tree after each operation against the expected * postconditions for that operation. In particular, testing at m == 3 helps * to check for fenceposts in the split/join logic. * * Note: a branching factor of three (3) is equivalent to a 2-3 tree, where * the minimum #of children (for a node) or values (for a leaf) is two (2) * and the maximum #of children (for a node) or values (for a leaf) is three * (3). This makes it very easy to provoke splits and joins. * * There is another version of this test that builds the same tree but uses * a different sequence of keys during removal. This provokes some code * paths in {@link Node#merge(AbstractNode, boolean)} and * {@link Node#redistributeKeys(AbstractNode, boolean)} that are not * exercised by this test. * * @see #test_removeOrder3a() */ public void test_removeOrder3a() { /* * Generate keys, values, and visitation order. */ // keys final int[] keys = new int[]{5,6,7,8,3,4,2,1}; // values final SimpleEntry v1 = new SimpleEntry(1); final SimpleEntry v2 = new SimpleEntry(2); final SimpleEntry v3 = new SimpleEntry(3); final SimpleEntry v4 = new SimpleEntry(4); final SimpleEntry v5 = new SimpleEntry(5); final SimpleEntry v6 = new SimpleEntry(6); final SimpleEntry v7 = new SimpleEntry(7); final SimpleEntry v8 = new SimpleEntry(8); final SimpleEntry[] vals = new SimpleEntry[]{v5,v6,v7,v8,v3,v4,v2,v1}; // permutation vector for visiting values in key order. final int[] order = new int[keys.length]; // generate visitation order. { System.arraycopy(keys, 0, order, 0, keys.length); Arrays.sort(order); if (log.isInfoEnabled()) { log.info("keys=" + Arrays.toString(keys)); log.info("vals=" + Arrays.toString(vals)); log.info("order=" + Arrays.toString(order)); } } final int m = 3; BTree btree = getBTree(m); assertEquals("height", 0, btree.height); assertEquals("#nodes", 0, btree.nnodes); assertEquals("#leaves", 1, btree.nleaves); assertEquals("#entries", 0, btree.nentries); assertTrue(btree.dump(System.err)); Leaf a = (Leaf) btree.getRoot(); assertKeys(new int[]{},a); assertValues(new Object[]{},a); int n = 0; { // insert(5,5) final int ikey = keys[n]; SimpleEntry val = vals[n++]; assert ikey == 5 && val.id() == ikey; final byte[] key = TestKeyBuilder.asSortKey(ikey); assertNull(btree.remove(key)); // not found / no change. assertNull(btree.lookup(key)); // not found. assertNull(btree.insert(key,val)); // insert. assertEquals(val,btree.lookup(key)); // found. // validate root leaf. assertKeys(new int[]{5},a); assertValues(new Object[]{v5},a); assertTrue(btree.dump(System.err)); } { // insert(6,6) final int ikey = keys[n]; SimpleEntry val = vals[n++]; assert ikey == 6 && val.id() == ikey; final byte[] key = TestKeyBuilder.asSortKey(ikey); assertNull(btree.remove(key)); // not found / no change. assertNull(btree.lookup(key)); // not found. assertNull(btree.insert(key,val)); // insert. assertEquals(val,btree.lookup(key)); // found. // validate root leaf. assertKeys(new int[]{5,6},a); assertValues(new Object[]{v5,v6},a); assertTrue(btree.dump(System.err)); } /* * fills the root leaf to capacity. * * postcondition: * * keys: [ 5 6 7 ] */ { // insert(7,7) final int ikey = keys[n]; SimpleEntry val = vals[n++]; assert ikey == 7 && val.id() == ikey; final byte[] key = TestKeyBuilder.asSortKey(ikey); assertNull(btree.remove(key)); // not found / no change. assertNull(btree.lookup(key)); // not found. assertNull(btree.insert(key,val)); // insert. assertEquals(val,btree.lookup(key)); // found. // validate root leaf. assertKeys(new int[]{5,6,7},a); assertValues(new Object[]{v5,v6,v7},a); assertTrue(btree.dump(System.err)); } /* * splits the root leaf * * split(a)->(a,b), c is the new root. * * postcondition: * * c.keys[ 7 - x ] * c.clds[ a b - ] * * a.keys[ 5 6 - ] * b.keys[ 7 8 - ] */ final Node c; final Leaf b; { // insert(8,8) final int ikey = keys[n]; SimpleEntry val = vals[n++]; assert ikey == 8 && val.id() == ikey; final byte[] key = TestKeyBuilder.asSortKey(ikey); assertNull(btree.remove(key)); // not found / no change. assertNull(btree.lookup(key)); // not found. assertNull(btree.insert(key,val)); // insert. assertEquals(val,btree.lookup(key)); // found. assertTrue(btree.dump(Level.DEBUG,System.err)); // validate new root (c). c = (Node)btree.getRoot(); assertKeys(new int[]{7},c); assertEquals(a,c.getChild(0)); assertNotNull(c.getChild(1)); assertNull(c.getChildRef(2)); b = (Leaf)c.getChild(1); assertEntryCounts(new int[]{2,2}, c); // validate original leaf (a). assertKeys(new int[]{5,6},a); assertValues(new Object[]{v5,v6},a); // validate new leaf (b). assertKeys(new int[]{7,8},b); assertValues(new Object[]{v7,v8},b); assertTrue(btree.dump(System.err)); } /* * insert(3,3) * * postcondition: * * c.keys[ 7 - x ] * c.clds[ a b - ] * * a.keys[ 3 5 6 ] * b.keys[ 7 8 - ] */ { final int ikey = keys[n]; SimpleEntry val = vals[n++]; assert ikey == 3 && val.id() == ikey; final byte[] key = TestKeyBuilder.asSortKey(ikey); assertNull(btree.remove(key)); // not found / no change. assertNull(btree.lookup(key)); // not found. assertNull(btree.insert(key,val)); // insert. assertEquals(val,btree.lookup(key)); // found. assertTrue(btree.dump(Level.DEBUG,System.err)); // validate original leaf (a). assertKeys(new int[]{3,5,6},a); assertValues(new Object[]{v3,v5,v6},a); assertEntryCounts(new int[]{3,2}, c); } /* * insert(4,4), causing split(a)->(a,d) and bringing (c) to capacity. * * postcondition: * * c.keys[ 5 7 x ] * c.clds[ a d b ] * * a.keys[ 3 4 - ] * d.keys[ 5 6 - ] * b.keys[ 7 8 - ] */ final Leaf d; { final int ikey = keys[n]; SimpleEntry val = vals[n++]; assert ikey == 4 && val.id() == ikey; final byte[] key = TestKeyBuilder.asSortKey(ikey); assertNull(btree.remove(key)); // not found / no change. assertNull(btree.lookup(key)); // not found. assertNull(btree.insert(key,val)); // insert. assertEquals(val,btree.lookup(key)); // found. assertTrue(btree.dump(Level.DEBUG,System.err)); // validate root (c). assertKeys(new int[]{5,7},c); assertEquals(a,c.getChild(0)); assertNotNull(c.getChildRef(1)); d = (Leaf) c.getChild(1); assertEquals(b,c.getChild(2)); assertEntryCounts(new int[]{2,2,2}, c); // validate original leaf (a). assertKeys(new int[]{3,4},a); assertValues(new Object[]{v3,v4},a); // validate new leaf (d). assertKeys(new int[]{5,6},d); assertValues(new Object[]{v5,v6},d); // validate leaf (b). assertKeys(new int[]{7,8},b); assertValues(new Object[]{v7,v8},b); } /* * insert(2,2), bringing (a) to capacity again. */ { final int ikey = keys[n]; SimpleEntry val = vals[n++]; assert ikey == 2 && val.id() == ikey; final byte[] key = TestKeyBuilder.asSortKey(ikey); assertNull(btree.remove(key)); // not found / no change. assertNull(btree.lookup(key)); // not found. assertNull(btree.insert(key,val)); // insert. assertEquals(val,btree.lookup(key)); // found. assertTrue(btree.dump(Level.DEBUG,System.err)); assertEntryCounts(new int[]{3,2,2}, c); // validate original leaf (a). assertKeys(new int[]{2,3,4},a); assertValues(new Object[]{v2,v3,v4},a); } /* * insert(1,1) causing (a) to split(a)->(a,e). Since the root (c) is * already at capacity this also causes the root to split(c)->(c,f) and * creating a new root(g). * * postcondition: * * g.keys[ 5 - x ] * g.clds[ c f - ] * * c.keys[ 3 - x ] * c.clds[ a e - ] * * f.keys[ 7 - x ] * f.clds[ d b - ] * * a.keys[ 1 2 - ] * e.keys[ 3 4 - ] * d.keys[ 5 6 - ] * b.keys[ 7 8 - ] */ final Leaf e; final Node f, g; { final int ikey = keys[n]; final SimpleEntry val = vals[n++]; assert ikey == 1 && val.id() == ikey; final byte[] key = TestKeyBuilder.asSortKey(ikey); assertNull(btree.remove(key)); // not found / no change. assertNull(btree.lookup(key)); // not found. assertNull(btree.insert(key,val)); // insert. assertEquals(val,btree.lookup(key)); // found. assertTrue(btree.dump(Level.DEBUG,System.err)); // validate the new root(g). assertNotSame(c,btree.getRoot()); g = (Node)btree.getRoot(); assertKeys(new int[]{5},g); assertEquals(c,g.getChild(0)); assertNotNull(g.getChildRef(1)); f = (Node) g.getChild(1); assertNull(g.getChildRef(2)); assertEntryCounts(new int[]{4,4}, g); // validate old root (c). assertKeys(new int[]{3},c); assertEquals(a,c.getChild(0)); assertNotNull(c.getChildRef(1)); e = (Leaf) c.getChild(1); assertNull(c.getChildRef(2)); assertEntryCounts(new int[]{2,2}, c); // validate node(f) split from the old root split(c)->(c,f). assertKeys(new int[]{7},f); assertEquals(d,f.getChild(0)); assertEquals(b,f.getChild(1)); assertNull(f.getChildRef(2)); assertEntryCounts(new int[]{2,2}, f); // validate original leaf (a), which was re-split into (a,e). assertKeys(new int[]{1,2},a); assertValues(new Object[]{v1,v2},a); // validate new leaf (e). assertKeys(new int[]{3,4},e); assertValues(new Object[]{v3,v4},e); // validate new leaf (d). assertKeys(new int[]{5,6},d); assertValues(new Object[]{v5,v6},d); // validate leaf (b). assertKeys(new int[]{7,8},b); assertValues(new Object[]{v7,v8},b); } /* * Do some tests of isLeftMostNode() and isRightMostNode(). */ { // test the root. assertTrue(g.isLeftMostNode()); assertTrue(g.isRightMostNode()); // test the next layer of nodes. assertTrue(c.isLeftMostNode()); assertFalse(c.isRightMostNode()); assertFalse(f.isLeftMostNode()); assertTrue(f.isRightMostNode()); // test the leaves. assertTrue(a.isLeftMostNode()); assertFalse(a.isRightMostNode()); assertFalse(e.isLeftMostNode()); assertFalse(e.isRightMostNode()); assertFalse(d.isLeftMostNode()); assertFalse(d.isRightMostNode()); assertFalse(b.isLeftMostNode()); assertTrue(b.isRightMostNode()); } /* * Do some tests of getRightMostChild() */ { assertTrue(f == btree.getRightMostNode(true/* nodesOnly */)); assertTrue(b == btree.getRightMostNode(false/* nodesOnly */)); } /* * At this point the tree is setup and we start deleting keys. We delete * the keys in (nearly) the reverse order and verify that joins correctly * reduce the tree as each node or leaf is reduced below its minimum. * * before: * * g.keys[ 5 - x ] * g.clds[ c f - ] * * c.keys[ 3 - x ] * c.clds[ a e - ] * * f.keys[ 7 - x ] * f.clds[ d b - ] * * a.keys[ 1 2 - ] * e.keys[ 3 4 - ] * d.keys[ 5 6 - ] * b.keys[ 7 8 - ] */ assertTrue("before removing keys", btree.dump(Level.DEBUG,System.err)); /* * step#1 : remove(1) triggers a cascade of operations: a.join(e) calls * a.merge(e) and c.removeChild(3,e). This forces c.join(f), which calls * c.merge(f) and g.removeChild(5,f). Since (g) now has a single child * we replace the root with (c). e, f, and g are deleted as we go. * * postcondition: * * c.keys[ 5 7 x ] * c.clds[ a d b ] * * a.keys[ 2 3 4 ] * d.keys[ 5 6 - ] * b.keys[ 7 8 - ] * * e, f, g are deleted. */ assertEquals(v1,btree.remove(TestKeyBuilder.asSortKey(1))); assertTrue("after remove(1)", btree.dump(Level.DEBUG,System.err)); // verify leaves. assertKeys(new int[]{2,3,4},a); assertValues(new Object[]{v2,v3,v4},a); assertKeys(new int[]{5,6},d); assertValues(new Object[]{v5,v6},d); assertKeys(new int[]{7,8},b); assertValues(new Object[]{v7,v8},b); // verify the new root. assertKeys(new int[]{5,7},c); assertEquals(c,btree.root); assertEquals(a,c.getChild(0)); assertEquals(d,c.getChild(1)); assertEquals(b,c.getChild(2)); assertEntryCounts(new int[]{3,2,2}, c); // verify deleted nodes and leaves. assertTrue(e.isDeleted()); assertTrue(f.isDeleted()); assertTrue(g.isDeleted()); /* * step#2 : remove(2) - simple operation just removes(2) from (a). */ assertEquals(v2,btree.remove(TestKeyBuilder.asSortKey(2))); assertTrue("after remove(2)", btree.dump(Level.DEBUG,System.err)); assertKeys(new int[]{3,4},a); assertValues(new Object[]{v3,v4},a); assertEntryCounts(new int[]{2,2,2}, c); /* * step#3 : remove(4) triggers a.join(d), which in turn calls a.merge(d) * and causes c.removeChild(5,d). */ assertEquals(v4,btree.remove(TestKeyBuilder.asSortKey(4))); assertTrue("after remove(4)", btree.dump(Level.DEBUG,System.err)); // verify leaves. assertKeys(new int[]{3,5,6},a); assertValues(new Object[]{v3,v5,v6},a); assertKeys(new int[]{7,8},b); assertValues(new Object[]{v7,v8},b); // verify the root. assertKeys(new int[]{7},c); assertEquals(c,btree.root); assertEquals(a,c.getChild(0)); assertEquals(b,c.getChild(1)); assertNull(c.getChildRef(2)); assertEntryCounts(new int[]{3,2}, c); // verify deleted nodes and leaves. assertTrue(d.isDeleted()); /* * step#4 : remove(8) triggers b.join(a), which in turn calls * b.redistributeKeys(a) which sends (6,v6) to (b) and updates the * separatorKey in (c) to (6). */ assertEquals(v8,btree.remove(TestKeyBuilder.asSortKey(8))); assertTrue("after remove(8)", btree.dump(Level.DEBUG,System.err)); // verify leaves. assertKeys(new int[]{3,5},a); assertValues(new Object[]{v3,v5},a); assertKeys(new int[]{6,7},b); assertValues(new Object[]{v6,v7},b); // verify the root. assertKeys(new int[]{6},c); assertEquals(c,btree.root); assertEquals(a,c.getChild(0)); assertEquals(b,c.getChild(1)); assertNull(c.getChildRef(2)); assertEntryCounts(new int[]{2,2}, c); /* * step#5 : remove(6) triggers b.join(a), which calls b.merge(a) and * c.removeChild(-,a). Since (c) now has a single child we replace the * root of the tree with (b). */ assertEquals(v6,btree.remove(TestKeyBuilder.asSortKey(6))); assertTrue("after remove(6)", btree.dump(Level.DEBUG,System.err)); // verify the new root leaf. assertKeys(new int[]{3,5,7},b); assertValues(new Object[]{v3,v5,v7},b); assertEquals(b,btree.root); assertTrue(a.isDeleted()); assertTrue(c.isDeleted()); assertEquals(v7,btree.remove(TestKeyBuilder.asSortKey(7))); assertTrue("after remove(7)", btree.dump(Level.DEBUG,System.err)); assertKeys(new int[]{3,5},b); assertValues(new Object[]{v3,v5},b); assertEquals(v3,btree.remove(TestKeyBuilder.asSortKey(3))); assertTrue("after remove(3)", btree.dump(Level.DEBUG,System.err)); assertKeys(new int[]{5},b); assertValues(new Object[]{v5},b); assertEquals(v5,btree.remove(TestKeyBuilder.asSortKey(5))); assertTrue("after remove(5)", btree.dump(Level.DEBUG,System.err)); assertKeys(new int[]{},b); assertValues(new Object[]{},b); assertEquals("height",0,btree.height); assertEquals("nodes",0,btree.nnodes); assertEquals("leaves",1,btree.nleaves); assertEquals("entries",0,btree.nentries); } /** * Variant of {@link #test_removeOrder3a()} that excercises some different * code paths while removing keys by choosing a different order in which to * remove some keys. Both tests build the same initial tree. However, this * tests begins by removing a key (7) from the right edge of the tree while * the other test beings by removing a key (1) from the left edge of the * tree. */ public void test_removeOrder3b() { /* * Generate keys, values, and visitation order. */ // keys final int[] keys = new int[]{5,6,7,8,3,4,2,1}; // values final SimpleEntry v1 = new SimpleEntry(1); final SimpleEntry v2 = new SimpleEntry(2); final SimpleEntry v3 = new SimpleEntry(3); final SimpleEntry v4 = new SimpleEntry(4); final SimpleEntry v5 = new SimpleEntry(5); final SimpleEntry v6 = new SimpleEntry(6); final SimpleEntry v7 = new SimpleEntry(7); final SimpleEntry v8 = new SimpleEntry(8); final SimpleEntry[] vals = new SimpleEntry[]{v5,v6,v7,v8,v3,v4,v2,v1}; // permutation vector for visiting values in key order. final int[] order = new int[keys.length]; // generate visitation order. { System.arraycopy(keys, 0, order, 0, keys.length); Arrays.sort(order); if (log.isInfoEnabled()) { log.info("keys=" + Arrays.toString(keys)); log.info("vals=" + Arrays.toString(vals)); log.info("order=" + Arrays.toString(order)); } } final int m = 3; BTree btree = getBTree(m); assertEquals("height", 0, btree.height); assertEquals("#nodes", 0, btree.nnodes); assertEquals("#leaves", 1, btree.nleaves); assertEquals("#entries", 0, btree.nentries); assertTrue(btree.dump(System.err)); Leaf a = (Leaf) btree.getRoot(); assertKeys(new int[]{},a); assertValues(new Object[]{},a); int n = 0; { // insert(5,5) final int ikey = keys[n]; SimpleEntry val = vals[n++]; assert ikey == 5 && val.id() == ikey; final byte[] key = TestKeyBuilder.asSortKey(ikey); assertNull(btree.remove(key)); // not found / no change. assertNull(btree.lookup(key)); // not found. assertNull(btree.insert(key,val)); // insert. assertEquals(val,btree.lookup(key)); // found. // validate root leaf. assertKeys(new int[]{5},a); assertValues(new Object[]{v5},a); assertTrue(btree.dump(System.err)); } { // insert(6,6) final int ikey = keys[n]; SimpleEntry val = vals[n++]; assert ikey == 6 && val.id() == ikey; final byte[] key = TestKeyBuilder.asSortKey(ikey); assertNull(btree.remove(key)); // not found / no change. assertNull(btree.lookup(key)); // not found. assertNull(btree.insert(key,val)); // insert. assertEquals(val,btree.lookup(key)); // found. // validate root leaf. assertKeys(new int[]{5,6},a); assertValues(new Object[]{v5,v6},a); assertTrue(btree.dump(System.err)); } /* * fills the root leaf to capacity. * * postcondition: * * keys: [ 5 6 7 ] */ { // insert(7,7) final int ikey = keys[n]; SimpleEntry val = vals[n++]; assert ikey == 7 && val.id() == ikey; final byte[] key = TestKeyBuilder.asSortKey(ikey); assertNull(btree.remove(key)); // not found / no change. assertNull(btree.lookup(key)); // not found. assertNull(btree.insert(key,val)); // insert. assertEquals(val,btree.lookup(key)); // found. // validate root leaf. assertKeys(new int[]{5,6,7},a); assertValues(new Object[]{v5,v6,v7},a); assertTrue(btree.dump(System.err)); } /* * splits the root leaf * * split(a)->(a,b), c is the new root. * * postcondition: * * c.keys[ 7 - x ] * c.clds[ a b - ] * * a.keys[ 5 6 - ] * b.keys[ 7 8 - ] */ final Node c; final Leaf b; { // insert(8,8) final int ikey = keys[n]; SimpleEntry val = vals[n++]; assert ikey == 8 && val.id() == ikey; final byte[] key = TestKeyBuilder.asSortKey(ikey); assertNull(btree.remove(key)); // not found / no change. assertNull(btree.lookup(key)); // not found. assertNull(btree.insert(key,val)); // insert. assertEquals(val,btree.lookup(key)); // found. assertTrue(btree.dump(Level.DEBUG,System.err)); // validate new root (c). c = (Node)btree.getRoot(); assertKeys(new int[]{7},c); assertEquals(a,c.getChild(0)); assertNotNull(c.getChild(1)); assertNull(c.getChildRef(2)); b = (Leaf)c.getChild(1); assertEntryCounts(new int[]{2,2}, c); // validate original leaf (a). assertKeys(new int[]{5,6},a); assertValues(new Object[]{v5,v6},a); // validate new leaf (b). assertKeys(new int[]{7,8},b); assertValues(new Object[]{v7,v8},b); assertTrue(btree.dump(System.err)); } /* * insert(3,3) * * postcondition: * * c.keys[ 7 - x ] * c.clds[ a b - ] * * a.keys[ 3 5 6 ] * b.keys[ 7 8 - ] */ { final int ikey = keys[n]; SimpleEntry val = vals[n++]; assert ikey == 3 && val.id() == ikey; final byte[] key = TestKeyBuilder.asSortKey(ikey); assertNull(btree.remove(key)); // not found / no change. assertNull(btree.lookup(key)); // not found. assertNull(btree.insert(key,val)); // insert. assertEquals(val,btree.lookup(key)); // found. assertTrue(btree.dump(Level.DEBUG,System.err)); // validate original leaf (a). assertKeys(new int[]{3,5,6},a); assertValues(new Object[]{v3,v5,v6},a); // validate root. assertEntryCounts(new int[]{3,2}, c); } /* * insert(4,4), causing split(a)->(a,d) and bringing (c) to capacity. * * postcondition: * * c.keys[ 5 7 x ] * c.clds[ a d b ] * * a.keys[ 3 4 - ] * d.keys[ 5 6 - ] * b.keys[ 7 8 - ] */ final Leaf d; { final int ikey = keys[n]; SimpleEntry val = vals[n++]; assert ikey == 4 && val.id() == ikey; final byte[] key = TestKeyBuilder.asSortKey(ikey); assertNull(btree.remove(key)); // not found / no change. assertNull(btree.lookup(key)); // not found. assertNull(btree.insert(key,val)); // insert. assertEquals(val,btree.lookup(key)); // found. assertTrue(btree.dump(Level.DEBUG,System.err)); // validate root (c). assertKeys(new int[]{5,7},c); assertEquals(a,c.getChild(0)); assertNotNull(c.getChildRef(1)); d = (Leaf) c.getChild(1); assertEquals(b,c.getChild(2)); assertEntryCounts(new int[]{2,2,2}, c); // validate original leaf (a). assertKeys(new int[]{3,4},a); assertValues(new Object[]{v3,v4},a); // validate new leaf (d). assertKeys(new int[]{5,6},d); assertValues(new Object[]{v5,v6},d); // validate leaf (b). assertKeys(new int[]{7,8},b); assertValues(new Object[]{v7,v8},b); } /* * insert(2,2), bringing (a) to capacity again. */ { final int ikey = keys[n]; SimpleEntry val = vals[n++]; assert ikey == 2 && val.id() == ikey; final byte[] key = TestKeyBuilder.asSortKey(ikey); assertNull(btree.remove(key)); // not found / no change. assertNull(btree.lookup(key)); // not found. assertNull(btree.insert(key,val)); // insert. assertEquals(val,btree.lookup(key)); // found. assertTrue(btree.dump(Level.DEBUG,System.err)); // validate original leaf (a). assertKeys(new int[]{2,3,4},a); assertValues(new Object[]{v2,v3,v4},a); // validate root. assertEntryCounts(new int[]{3,2,2}, c); } /* * insert(1,1) causing (a) to split(a)->(a,e). Since the root (c) is * already at capacity this also causes the root to split(c)->(c,f) and * creating a new root(g). * * postcondition: * * g.keys[ 5 - x ] * g.clds[ c f - ] * * c.keys[ 3 - x ] * c.clds[ a e - ] * * f.keys[ 7 - x ] * f.clds[ d b - ] * * a.keys[ 1 2 - ] * e.keys[ 3 4 - ] * d.keys[ 5 6 - ] * b.keys[ 7 8 - ] */ final Leaf e; final Node f, g; { final int ikey = keys[n]; SimpleEntry val = vals[n++]; assert ikey == 1 && val.id() == ikey; final byte[] key = TestKeyBuilder.asSortKey(ikey); assertNull(btree.remove(key)); // not found / no change. assertNull(btree.lookup(key)); // not found. assertNull(btree.insert(key,val)); // insert. assertEquals(val,btree.lookup(key)); // found. assertTrue(btree.dump(Level.DEBUG,System.err)); // validate the new root(g). assertNotSame(c,btree.getRoot()); g = (Node)btree.getRoot(); assertKeys(new int[]{5},g); assertEquals(c,g.getChild(0)); assertNotNull(g.getChildRef(1)); f = (Node) g.getChild(1); assertNull(g.getChildRef(2)); assertEntryCounts(new int[]{4,4}, g); // validate old root (c). assertKeys(new int[]{3},c); assertEquals(a,c.getChild(0)); assertNotNull(c.getChildRef(1)); e = (Leaf) c.getChild(1); assertNull(c.getChildRef(2)); assertEntryCounts(new int[]{2,2}, c); // validate node(f) split from the old root split(c)->(c,f). assertKeys(new int[]{7},f); assertEquals(d,f.getChild(0)); assertEquals(b,f.getChild(1)); assertNull(f.getChildRef(2)); assertEntryCounts(new int[]{2,2}, f); // validate original leaf (a), which was re-split into (a,e). assertKeys(new int[]{1,2},a); assertValues(new Object[]{v1,v2},a); // validate new leaf (e). assertKeys(new int[]{3,4},e); assertValues(new Object[]{v3,v4},e); // validate new leaf (d). assertKeys(new int[]{5,6},d); assertValues(new Object[]{v5,v6},d); // validate leaf (b). assertKeys(new int[]{7,8},b); assertValues(new Object[]{v7,v8},b); } /* * At this point the tree is setup and we start deleting keys. We delete * the keys in (nearly) the reverse order and verify that joins correctly * reduce the tree as each node or leaf is reduced below its minimum. * * before: * * g.keys[ 5 - x ] * g.clds[ c f - ] * * c.keys[ 3 - x ] * c.clds[ a e - ] * * f.keys[ 7 - x ] * f.clds[ d b - ] * * a.keys[ 1 2 - ] * e.keys[ 3 4 - ] * d.keys[ 5 6 - ] * b.keys[ 7 8 - ] */ assertTrue("before removing keys", btree.dump(Level.DEBUG,System.err)); /* * step#1 : remove(7) triggers a cascade of operations: b.join(d) calls * b.merge(d) and f.removeChild(-,d). This forces f.join(c), which calls * f.merge(c) and g.removeChild(-,c). Since (g) now has a single child * we replace the root with (f). d, c, and g are deleted as we go. * * postcondition: * * f.keys[ 3 5 x ] * f.clds[ a e b ] * * a.keys[ 1 2 - ] * e.keys[ 3 4 - ] * b.keys[ 5 6 8 ] * * c, d, g are deleted. */ assertEquals(v7,btree.remove(TestKeyBuilder.asSortKey(7))); assertTrue("after remove(7)", btree.dump(Level.DEBUG,System.err)); // verify leaves. assertKeys(new int[]{1,2},a); assertValues(new Object[]{v1,v2},a); assertKeys(new int[]{3,4},e); assertValues(new Object[]{v3,v4},e); assertKeys(new int[]{5,6,8},b); assertValues(new Object[]{v5,v6,v8},b); // verify the new root. assertKeys(new int[]{3,5},f); assertEquals(f,btree.root); assertEquals(a,f.getChild(0)); assertEquals(e,f.getChild(1)); assertEquals(b,f.getChild(2)); assertEntryCounts(new int[]{2,2,3}, f); // verify deleted nodes and leaves. assertTrue(c.isDeleted()); assertTrue(d.isDeleted()); assertTrue(g.isDeleted()); /* * step#2 : remove(3) triggers e.join(b) which calls * e.redistributeKeys(b) and sends (5,v5) to e. (This tests the code * path for redistribution of keys with a rightSibling of a leaf). * * postcondition: * * f.keys[ 3 6 x ] * f.clds[ a e b ] * * a.keys[ 1 2 - ] * e.keys[ 4 5 - ] * b.keys[ 6 8 - ] */ assertEquals(v3,btree.remove(TestKeyBuilder.asSortKey(3))); assertTrue("after remove(3)", btree.dump(Level.DEBUG,System.err)); // verify leaves. assertKeys(new int[]{1,2},a); assertValues(new Object[]{v1,v2},a); assertKeys(new int[]{4,5},e); assertValues(new Object[]{v4,v5},e); assertKeys(new int[]{6,8},b); assertValues(new Object[]{v6,v8},b); // verify the new root. assertKeys(new int[]{3,6},f); assertEquals(f,btree.root); assertEquals(a,f.getChild(0)); assertEquals(e,f.getChild(1)); assertEquals(b,f.getChild(2)); assertEntryCounts(new int[]{2,2,2}, f); /* * step#3 : remove(8) triggers b.join(e) which calls b.merge(e), which * updates the separator in (f) to the separator for the leftSibling * which is (3) and then invokes f.removeChild(e). * * postcondition: * * f.keys[ 3 - x ] * f.clds[ a b - ] * * a.keys[ 1 2 - ] * b.keys[ 4 5 6 ] * * e is deleted. */ assertEquals(v8,btree.remove(TestKeyBuilder.asSortKey(8))); assertTrue("after remove(8)", btree.dump(Level.DEBUG,System.err)); // verify leaves. assertKeys(new int[]{1,2},a); assertValues(new Object[]{v1,v2},a); assertKeys(new int[]{4,5,6},b); assertValues(new Object[]{v4,v5,v6},b); // verify the root. assertKeys(new int[]{3},f); assertEquals(f,btree.root); assertEquals(a,f.getChild(0)); assertEquals(b,f.getChild(1)); assertNull(f.getChildRef(2)); assertTrue(e.isDeleted()); assertEntryCounts(new int[]{2,3}, f); /* * step#4 : remove(2) triggers a.join(b) which triggers * a.redistributeKeys(b) which sends (4,v4) to (a) and updates the * separatorKey on (f) to (5). * * postcondition: * * f.keys[ 3 - x ] * f.clds[ a b - ] * * a.keys[ 1 4 - ] * b.keys[ 5 6 - ] */ assertEquals(v2,btree.remove(TestKeyBuilder.asSortKey(2))); assertTrue("after remove(2)", btree.dump(Level.DEBUG,System.err)); // verify leaves. assertKeys(new int[]{1,4},a); assertValues(new Object[]{v1,v4},a); assertKeys(new int[]{5,6},b); assertValues(new Object[]{v5,v6},b); // verify the root. assertKeys(new int[]{5},f); assertEquals(f,btree.root); assertEquals(a,f.getChild(0)); assertEquals(b,f.getChild(1)); assertNull(f.getChildRef(2)); assertEntryCounts(new int[]{2,2}, f); /* * step#5 : remove(1) triggers a.join(b) which calls a.merge(b) and * f.removeChild(b). Since this leaves (f) with only one child, we make * (a) the new root of the tree. * * postcondition: * * a.keys[ 4 5 6 ] * * b, f is deleted. */ assertEquals(v1,btree.remove(TestKeyBuilder.asSortKey(1))); assertTrue("after remove(1)", btree.dump(Level.DEBUG,System.err)); // verify the remaining leaf, which is now the root of the tree. assertKeys(new int[]{4,5,6},a); assertValues(new Object[]{v4,v5,v6},a); assertEquals(a,btree.root); assertTrue(b.isDeleted()); assertTrue(f.isDeleted()); /* * At this point we have only the root leaf and we just delete the final * keys. */ assertEquals(v4,btree.remove(TestKeyBuilder.asSortKey(4))); assertTrue("after remove(4)", btree.dump(Level.DEBUG,System.err)); assertKeys(new int[]{5,6},a); assertValues(new Object[]{v5,v6},a); assertEquals(a,btree.root); assertEquals(v6,btree.remove(TestKeyBuilder.asSortKey(6))); assertTrue("after remove(6)", btree.dump(Level.DEBUG,System.err)); assertKeys(new int[]{5},a); assertValues(new Object[]{v5},a); assertEquals(a,btree.root); assertEquals(v5,btree.remove(TestKeyBuilder.asSortKey(5))); assertTrue("after remove(5)", btree.dump(Level.DEBUG,System.err)); assertKeys(new int[]{},a); assertValues(new Object[]{},a); assertEquals(a,btree.root); assertEquals("height",0,btree.height); assertEquals("nodes",0,btree.nnodes); assertEquals("leaves",1,btree.nleaves); assertEquals("entries",0,btree.nentries); } /** * Variant of {@link #test_removeOrder3a()} that is focused on testing the * redistribution of keys among the left and right siblings of a node when * that node underflows during a deletion operation. */ public void test_removeOrder3c() { /* * Generate keys, values, and visitation order. */ // keys final int[] keys = new int[]{5,6,7,8,3,4,2,1}; // values final SimpleEntry v1 = new SimpleEntry(1); final SimpleEntry v2 = new SimpleEntry(2); final SimpleEntry v3 = new SimpleEntry(3); final SimpleEntry v4 = new SimpleEntry(4); final SimpleEntry v5 = new SimpleEntry(5); final SimpleEntry v6 = new SimpleEntry(6); final SimpleEntry v7 = new SimpleEntry(7); final SimpleEntry v8 = new SimpleEntry(8); final SimpleEntry[] vals = new SimpleEntry[]{v5,v6,v7,v8,v3,v4,v2,v1}; // permutation vector for visiting values in key order. final int[] order = new int[keys.length]; // generate visitation order. { System.arraycopy(keys, 0, order, 0, keys.length); Arrays.sort(order); if (log.isInfoEnabled()) { log.info("keys=" + Arrays.toString(keys)); log.info("vals=" + Arrays.toString(vals)); log.info("order=" + Arrays.toString(order)); } } final int m = 3; BTree btree = getBTree(m); assertEquals("height", 0, btree.height); assertEquals("#nodes", 0, btree.nnodes); assertEquals("#leaves", 1, btree.nleaves); assertEquals("#entries", 0, btree.nentries); assertTrue(btree.dump(System.err)); Leaf a = (Leaf) btree.getRoot(); assertKeys(new int[]{},a); assertValues(new Object[]{},a); int n = 0; { // insert(5,5) final int ikey = keys[n]; SimpleEntry val = vals[n++]; assert ikey == 5 && val.id() == ikey; final byte[] key = TestKeyBuilder.asSortKey(ikey); assertNull(btree.remove(key)); // not found / no change. assertNull(btree.lookup(key)); // not found. assertNull(btree.insert(key,val)); // insert. assertEquals(val,btree.lookup(key)); // found. // validate root leaf. assertKeys(new int[]{5},a); assertValues(new Object[]{v5},a); assertTrue(btree.dump(System.err)); } { // insert(6,6) final int ikey = keys[n]; SimpleEntry val = vals[n++]; assert ikey == 6 && val.id() == ikey; final byte[] key = TestKeyBuilder.asSortKey(ikey); assertNull(btree.remove(key)); // not found / no change. assertNull(btree.lookup(key)); // not found. assertNull(btree.insert(key,val)); // insert. assertEquals(val,btree.lookup(key)); // found. // validate root leaf. assertKeys(new int[]{5,6},a); assertValues(new Object[]{v5,v6},a); assertTrue(btree.dump(System.err)); } /* * fills the root leaf to capacity. * * postcondition: * * keys: [ 5 6 7 ] */ { // insert(7,7) final int ikey = keys[n]; SimpleEntry val = vals[n++]; assert ikey == 7 && val.id() == ikey; final byte[] key = TestKeyBuilder.asSortKey(ikey); assertNull(btree.remove(key)); // not found / no change. assertNull(btree.lookup(key)); // not found. assertNull(btree.insert(key,val)); // insert. assertEquals(val,btree.lookup(key)); // found. // validate root leaf. assertKeys(new int[]{5,6,7},a); assertValues(new Object[]{v5,v6,v7},a); assertTrue(btree.dump(System.err)); } /* * splits the root leaf * * split(a)->(a,b), c is the new root. * * postcondition: * * c.keys[ 7 - x ] * c.clds[ a b - ] * * a.keys[ 5 6 - ] * b.keys[ 7 8 - ] */ final Node c; final Leaf b; { // insert(8,8) final int ikey = keys[n]; SimpleEntry val = vals[n++]; assert ikey == 8 && val.id() == ikey; final byte[] key = TestKeyBuilder.asSortKey(ikey); assertNull(btree.remove(key)); // not found / no change. assertNull(btree.lookup(key)); // not found. assertNull(btree.insert(key,val)); // insert. assertEquals(val,btree.lookup(key)); // found. assertTrue(btree.dump(Level.DEBUG,System.err)); // validate new root (c). c = (Node)btree.getRoot(); assertKeys(new int[]{7},c); assertEquals(a,c.getChild(0)); assertNotNull(c.getChild(1)); assertNull(c.getChildRef(2)); b = (Leaf)c.getChild(1); assertEntryCounts(new int[]{2,2}, c); // validate original leaf (a). assertKeys(new int[]{5,6},a); assertValues(new Object[]{v5,v6},a); // validate new leaf (b). assertKeys(new int[]{7,8},b); assertValues(new Object[]{v7,v8},b); assertTrue(btree.dump(System.err)); } /* * insert(3,3) * * postcondition: * * c.keys[ 7 - x ] * c.clds[ a b - ] * * a.keys[ 3 5 6 ] * b.keys[ 7 8 - ] */ { final int ikey = keys[n]; SimpleEntry val = vals[n++]; assert ikey == 3 && val.id() == ikey; final byte[] key = TestKeyBuilder.asSortKey(ikey); assertNull(btree.remove(key)); // not found / no change. assertNull(btree.lookup(key)); // not found. assertNull(btree.insert(key,val)); // insert. assertEquals(val,btree.lookup(key)); // found. assertTrue(btree.dump(Level.DEBUG,System.err)); // validate original leaf (a). assertKeys(new int[]{3,5,6},a); assertValues(new Object[]{v3,v5,v6},a); // validate root. assertEntryCounts(new int[]{3,2}, c); } /* * insert(4,4), causing split(a)->(a,d) and bringing (c) to capacity. * * postcondition: * * c.keys[ 5 7 x ] * c.clds[ a d b ] * * a.keys[ 3 4 - ] * d.keys[ 5 6 - ] * b.keys[ 7 8 - ] */ final Leaf d; { final int ikey = keys[n]; SimpleEntry val = vals[n++]; assert ikey == 4 && val.id() == ikey; final byte[] key = TestKeyBuilder.asSortKey(ikey); assertNull(btree.remove(key)); // not found / no change. assertNull(btree.lookup(key)); // not found. assertNull(btree.insert(key,val)); // insert. assertEquals(val,btree.lookup(key)); // found. assertTrue(btree.dump(Level.DEBUG,System.err)); // validate root (c). assertKeys(new int[]{5,7},c); assertEquals(a,c.getChild(0)); assertNotNull(c.getChildRef(1)); d = (Leaf) c.getChild(1); assertEquals(b,c.getChild(2)); assertEntryCounts(new int[]{2,2,2}, c); // validate original leaf (a). assertKeys(new int[]{3,4},a); assertValues(new Object[]{v3,v4},a); // validate new leaf (d). assertKeys(new int[]{5,6},d); assertValues(new Object[]{v5,v6},d); // validate leaf (b). assertKeys(new int[]{7,8},b); assertValues(new Object[]{v7,v8},b); } /* * insert(2,2), bringing (a) to capacity again. */ { final int ikey = keys[n]; SimpleEntry val = vals[n++]; assert ikey == 2 && val.id() == ikey; final byte[] key = TestKeyBuilder.asSortKey(ikey); assertNull(btree.remove(key)); // not found / no change. assertNull(btree.lookup(key)); // not found. assertNull(btree.insert(key,val)); // insert. assertEquals(val,btree.lookup(key)); // found. assertTrue(btree.dump(Level.DEBUG,System.err)); // validate original leaf (a). assertKeys(new int[]{2,3,4},a); assertValues(new Object[]{v2,v3,v4},a); // validate root. assertEntryCounts(new int[]{3,2,2}, c); } /* * insert(1,1) causing (a) to split(a)->(a,e). Since the root (c) is * already at capacity this also causes the root to split(c)->(c,f) and * creating a new root(g). * * postcondition: * * g.keys[ 5 - x ] * g.clds[ c f - ] * * c.keys[ 3 - x ] * c.clds[ a e - ] * * f.keys[ 7 - x ] * f.clds[ d b - ] * * a.keys[ 1 2 - ] * e.keys[ 3 4 - ] * d.keys[ 5 6 - ] * b.keys[ 7 8 - ] */ final Leaf e; final Node f, g; { final int ikey = keys[n]; SimpleEntry val = vals[n++]; assert ikey == 1 && val.id() == ikey; final byte[] key = TestKeyBuilder.asSortKey(ikey); assertNull(btree.remove(key)); // not found / no change. assertNull(btree.lookup(key)); // not found. assertNull(btree.insert(key,val)); // insert. assertEquals(val,btree.lookup(key)); // found. assertTrue(btree.dump(Level.DEBUG,System.err)); // validate the new root(g). assertNotSame(c,btree.getRoot()); g = (Node)btree.getRoot(); assertKeys(new int[]{5},g); assertEquals(c,g.getChild(0)); assertNotNull(g.getChildRef(1)); f = (Node) g.getChild(1); assertNull(g.getChildRef(2)); assertEntryCounts(new int[]{4,4}, g); // validate old root (c). assertKeys(new int[]{3},c); assertEquals(a,c.getChild(0)); assertNotNull(c.getChildRef(1)); e = (Leaf) c.getChild(1); assertNull(c.getChildRef(2)); assertEntryCounts(new int[]{2,2}, c); // validate node(f) split from the old root split(c)->(c,f). assertKeys(new int[]{7},f); assertEquals(d,f.getChild(0)); assertEquals(b,f.getChild(1)); assertNull(f.getChildRef(2)); assertEntryCounts(new int[]{2,2}, f); // validate original leaf (a), which was re-split into (a,e). assertKeys(new int[]{1,2},a); assertValues(new Object[]{v1,v2},a); // validate new leaf (e). assertKeys(new int[]{3,4},e); assertValues(new Object[]{v3,v4},e); // validate new leaf (d). assertKeys(new int[]{5,6},d); assertValues(new Object[]{v5,v6},d); // validate leaf (b). assertKeys(new int[]{7,8},b); assertValues(new Object[]{v7,v8},b); } /* * At this point this test forks from the other two tests for trees of * order three (3) in this suite. Rather than begin deleting keys we * first bring some of the internal nodes to capacity so that deletes * will cause the redistribution of keys among the internal nodes. We * will do this a few times to test redistribution of keys with the left * and the right sibling, each time inserting more keys so that a * redistribution will be forced rather than forcing the merger of a * node with its left or right sibling. * * before: * * g.keys[ 5 - x ] * g.clds[ c f - ] * * c.keys[ 3 - x ] * c.clds[ a e - ] * * f.keys[ 7 - x ] * f.clds[ d b - ] * * a.keys[ 1 2 - ] * e.keys[ 3 4 - ] * d.keys[ 5 6 - ] * b.keys[ 7 8 - ] */ assertTrue("common baseline established", btree.dump(Level.DEBUG,System.err)); /* * insert(9,9) and insert(10,10), bringing the tree to the point where a * delete will trigger the redistribution of keys among the internal * nodes. * * postcondition: * * g.keys[ 5 - x ] * g.clds[ c f - ] * * c.keys[ 3 - x ] * c.clds[ a e - ] * * f.keys[ 7 9 x ] * f.clds[ d b h ] * * a.keys[ 1 2 - ] * e.keys[ 3 4 - ] * d.keys[ 5 6 - ] * b.keys[ 7 8 - ] * h.keys[ 9 10 - ] */ final SimpleEntry v9 = new SimpleEntry(9); final SimpleEntry v10 = new SimpleEntry(10); final Leaf h; { { // insert(9,v9) final byte[] key = TestKeyBuilder.asSortKey(9); SimpleEntry val = v9; assertNull(btree.remove(key)); // not found / no change. assertNull(btree.lookup(key)); // not found. assertNull(btree.insert(key, val)); // insert. assertEquals(val, btree.lookup(key)); // found. assertTrue(btree.dump(Level.DEBUG, System.err)); assertEntryCounts(new int[]{2,3}, f); } { /* * insert(10,v10) - splits (b) into (b,h) and adds (h) as a * child of (f). */ final byte[] key = TestKeyBuilder.asSortKey(10); SimpleEntry val = v10; assertNull(btree.remove(key)); // not found / no change. assertNull(btree.lookup(key)); // not found. assertNull(btree.insert(key,val)); // insert. assertEquals(val,btree.lookup(key)); // found. assertTrue(btree.dump(Level.DEBUG,System.err)); assertEntryCounts(new int[]{2,2,2}, f); } // validate the root(g). assertEquals(g,btree.getRoot()); assertKeys(new int[]{5},g); assertEquals(c,g.getChild(0)); assertEquals(f,g.getChild(1)); assertEntryCounts(new int[]{4,6}, g); // validate node(c). assertKeys(new int[]{3},c); assertEquals(a,c.getChild(0)); assertEquals(e,c.getChild(1)); assertEntryCounts(new int[]{2,2}, c); // validate node(f). assertKeys(new int[]{7,9},f); assertEquals(d,f.getChild(0)); assertEquals(b,f.getChild(1)); assertNotNull(f.getChildRef(2)); h = (Leaf) f.getChild(2); assertEntryCounts(new int[]{2,2,2}, f); // validate original leaf (a). assertKeys(new int[]{1,2},a); assertValues(new Object[]{v1,v2},a); // validate leaf (e). assertKeys(new int[]{3,4},e); assertValues(new Object[]{v3,v4},e); // validate leaf (d). assertKeys(new int[]{5,6},d); assertValues(new Object[]{v5,v6},d); // validate leaf (b). assertKeys(new int[]{7,8},b); assertValues(new Object[]{v7,v8},b); // validate new leaf (h). assertKeys(new int[]{9,10},h); assertValues(new Object[]{v9,v10},h); } /* * Test redistribution of keys among nodes in the tree. */ assertTrue("before removing keys", btree.dump(Level.DEBUG,System.err)); /* * step#1 : remove(1). This triggers a.join(e) which forces * a.merge(e,rightSibling:=true). Since (e) is merged into (a) we then * c.removeChild(3,e). This causes (c) to underflow, triggering * c.join(f). Since (f) is over its minimum capacity this causes * c.redistributeKeys(f,rightSibling:=true) - this is the first time * that we have triggered the redistribution of keys among the internal * nodes of the tree. This causes the first key (7) in (f) to be rotated * through the common parent (g) (which also happens to be the root) and * sends the ke (5) down from the parent (g) to (c). The child * associated with (7) in (f) is appended as the last child in (c). * * postcondition: * * g.keys[ 7 - x ] * g.clds[ c f - ] * * c.keys[ 5 - x ] * c.clds[ a d - ] * * f.keys[ 9 - x ] * f.clds[ b h - ] * * a.keys[ 1 2 - ] * d.keys[ 5 6 - ] * b.keys[ 7 8 - ] * h.keys[ 9 10 - ] * * e is deleted. */ { assertEquals(v1,btree.remove(TestKeyBuilder.asSortKey(1))); assertTrue("after remove(1)", btree.dump(Level.DEBUG,System.err)); // validate the root(g). assertEquals(g,btree.getRoot()); assertKeys(new int[]{7},g); assertEquals(c,g.getChild(0)); assertEquals(f,g.getChild(1)); assertEntryCounts(new int[]{5,4}, g); // validate node(c). assertKeys(new int[]{5},c); assertEquals(a,c.getChild(0)); assertEquals(d,c.getChild(1)); assertEntryCounts(new int[]{3,2}, c); // validate node(f). assertKeys(new int[]{9},f); assertEquals(b,f.getChild(0)); assertEquals(h,f.getChild(1)); assertEntryCounts(new int[]{2,2}, f); // validate original leaf (a). assertKeys(new int[]{2,3,4},a); assertValues(new Object[]{v2,v3,v4},a); // validate leaf (e). assertTrue(e.isDeleted()); // validate leaf (d). assertKeys(new int[]{5,6},d); assertValues(new Object[]{v5,v6},d); // validate leaf (b). assertKeys(new int[]{7,8},b); assertValues(new Object[]{v7,v8},b); // validate leaf (h). assertKeys(new int[]{9,10},h); assertValues(new Object[]{v9,v10},h); } /* * step#2 : insert(1). This causes leaf(a) to overflow, splitting into * leaf(a) and leaf(i) and inserts (i) as the rightSibling of (a) into * (c). This brings (c) over its minimal capacity and paves the way for * us to force the redistribution of a key from (c) to (f) in the next * step. * * postcondition: * * g.keys[ 7 - x ] * g.clds[ c f - ] * * c.keys[ 3 5 x ] * c.clds[ a i d ] * * f.keys[ 9 - x ] * f.clds[ b h - ] * * a.keys[ 1 2 - ] * i.keys[ 3 4 - ] * d.keys[ 5 6 - ] * b.keys[ 7 8 - ] * h.keys[ 9 10 - ] */ final Leaf i; { assertNull(btree.insert(TestKeyBuilder.asSortKey(1),v1)); assertTrue("after insert(1)", btree.dump(Level.DEBUG,System.err)); // validate the root(g). assertEquals(g,btree.getRoot()); assertKeys(new int[]{7},g); assertEquals(c,g.getChild(0)); assertEquals(f,g.getChild(1)); assertEntryCounts(new int[]{6,4}, g); // validate node(c). assertKeys(new int[]{3,5},c); assertEquals(a,c.getChild(0)); assertNotNull(c.getChildRef(1)); i = (Leaf) c.getChild(1); assertEquals(d,c.getChild(2)); assertEntryCounts(new int[]{2,2,2}, c); // validate node(f). assertKeys(new int[]{9},f); assertEquals(b,f.getChild(0)); assertEquals(h,f.getChild(1)); assertEntryCounts(new int[]{2,2}, f); // validate original leaf (a), which we just split into (a,i) assertKeys(new int[]{1,2},a); assertValues(new Object[]{v1,v2},a); // validate the new leaf (i) assertKeys(new int[]{3,4},i); assertValues(new Object[]{v3,v4},i); // validate leaf (d). assertKeys(new int[]{5,6},d); assertValues(new Object[]{v5,v6},d); // validate leaf (b). assertKeys(new int[]{7,8},b); assertValues(new Object[]{v7,v8},b); // validate leaf (h). assertKeys(new int[]{9,10},h); assertValues(new Object[]{v9,v10},h); } /* * step#3 : remove(10). This causes (h) to underflow, triggering * h.join(b) and h.merge(b). This causes f.removeChild(-,b). Since (f) * now underflows this triggers f.join(c,rightSibling:=false) and * f.redistributeKeys(c,rightSibiling:=false). The right most key in * (c) is 5. This key becomes the new separatorKey in the parent (g), * which happens to be the root node as well. The old separatorKey is * 7 and that key is moved down to become the first key in (f). The * data for last child in (c) is moved from (c) to (f). * * postcondition: * * g.keys[ 5 - x ] * g.clds[ c f - ] * * c.keys[ 3 - x ] * c.clds[ a i - ] * * f.keys[ 7 - x ] * f.clds[ d h - ] * * a.keys[ 1 2 - ] * i.keys[ 3 4 - ] * d.keys[ 5 6 - ] * h.keys[ 7 8 9 ] * * b is deleted. */ { assertEquals(v10,btree.remove(TestKeyBuilder.asSortKey(10))); assertTrue("after remove(10)", btree.dump(Level.DEBUG,System.err)); // validate the root(g). assertEquals(g,btree.getRoot()); assertKeys(new int[]{5},g); assertEquals(c,g.getChild(0)); assertEquals(f,g.getChild(1)); assertEntryCounts(new int[]{4,5}, g); // validate node(c). assertKeys(new int[]{3},c); assertEquals(a,c.getChild(0)); assertEquals(i,c.getChild(1)); assertNull(c.getChildRef(2)); assertEntryCounts(new int[]{2,2}, c); // validate node(f). assertKeys(new int[]{7},f); assertEquals(d,f.getChild(0)); assertEquals(h,f.getChild(1)); assertEntryCounts(new int[]{2,3}, f); // validate original leaf (a) assertKeys(new int[]{1,2},a); assertValues(new Object[]{v1,v2},a); // validate leaf (i) assertKeys(new int[]{3,4},i); assertValues(new Object[]{v3,v4},i); // validate leaf (d), which was just moved from (c) to (f). assertKeys(new int[]{5,6},d); assertValues(new Object[]{v5,v6},d); // validate leaf (b), which was just deleted. assertTrue(b.isDeleted()); // validate leaf (h). assertKeys(new int[]{7,8,9},h); assertValues(new Object[]{v7,v8,v9},h); } } }