/* 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 junit.framework.TestCase2; import com.bigdata.btree.keys.TestKeyBuilder; import com.bigdata.btree.raba.ReadOnlyKeysRaba; import com.bigdata.rawstore.SimpleMemoryRawStore; /** * Abstract base class for {@link ITupleCursor} test suites. * * @todo also run tests against the FusedView and the scale-out federation * variant (progressive forward or reverse scan against a partitioned * index). * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> * @version $Id$ */ abstract public class AbstractTupleCursorTestCase extends TestCase2 { /** * */ public AbstractTupleCursorTestCase() { } /** * @param arg0 */ public AbstractTupleCursorTestCase(String arg0) { super(arg0); } /** * Create an appropriate cursor instance for the given B+Tree. * * @param btree * @param flags * @param fromKey * @param toKey * * @return An {@link ITupleCursor} for that B+Tree. */ abstract protected ITupleCursor2<String> newCursor(AbstractBTree btree, int flags, byte[] fromKey, byte[] toKey); /** * Create an appropriate cursor instance for the given B+Tree. * * @param btree * * @return */ protected ITupleCursor2<String> newCursor(AbstractBTree btree) { return newCursor(btree, IRangeQuery.DEFAULT, null/* fromKey */, null/* toKey */); } /** * Return a B+Tree populated with data for * {@link #doBaseCaseTest(AbstractBTree)}. */ protected BTree getBaseCaseBTree() { final BTree btree = BTree.create(new SimpleMemoryRawStore(), new IndexMetadata(UUID.randomUUID())); btree.insert(10, "Bryan"); btree.insert(20, "Mike"); btree.insert(30, "James"); return btree; } /** * Test helper tests first(), last(), next(), prior(), and seek() given a * B+Tree that has been pre-populated with some known tuples. * * @param btree * The B+Tree. * * @see #getBaseCaseBTree() */ protected void doBaseCaseTest(final AbstractBTree btree) { // test first() { ITupleCursor2<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, null/* fromKey */, null/* toKey */); assertEquals(new TestTuple<String>(10, "Bryan"), cursor.first()); } // test last() { ITupleCursor2<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, null/* fromKey */, null/* toKey */); assertEquals(new TestTuple<String>(30, "James"), cursor.last()); } // test tuple() { ITupleCursor2<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, null/* fromKey */, null/* toKey */); // current tuple is initially not defined. assertNull(cursor.tuple()); // defines the current tuple. assertEquals(new TestTuple<String>(10, "Bryan"), cursor.first()); // same tuple. assertEquals(new TestTuple<String>(10, "Bryan"), cursor.tuple()); } // test next() { ITupleCursor2<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, null/* fromKey */, null/* toKey */); assertTrue(cursor.hasNext()); assertEquals(new TestTuple<String>(10, "Bryan"), cursor.next()); assertTrue(cursor.hasNext()); assertEquals(new TestTuple<String>(20, "Mike"), cursor.next()); assertTrue(cursor.hasNext()); assertEquals(new TestTuple<String>(30, "James"), cursor.next()); // itr is exhausted. assertFalse(cursor.hasNext()); // the cursor position is still defined. assertTrue(cursor.isCursorPositionDefined()); // itr is exhausted. try { cursor.next(); fail("Expecting " + NoSuchElementException.class); } catch (NoSuchElementException ex) { log.info("Ignoring expected exception: " + ex); } // make sure that the iterator will not restart. assertFalse(cursor.hasNext()); // make sure that the iterator will not restart. try { cursor.next(); fail("Expecting " + NoSuchElementException.class); } catch (NoSuchElementException ex) { log.info("Ignoring expected exception: " + ex); } } // test prior() { ITupleCursor2<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, null/* fromKey */, null/* toKey */); assertTrue(cursor.hasPrior()); assertEquals(new TestTuple<String>(30, "James"), cursor.prior()); assertTrue(cursor.hasPrior()); assertEquals(new TestTuple<String>(20, "Mike"), cursor.prior()); assertTrue(cursor.hasPrior()); assertEquals(new TestTuple<String>(10, "Bryan"), cursor.prior()); // itr is exhausted. assertFalse(cursor.hasPrior()); // the cursor position is still defined. assertTrue(cursor.isCursorPositionDefined()); // itr is exhausted. try { cursor.prior(); fail("Expecting " + NoSuchElementException.class); } catch (NoSuchElementException ex) { log.info("Ignoring expected exception: " + ex); } // make sure that the iterator will not restart. assertFalse(cursor.hasPrior()); // make sure that the iterator will not restart. try { cursor.prior(); fail("Expecting " + NoSuchElementException.class); } catch (NoSuchElementException ex) { log.info("Ignoring expected exception: " + ex); } } /* * test seek(), including prior() and next() after a seek() */ { final ITupleCursor<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, null/* fromKey */, null/* toKey */); // probe(30) assertEquals(new TestTuple<String>(30, "James"), cursor.seek(30)); assertFalse(cursor.hasNext()); assertTrue(cursor.hasPrior()); assertEquals(new TestTuple<String>(20, "Mike"), cursor.prior()); // probe(10) assertEquals(new TestTuple<String>(10, "Bryan"), cursor.seek(10)); assertFalse(cursor.hasPrior()); assertTrue(cursor.hasNext()); assertEquals(new TestTuple<String>(20, "Mike"), cursor.next()); // probe(20) assertEquals(new TestTuple<String>(20, "Mike"), cursor.seek(20)); assertTrue(cursor.hasNext()); assertEquals(new TestTuple<String>(30, "James"), cursor.next()); assertEquals(new TestTuple<String>(20, "Mike"), cursor.seek(20)); assertTrue(cursor.hasPrior()); assertEquals(new TestTuple<String>(10, "Bryan"), cursor.prior()); } /* * test seek() when the probe key is not found / visitable. * * this also tests prior() and next() after the seek to a probe key that * does not exist in the index. */ { final ITupleCursor2<String> cursor = newCursor(btree); // seek to a probe key that does not exist. assertEquals(null, cursor.seek(29)); assertEquals(null, cursor.tuple()); assertEquals(TestKeyBuilder.asSortKey(29),cursor.currentKey()); assertTrue(cursor.hasNext()); assertEquals(new TestTuple<String>(30, "James"), cursor.next()); assertEquals(new TestTuple<String>(30, "James"), cursor.tuple()); assertEquals(TestKeyBuilder.asSortKey(30),cursor.currentKey()); assertFalse(cursor.hasNext()); assertTrue(cursor.hasPrior()); assertEquals(new TestTuple<String>(20, "Mike"), cursor.prior()); assertEquals(new TestTuple<String>(20, "Mike"), cursor.tuple()); assertEquals(TestKeyBuilder.asSortKey(20),cursor.currentKey()); // seek to a probe key that does not exist. assertEquals(null, cursor.seek(9)); assertEquals(null, cursor.tuple()); assertEquals(TestKeyBuilder.asSortKey(9),cursor.currentKey()); assertTrue(cursor.hasNext()); assertEquals(new TestTuple<String>(10, "Bryan"), cursor.next()); assertEquals(new TestTuple<String>(10, "Bryan"), cursor.tuple()); assertEquals(TestKeyBuilder.asSortKey(10),cursor.currentKey()); assertFalse(cursor.hasPrior()); assertTrue(cursor.hasNext()); assertEquals(new TestTuple<String>(20, "Mike"), cursor.next()); assertEquals(TestKeyBuilder.asSortKey(20),cursor.currentKey()); // seek to a probe key that does not exist and scan forward. assertEquals(null, cursor.seek(19)); assertEquals(null, cursor.tuple()); assertEquals(TestKeyBuilder.asSortKey(19),cursor.currentKey()); assertTrue(cursor.hasNext()); assertEquals(new TestTuple<String>(20, "Mike"), cursor.next()); assertEquals(new TestTuple<String>(20, "Mike"), cursor.tuple()); assertEquals(TestKeyBuilder.asSortKey(20),cursor.currentKey()); assertTrue(cursor.hasNext()); assertEquals(new TestTuple<String>(30, "James"), cursor.next()); assertEquals(new TestTuple<String>(30, "James"), cursor.tuple()); assertEquals(TestKeyBuilder.asSortKey(30),cursor.currentKey()); // seek to a probe key that does not exist and scan backward. assertEquals(null, cursor.seek(19)); assertEquals(null, cursor.tuple()); assertEquals(TestKeyBuilder.asSortKey(19),cursor.currentKey()); assertTrue(cursor.hasPrior()); assertEquals(new TestTuple<String>(10, "Bryan"), cursor.prior()); assertEquals(new TestTuple<String>(10, "Bryan"), cursor.tuple()); assertEquals(TestKeyBuilder.asSortKey(10),cursor.currentKey()); assertFalse(cursor.hasPrior()); // seek to a probe key that does not exist (after all valid tuples). assertEquals(null, cursor.seek(31)); assertEquals(null, cursor.tuple()); assertTrue(cursor.isCursorPositionDefined()); assertEquals(TestKeyBuilder.asSortKey(31),cursor.currentKey()); assertFalse(cursor.hasNext()); // seek to a probe key that does not exist (after all valid tuples). assertEquals(null, cursor.seek(31)); assertEquals(null, cursor.tuple()); assertTrue(cursor.isCursorPositionDefined()); assertEquals(TestKeyBuilder.asSortKey(31),cursor.currentKey()); assertTrue(cursor.hasPrior()); assertEquals(new TestTuple<String>(30, "James"), cursor.prior()); } /* * Test to verify that optional range constraints are correctly imposed, * including when the inclusive lower bound and the exclusive upper * bound correspond to tuples actually present in the B+Tree. */ { /* * The inclusive lower bound (fromKey) is on a tuple that exists in * the B+Tree (the first tuple). * * The exclusive upper bound (toKey) is on a tuple that exists and * which is the successor of the first tuple. * * The cursor should only visit the first tuple. */ { final byte[] fromKey = TestKeyBuilder.asSortKey(10); final byte[] toKey = TestKeyBuilder.asSortKey(20); final ITupleCursor2<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, fromKey, toKey); assertTrue(cursor.hasNext()); assertEquals(new TestTuple<String>(10,"Bryan"),cursor.next()); assertFalse(cursor.hasNext()); // now seek to the last tuple. assertEquals(new TestTuple<String>(10,"Bryan"),cursor.last()); assertFalse(cursor.hasNext()); assertFalse(cursor.hasPrior()); // accessible via seek() assertEquals(new TestTuple<String>(10,"Bryan"),cursor.seek(10)); // not accessible via seek(). try { cursor.seek(20); fail("Expecting: "+IllegalArgumentException.class); } catch(IllegalArgumentException ex) { log.info("Ignoring expected exception: "+ex); } } /* * The inclusive lower bound (fromKey) is on a tuple that exists in * the B+Tree (the second tuple). * * The exclusive upper bound (toKey) is on a tuple that exists in * the B+Tree (the third and last tuple). * * The cursor should only visit the 2nd tuple. */ { final byte[] fromKey = TestKeyBuilder.asSortKey(20); final byte[] toKey = TestKeyBuilder.asSortKey(30); final ITupleCursor2<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, fromKey, toKey); assertTrue(cursor.hasNext()); assertEquals(new TestTuple<String>(20,"Mike"),cursor.next()); assertFalse(cursor.hasNext()); assertFalse(cursor.hasPrior()); // now seek to the last tuple. assertEquals(new TestTuple<String>(20,"Mike"),cursor.last()); assertFalse(cursor.hasNext()); assertFalse(cursor.hasPrior()); // accessible via seek() assertEquals(new TestTuple<String>(20,"Mike"),cursor.seek(20)); // not accessible via seek(). try { cursor.seek(10); fail("Expecting: "+IllegalArgumentException.class); } catch(IllegalArgumentException ex) { log.info("Ignoring expected exception: "+ex); } // not accessible via seek(). try { cursor.seek(30); fail("Expecting: "+IllegalArgumentException.class); } catch(IllegalArgumentException ex) { log.info("Ignoring expected exception: "+ex); } } /* * Test when the toKey does not exist for reverse traversal. */ { final byte[] fromKey = TestKeyBuilder.asSortKey(10); final byte[] toKey = TestKeyBuilder.asSortKey(19); final ITupleCursor2<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, fromKey, toKey); // seek to last and scan backward. // assertEquals(null, cursor.last()); // assertEquals(null, cursor.tuple()); // assertEquals(KeyBuilder.asSortKey(19),cursor.currentKey()); assertTrue(cursor.hasPrior()); assertEquals(new TestTuple<String>(10, "Bryan"), cursor.prior()); assertEquals(TestKeyBuilder.asSortKey(10),cursor.currentKey()); assertFalse(cursor.hasPrior()); } /* * Test when the toKey does not exist for reverse traversal. */ { final byte[] fromKey = TestKeyBuilder.asSortKey(10); final byte[] toKey = TestKeyBuilder.asSortKey(29); final ITupleCursor2<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, fromKey, toKey); assertTrue(cursor.hasPrior()); assertEquals(new TestTuple<String>(20, "Mike"), cursor.prior()); assertEquals(TestKeyBuilder.asSortKey(20),cursor.currentKey()); assertTrue(cursor.hasPrior()); assertEquals(new TestTuple<String>(10, "Bryan"), cursor.prior()); assertEquals(TestKeyBuilder.asSortKey(10),cursor.currentKey()); assertFalse(cursor.hasPrior()); } /* * Test when the toKey does not exist for reverse traversal. */ { final byte[] fromKey = TestKeyBuilder.asSortKey(10); final byte[] toKey = TestKeyBuilder.asSortKey(11); final ITupleCursor2<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, fromKey, toKey); assertTrue(cursor.hasPrior()); assertEquals(new TestTuple<String>(10, "Bryan"), cursor.prior()); assertEquals(TestKeyBuilder.asSortKey(10),cursor.currentKey()); assertFalse(cursor.hasPrior()); } } // end test optional range constraints } /** * Return a B+Tree populated with data for * {@link #doReverseTraversalTest(AbstractBTree)}. * <p> * Note: this unit test is setup to create a B+Tree with 2 leaves and a root * node. This allows us to test the edge case where reverse traversal begins * with a key that is LTE the first key in the 2nd leaf. * <p> * Note: This unit test does not work for the {@link IndexSegment} because * the {@link IndexSegmentBuilder} will fill up each leaf in turn, so the * first leaf winds up with 3 tuples and the second with only 2 rather than * it being the other way around. */ protected BTree getReverseTraversalBTree() { final IndexMetadata md = new IndexMetadata(UUID.randomUUID()); md.setBranchingFactor(3); md.setIndexSegmentBranchingFactor(3); final BTree btree = BTree.create(new SimpleMemoryRawStore(), md); // leaf one. btree.insert(10, "Bryan"); btree.insert(20, "Mike"); // leaf two. btree.insert(30, "James"); btree.insert(40, "Amy"); btree.insert(50, "Mary"); // btree.insert(60, "Karen"); assertReverseTraversalData(btree); return btree; } /** * Verify the data expectations. * * @todo Is this possible when the {@link IndexSegment} is generated since * the plan can assign 3 tuples to the first leaf and two to the 2nd * leaf. */ private void assertReverseTraversalData(final AbstractBTree btree) { assertEquals("height", 1, btree.getHeight()); assertEquals("nnodes", 1, btree.getNodeCount()); assertEquals("nleaves", 2, btree.getLeafCount()); assertEquals("ntuples", 5, btree.getEntryCount()); // The separator key is (30). assertEquals(TestKeyBuilder.asSortKey(30), ((Node) btree.getRoot()) .getKeys().get(0)); // Verify the expected keys in the 1st leaf. AbstractBTreeTestCase.assertKeys( // new ReadOnlyKeysRaba(new byte[][] {// TestKeyBuilder.asSortKey(10), // TestKeyBuilder.asSortKey(20), // }),// ((Node) btree.getRoot()).getChild(0/* 1st leaf */).getKeys()); // Verify the expected keys in the 2nd leaf. AbstractBTreeTestCase.assertKeys( // new ReadOnlyKeysRaba(new byte[][] {// TestKeyBuilder.asSortKey(30), // TestKeyBuilder.asSortKey(40), // TestKeyBuilder.asSortKey(50),// }),// ((Node) btree.getRoot()).getChild(1/* 2nd leaf */).getKeys()); } /** * Unit test for reverse traversal under a variety of edge cases. The data * is a B+Tree with two leaves * * <pre> * (10,Bryan) * (20,Mike) * * and * * (30,James) * (40,Amy) * (50,Mary) * </pre> * * This allows us to test the edge case where reverse traversal begins with * a key that is LTE the first key in the 2nd leaf. * * @param btree */ protected void doReverseTraversalTest(final AbstractBTree btree) { assertReverseTraversalData(btree); /* * Test when the toKey exists and is at index zero on the 2nd leaf. */ { final byte[] fromKey = TestKeyBuilder.asSortKey(10); final byte[] toKey = TestKeyBuilder.asSortKey(30); final ITupleCursor2<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, fromKey, toKey); assertTrue(cursor.hasPrior()); assertEquals(new TestTuple<String>(20, "Mike"), cursor.prior()); assertEquals(TestKeyBuilder.asSortKey(20),cursor.currentKey()); } /* * Test when the toKey does not exist for reverse traversal and the * insertion point returned by the search on the leaf is -2 for the * FIRST leaf. */ { final byte[] fromKey = TestKeyBuilder.asSortKey(0); final byte[] toKey = TestKeyBuilder.asSortKey(19); final ITupleCursor2<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, fromKey, toKey); assertTrue(cursor.hasPrior()); assertEquals(new TestTuple<String>(10, "Bryan"), cursor.prior()); assertEquals(TestKeyBuilder.asSortKey(10),cursor.currentKey()); assertFalse(cursor.hasPrior()); } /* * Test when the toKey does not exist for reverse traversal and the * insertion point returned by the search on the leaf is -1 for the * FIRST leaf. */ { final byte[] fromKey = TestKeyBuilder.asSortKey(0); final byte[] toKey = TestKeyBuilder.asSortKey(9); final ITupleCursor2<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, fromKey, toKey); assertFalse(cursor.hasPrior()); } /* * Test when the toKey does not exist for reverse traversal and the * insertion point returned by search on the 2nd leaf is -1, which * corresponds to index 0 when it is converted to an index position in * the leaf. In this case the cursor should be adjusted to the last * tuple in the prior leaf. * * Note: This condition can not arise if the separator key in the parent * is the first tuple in the rightSibling. This is normally the case. * However, if delete markers are not enabled then we can delete the * first tuple in the 2nd leaf, at which point the separatorKey no * longer corresponds to the first tuple in that leaf. */ if (!btree.isReadOnly() && !btree.getIndexMetadata().getDeleteMarkers()) { /* * Verify that the separatorKey in the parent is the first tuple we * expect to find in the 2nd leaf. */ assertEquals(TestKeyBuilder.asSortKey(30), ((Node) btree.getRoot()) .getKeys().get(0)); /* * Modify the B+Tree such that (30) is still the separatorKey for * the two leaves, so anything GTE (30) is directed to the 2nd leaf. * However, the key (30) is no longer found in the B+Tree (not even * as a deleted tuple). */ // Remove the first tuple in the 2nd leaf. btree.remove(30); // The separator key has not been changed. assertEquals(((Node) btree.getRoot()).getKeys().get(0), TestKeyBuilder .asSortKey(30)); // The #of leaves has not been changed. assertEquals(2, btree.getLeafCount()); // Verify the expected keys in the 2nd leaf. AbstractBTreeTestCase.assertKeys(// new ReadOnlyKeysRaba(new byte[][]{// TestKeyBuilder.asSortKey(40),// TestKeyBuilder.asSortKey(50),// }),// ((Node) btree.getRoot()).getChild(1/*2nd leaf*/).getKeys()); final byte[] fromKey = TestKeyBuilder.asSortKey(10); // search for the tuple we just deleted from the 2nd leaf. final byte[] toKey = TestKeyBuilder.asSortKey(30); final ITupleCursor2<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, fromKey, toKey); assertTrue(cursor.hasPrior()); assertEquals(new TestTuple<String>(20, "Mike"), cursor.prior()); assertEquals(TestKeyBuilder.asSortKey(20), cursor.currentKey()); assertTrue(cursor.hasPrior()); } } /** * Test helper tests for fence posts when the index is empty * <p> * Note: this test can not be written for an {@link IndexSegment} since you * can't have an empty {@link IndexSegment}. * * @param btree * An empty B+Tree. */ protected void doEmptyIndexTest(final AbstractBTree btree) { /* * Test with no range limits. */ { // first() { final ITupleCursor2<String> cursor = newCursor(btree); assertNull(cursor.first()); // since there was nothing visitable the cursor position NOT // defined assertFalse(cursor.isCursorPositionDefined()); // no current key. assertEquals(null, cursor.currentKey()); assertNull(cursor.tuple()); assertFalse(cursor.hasNext()); assertFalse(cursor.hasPrior()); } // last() { final ITupleCursor2<String> cursor = newCursor(btree); assertNull(cursor.last()); // since there was nothing visitable the cursor position NOT // defined assertFalse(cursor.isCursorPositionDefined()); // no current key. assertEquals(null, cursor.currentKey()); assertNull(cursor.tuple()); assertFalse(cursor.hasNext()); assertFalse(cursor.hasPrior()); } // tuple() { final ITupleCursor2<String> cursor = newCursor(btree); assertNull(cursor.tuple()); } // hasNext(), next(). { final ITupleCursor<String> cursor = newCursor(btree); assertFalse(cursor.hasNext()); try { cursor.next(); fail("Expecting " + NoSuchElementException.class); } catch (NoSuchElementException ex) { log.info("Ignoring expected exception: " + ex); } } // hasPrior(), prior(). { final ITupleCursor<String> cursor = newCursor(btree); assertFalse(cursor.hasPrior()); try { cursor.prior(); fail("Expecting " + NoSuchElementException.class); } catch (NoSuchElementException ex) { log.info("Ignoring expected exception: " + ex); } } // seek() { final ITupleCursor<String> cursor = newCursor(btree); assertNull(cursor.seek(1)); assertFalse(cursor.hasPrior()); assertFalse(cursor.hasNext()); } } /* * Test with range limit. Since there is no data in the index the actual * range limits imposed matter very little. */ { final byte[] fromKey = TestKeyBuilder.asSortKey(2); final byte[] toKey = TestKeyBuilder.asSortKey(7); // first() { final ITupleCursor2<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, fromKey, toKey); assertNull(cursor.first()); // since there was nothing visitable the cursor position NOT // defined assertFalse(cursor.isCursorPositionDefined()); // no current key. assertEquals(null, cursor.currentKey()); assertNull(cursor.tuple()); assertFalse(cursor.hasNext()); assertFalse(cursor.hasPrior()); } // last() { final ITupleCursor2<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, fromKey, toKey); assertNull(cursor.last()); // since there was nothing visitable the cursor position NOT // defined assertFalse(cursor.isCursorPositionDefined()); // no current key. assertEquals(null, cursor.currentKey()); assertNull(cursor.tuple()); assertFalse(cursor.hasNext()); assertFalse(cursor.hasPrior()); } // tuple() { final ITupleCursor2<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, fromKey, toKey); assertNull(cursor.tuple()); } // hasNext(), next(). { final ITupleCursor<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, fromKey, toKey); assertFalse(cursor.hasNext()); try { cursor.next(); fail("Expecting " + NoSuchElementException.class); } catch (NoSuchElementException ex) { log.info("Ignoring expected exception: " + ex); } } // hasPrior(), prior(). { final ITupleCursor<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, fromKey, toKey); assertFalse(cursor.hasPrior()); try { cursor.prior(); fail("Expecting " + NoSuchElementException.class); } catch (NoSuchElementException ex) { log.info("Ignoring expected exception: " + ex); } } // seek() { final ITupleCursor<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, fromKey, toKey); assertNull(cursor.seek(4)); assertFalse(cursor.hasPrior()); assertFalse(cursor.hasNext()); } } } /** * Creates, populates and returns a {@link BTree} for * {@link #doOneTupleTest(AbstractBTree)} */ protected BTree getOneTupleBTree() { final BTree btree = BTree.create(new SimpleMemoryRawStore(), new IndexMetadata(UUID.randomUUID())); btree.insert(10, "Bryan"); return btree; } /** * Test helper for fence posts when there is only a single tuple. including * when attempting to visit tuples in a key range that does not overlap with * the tuple that is actually in the index. * * @param btree */ protected void doOneTupleTest(AbstractBTree btree) { /* * Test with no range limits. */ { // first() { final ITupleCursor2<String> cursor = newCursor(btree); assertEquals(new TestTuple<String>(10, "Bryan"), cursor.first()); assertEquals(new TestTuple<String>(10, "Bryan"), cursor.tuple()); assertFalse(cursor.hasNext()); assertFalse(cursor.hasPrior()); } // last() { final ITupleCursor2<String> cursor = newCursor(btree); assertEquals(new TestTuple<String>(10, "Bryan"), cursor.last()); assertEquals(new TestTuple<String>(10, "Bryan"), cursor.tuple()); assertFalse(cursor.hasNext()); assertFalse(cursor.hasPrior()); } // tuple() { final ITupleCursor2<String> cursor = newCursor(btree); assertNull(cursor.tuple()); } // hasNext(), next(). { final ITupleCursor2<String> cursor = newCursor(btree); assertTrue(cursor.hasNext()); assertEquals(new TestTuple<String>(10, "Bryan"), cursor.next()); assertEquals(new TestTuple<String>(10, "Bryan"), cursor.tuple()); assertFalse(cursor.hasNext()); try { cursor.next(); fail("Expecting " + NoSuchElementException.class); } catch (NoSuchElementException ex) { log.info("Ignoring expected exception: " + ex); } } // hasPrior(), prior(). { final ITupleCursor2<String> cursor = newCursor(btree); assertTrue(cursor.hasPrior()); assertEquals(new TestTuple<String>(10, "Bryan"), cursor.prior()); assertEquals(new TestTuple<String>(10, "Bryan"), cursor.tuple()); assertFalse(cursor.hasPrior()); try { cursor.prior(); fail("Expecting " + NoSuchElementException.class); } catch (NoSuchElementException ex) { log.info("Ignoring expected exception: " + ex); } } // seek() (found) { final ITupleCursor2<String> cursor = newCursor(btree); assertEquals(new TestTuple<String>(10, "Bryan"), cursor .seek(10)); assertFalse(cursor.hasPrior()); assertFalse(cursor.hasNext()); } // seek() (not found before a valid tuple) { final ITupleCursor2<String> cursor = newCursor(btree); assertNull(cursor.seek(1)); assertEquals(TestKeyBuilder.asSortKey(1), cursor.currentKey()); assertFalse(cursor.hasPrior()); assertTrue(cursor.hasNext()); assertEquals(new TestTuple<String>(10, "Bryan"), cursor.next()); } // seek() (not found after a valid tuple) { final ITupleCursor2<String> cursor = newCursor(btree); assertNull(cursor.seek(11)); assertTrue(cursor.hasPrior()); assertFalse(cursor.hasNext()); assertEquals(new TestTuple<String>(10, "Bryan"), cursor.prior()); } } /* * Now use a cursor whose key-range constraint does not overlap the * tuple (the cursor is constrained to only visit tuples that are * ordered BEFORE the sole tuple actually present in the index). */ { final byte[] fromKey = TestKeyBuilder.asSortKey(5); final byte[] toKey = TestKeyBuilder.asSortKey(9); // first() { final ITupleCursor2<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, fromKey, toKey); assertEquals(null, cursor.first()); // since there was nothing visitable the cursor position NOT defined assertFalse(cursor.isCursorPositionDefined()); // no current key. assertEquals(null,cursor.currentKey()); assertFalse(cursor.hasNext()); assertFalse(cursor.hasPrior()); } // last() { final ITupleCursor2<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, fromKey, toKey); assertEquals(null, cursor.last()); // since there was nothing visitable the cursor position NOT defined assertFalse(cursor.isCursorPositionDefined()); // no current key. assertEquals(null,cursor.currentKey()); assertFalse(cursor.hasNext()); assertFalse(cursor.hasPrior()); } // tuple() { final ITupleCursor2<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, fromKey, toKey); assertNull(cursor.tuple()); } // hasNext(), next(). { final ITupleCursor2<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, fromKey, toKey); assertFalse(cursor.hasNext()); try { cursor.next(); fail("Expecting " + NoSuchElementException.class); } catch (NoSuchElementException ex) { log.info("Ignoring expected exception: " + ex); } } // hasPrior(), prior(). { final ITupleCursor2<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, fromKey, toKey); assertFalse(cursor.hasPrior()); try { cursor.prior(); fail("Expecting " + NoSuchElementException.class); } catch (NoSuchElementException ex) { log.info("Ignoring expected exception: " + ex); } } // seek() (not found) { final ITupleCursor2<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, fromKey, toKey); assertNull(cursor.seek(7)); assertEquals(TestKeyBuilder.asSortKey(7), cursor.currentKey()); assertFalse(cursor.hasPrior()); assertFalse(cursor.hasNext()); } } /* * Now use a cursor whose key-range constraint does not overlap the * tuple (the cursor is constrained to only visit tuples that are * ordered AFTER the sole tuple actually present in the index). */ { final byte[] fromKey = TestKeyBuilder.asSortKey(15); final byte[] toKey = TestKeyBuilder.asSortKey(19); // first() { final ITupleCursor2<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, fromKey, toKey); assertEquals(null, cursor.first()); assertFalse(cursor.hasNext()); assertFalse(cursor.hasPrior()); } // last() { final ITupleCursor2<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, fromKey, toKey); assertEquals(null, cursor.last()); assertFalse(cursor.hasNext()); assertFalse(cursor.hasPrior()); } // tuple() { final ITupleCursor2<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, fromKey, toKey); assertNull(cursor.tuple()); } // hasNext(), next(). { final ITupleCursor2<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, fromKey, toKey); assertFalse(cursor.hasNext()); try { cursor.next(); fail("Expecting " + NoSuchElementException.class); } catch (NoSuchElementException ex) { log.info("Ignoring expected exception: " + ex); } } // hasPrior(), prior(). { final ITupleCursor2<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, fromKey, toKey); assertFalse(cursor.hasPrior()); try { cursor.prior(); fail("Expecting " + NoSuchElementException.class); } catch (NoSuchElementException ex) { log.info("Ignoring expected exception: " + ex); } } // seek() (not found) { final ITupleCursor2<String> cursor = newCursor(btree, IRangeQuery.DEFAULT, fromKey, toKey); assertNull(cursor.seek(17)); assertEquals(TestKeyBuilder.asSortKey(17), cursor.currentKey()); assertFalse(cursor.hasPrior()); assertFalse(cursor.hasNext()); } } } /** * Compares two tuples for equality based on their data (flags, keys, * values, deleted marker, and version timestamp). * <p> * Note: This will fail if you apply it to tuples reported by * {@link ITupleIterator}s whose DELETE flag was different since it verifies * the DELETE flag state and that is a property of the iterator NOT the * tuple. Whether or not a tuple is deleted is detected using * {@link ITuple#isDeletedVersion()}. * * @param expected * @param actual */ public static void assertEquals(ITuple expected, ITuple actual) { if (expected == null) { assertNull("Expecting a null tuple", actual); return; } else { assertNotNull("Not expecting a null tuple", actual); } assertEquals("flags.KEYS", ((expected.flags() & IRangeQuery.KEYS) != 0), ((actual.flags() & IRangeQuery.KEYS) != 0)); assertEquals("flags.VALS", ((expected.flags() & IRangeQuery.VALS) != 0), ((actual.flags() & IRangeQuery.VALS) != 0)); assertEquals("flags.DELETED",// ((expected.flags() & IRangeQuery.DELETED) != 0),// ((actual.flags() & IRangeQuery.DELETED) != 0)); assertEquals("flags.REMOVEALL",// ((expected.flags() & IRangeQuery.REMOVEALL) != 0),// ((actual.flags() & IRangeQuery.REMOVEALL) != 0)); assertEquals("flags.CURSOR",// ((expected.flags() & IRangeQuery.CURSOR) != 0),// ((actual.flags() & IRangeQuery.CURSOR) != 0)); assertEquals("flags.REVERSE",// ((expected.flags() & IRangeQuery.REVERSE) != 0),// ((actual.flags() & IRangeQuery.REVERSE) != 0)); assertEquals("flags.READONLY",// ((expected.flags() & IRangeQuery.READONLY) != 0),// ((actual.flags() & IRangeQuery.READONLY) != 0)); assertEquals("flags.PARALLEL",// ((expected.flags() & IRangeQuery.PARALLEL) != 0),// ((actual.flags() & IRangeQuery.PARALLEL) != 0)); assertEquals("flags", expected.flags(), actual.flags()); assertEquals("key", expected.getKey(), actual.getKey()); assertEquals("deleted", expected.isDeletedVersion(), actual .isDeletedVersion()); if (!expected.isDeletedVersion()) { assertEquals("val", expected.getValue(), actual.getValue()); } assertEquals("isNull", expected.isNull(), actual.isNull()); assertEquals("timestamp", expected.getVersionTimestamp(), actual .getVersionTimestamp()); } }