/**
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 Apr 23, 2007
*/
package com.bigdata.service;
import java.io.IOException;
import java.util.UUID;
import com.bigdata.btree.IIndex;
import com.bigdata.btree.IRangeQuery;
import com.bigdata.btree.ITuple;
import com.bigdata.btree.ITupleIterator;
import com.bigdata.btree.ITupleSerializer;
import com.bigdata.btree.IndexMetadata;
import com.bigdata.btree.NOPTupleSerializer;
import com.bigdata.btree.TestTuple;
import com.bigdata.btree.filter.TupleFilter;
import com.bigdata.btree.keys.KeyBuilder;
import com.bigdata.btree.keys.TestKeyBuilder;
import com.bigdata.btree.proc.BatchInsert.BatchInsertConstructor;
import com.bigdata.journal.ITx;
import com.bigdata.mdi.PartitionLocator;
import com.bigdata.service.ndx.ClientIndexView;
import com.bigdata.service.ndx.PartitionedTupleIterator;
import com.bigdata.util.BytesUtil;
/**
* Test suite for the {@link IRangeQuery} API.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
* @version $Id$
*/
public class TestRangeQuery extends AbstractEmbeddedFederationTestCase {
public TestRangeQuery() {
}
public TestRangeQuery(String name) {
super(name);
}
/*
* Range query tests with static partitions.
*/
/**
* Range count tests with two (2) static partitions where the successor of a
* key is found in the next partition (tests the fence post for the mapping
* of the rangeCount operation over the different partitions).
*
* @throws IOException
*/
public void test_rangeCount_staticPartitions_01() throws IOException {
final String name = "testIndex";
final IndexMetadata metadata = new IndexMetadata(name,UUID.randomUUID());
/*
* Register and statically partition an index.
*/
fed.registerIndex( metadata, new byte[][]{//
new byte[]{}, // keys less than 5...
new byte[]{5} // keys GTE 5....
}, new UUID[]{//
dataService0.getServiceUUID(),
dataService1.getServiceUUID()
});
/*
* Request a view of that partitioned index.
*/
ClientIndexView ndx = (ClientIndexView) fed.getIndex(name,ITx.UNISOLATED);
/*
* Range count the view to verify that it is empty.
*/
assertEquals("rangeCount",0,ndx.rangeCount(null, null));
/*
* Get metadata for the index partitions that we will need to verify
* the splits.
*/
final PartitionLocator pmd0 = ndx.getMetadataIndex().get(new byte[]{});
final PartitionLocator pmd1 = ndx.getMetadataIndex().get(new byte[]{5});
assertNotNull("partition#0",pmd0);
assertNotNull("partition#1",pmd1);
/*
* Insert keys into each partition, but not on the partition
* separator.
*/
ndx.insert(new byte[]{3}, new byte[]{3});
ndx.insert(new byte[]{4}, new byte[]{4});
ndx.insert(new byte[]{6}, new byte[]{6});
/*
* Verify range counts.
*/
assertEquals("rangeCount",2,ndx.rangeCount(null, new byte[]{5}));
assertEquals("rangeCount",1,ndx.rangeCount(new byte[]{5},null));
assertEquals("rangeCount",3,ndx.rangeCount(null, null));
/*
* Insert another key right on the partition separator.
*/
ndx.insert(new byte[]{5}, new byte[]{5});
/*
* Verify range counts.
*/
assertEquals("rangeCount",2,ndx.rangeCount(null, new byte[]{5}));
assertEquals("rangeCount",2,ndx.rangeCount(new byte[]{5},null));
assertEquals("rangeCount",4,ndx.rangeCount(null, null));
}
/**
* Test unbounded range query with an empty index and two partitions.
*
* @throws IOException
*/
public void test_rangeQuery_staticPartitions_unbounded_emptyIndex_2partitions() throws IOException {
final String name = "testIndex";
final IndexMetadata metadata = new IndexMetadata(name,UUID.randomUUID());
fed.registerIndex(metadata, new byte[][]{//
new byte[]{},
new byte[]{5}
}, new UUID[]{//
dataService0.getServiceUUID(),
dataService1.getServiceUUID()
});
final IIndex ndx = fed.getIndex(name,ITx.UNISOLATED);
/*
* Query entire key range.
*/
{
final PartitionedTupleIterator itr = (PartitionedTupleIterator) ndx
.rangeIterator(null, null);
// nothing visited yet.
assertEquals("nvisited", 0, itr.getVisitedCount());
// no partitions queried yet.
assertEquals("npartitions", 0, itr.getPartitionCount());
// look for the first matching index entry (there are none).
assertFalse("hasNext", itr.hasNext());
// nothing was visited.
assertEquals("nvisited", 0, itr.getVisitedCount());
// we queried two index partitions.
assertEquals("npartitions", 2, itr.getPartitionCount());
}
}
/**
* Test unbounded range query with one entry in the index and two index
* partitions. The entry is in the first partition.
*
* @throws IOException
*/
public void test_rangeQuery_staticPartitions_unbounded_1entry_2partitions_01() throws IOException {
final String name = "testIndex";
final IndexMetadata metadata = new IndexMetadata(name,UUID.randomUUID());
fed.registerIndex( metadata, new byte[][]{//
new byte[]{},
new byte[]{5}
}, new UUID[]{//
dataService0.getServiceUUID(),
dataService1.getServiceUUID()
});
final IIndex ndx = fed.getIndex(name,ITx.UNISOLATED);
/*
* Insert an entry into the first partition.
*/
ndx.insert(new byte[] { 1 }, new byte[] { 1 });
/*
* Query the entire key range.
*/
{
final ITupleIterator itr = ndx.rangeIterator(null, null);
assertTrue("hasNext", itr.hasNext());
final ITuple tuple = itr.next();
assertEquals("getKey()", new byte[] { 1 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 1 }, tuple.getValue());
assertFalse("hasNext", itr.hasNext());
}
}
/**
* Test unbounded range query with one entry in the index and two index
* partitions. The entry is in the 2nd partition.
*
* @throws IOException
*/
public void test_rangeQuery_staticPartitions_unbounded_1entry_2partitions_02() throws IOException {
final String name = "testIndex";
final IndexMetadata metadata = new IndexMetadata(name,UUID.randomUUID());
fed.registerIndex( metadata, new byte[][]{//
new byte[]{},
new byte[]{5}
}, new UUID[]{//
dataService0.getServiceUUID(),
dataService1.getServiceUUID()
});
IIndex ndx = fed.getIndex(name,ITx.UNISOLATED);
/*
* Insert an entry into the 2nd partition.
*/
ndx.insert(new byte[] { 5 }, new byte[] { 5 });
/*
* Query the entire key range.
*/
{
final ITupleIterator itr = ndx.rangeIterator(null, null);
assertTrue("hasNext", itr.hasNext());
final ITuple tuple = itr.next();
assertEquals("getKey()", new byte[] { 5 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 5 }, tuple.getValue());
assertFalse("hasNext", itr.hasNext());
}
}
/**
* Test unbounded range query with two entries in the index and two index
* partitions. There is one entry in each partition.
*
* @throws IOException
*/
public void test_rangeQuery_staticPartitions_unbounded_2entries_2partitions_01() throws IOException {
final String name = "testIndex";
final IndexMetadata metadata = new IndexMetadata(name,UUID.randomUUID());
fed.registerIndex(metadata, new byte[][]{//
new byte[]{},
new byte[]{5}
}, new UUID[]{//
dataService0.getServiceUUID(),
dataService1.getServiceUUID()
});
final IIndex ndx = fed.getIndex(name,ITx.UNISOLATED);
/*
* Insert an entry into the first partition.
*/
ndx.insert(new byte[] { 1 }, new byte[] { 1 });
/*
* Insert an entry into the 2nd partition.
*/
ndx.insert(new byte[] { 5 }, new byte[] { 5 });
/*
* Query the entire key range.
*/
{
final ITupleIterator itr = ndx.rangeIterator(null, null);
assertTrue("hasNext", itr.hasNext());
ITuple tuple = itr.next();
assertEquals("getKey()",new byte[]{1},tuple.getKey());
assertEquals("getValue()",new byte[]{1},tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()",new byte[]{5},tuple.getKey());
assertEquals("getValue()",new byte[]{5},tuple.getValue());
assertFalse("hasNext", itr.hasNext());
}
}
/**
* Test unbounded range query with two entries in the index and two index
* partitions. Both entries are in the 1st index partition and we limit the
* data service query to one result per query.
*
*/
public void test_rangeQuery_staticPartitions_unbounded_2entries_2partitions_02() throws IOException {
final String name = "testIndex";
final IndexMetadata metadata = new IndexMetadata(name,UUID.randomUUID());
fed.registerIndex(metadata, new byte[][]{//
new byte[]{},
new byte[]{5}
}, new UUID[]{//
dataService0.getServiceUUID(),
dataService1.getServiceUUID()
});
final IIndex ndx = fed.getIndex(name, ITx.UNISOLATED);
/*
* Insert the entries into the first partition.
*/
ndx.insert(new byte[] { 1 }, new byte[] { 1 });
ndx.insert(new byte[] { 2 }, new byte[] { 2 });
/*
* Query the entire key range.
*/
{
// Limit to one entry per data service query.
final int capacity = 1;
final int flags = IRangeQuery.KEYS | IRangeQuery.VALS;
final ITupleIterator itr = ndx.rangeIterator(null, null, capacity,
flags, null/* filter */);
assertTrue("hasNext", itr.hasNext());
ITuple tuple = itr.next();
assertEquals("getKey()", new byte[] { 1 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 1 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 2 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 2 }, tuple.getValue());
assertFalse("hasNext", itr.hasNext());
}
}
/**
* Test of {@link IRangeQuery#REMOVEALL} using a limit (capacity := 1). This
* form of the iterator is used to support queue constructs since the delete
* is performed on the unisolated index. The state of the index is verified
* afterwards.
*
* @throws IOException
*/
public void test_removeAll_limit1_twoPartitions() throws IOException {
final String name = "testIndex";
final IndexMetadata metadata = new IndexMetadata(name,UUID.randomUUID());
fed.registerIndex(metadata, new byte[][]{//
new byte[]{},
TestKeyBuilder.asSortKey(5) // the half-way point.
}, new UUID[]{//
dataService0.getServiceUUID(),
dataService1.getServiceUUID()
});
final IIndex ndx = fed.getIndex(name,ITx.UNISOLATED);
final int nentries = 10;
final byte[][] keys = new byte[nentries][];
final byte[][] vals = new byte[nentries][];
for(int i=0; i<nentries; i++) {
keys[i] = TestKeyBuilder.asSortKey(i);
vals[i] = new byte[4];
r.nextBytes(vals[i]);
}
ndx.submit(0/*fromIndex*/,nentries/*toIndex*/, keys, vals,
BatchInsertConstructor.RETURN_NO_VALUES, null/*handler*/);
assertEquals(nentries, ndx.rangeCount(null,null));
/*
* Range delete the keys w/ limit of ONE (1).
*/
{
ITupleIterator itr = ndx.rangeIterator(
null,// fromKey,
null,// toKey
1, // capacity (aka limit)
// IRangeQuery.KEYS | IRangeQuery.VALS |
IRangeQuery.REMOVEALL,
null// filter
);
/*
* This should delete the first index entry but NOT buffer the next
* entry.
*/
itr.next();
}
/*
* Now verify the state of the index.
*/
{
// Note: range count is unchanged since delete markers are in use.
assertEquals(nentries, ndx.rangeCount(null,null));
int ndeleted = 0;
int nremaining = 0;
// iterator visits deleted entries too.
final ITupleIterator itr = ndx.rangeIterator(null, null,
0/* capacity */, IRangeQuery.ALL, null/* filter */);
int index = 0;
while(itr.hasNext()) {
final ITuple tuple = itr.next();
final byte[] key = tuple.getKey();
final int i = KeyBuilder.decodeInt(key, 0);
assertEquals(index, i);
assertEquals(keys[i], key);
if (index == 0) {
assertTrue(tuple.isDeletedVersion());
}
if(tuple.isDeletedVersion()) {
ndeleted++;
} else {
final byte[] val = tuple.getValue();
assertEquals(vals[i], val);
nremaining++;
}
index++;
}
assertEquals("#remaining", nentries - 1, nremaining);
}
}
/**
* Test of {@link IRangeQuery#REMOVEALL} using a filter. Only the even keys
* are deleted. The state of the index is verified afterwards.
*
* @throws IOException
*/
public void test_removeAll() throws IOException {
final String name = "testIndex";
final IndexMetadata metadata = new IndexMetadata(name,UUID.randomUUID());
metadata.setTupleSerializer(NOPTupleSerializer.INSTANCE);
fed.registerIndex(metadata, new byte[][]{//
new byte[]{},
TestKeyBuilder.asSortKey(5) // the half-way point.
}, new UUID[]{//
dataService0.getServiceUUID(),
dataService1.getServiceUUID()
});
IIndex ndx = fed.getIndex(name,ITx.UNISOLATED);
final int capacity = 5;
final int nentries = 10;
final byte[][] keys = new byte[nentries][];
final byte[][] vals = new byte[nentries][];
for(int i=0; i<nentries; i++) {
keys[i] = TestKeyBuilder.asSortKey(i);
vals[i] = new byte[4];
r.nextBytes(vals[i]);
}
ndx.submit(0/* fromIndex */, nentries/* toIndex */, keys, vals,
BatchInsertConstructor.RETURN_NO_VALUES, null/* handler */);
final TupleFilter filter = new TupleFilter() {
private static final long serialVersionUID = 1L;
protected boolean isValid(ITuple tuple) {
final byte[] key = tuple.getKey();
final int i = KeyBuilder.decodeInt(key, 0);
// delete only the even keys.
if (i % 2 == 0)
return true;
return false;
}
};
/*
* Range delete the keys matching the filter.
*/
{
ITupleIterator itr = ndx.rangeIterator(
null/* fromKey */,
null/* toKey */,
capacity,
IRangeQuery.KEYS | IRangeQuery.VALS | IRangeQuery.REMOVEALL,
filter);
int ndeleted = 0;
while (itr.hasNext()) {
ITuple tuple = itr.next();
byte[] key = tuple.getKey();
int i = KeyBuilder.decodeInt(key, 0);
// delete only the even keys.
assertEquals(0, (i % 2));
byte[] val = tuple.getValue();
assertEquals(keys[i], key);
assertEquals(vals[i], val);
ndeleted++;
}
assertEquals("#deleted", 5, ndeleted);
}
/*
* Now verify the state of the index.
*/
{
int nremaining = 0;
ITupleIterator itr = ndx.rangeIterator(null,null);
while(itr.hasNext()) {
ITuple tuple = itr.next();
byte[] key = tuple.getKey();
int i = KeyBuilder.decodeInt(key, 0);
// deleted only the even keys.
assertNotSame(0, (i % 2));
byte[] val = tuple.getValue();
assertEquals(keys[i], key);
assertEquals(vals[i], val);
nremaining++;
}
assertEquals("#remaining",5,nremaining);
}
}
/**
* Test the ability to scan a partitioned index in forward and reverse
* order. The test verifies that index partitions are visited in the correct
* order, that the chunks within each index partition are visited in the
* correct order, and that the tuples within each chunk are visited in the
* correct order. One of the index partitions is deliberately left empty in
* order to verify that the iterator will correctly cross over an index
* partition in which the chunked iterator does not visit anything.
*
* @throws IOException
*/
public void test_reverseScan() throws IOException {
final String name = "testIndex";
final IndexMetadata metadata = new IndexMetadata(name,UUID.randomUUID());
/*
* Note: Run with the {7} partition defined but empty to verify that the
* iterator is robust to empty partitions!
*/
fed.registerIndex(metadata, new byte[][]{//
new byte[]{},
new byte[]{4},
new byte[]{7},
new byte[]{10},
}, null/* dataServiceUUIDs */
);
final IIndex ndx = fed.getIndex(name,ITx.UNISOLATED);
/*
* Insert entries into the first partition.
*/
ndx.insert(new byte[] { 1 }, new byte[] { 1 });
ndx.insert(new byte[] { 2 }, new byte[] { 2 });
ndx.insert(new byte[] { 3 }, new byte[] { 3 });
/*
* Insert entries into the 2nd partition.
*/
ndx.insert(new byte[] { 4 }, new byte[] { 4 });
ndx.insert(new byte[] { 5 }, new byte[] { 5 });
ndx.insert(new byte[] { 6 }, new byte[] { 6 });
/*
* The 3rd partition is left empty to check for fence posts.
*/
/*
* Insert entries into the 4th partition.
*/
ndx.insert(new byte[] { 10 }, new byte[] { 10 });
ndx.insert(new byte[] { 11 }, new byte[] { 11 });
ndx.insert(new byte[] { 12 }, new byte[] { 12 });
/*
* Query the entire key range (forward scan).
*
* Note: This tests with a capacity of (2) in order to force the
* iterator to read from each partition in chunks of no more than (2)
* tuples at a time. This helps verify that the base iterator is in
* forward order, that the chunked iterator is moving forwards through
* the index partition, and that the total iterator is moving forwards
* through the index partitions.
*/
final int capacity = 2;
{
final ITupleIterator itr = ndx.rangeIterator(null, null, capacity,
IRangeQuery.DEFAULT, null);
ITuple tuple;
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 1 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 1 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 2 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 2 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 3 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 3 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 4 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 4 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 5 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 5 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 6 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 6 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 10 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 10 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 11 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 11 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 12 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 12 }, tuple.getValue());
assertFalse("hasNext", itr.hasNext());
}
/*
* Query the entire key range (reverse scan).
*
* Note: This tests with a capacity of (2) in order to force the
* iterator to read from each partition in chunks of no more than (2)
* tuples at a time. This helps verify that the base iterator is in
* reverse order, that the chunked iterator is moving backwards through
* the index partition, and that the total iterator is moving backwards
* through the index partitions.
*/
{
final ITupleIterator itr = ndx
.rangeIterator(null, null, capacity, IRangeQuery.DEFAULT
| IRangeQuery.REVERSE, null/* filter */);
ITuple tuple;
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 12 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 12 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 11 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 11 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 10 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 10 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 6 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 6 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 5 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 5 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 4 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 4 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 3 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 3 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 2 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 2 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 1 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 1 }, tuple.getValue());
assertFalse("hasNext", itr.hasNext());
}
/*
* Insert entries into the 3rd partition.
*/
ndx.insert(new byte[] { 7 }, new byte[] { 7 });
ndx.insert(new byte[] { 8 }, new byte[] { 8 });
ndx.insert(new byte[] { 9 }, new byte[] { 9 });
/*
* Filter that excludes tuples in the 3rd index partition.
*/
final TupleFilter filter = new TupleFilter(){
@Override
protected boolean isValid(ITuple tuple) {
final byte[] key = tuple.getKey();
if (key[0] >= 7 && key[0] < 10)
return false;
return true;
}};
// forward scan w/ filter
{
final ITupleIterator itr = ndx.rangeIterator(null, null,
capacity, IRangeQuery.DEFAULT, filter);
ITuple tuple;
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 1 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 1 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 2 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 2 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 3 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 3 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 4 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 4 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 5 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 5 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 6 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 6 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 10 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 10 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 11 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 11 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 12 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 12 }, tuple.getValue());
assertFalse("hasNext", itr.hasNext());
}
// reverse scan w/ filter.
{
final ITupleIterator itr = ndx.rangeIterator(null, null, capacity,
IRangeQuery.DEFAULT | IRangeQuery.REVERSE, filter);
ITuple tuple;
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 12 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 12 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 11 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 11 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 10 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 10 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 6 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 6 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 5 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 5 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 4 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 4 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 3 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 3 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 2 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 2 }, tuple.getValue());
assertTrue("hasNext", itr.hasNext());
tuple = itr.next();
assertEquals("getKey()", new byte[] { 1 }, tuple.getKey());
assertEquals("getValue()", new byte[] { 1 }, tuple.getValue());
assertFalse("hasNext", itr.hasNext());
}
}
/**
* Basic unit tests for the parallel range iterator.
*
* FIXME write unit tests for the parallel range iterator running across
* one, one or multiple index partitions, with all of its various flags
* (including REMOVEALL), when a stale locator exception must be handled,
* when the iterator is closed early, and when it is closed early with a
* read consistent tx created on the caller's behalf.
*/
@SuppressWarnings("unchecked")
public void test_parallelRangeIterator() {
final String name = "testIndex";
final IndexMetadata metadata = new IndexMetadata(name, UUID
.randomUUID());
// the keys and values at the application level are just byte[]s.
metadata.setTupleSerializer(NOPTupleSerializer.INSTANCE);
/*
* Note: Run with the {7} partition defined but empty to verify that the
* iterator is robust to empty partitions!
*/
fed.registerIndex(metadata, new byte[][]{//
new byte[]{},
new byte[]{4},
new byte[]{7},
new byte[]{10},
}, null/* dataServiceUUIDs */
);
final IIndex ndx = fed.getIndex(name,ITx.UNISOLATED);
/*
* Insert entries into the first partition.
*/
ndx.insert(new byte[] { 1 }, new byte[] { 1 });
ndx.insert(new byte[] { 2 }, new byte[] { 2 });
ndx.insert(new byte[] { 3 }, new byte[] { 3 });
/*
* Insert entries into the 2nd partition.
*/
ndx.insert(new byte[] { 4 }, new byte[] { 4 });
ndx.insert(new byte[] { 5 }, new byte[] { 5 });
ndx.insert(new byte[] { 6 }, new byte[] { 6 });
/*
* The 3rd partition is left empty to check for fence posts.
*/
/*
* Insert entries into the 4th partition.
*/
ndx.insert(new byte[] { 10 }, new byte[] { 10 });
ndx.insert(new byte[] { 11 }, new byte[] { 11 });
ndx.insert(new byte[] { 12 }, new byte[] { 12 });
final int capacity = 0; // default capacity.
final int flags = IRangeQuery.DEFAULT|IRangeQuery.PARALLEL;
final long timestamp = 0L;
final ITupleSerializer tupleSer = ndx.getIndexMetadata()
.getTupleSerializer();
{
/*
* Query the key range on a single index partition which corresponds
* to all of the data on that index partition.
*/
final ITupleIterator itr = ndx
.rangeIterator(new byte[] { 10 }/* fromKey */,
new byte[] { 13 }/* toKey */, capacity, flags, null/* filter */);
assertSameIteratorAnyOrder(new ITuple[] {//
new TestTuple(flags, tupleSer, new byte[] { 10 },
new byte[] { 10 }, false/* deleted */,
timestamp),
new TestTuple(flags, tupleSer, new byte[] { 11 },
new byte[] { 11 }, false/* deleted */,
timestamp),
new TestTuple(flags, tupleSer, new byte[] { 12 },
new byte[] { 12 }, false/* deleted */,
timestamp), }, itr);
}
{
/*
* Query only a subrange of the data on one index partition.
*/
final ITupleIterator itr = ndx
.rangeIterator(new byte[] { 11 }/* fromKey */,
new byte[] { 12 }/* toKey */, capacity, flags, null/* filter */);
assertSameIteratorAnyOrder(new ITuple[] {//
new TestTuple(flags, tupleSer, new byte[] { 11 },
new byte[] { 11 }, false/* deleted */,
timestamp),
}, itr);
}
{
/*
* Query the entire key range (forward scan).
*/
final ITupleIterator itr = ndx.rangeIterator(null/* fromKey */,
null/* toKey */, capacity, flags, null/* filter */);
assertSameIteratorAnyOrder(new ITuple[] {//
new TestTuple(flags, tupleSer, new byte[] { 1 },
new byte[] { 1 }, false/* deleted */,
timestamp),
new TestTuple(flags, tupleSer, new byte[] { 2 },
new byte[] { 2 }, false/* deleted */,
timestamp),
new TestTuple(flags, tupleSer, new byte[] { 3 },
new byte[] { 3 }, false/* deleted */,
timestamp),
new TestTuple(flags, tupleSer, new byte[] { 4 },
new byte[] { 4 }, false/* deleted */,
timestamp),
new TestTuple(flags, tupleSer, new byte[] { 5 },
new byte[] { 5 }, false/* deleted */,
timestamp),
new TestTuple(flags, tupleSer, new byte[] { 6 },
new byte[] { 6 }, false/* deleted */,
timestamp),
new TestTuple(flags, tupleSer, new byte[] { 10 },
new byte[] { 10 }, false/* deleted */,
timestamp),
new TestTuple(flags, tupleSer, new byte[] { 11 },
new byte[] { 11 }, false/* deleted */,
timestamp),
new TestTuple(flags, tupleSer, new byte[] { 12 },
new byte[] { 12 }, false/* deleted */,
timestamp), }, itr);
}
{
/*
* Query a subrange of the data on the first and last index
* partitions.
*/
final ITupleIterator itr = ndx
.rangeIterator(new byte[] { 2 }/* fromKey */,
new byte[] { 12 }/* toKey */, capacity, flags, null/* filter */);
assertSameIteratorAnyOrder(new ITuple[] {//
new TestTuple(flags, tupleSer, new byte[] { 2 },
new byte[] { 2 }, false/* deleted */,
timestamp),
new TestTuple(flags, tupleSer, new byte[] { 3 },
new byte[] { 3 }, false/* deleted */,
timestamp),
new TestTuple(flags, tupleSer, new byte[] { 4 },
new byte[] { 4 }, false/* deleted */,
timestamp),
new TestTuple(flags, tupleSer, new byte[] { 5 },
new byte[] { 5 }, false/* deleted */,
timestamp),
new TestTuple(flags, tupleSer, new byte[] { 6 },
new byte[] { 6 }, false/* deleted */,
timestamp),
new TestTuple(flags, tupleSer, new byte[] { 10 },
new byte[] { 10 }, false/* deleted */,
timestamp),
new TestTuple(flags, tupleSer, new byte[] { 11 },
new byte[] { 11 }, false/* deleted */,
timestamp), }, itr);
}
}
/**
* Verifies that the iterator visits the specified objects in some arbitrary
* ordering and that the iterator is exhausted once all expected objects
* have been visited. The implementation uses a selection without
* replacement "pattern".
*/
static public <E> void assertSameIteratorAnyOrder(
final ITuple<E>[] expected, final ITupleIterator<E> actual) {
assertSameIteratorAnyOrder("", expected, actual);
}
/**
* Verifies that the iterator visits the specified objects in some arbitrary
* ordering and that the iterator is exhausted once all expected objects
* have been visited. The implementation uses a selection without
* replacement "pattern".
*/
static public <E> void assertSameIteratorAnyOrder(final String msg,
final ITuple<E>[] expected, final ITupleIterator<E> actual) {
/*
* scan of list, testing for equals using custom code since ITuple does
* not necessarily implement hashCode() and equals() correctly.
*/
int nfound = 0;
while (nfound < expected.length) {
if (!actual.hasNext()) {
fail(msg + ": Index exhausted while expecting more object(s)"
+ ": nfound=" + nfound + ", nexpected="
+ expected.length);
}
final ITuple<E> actualTuple = actual.next();
if (log.isInfoEnabled())
log.info("nvisited=" + (nfound + 1) + ", actualTuple="
+ actualTuple);
boolean found = false;
for (int k = 0; k < expected.length && !found; k++) {
final ITuple<E> expectedTuple = expected[k];
if (expectedTuple == null)
continue;
if (sameTuple(expectedTuple, actualTuple)) {
expected[k] = null;
found = true;
nfound++;
break;
}
}
if (!found)
fail("Tuple not expected" + ": nvisited=" + (nfound + 1)
+ ", tuple=" + actualTuple);
}
if (actual.hasNext()) {
fail("Iterator will deliver too many tuples: next="+actual.next());
}
}
/**
* 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 boolean sameTuple(final ITuple<?> expected,
final ITuple<?> actual) {
if (expected == null)
throw new IllegalArgumentException();
if (actual == null)
throw new IllegalArgumentException();
if (!BytesUtil.bytesEqual(expected.getKey(), actual.getKey()))
return false;
if (expected.isNull() != actual.isNull())
return false;
if (!expected.isNull()) {
if (!BytesUtil.bytesEqual(expected.getValue(), actual.getValue()))
return false;
}
if (expected.flags() != actual.flags())
return false;
if (expected.isDeletedVersion() != actual.isDeletedVersion())
return false;
if (expected.getVersionTimestamp() != actual.getVersionTimestamp())
return false;
return true;
}
}