/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.cassandra.io.sstable;
import java.util.ArrayList;
import java.util.Collection;
import org.junit.Test;
import org.apache.cassandra.SchemaLoader;
import org.apache.cassandra.Util;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.columniterator.IdentityQueryFilter;
import org.apache.cassandra.db.compaction.ICompactionScanner;
import org.apache.cassandra.dht.Bounds;
import org.apache.cassandra.dht.BytesToken;
import org.apache.cassandra.dht.Range;
import org.apache.cassandra.dht.Token;
import org.apache.cassandra.utils.ByteBufferUtil;
import static org.junit.Assert.*;
public class SSTableScannerTest extends SchemaLoader
{
public static final String KEYSPACE = "Keyspace1";
public static final String TABLE = "Standard1";
private static String toKey(int key)
{
return String.format("%03d", key);
}
private static Bounds<RowPosition> boundsFor(int start, int end)
{
return new Bounds<RowPosition>(new BytesToken(toKey(start).getBytes()).minKeyBound(),
new BytesToken(toKey(end).getBytes()).maxKeyBound());
}
private static Range<Token> rangeFor(int start, int end)
{
return new Range<Token>(new BytesToken(toKey(start).getBytes()),
new BytesToken(toKey(end).getBytes()));
}
private static Collection<Range<Token>> makeRanges(int ... keys)
{
Collection<Range<Token>> ranges = new ArrayList<Range<Token>>(keys.length / 2);
for (int i = 0; i < keys.length; i += 2)
ranges.add(rangeFor(keys[i], keys[i + 1]));
return ranges;
}
private static void insertRowWithKey(int key)
{
long timestamp = System.currentTimeMillis();
DecoratedKey decoratedKey = Util.dk(toKey(key));
RowMutation rm = new RowMutation(KEYSPACE, decoratedKey.key);
rm.add(TABLE, ByteBufferUtil.bytes("col"), ByteBufferUtil.EMPTY_BYTE_BUFFER, timestamp, 1000);
rm.apply();
}
private static void assertScanMatches(SSTableReader sstable, int scanStart, int scanEnd, int expectedStart, int expectedEnd)
{
SSTableScanner scanner = sstable.getScanner(new DataRange(boundsFor(scanStart, scanEnd), new IdentityQueryFilter()));
for (int i = expectedStart; i <= expectedEnd; i++)
assertEquals(toKey(i), new String(scanner.next().getKey().key.array()));
assertFalse(scanner.hasNext());
}
private static void assertScanEmpty(SSTableReader sstable, int scanStart, int scanEnd)
{
SSTableScanner scanner = sstable.getScanner(new DataRange(boundsFor(scanStart, scanEnd), new IdentityQueryFilter()));
assertFalse(String.format("scan of (%03d, %03d] should be empty", scanStart, scanEnd), scanner.hasNext());
}
@Test
public void testSingleDataRange()
{
Keyspace keyspace = Keyspace.open(KEYSPACE);
ColumnFamilyStore store = keyspace.getColumnFamilyStore(TABLE);
store.clearUnsafe();
// disable compaction while flushing
store.disableAutoCompaction();
for (int i = 2; i < 10; i++)
insertRowWithKey(i);
store.forceBlockingFlush();
assertEquals(1, store.getSSTables().size());
SSTableReader sstable = store.getSSTables().iterator().next();
// full range scan
SSTableScanner scanner = sstable.getScanner();
for (int i = 2; i < 10; i++)
assertEquals(toKey(i), new String(scanner.next().getKey().key.array()));
// a simple read of a chunk in the middle
assertScanMatches(sstable, 3, 6, 3, 6);
// start of range edge conditions
assertScanMatches(sstable, 1, 9, 2, 9);
assertScanMatches(sstable, 2, 9, 2, 9);
assertScanMatches(sstable, 3, 9, 3, 9);
// end of range edge conditions
assertScanMatches(sstable, 1, 8, 2, 8);
assertScanMatches(sstable, 1, 9, 2, 9);
assertScanMatches(sstable, 1, 9, 2, 9);
// single item ranges
assertScanMatches(sstable, 2, 2, 2, 2);
assertScanMatches(sstable, 5, 5, 5, 5);
assertScanMatches(sstable, 9, 9, 9, 9);
// empty ranges
assertScanEmpty(sstable, 0, 1);
assertScanEmpty(sstable, 10, 11);
}
private static void assertScanContainsRanges(ICompactionScanner scanner, int ... rangePairs)
{
assert rangePairs.length % 2 == 0;
for (int pairIdx = 0; pairIdx < rangePairs.length; pairIdx += 2)
{
int rangeStart = rangePairs[pairIdx];
int rangeEnd = rangePairs[pairIdx + 1];
for (int expected = rangeStart; expected <= rangeEnd; expected++)
{
assertTrue(String.format("Expected to see key %03d", expected), scanner.hasNext());
assertEquals(toKey(expected), new String(scanner.next().getKey().key.array()));
}
}
assertFalse(scanner.hasNext());
}
@Test
public void testMultipleRanges()
{
Keyspace keyspace = Keyspace.open(KEYSPACE);
ColumnFamilyStore store = keyspace.getColumnFamilyStore(TABLE);
store.clearUnsafe();
// disable compaction while flushing
store.disableAutoCompaction();
for (int i = 0; i < 3; i++)
for (int j = 2; j < 10; j++)
insertRowWithKey(i * 100 + j);
store.forceBlockingFlush();
assertEquals(1, store.getSSTables().size());
SSTableReader sstable = store.getSSTables().iterator().next();
// full range scan
SSTableScanner fullScanner = sstable.getScanner();
assertScanContainsRanges(fullScanner,
2, 9,
102, 109,
202, 209);
// scan all three ranges separately
ICompactionScanner scanner = sstable.getScanner(makeRanges(1, 9,
101, 109,
201, 209),
null);
assertScanContainsRanges(scanner,
2, 9,
102, 109,
202, 209);
// skip the first range
scanner = sstable.getScanner(makeRanges(101, 109,
201, 209),
null);
assertScanContainsRanges(scanner,
102, 109,
202, 209);
// skip the second range
scanner = sstable.getScanner(makeRanges(1, 9,
201, 209),
null);
assertScanContainsRanges(scanner,
2, 9,
202, 209);
// skip the last range
scanner = sstable.getScanner(makeRanges(1, 9,
101, 109),
null);
assertScanContainsRanges(scanner,
2, 9,
102, 109);
// the first scanned range stops short of the actual data in the first range
scanner = sstable.getScanner(makeRanges(1, 5,
101, 109,
201, 209),
null);
assertScanContainsRanges(scanner,
2, 5,
102, 109,
202, 209);
// the first scanned range requests data beyond actual data in the first range
scanner = sstable.getScanner(makeRanges(1, 20,
101, 109,
201, 209),
null);
assertScanContainsRanges(scanner,
2, 9,
102, 109,
202, 209);
// the middle scan range splits the outside two data ranges
scanner = sstable.getScanner(makeRanges(1, 5,
6, 205,
206, 209),
null);
assertScanContainsRanges(scanner,
2, 5,
7, 9,
102, 109,
202, 205,
207, 209);
// empty ranges
scanner = sstable.getScanner(makeRanges(0, 1,
2, 20,
101, 109,
150, 159,
201, 209,
1000, 1001),
null);
assertScanContainsRanges(scanner,
3, 9,
102, 109,
202, 209);
// out of order ranges
scanner = sstable.getScanner(makeRanges(201, 209,
1, 20,
201, 209,
101, 109,
1000, 1001,
150, 159),
null);
assertScanContainsRanges(scanner,
2, 9,
102, 109,
202, 209);
// only empty ranges
scanner = sstable.getScanner(makeRanges(0, 1,
150, 159,
250, 259),
null);
assertFalse(scanner.hasNext());
// no ranges is equivalent to a full scan
scanner = sstable.getScanner(new ArrayList<Range<Token>>(), null);
assertFalse(scanner.hasNext());
}
}