/* 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 Jun 10, 2008 */ package com.bigdata.btree; import java.util.NoSuchElementException; import java.util.UUID; import com.bigdata.btree.AbstractBTreeTupleCursor.MutableBTreeTupleCursor; import com.bigdata.btree.keys.TestKeyBuilder; import com.bigdata.rawstore.SimpleMemoryRawStore; /** * Test ability to traverse tuples using an {@link ITupleCursor} while the SAME * THREAD is used to insert, update, or remove tuples from a mutable * {@link BTree}. * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id$ */ public class TestMutableBTreeCursors extends AbstractBTreeCursorTestCase { /** * */ public TestMutableBTreeCursors() { } /** * @param name */ public TestMutableBTreeCursors(String name) { super(name); } @Override protected boolean isReadOnly() { return false; } @Override protected ITupleCursor2<String> newCursor(AbstractBTree btree, int flags, byte[] fromKey, byte[] toKey) { assert ! btree.isReadOnly(); return new MutableBTreeTupleCursor<String>((BTree) btree, new Tuple<String>(btree, flags), fromKey, toKey); } /** * Test ability to remove tuples using {@link ITupleCursor#remove()} during * forward traversal. */ public void test_cursor_remove_during_forward_traversal() { final BTree btree; { btree = BTree.create(new SimpleMemoryRawStore(), new IndexMetadata( UUID.randomUUID())); btree.insert(10, "Bryan"); btree.insert(20, "Mike"); btree.insert(30, "James"); } /* * loop over the index removing entries as we go. * * Note: this loop is unrolled in order to make it easier to track the * state changed as tuples are deleted. */ { ITupleCursor2<String> cursor = newCursor(btree); // visit (10,Bryan) and then delete that tuple. { // visit the first tuple and verify its state. assertEquals(new TestTuple<String>(10,"Bryan"),cursor.next()); // remove it. cursor.remove(); // verify cursor reports tuple is null since it no longer exists in the index. assertNull(cursor.tuple()); } // visit (20,Mike) and then delete that tuple. { // visit the next tuple and verify its state. assertEquals(new TestTuple<String>(20,"Mike"),cursor.next()); // remove it. cursor.remove(); // verify cursor reports tuple is null since it no longer exists in the index. assertNull(cursor.tuple()); } // visit (30,James) and then delete that tuple. { // visit the next tuple and verify its state. assertEquals(new TestTuple<String>(30,"James"),cursor.next()); // remove it. cursor.remove(); // verify cursor reports tuple is null since it no longer exists in the index. assertNull(cursor.tuple()); } // the iterator is exhausted. assertFalse(cursor.hasNext()); // the index is empty. assertEquals(0,btree.getEntryCount()); } } /** * Test ability to remove tuples using {@link ITupleCursor#remove()} during * reverse traversal. */ public void test_cursor_remove_during_reverse_traversal() { final BTree btree; { btree = BTree.create(new SimpleMemoryRawStore(), new IndexMetadata( UUID.randomUUID())); btree.insert(10, "Bryan"); btree.insert(20, "Mike"); btree.insert(30, "James"); } /* * loop over the index removing entries as we go. * * Note: this loop is unrolled in order to make it easier to track the * state changed as tuples are deleted. */ { ITupleCursor2<String> cursor = newCursor(btree); // visit (30,James) and then delete that tuple. { // verify the state of the last tuple. assertEquals(new TestTuple<String>(30,"James"),cursor.prior()); // remove it. cursor.remove(); // verify tuple() reports null since it was deleted. assertNull(cursor.tuple()); } // visit (20,Mike) and then delete that tuple. { // visit the prior tuple and verify its state. assertEquals(new TestTuple<String>(20,"Mike"),cursor.prior()); // remove it. cursor.remove(); // verify tuple() reports null since it was deleted. assertNull(cursor.tuple()); } // visit (10,Bryan) and then delete that tuple. { // visit the prior tuple and verify its state. assertEquals(new TestTuple<String>(10,"Bryan"),cursor.prior()); // remote it. cursor.remove(); // verify tuple() reports null since it was deleted. assertNull(cursor.tuple()); } // the iterator is exhausted. assertFalse(cursor.hasPrior()); // the index is empty. assertEquals(0,btree.getEntryCount()); } } /** * Test for update (the tuple state must be re-copied from the index). * <p> * Note that copy-on-write is handled differently even when the trigger is * an update (vs an insert or a remove). */ public void test_concurrent_modification_update() { final BTree btree; { btree = BTree.create(new SimpleMemoryRawStore(), new IndexMetadata( UUID.randomUUID())); btree.insert(10, "Bryan"); btree.insert(20, "Mike"); btree.insert(30, "James"); } /* * seek to a tuple, update it using the btree api and then verify that * the tuple state is re-copied from the btree by tuple(). */ { ITupleCursor2<String> cursor = newCursor(btree); // seek to a tuple and verify its state. assertEquals(new TestTuple<String>(20, "Mike"), cursor.seek(20)); // verify state reported by tuple() assertEquals(new TestTuple<String>(20, "Mike"), cursor.tuple()); // update the tuple. btree.insert(20, "Michael"); // verify update is reflected by the tuple. assertEquals(new TestTuple<String>(20, "Michael"), cursor.tuple()); } } /** * Unit test for concurrent modification resulting from insert() and remove(). */ public void test_concurrent_modification_insert() { final BTree btree; { IndexMetadata md = new IndexMetadata(UUID.randomUUID()); // Explictly specify a large branching factor so that we do not split the root leaf. md.setBranchingFactor(20); btree = BTree.create(new SimpleMemoryRawStore(), md); btree.insert(10, "Bryan"); btree.insert(20, "Mike"); btree.insert(30, "James"); } /* * seek to a tuple, insert another tuple using the btree api and then * verify that the current tuple / cursor position appears unchanged * from the perspective of the cursor. */ { ITupleCursor2<String> cursor = newCursor(btree); // seek to a tuple and verify its state. assertEquals(new TestTuple<String>(20, "Mike"), cursor.seek(20)); // verify same state reported by tuple(). assertEquals(new TestTuple<String>(20, "Mike"), cursor.tuple()); /* * insert a tuple before the current tuple (moves the current tuple * down by one). */ btree.insert(15, "Paul"); // verify the cursor position is unchanged. assertEquals(TestKeyBuilder.asSortKey(20),cursor.currentKey()); // verify the current tuple state is unchanged. assertEquals(new TestTuple<String>(20, "Mike"), cursor.tuple()); // visit the prior tuple (the one we just inserted). assertEquals(new TestTuple<String>(15, "Paul"), cursor.prior()); // verify the tuple state using tuple(). assertEquals(new TestTuple<String>(15, "Paul"), cursor.tuple()); /* * remove the current tuple (moves the successors of this tuple in * the leaf down by one). */ btree.remove(15); // verify the tuple state - it should be null since the tuple for that key was just deleted. assertEquals(null, cursor.tuple()); // verify the cursor position is unchanged. assertEquals(TestKeyBuilder.asSortKey(15),cursor.currentKey()); // visit the next tuple. assertEquals(new TestTuple<String>(20, "Mike"), cursor.next()); // delete that tuple. btree.remove(20); // verify the tuple state - it should be null since the tuple for that key was just deleted. assertEquals(null, cursor.tuple()); // verify the cursor position is unchanged. assertEquals(TestKeyBuilder.asSortKey(20),cursor.currentKey()); // insert another tuple that is a successor of the deleted tuple. btree.insert(25, "Allen"); // verify the cursor position is unchanged. assertEquals(TestKeyBuilder.asSortKey(20),cursor.currentKey()); // verify the tuple state - still null since we have not repositioned the cursor. assertEquals(null, cursor.tuple()); // advance the cursor and verify the tuple state assertEquals(new TestTuple<String>(25,"Allen"), cursor.next()); // verify the tuple state using tuple(). assertEquals(new TestTuple<String>(25,"Allen"), cursor.tuple()); } } /** * Unit test for concurrent modification resulting from insert() and * remove() including (a) where the root leaf is split by the insert() and * (b) where remove() causes an underflow that triggers a join of the leaf * with its sibling forcing the underflow of the parent such that the leaf * then becomes the new root leaf. * <p> * Note: This test does not covert overflow where a tuple is rotated to the * sibling or underflow where a tuple is rotated from the sibling. Those * cases (and all cases involving deeper trees) are covered by the various * stress tests where a BTree is perturbed randomly and checked against * ground truth. */ public void test_concurrent_modification_insert_split_root_leaf() { final BTree btree; { IndexMetadata md = new IndexMetadata(UUID.randomUUID()); // Note: at m=3 this example splits the root leaf). md.setBranchingFactor(3); btree = BTree.create(new SimpleMemoryRawStore(), md); btree.insert(10, "Bryan"); btree.insert(20, "Mike"); btree.insert(30, "James"); } /* * seek to a tuple, insert another tuple using the btree api and then * verify that the current tuple / cursor position appears unchanged * from the perspective of the cursor. */ { ITupleCursor2<String> cursor = newCursor(btree); // seek to a tuple and verify its state. assertEquals(new TestTuple<String>(20, "Mike"), cursor.seek(20)); // verify same state reported by tuple(). assertEquals(new TestTuple<String>(20, "Mike"), cursor.tuple()); // insert a tuple before the current tuple (forces the leaf to be split). btree.insert(15, "Paul"); // verify the cursor position is unchanged. assertEquals(TestKeyBuilder.asSortKey(20),cursor.currentKey()); // verify the current tuple state is unchanged. assertEquals(new TestTuple<String>(20, "Mike"), cursor.tuple()); // visit the prior tuple (the one we just inserted). assertEquals(new TestTuple<String>(15, "Paul"), cursor.prior()); // verify the tuple state using tuple(). assertEquals(new TestTuple<String>(15, "Paul"), cursor.tuple()); /* * remove the current tuple. This causes the leaf to underflow and * since the parent would then be deficient the leaf becomes the new * root leaf and its right sibling is discarded. */ btree.remove(15); // verify the cursor position is unchanged. assertEquals(TestKeyBuilder.asSortKey(15),cursor.currentKey()); // verify the tuple state - it should be null since the tuple for that key was just deleted. assertEquals(null, cursor.tuple()); // visit the next tuple. assertEquals(new TestTuple<String>(20, "Mike"), cursor.next()); // delete that tuple. btree.remove(20); // verify the cursor position is unchanged. assertEquals(TestKeyBuilder.asSortKey(20),cursor.currentKey()); // verify the tuple state - it should be null since the tuple for that key was just deleted. assertEquals(null, cursor.tuple()); // insert another tuple that is a successor of the deleted tuple. btree.insert(25, "Allen"); // verify the cursor position is unchanged. assertEquals(TestKeyBuilder.asSortKey(20),cursor.currentKey()); // verify the tuple state - still null since we have not repositioned the cursor. assertEquals(null, cursor.tuple()); // advance the cursor and verify the tuple state assertEquals(new TestTuple<String>(25,"Allen"), cursor.next()); // verify the tuple state using tuple(). assertEquals(new TestTuple<String>(25,"Allen"), cursor.tuple()); } } /** * Unit test for copy-on-write (the leaf is clean and then an update, * insert, or remove is requested which forces copy-on-write to clone the * leaf). The listener needs to notice the event and relocate itself within * the BTree. */ public void test_concurrent_modification_copy_on_write() { final BTree btree; { final IndexMetadata md = new IndexMetadata(UUID.randomUUID()); // Note: at m=3 this example splits the root leaf if anything is inserted. md.setBranchingFactor(3); btree = BTree.create(new SimpleMemoryRawStore(), md); btree.insert(10, "Bryan"); btree.insert(20, "Mike"); btree.insert(30, "James"); } { final ITupleCursor2<String> cursor = newCursor(btree); /* * flush the btree to the store making all nodes clean. any mutation * will trigger copy on write. */ // assertTrue(btree.flush()); btree.writeCheckpoint2(); assertTrue(cursor.hasNext()); // visit the first tuple. assertEquals(new TestTuple<String>(10,"Bryan"),cursor.next()); // remove that tuple from the index (triggers copy-on-write). btree.remove(10); // verify the cursor position is unchanged. assertEquals(TestKeyBuilder.asSortKey(10),cursor.currentKey()); // verify the tuple state - still null since we have not repositioned the cursor. assertEquals(null, cursor.tuple()); // visit the next tuple. assertEquals(new TestTuple<String>(20,"Mike"),cursor.next()); // verify the cursor position. assertEquals(TestKeyBuilder.asSortKey(20),cursor.currentKey()); /* * flush the btree again making all nodes clean. */ // assertTrue(btree.flush()); btree.writeCheckpoint2(); // insert a tuple (triggers copy-on-write). btree.insert(10, "Bryan"); // verify the cursor position is unchanged. assertEquals(TestKeyBuilder.asSortKey(20),cursor.currentKey()); // verify the current tuple state. assertEquals(new TestTuple<String>(20,"Mike"),cursor.tuple()); // visit the prior tuple (the one that we just inserted). assertEquals(new TestTuple<String>(10,"Bryan"),cursor.prior()); } } /** * Test examines the behavior of the cursor when delete markers are enabled * and the cursor is willing to visited deleted tuples. Since delete markers * are enabled remove() will actually be an update that sets the delete * marker on the tuple and clears the value associated with that tuple. * Since deleted tuples are being visited by the cursor, the caller will see * the deleted tuples. */ public void test_delete_markers_visitDeleted() { final BTree btree; { IndexMetadata md = new IndexMetadata(UUID.randomUUID()); // enable delete markers. md.setDeleteMarkers(true); btree = BTree.create(new SimpleMemoryRawStore(), md); btree.insert(10, "Bryan"); btree.insert(20, "Mike"); btree.insert(30, "James"); } final int flags = IRangeQuery.DEFAULT | IRangeQuery.DELETED; /* * remove() */ { ITupleCursor2<String> cursor = newCursor(btree, flags, null/* fromKey */, null/* toKey */); assertEquals(new TestTuple<String>(flags, 10, "Bryan", false/* deleted */, 0L/* timestamp */), cursor.seek(10)); cursor.remove(); assertEquals(new TestTuple<String>(flags, 10, null, true/* deleted */, 0L/* timestamp */), cursor.tuple()); assertTrue(cursor.hasNext()); assertEquals(new TestTuple<String>(flags, 20, "Mike", false/* deleted */, 0L/* timestamp */), cursor.next()); assertTrue(cursor.hasPrior()); assertEquals(new TestTuple<String>(flags, 10, null, true/* deleted */, 0L/* timestamp */), cursor.prior()); } } /** * Test examines the behavior of the cursor when delete markers are enabled * and the cursor is NOT willing to visited deleted tuples. Since delete * markers are enabled remove() will actually be an update that sets the * delete marker on the tuple and clears the value associated with that * tuple. Since deleted tuples are NOT being visited by the cursor, the * caller deleted tuples will appear to "disappear" just like they do when a * tuple is remove()'d from an index that does not support delete markers. */ public void test_delete_markers_doNotVisitDeleted() { final BTree btree; { IndexMetadata md = new IndexMetadata(UUID.randomUUID()); // enable delete markers. md.setDeleteMarkers(true); btree = BTree.create(new SimpleMemoryRawStore(), md); btree.insert(10, "Bryan"); btree.insert(20, "Mike"); btree.insert(30, "James"); } // Note: Cursor will NOT visit the deleted tuples. final int flags = IRangeQuery.DEFAULT; /* * remove() */ { ITupleCursor2<String> cursor = newCursor(btree, flags, null/* fromKey */, null/* toKey */); assertEquals(new TestTuple<String>(flags, 10, "Bryan", false/* deleted */, 0L/* timestamp */), cursor.seek(10)); cursor.remove(); // tuple is no longer visitable since the delete flag was set. assertEquals(null, cursor.tuple()); assertTrue(cursor.hasNext()); assertEquals(new TestTuple<String>(flags, 20, "Mike", false/* deleted */, 0L/* timestamp */), cursor.next()); // the prior tuple is the one that we deleted so it is not visitable. assertFalse(cursor.hasPrior()); try { cursor.prior(); fail("Expecting " + NoSuchElementException.class); } catch (NoSuchElementException ex) { log.info("Ignoring expected exception: " + ex); } } } /** * Test verifies that an iterator which is scanning in forward order can be * exhausted when it reaches the end of the visitable tuples but that a * visitable tuple inserted after the current cursor position will be * noticed by {@link ITupleCursor#hasNext()} and visited. */ public void test_hasNext_continues_after_insert() { final BTree btree; { IndexMetadata md = new IndexMetadata(UUID.randomUUID()); btree = BTree.create(new SimpleMemoryRawStore(), md); btree.insert(10, "Bryan"); } { ITupleCursor2<String> cursor = newCursor(btree); assertTrue(cursor.hasNext()); assertEquals(new TestTuple<String>(10,"Bryan"),cursor.next()); assertFalse(cursor.hasNext()); // insert after btree.insert(20, "Mike"); assertEquals(new TestTuple<String>(10,"Bryan"),cursor.tuple()); assertTrue(cursor.hasNext()); assertEquals(new TestTuple<String>(20,"Mike"),cursor.next()); } } /** * Test verifies that an iterator which is scanning in reverse order can be * exhausted when it reaches the end of the visitable tuples but that a * visitable tuple inserted after the current cursor position will be * noticed by {@link ITupleCursor#hasPrior()} and visited. */ public void test_hasPrior_continues_after_insert() { final BTree btree; { IndexMetadata md = new IndexMetadata(UUID.randomUUID()); btree = BTree.create(new SimpleMemoryRawStore(), md); btree.insert(20, "Mike"); } { ITupleCursor2<String> cursor = newCursor(btree); assertTrue(cursor.hasPrior()); assertEquals(new TestTuple<String>(20,"Mike"),cursor.prior()); assertFalse(cursor.hasPrior()); // insert before. btree.insert(10, "Bryan"); assertEquals(new TestTuple<String>(20,"Mike"),cursor.tuple()); assertTrue(cursor.hasPrior()); assertEquals(new TestTuple<String>(10,"Bryan"),cursor.prior()); } } }