/*
* 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.accumulo.core.file.rfile;
import static org.junit.Assert.assertEquals;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.util.ArrayList;
import org.apache.accumulo.core.data.ArrayByteSequence;
import org.apache.accumulo.core.data.Key;
import org.apache.accumulo.core.data.PartialKey;
import org.apache.accumulo.core.data.Value;
import org.apache.accumulo.core.file.rfile.RelativeKey.SkippR;
import org.apache.accumulo.core.util.MutableByteSequence;
import org.apache.accumulo.core.util.UnsynchronizedBuffer;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
public class RelativeKeyTest {
@Test
public void testBasicRelativeKey() {
assertEquals(1, UnsynchronizedBuffer.nextArraySize(0));
assertEquals(1, UnsynchronizedBuffer.nextArraySize(1));
assertEquals(2, UnsynchronizedBuffer.nextArraySize(2));
assertEquals(4, UnsynchronizedBuffer.nextArraySize(3));
assertEquals(4, UnsynchronizedBuffer.nextArraySize(4));
assertEquals(8, UnsynchronizedBuffer.nextArraySize(5));
assertEquals(8, UnsynchronizedBuffer.nextArraySize(8));
assertEquals(16, UnsynchronizedBuffer.nextArraySize(9));
assertEquals(1 << 16, UnsynchronizedBuffer.nextArraySize((1 << 16) - 1));
assertEquals(1 << 16, UnsynchronizedBuffer.nextArraySize(1 << 16));
assertEquals(1 << 17, UnsynchronizedBuffer.nextArraySize((1 << 16) + 1));
assertEquals(1 << 30, UnsynchronizedBuffer.nextArraySize((1 << 30) - 1));
assertEquals(1 << 30, UnsynchronizedBuffer.nextArraySize(1 << 30));
assertEquals(Integer.MAX_VALUE, UnsynchronizedBuffer.nextArraySize(Integer.MAX_VALUE - 1));
assertEquals(Integer.MAX_VALUE, UnsynchronizedBuffer.nextArraySize(Integer.MAX_VALUE));
}
@Test
public void testCommonPrefix() {
// exact matches
ArrayByteSequence exact = new ArrayByteSequence("abc");
assertEquals(-1, RelativeKey.getCommonPrefix(exact, exact));
assertEquals(-1, commonPrefixHelper("", ""));
assertEquals(-1, commonPrefixHelper("a", "a"));
assertEquals(-1, commonPrefixHelper("aa", "aa"));
assertEquals(-1, commonPrefixHelper("aaa", "aaa"));
assertEquals(-1, commonPrefixHelper("abab", "abab"));
assertEquals(-1, commonPrefixHelper(new String("aaa"), new ArrayByteSequence("aaa").toString()));
assertEquals(-1, commonPrefixHelper("abababababab".substring(3, 6), "ccababababcc".substring(3, 6)));
// no common prefix
assertEquals(0, commonPrefixHelper("", "a"));
assertEquals(0, commonPrefixHelper("a", ""));
assertEquals(0, commonPrefixHelper("a", "b"));
assertEquals(0, commonPrefixHelper("aaaa", "bbbb"));
// some common prefix
assertEquals(1, commonPrefixHelper("a", "ab"));
assertEquals(1, commonPrefixHelper("ab", "ac"));
assertEquals(1, commonPrefixHelper("ab", "ac"));
assertEquals(2, commonPrefixHelper("aa", "aaaa"));
assertEquals(4, commonPrefixHelper("aaaaa", "aaaab"));
}
private int commonPrefixHelper(String a, String b) {
return RelativeKey.getCommonPrefix(new ArrayByteSequence(a), new ArrayByteSequence(b));
}
@Test
public void testReadWritePrefix() throws IOException {
Key prevKey = new Key("row1", "columnfamily1", "columnqualifier1", "columnvisibility1", 1000);
Key newKey = new Key("row2", "columnfamily2", "columnqualifier2", "columnvisibility2", 3000);
RelativeKey expected = new RelativeKey(prevKey, newKey);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(baos);
expected.write(out);
RelativeKey actual = new RelativeKey();
actual.setPrevKey(prevKey);
actual.readFields(new DataInputStream(new ByteArrayInputStream(baos.toByteArray())));
assertEquals(expected.getKey(), actual.getKey());
}
private static ArrayList<Key> expectedKeys;
private static ArrayList<Value> expectedValues;
private static ArrayList<Integer> expectedPositions;
private static ByteArrayOutputStream baos;
@BeforeClass
public static void initSource() throws IOException {
int initialListSize = 10000;
baos = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(baos);
expectedKeys = new ArrayList<>(initialListSize);
expectedValues = new ArrayList<>(initialListSize);
expectedPositions = new ArrayList<>(initialListSize);
Key prev = null;
int val = 0;
for (int row = 0; row < 4; row++) {
String rowS = RFileTest.formatString("r_", row);
for (int cf = 0; cf < 4; cf++) {
String cfS = RFileTest.formatString("cf_", cf);
for (int cq = 0; cq < 4; cq++) {
String cqS = RFileTest.formatString("cq_", cq);
for (int cv = 'A'; cv < 'A' + 4; cv++) {
String cvS = "" + (char) cv;
for (int ts = 4; ts > 0; ts--) {
Key k = RFileTest.newKey(rowS, cfS, cqS, cvS, ts);
k.setDeleted(true);
Value v = RFileTest.newValue("" + val);
expectedPositions.add(out.size());
new RelativeKey(prev, k).write(out);
prev = k;
v.write(out);
expectedKeys.add(k);
expectedValues.add(v);
k = RFileTest.newKey(rowS, cfS, cqS, cvS, ts);
v = RFileTest.newValue("" + val);
expectedPositions.add(out.size());
new RelativeKey(prev, k).write(out);
prev = k;
v.write(out);
expectedKeys.add(k);
expectedValues.add(v);
val++;
}
}
}
}
}
}
private DataInputStream in;
@Before
public void setupDataInputStream() {
in = new DataInputStream(new ByteArrayInputStream(baos.toByteArray()));
in.mark(0);
}
@Test
public void testSeekBeforeEverything() throws IOException {
Key seekKey = new Key();
Key prevKey = new Key();
Key currKey = null;
MutableByteSequence value = new MutableByteSequence(new byte[64], 0, 0);
RelativeKey.SkippR skippr = RelativeKey.fastSkip(in, seekKey, value, prevKey, currKey, expectedKeys.size());
assertEquals(1, skippr.skipped);
assertEquals(new Key(), skippr.prevKey);
assertEquals(expectedKeys.get(0), skippr.rk.getKey());
assertEquals(expectedValues.get(0).toString(), value.toString());
// ensure we can advance after fastskip
skippr.rk.readFields(in);
assertEquals(expectedKeys.get(1), skippr.rk.getKey());
in.reset();
seekKey = new Key("a", "b", "c", "d", 1);
seekKey.setDeleted(true);
skippr = RelativeKey.fastSkip(in, seekKey, value, prevKey, currKey, expectedKeys.size());
assertEquals(1, skippr.skipped);
assertEquals(new Key(), skippr.prevKey);
assertEquals(expectedKeys.get(0), skippr.rk.getKey());
assertEquals(expectedValues.get(0).toString(), value.toString());
skippr.rk.readFields(in);
assertEquals(expectedKeys.get(1), skippr.rk.getKey());
}
@Test(expected = EOFException.class)
public void testSeekAfterEverythingWrongCount() throws IOException {
Key seekKey = new Key("s", "t", "u", "v", 1);
Key prevKey = new Key();
Key currKey = null;
MutableByteSequence value = new MutableByteSequence(new byte[64], 0, 0);
RelativeKey.fastSkip(in, seekKey, value, prevKey, currKey, expectedKeys.size() + 1);
}
public void testSeekAfterEverything() throws IOException {
Key seekKey = new Key("s", "t", "u", "v", 1);
Key prevKey = new Key();
Key currKey = null;
MutableByteSequence value = new MutableByteSequence(new byte[64], 0, 0);
SkippR skippr = RelativeKey.fastSkip(in, seekKey, value, prevKey, currKey, expectedKeys.size());
assertEquals(expectedKeys.size(), skippr.skipped);
}
@Test
public void testSeekMiddle() throws IOException {
int seekIndex = expectedKeys.size() / 2;
Key seekKey = expectedKeys.get(seekIndex);
Key prevKey = new Key();
Key currKey = null;
MutableByteSequence value = new MutableByteSequence(new byte[64], 0, 0);
RelativeKey.SkippR skippr = RelativeKey.fastSkip(in, seekKey, value, prevKey, currKey, expectedKeys.size());
assertEquals(seekIndex + 1, skippr.skipped);
assertEquals(expectedKeys.get(seekIndex - 1), skippr.prevKey);
assertEquals(expectedKeys.get(seekIndex), skippr.rk.getKey());
assertEquals(expectedValues.get(seekIndex).toString(), value.toString());
skippr.rk.readFields(in);
assertEquals(expectedValues.get(seekIndex + 1).toString(), value.toString());
// try fast skipping to a key that does not exist
in.reset();
Key fKey = expectedKeys.get(seekIndex).followingKey(PartialKey.ROW_COLFAM_COLQUAL);
int i;
for (i = seekIndex; expectedKeys.get(i).compareTo(fKey) < 0; i++) {}
int left = expectedKeys.size();
skippr = RelativeKey.fastSkip(in, expectedKeys.get(i), value, prevKey, currKey, expectedKeys.size());
assertEquals(i + 1, skippr.skipped);
left -= skippr.skipped;
assertEquals(expectedKeys.get(i - 1), skippr.prevKey);
assertEquals(expectedKeys.get(i), skippr.rk.getKey());
assertEquals(expectedValues.get(i).toString(), value.toString());
// try fast skipping to our current location
skippr = RelativeKey.fastSkip(in, expectedKeys.get(i), value, expectedKeys.get(i - 1), expectedKeys.get(i), left);
assertEquals(0, skippr.skipped);
assertEquals(expectedKeys.get(i - 1), skippr.prevKey);
assertEquals(expectedKeys.get(i), skippr.rk.getKey());
assertEquals(expectedValues.get(i).toString(), value.toString());
// try fast skipping 1 column family ahead from our current location, testing fastskip from middle of block as opposed to stating at beginning of block
fKey = expectedKeys.get(i).followingKey(PartialKey.ROW_COLFAM);
int j;
for (j = i; expectedKeys.get(j).compareTo(fKey) < 0; j++) {}
skippr = RelativeKey.fastSkip(in, fKey, value, expectedKeys.get(i - 1), expectedKeys.get(i), left);
assertEquals(j - i, skippr.skipped);
assertEquals(expectedKeys.get(j - 1), skippr.prevKey);
assertEquals(expectedKeys.get(j), skippr.rk.getKey());
assertEquals(expectedValues.get(j).toString(), value.toString());
}
}