/* 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 */ package com.bigdata.btree; import java.nio.ByteBuffer; import java.util.UUID; import com.bigdata.btree.data.ILeafData; import com.bigdata.io.TestCase3; import com.bigdata.rawstore.IRawStore; import com.bigdata.rawstore.RawStoreDelegate; import com.bigdata.rawstore.SimpleMemoryRawStore; /** * Unit tests for a B+Tree with raw record support enabled (this is where a * large <code>byte[]</code> value is written directly onto the backing store * rather than being stored within the leaf). * * @author thompsonbry */ public class TestRawRecords extends AbstractBTreeTestCase { public TestRawRecords() { } public TestRawRecords(String name) { super(name); } /** * Unit test for the insert of a large <code>byte[]</code> value into an * index such that it is represented as a raw record on the backing store * rather than inline within the B+Tree leaf. */ public void test_insertLargeValue() { final IRawStore store = new SimpleMemoryRawStore(); final IndexMetadata metadata = new IndexMetadata(UUID.randomUUID()); metadata.setRawRecords(true); metadata.setMaxRecLen(64); final BTree btree = BTree.create(store, metadata); assertEquals(64, btree.getMaxRecLen()); assertTrue(((ILeafData) btree.getRoot()).hasRawRecords()); final byte[] key = new byte[] { 1 }; final byte[] val = new byte[btree.getMaxRecLen() + 1]; r.nextBytes(val); // insert an entry under that key. assertNull(btree.insert(key, val)); // examine the root leaf. final Leaf root = (Leaf) btree.getRoot(); // the addr of the raw record. final long addr = root.getRawRecord(0/* entryIndex */); assertTrue(addr != IRawStore.NULL); // read the raw record from the store. final ByteBuffer actual = btree.readRawRecord(addr); // verify that the expected data were read. TestCase3.assertEquals(val, actual); } /** * Unit test in which we update a small value (inline within the leaf) with * a large value (stored as a raw record). */ public void test_updateSmallValueWithLargeValue() { final IRawStore store = new SimpleMemoryRawStore(); final IndexMetadata metadata = new IndexMetadata(UUID.randomUUID()); metadata.setRawRecords(true); metadata.setMaxRecLen(64); final BTree btree = BTree.create(store, metadata); assertEquals(64, btree.getMaxRecLen()); assertTrue(((ILeafData) btree.getRoot()).hasRawRecords()); final byte[] key = new byte[] { 1 }; final byte[] val = new byte[btree.getMaxRecLen() - 1]; r.nextBytes(val); // insert an entry under that key. assertNull(btree.insert(key, val)); // examine the root leaf. final Leaf root = (Leaf) btree.getRoot(); // verify not a raw record. assertEquals(IRawStore.NULL,root.getRawRecord(0/* entryIndex */)); // create a new byte[] which will be handled as a large record. final byte[] newval = new byte[btree.getMaxRecLen() + 1]; r.nextBytes(newval); // update the value under that key. final byte[] oldval = btree.insert(key, newval); // verify the old value was returned. assertEquals(val, oldval); // the addr of the raw record. final long addr = root.getRawRecord(0/* entryIndex */); // verify a raw record. assertTrue(addr != IRawStore.NULL); // read the raw record from the store. final ByteBuffer actual = btree.readRawRecord(addr); // verify that the expected data were read. TestCase3.assertEquals(newval, actual); } /** * Unit test in which we update a large value (represented directly on the * backing store) with a small value (inline within the leaf). The test * verifies that the original large value is deleted. */ public void test_updateLargeValueWithSmallValue() { final MyRawStore store = new MyRawStore(new SimpleMemoryRawStore()); final IndexMetadata metadata = new IndexMetadata(UUID.randomUUID()); metadata.setRawRecords(true); metadata.setMaxRecLen(64); final BTree btree = BTree.create(store, metadata); assertEquals(64, btree.getMaxRecLen()); assertTrue(((ILeafData) btree.getRoot()).hasRawRecords()); // create a byte[] which will be handled as a large record. final byte[] key = new byte[] { 1 }; final byte[] val = new byte[btree.getMaxRecLen() + 1]; r.nextBytes(val); // insert an entry under that key. assertNull(btree.insert(key, val)); // examine the root leaf. final Leaf root = (Leaf) btree.getRoot(); // the addr of the raw record. final long addr = root.getRawRecord(0/* entryIndex */); // verify a raw record. assertTrue(addr != IRawStore.NULL); // read the raw record from the store. final ByteBuffer actual = btree.readRawRecord(addr); // verify that the expected data were read. TestCase3.assertEquals(val, actual); // create a new byte[] which not will be handled as a large record. final byte[] newval = new byte[btree.getMaxRecLen() - 1]; r.nextBytes(newval); // setup for the expected delete of the raw record. store.expectDelete = addr; // update the value under that key. final byte[] oldval = btree.insert(key, newval); // verify that the record was deleted. assertEquals(IRawStore.NULL, store.expectDelete); // verify the old value was returned. assertEquals(val, oldval); // verify not a raw record. assertEquals(IRawStore.NULL,root.getRawRecord(0/* entryIndex */)); } /** * Unit test in which we update a large value (represented directly on the * backing store) with another large value (also represented directly on the * store). The test verifies that the original large value is deleted. */ public void test_updateLargeValueWithLargeValue() { final MyRawStore store = new MyRawStore(new SimpleMemoryRawStore()); final IndexMetadata metadata = new IndexMetadata(UUID.randomUUID()); metadata.setRawRecords(true); metadata.setMaxRecLen(64); final BTree btree = BTree.create(store, metadata); assertEquals(64, btree.getMaxRecLen()); assertTrue(((ILeafData) btree.getRoot()).hasRawRecords()); // create a byte[] which will be handled as a large record. final byte[] key = new byte[] { 1 }; final byte[] val = new byte[btree.getMaxRecLen() + 1]; r.nextBytes(val); // insert an entry under that key. assertNull(btree.insert(key, val)); // examine the root leaf. final Leaf root = (Leaf) btree.getRoot(); // the addr of the raw record. final long addr = root.getRawRecord(0/* entryIndex */); // verify a raw record. assertTrue(addr != IRawStore.NULL); // read the raw record from the store. final ByteBuffer actual = btree.readRawRecord(addr); // verify that the expected data were read. TestCase3.assertEquals(val, actual); // create a new byte[] which will be handled as a large record. final byte[] newval = new byte[btree.getMaxRecLen() + 1]; r.nextBytes(newval); // setup for the expected delete of the raw record. store.expectDelete = addr; // update the value under that key. final byte[] oldval = btree.insert(key, newval); // verify that the record was deleted. assertEquals(IRawStore.NULL, store.expectDelete); // verify the old value was returned. assertEquals(val, oldval); // the addr of the raw record. final long addr2 = root.getRawRecord(0/* entryIndex */); // verify a raw record. assertTrue(addr2 != IRawStore.NULL); // read the raw record from the store. final ByteBuffer actual2 = btree.readRawRecord(addr2); // verify that the expected data were read. TestCase3.assertEquals(newval, actual2); } /** * Unit test in which we insert a large value (represented directly on the * backing store) and then delete the key under which that value was stored. * The test verifies that the original large value is deleted. */ public void test_insertLargeValueThenDelete() { final MyRawStore store = new MyRawStore(new SimpleMemoryRawStore()); final IndexMetadata metadata = new IndexMetadata(UUID.randomUUID()); metadata.setRawRecords(true); metadata.setMaxRecLen(64); final BTree btree = BTree.create(store, metadata); assertEquals(64, btree.getMaxRecLen()); assertTrue(((ILeafData) btree.getRoot()).hasRawRecords()); // create a byte[] which will be handled as a large record. final byte[] key = new byte[] { 1 }; final byte[] val = new byte[btree.getMaxRecLen() + 1]; r.nextBytes(val); // insert an entry under that key. assertNull(btree.insert(key, val)); // examine the root leaf. final Leaf root = (Leaf) btree.getRoot(); // the addr of the raw record. final long addr = root.getRawRecord(0/* entryIndex */); // verify a raw record. assertTrue(addr != IRawStore.NULL); // read the raw record from the store. final ByteBuffer actual = btree.readRawRecord(addr); // verify that the expected data were read. TestCase3.assertEquals(val, actual); // setup for the expected delete of the raw record. store.expectDelete = addr; // update the value under that key. final byte[] oldval = btree.remove(key); // verify that the record was deleted. assertEquals(IRawStore.NULL, store.expectDelete); // verify the old value was returned. assertEquals(val, oldval); } /** * Helper class is used to watch for deletes of raw records from the backing * store. * * @author thompsonbry */ private static class MyRawStore extends RawStoreDelegate { long expectDelete = IRawStore.NULL; public MyRawStore(final IRawStore delegate) { super(delegate); } public void delete(long addr) { if (expectDelete != IRawStore.NULL) { assertEquals(expectDelete, addr); expectDelete = IRawStore.NULL; } delegate.delete(addr); } } }