package org.exist.storage.btree;
import org.easymock.EasyMock;
import org.exist.EXistException;
import org.exist.storage.BrokerPool;
import org.exist.storage.DefaultCacheManager;
import org.exist.test.ExistEmbeddedServer;
import org.exist.util.*;
import org.exist.xquery.TerminatedException;
import org.exist.xquery.value.AtomicValue;
import org.exist.xquery.value.DoubleValue;
import org.junit.*;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Random;
import java.util.TreeMap;
/**
* Low-level tests on the B+tree.
*/
public class BTreeTest {
private Path file = null;
private int count = 0;
private static final int COUNT = 5000;
@Test
public void simpleUpdates() throws DBException, IOException, TerminatedException {
final BrokerPool pool = existEmbeddedServer.getBrokerPool();
try(final BTree btree = new BTree(pool, (byte) 0, false, pool.getCacheManager(), file)) {
btree.create((short) -1);
String prefixStr = "K";
for (int i = 1; i <= COUNT; i++) {
Value value = new Value(prefixStr + Integer.toString(i));
btree.addValue(value, i);
}
//Testing IndexQuery.TRUNC_RIGHT
IndexQuery query = new IndexQuery(IndexQuery.TRUNC_RIGHT, new Value(prefixStr));
btree.query(query, new StringIndexCallback());
assertEquals(COUNT, count);
btree.flush();
//Removing index entries
btree.remove(query, new StringIndexCallback());
assertEquals(COUNT, count);
btree.flush();
//Reading data
for (int i = 1; i <= COUNT; i++) {
Value value = new Value(prefixStr + Integer.toString(i));
btree.addValue(value, i);
}
//Testing IndexQuery.TRUNC_RIGHT
btree.query(query, new StringIndexCallback());
assertEquals(COUNT, count);
btree.flush();
}
}
@Test
public void strings() throws DBException, IOException, TerminatedException {
final BrokerPool pool = existEmbeddedServer.getBrokerPool();
try(final BTree btree = new BTree(pool, (byte) 0, false, pool.getCacheManager(), file)) {
btree.create((short) -1);
String prefixStr = "C";
for (int i = 1; i <= COUNT; i++) {
Value value = new Value(prefixStr + Integer.toString(i));
btree.addValue(value, i);
}
btree.flush();
try(final StringWriter writer = new StringWriter()) {
btree.dump(writer);
}
for (int i = 1; i <= COUNT; i++) {
long p = btree.findValue(new Value(prefixStr + Integer.toString(i)));
assertEquals(p, i);
}
//Testing IndexQuery.TRUNC_RIGHT
IndexQuery query = new IndexQuery(IndexQuery.TRUNC_RIGHT, new Value(prefixStr));
btree.query(query, new StringIndexCallback());
assertEquals(COUNT, count);
//Testing IndexQuery.TRUNC_RIGHT
query = new IndexQuery(IndexQuery.TRUNC_RIGHT, new Value(prefixStr + "1"));
btree.query(query, new StringIndexCallback());
assertEquals(1111, count);
//Testing IndexQuery.NEQ
query = new IndexQuery(IndexQuery.NEQ, new Value(prefixStr + "10"));
btree.query(query, new StringIndexCallback());
assertEquals(COUNT - 1, count);
//Testing IndexQuery.GT
query = new IndexQuery(IndexQuery.GT, new Value(prefixStr));
btree.query(query, new StringIndexCallback());
assertEquals(COUNT, count);
//Testing IndexQuery.GT
query = new IndexQuery(IndexQuery.GT, new Value(prefixStr + "1"));
btree.query(query, new StringIndexCallback());
assertEquals(COUNT - 1, count);
//Testing IndexQuery.LT
query = new IndexQuery(IndexQuery.LT, new Value(prefixStr));
btree.query(query, new StringIndexCallback());
assertEquals(count, 0);
}
}
@Test
public void longStrings() throws DBException, IOException {
// Test storage of long keys up to half of the page size (4k)
final Random rand = new Random(System.currentTimeMillis());
final BrokerPool pool = existEmbeddedServer.getBrokerPool();
try(final BTree btree = new BTree(pool, (byte) 0, false, pool.getCacheManager(), file)) {
btree.setSplitFactor(0.7);
btree.create((short) -1);
Map<String, Integer> keys = new TreeMap<>();
String prefixStr = "C";
for (int i = 1; i <= COUNT; i++) {
StringBuilder buf = new StringBuilder();
buf.append(prefixStr).append(Integer.toString(i));
int nextLen = rand.nextInt(2000);
while (nextLen < 512) {
nextLen = rand.nextInt(2000);
}
for (int j = 0; j < nextLen; j++) {
buf.append('x');
}
final String key = buf.toString();
Value value = new Value(key);
btree.addValue(value, i);
keys.put(key, i);
}
btree.flush();
for (Map.Entry<String, Integer> entry: keys.entrySet()) {
long p = btree.findValue(new Value(entry.getKey().toString()));
assertEquals(p, entry.getValue().intValue());
}
}
}
@Test
public void stringsTruncated() throws DBException, IOException, TerminatedException {
final BrokerPool pool = existEmbeddedServer.getBrokerPool();
try(BTree btree = new BTree(pool, (byte) 0, false, pool.getCacheManager(), file)) {
btree.create((short) -1);
char prefix = 'A';
for (int i = 0; i < 24; i++) {
for (int j = 1; j <= COUNT; j++) {
Value value = new Value(prefix + Integer.toString(j));
btree.addValue(value, j);
}
prefix++;
}
btree.flush();
//Testing IndexQuery.TRUNC_RIGHT
prefix = 'A';
for (int i = 0; i < 24; i++) {
IndexQuery query = new IndexQuery(IndexQuery.TRUNC_RIGHT, new Value(Character.toString(prefix)));
btree.query(query, new StringIndexCallback());
assertEquals(COUNT, count);
prefix++;
}
}
}
@Test
public void removeStrings() throws DBException, IOException, TerminatedException {
final BrokerPool pool = existEmbeddedServer.getBrokerPool();
try(final BTree btree = new BTree(pool, (byte) 0, false, pool.getCacheManager(), file)) {
btree.create((short) -1);
char prefix = 'A';
for (int i = 0; i < 24; i++) {
for (int j = 1; j <= COUNT; j++) {
Value value = new Value(prefix + Integer.toString(j));
btree.addValue(value, j);
}
prefix++;
}
btree.flush();
prefix = 'A';
for (int i = 0; i < 24; i++) {
IndexQuery query = new IndexQuery(IndexQuery.TRUNC_RIGHT, new Value(Character.toString(prefix)));
btree.remove(query, new StringIndexCallback());
assertEquals(COUNT, count);
assertEquals(-1, btree.findValue(new Value(prefix + Integer.toString(100))));
query = new IndexQuery(IndexQuery.TRUNC_RIGHT, new Value(prefix + Integer.toString(100)));
btree.query(query, new StringIndexCallback());
assertEquals(0, count);
prefix++;
}
//Testing IndexQuery.TRUNC_RIGHT
IndexQuery query = new IndexQuery(IndexQuery.TRUNC_RIGHT, new Value(Character.toString('D')));
btree.query(query, new StringIndexCallback());
assertEquals(0, count);
}
}
@Test
public void numbers() throws TerminatedException, DBException, EXistException, IOException {
final BrokerPool pool = existEmbeddedServer.getBrokerPool();
try(final BTree btree = new BTree(pool, (byte) 0, false, pool.getCacheManager(), file)) {
btree.create((short) -1);
for (int i = 1; i <= COUNT; i++) {
Value value = new SimpleValue(new DoubleValue(i));
btree.addValue(value, i);
}
btree.flush();
for (int i = 1; i <= COUNT; i++) {
long p = btree.findValue(new SimpleValue(new DoubleValue(i)));
assertEquals(p, i);
}
//Testing IndexQuery.GT
IndexQuery query;
for (int i = 0; i < COUNT; i += 10) {
query = new IndexQuery(IndexQuery.GT, new SimpleValue(new DoubleValue(i)));
btree.query(query, new SimpleCallback());
assertEquals(COUNT - i, count);
}
//Testing IndexQuery.GEQ
query = new IndexQuery(IndexQuery.GEQ, new SimpleValue(new DoubleValue(COUNT / 2)));
btree.query(query, new SimpleCallback());
assertEquals(COUNT / 2 + 1, count);
//Testing IndexQuery.NEQ
for (int i = 1; i <= COUNT / 8; i++) {
query = new IndexQuery(IndexQuery.NEQ, new SimpleValue(new DoubleValue(i)));
btree.query(query, new SimpleCallback());
assertEquals(COUNT - 1, count);
}
}
}
@Test
public void numbersWithPrefix() throws DBException, EXistException, IOException, TerminatedException {
final BrokerPool pool = existEmbeddedServer.getBrokerPool();
try(final BTree btree = new BTree(pool, (byte) 0, false, pool.getCacheManager(), file)) {
btree.create((short) -1);
for (int i = 1; i <= COUNT; i++) {
Value value = new PrefixValue(99, new DoubleValue(i));
btree.addValue(value, i);
}
for (int i = 1; i <= COUNT; i++) {
Value value = new PrefixValue(100, new DoubleValue(i));
btree.addValue(value, i);
}
btree.flush();
for (int i = 1; i <= COUNT; i++) {
long p = btree.findValue(new PrefixValue(99, new DoubleValue(i)));
assertEquals(p, i);
}
Value prefix = new PrefixValue(99);
//Testing IndexQuery.TRUNC_RIGHT
IndexQuery query = new IndexQuery(IndexQuery.TRUNC_RIGHT, new PrefixValue(99));
btree.query(query, new PrefixIndexCallback());
assertEquals(COUNT, count);
//Testing IndexQuery.GT
for (int i = 0; i < COUNT; i += 10) {
query = new IndexQuery(IndexQuery.GT, new PrefixValue(99, new DoubleValue(i)));
btree.query(query, prefix, new PrefixIndexCallback());
assertEquals(COUNT - i, count);
}
//Testing IndexQuery.GEQ
query = new IndexQuery(IndexQuery.GEQ, new PrefixValue(99, new DoubleValue(COUNT / 2)));
btree.query(query, prefix, new PrefixIndexCallback());
assertEquals(COUNT / 2 + 1, count);
//Testing IndexQuery.LT
query = new IndexQuery(IndexQuery.LT, new PrefixValue(99, new DoubleValue(COUNT / 2)));
btree.query(query, prefix, new PrefixIndexCallback());
assertEquals(COUNT / 2 - 1, count);
//Testing IndexQuery.LEQ
query = new IndexQuery(IndexQuery.LEQ, new PrefixValue(99, new DoubleValue(COUNT / 2)));
btree.query(query, prefix, new PrefixIndexCallback());
assertEquals(COUNT / 2, count);
//Testing IndexQuery.NEQ
for (int i = 1; i <= COUNT / 8; i++) {
count = 0;
query = new IndexQuery(IndexQuery.NEQ, new PrefixValue(99, new DoubleValue(i)));
btree.query(query, prefix, new PrefixIndexCallback());
assertEquals(COUNT - 1, count);
}
}
}
@ClassRule
public static final ExistEmbeddedServer existEmbeddedServer = new ExistEmbeddedServer(true, false);
@Before
public void initialize() {
file = Paths.get(System.getProperty("exist.home", ".")).resolve("test/junit/test.dbx");
assertFalse(Files.exists(file));
}
@After
public void cleanUp() {
FileUtils.deleteQuietly(file);
}
private final class SimpleCallback implements BTreeCallback {
public SimpleCallback() {
count = 0;
}
@Override
public boolean indexInfo(Value value, long pointer) throws TerminatedException {
count++;
return false;
}
}
private final class PrefixIndexCallback implements BTreeCallback {
public PrefixIndexCallback() {
count = 0;
}
@Override
public boolean indexInfo(Value value, long pointer) throws TerminatedException {
int prefix = ByteConversion.byteToInt(value.data(), value.start());
assertEquals(99, prefix);
// XMLString key = UTF8.decode(value.data(), value.start() + 4, value.getLength() - 4);
count++;
return false;
}
}
private final class StringIndexCallback implements BTreeCallback {
public StringIndexCallback() {
count = 0;
}
@Override
public boolean indexInfo(Value value, long pointer) throws TerminatedException {
@SuppressWarnings("unused")
XMLString key = UTF8.decode(value.data(), value.start(), value.getLength());
count++;
return false;
}
}
private class SimpleValue extends Value {
public SimpleValue(AtomicValue value) throws EXistException {
data = value.serializeValue(0);
len = data.length;
pos = 0;
}
}
private class PrefixValue extends Value {
public PrefixValue(int prefix) {
len = 4;
data = new byte[len];
ByteConversion.intToByte(prefix, data, 0);
pos = 0;
}
public PrefixValue(int prefix, AtomicValue value) throws EXistException {
data = value.serializeValue(4);
len = data.length;
ByteConversion.intToByte(prefix, data, 0);
pos = 0;
}
}
}