package be.bagofwords.db.experimental.rocksdb;
import be.bagofwords.db.CoreDataInterface;
import be.bagofwords.db.combinator.Combinator;
import be.bagofwords.iterator.CloseableIterator;
import be.bagofwords.util.DataLock;
import be.bagofwords.util.KeyValue;
import be.bagofwords.util.SerializationUtils;
import org.apache.commons.io.FileUtils;
import org.rocksdb.*;
import java.io.File;
import java.util.Iterator;
/**
* Created by Koen Deschacht (koendeschacht@gmail.com) on 9/17/14.
*/
public class RocksDBDataInterface<T> extends CoreDataInterface<T> {
private final boolean useMergeHack;
private final DataLock writeLock;
private final WriteOptions delayedWriteOptions;
private final File dbDirectory;
private RocksDB db;
public RocksDBDataInterface(String name, Class<T> objectClass, Combinator<T> combinator, String directory, boolean usePatch, boolean isTemporaryDataInterface) {
super(name, objectClass, combinator, isTemporaryDataInterface);
this.useMergeHack = usePatch;
try {
if (useMergeHack && objectClass == Long.class) {
name = "_long_count_" + name;
}
dbDirectory = new File(new File(directory), name);
if (dbDirectory.isFile()) {
throw new RuntimeException(dbDirectory.getAbsolutePath() + " is a file, should be a directory...");
} else if (!dbDirectory.exists()) {
boolean success = dbDirectory.mkdirs();
if (!success) {
throw new RuntimeException("Failed to create directory " + dbDirectory.getAbsolutePath());
}
}
openDatabase();
} catch (RocksDBException e) {
throw new RuntimeException("Failed to create database", e);
}
this.writeLock = new DataLock();
this.delayedWriteOptions = new WriteOptions();
}
private void openDatabase() throws RocksDBException {
Options options = new Options().setCreateIfMissing(true);
db = RocksDB.open(options, dbDirectory.getAbsolutePath());
}
@Override
public void write(Iterator<KeyValue<T>> entries) {
writeLock.lockWriteAll();
try {
WriteBatch writeBatch = new WriteBatch();
if (useMergeHack && getObjectClass() == Long.class) {
while (entries.hasNext()) {
KeyValue<T> next = entries.next();
byte[] keyAsBytes = SerializationUtils.longToBytes(next.getKey());
if (next.getValue() == null) {
writeBatch.remove(keyAsBytes);
} else {
writeBatch.merge(keyAsBytes, SerializationUtils.objectToBytes(next.getValue(), getObjectClass()));
}
}
} else {
while (entries.hasNext()) {
KeyValue<T> next = entries.next();
byte[] keyAsBytes = SerializationUtils.longToBytes(next.getKey());
T combinedValue = combineWithCurrentValue(next.getValue(), keyAsBytes);
if (combinedValue == null) {
writeBatch.remove(keyAsBytes);
} else {
writeBatch.put(keyAsBytes, SerializationUtils.objectToBytes(combinedValue, getObjectClass()));
}
}
}
db.write(delayedWriteOptions, writeBatch);
} catch (RocksDBException e) {
throw new RuntimeException("Received exception while trying to write multiple values to the DB", e);
} finally {
writeLock.unlockWriteAll();
}
}
@Override
public void write(long key, T value) {
byte[] keyAsBytes = SerializationUtils.longToBytes(key);
writeLock.lockWrite(key);
try {
T combinedValue = combineWithCurrentValue(value, keyAsBytes);
if (combinedValue == null) {
db.remove(delayedWriteOptions, keyAsBytes);
} else {
db.put(delayedWriteOptions, keyAsBytes, SerializationUtils.objectToBytes(combinedValue, getObjectClass()));
}
} catch (RocksDBException exp) {
throw new RuntimeException("Received exception while trying to write a single value to the DB", exp);
} finally {
writeLock.unlockWrite(key);
}
}
private T combineWithCurrentValue(T value, byte[] keyAsBytes) throws RocksDBException {
T currentValue = SerializationUtils.bytesToObject(db.get(keyAsBytes), getObjectClass());
T combinedValue;
if (currentValue == null || value == null) {
combinedValue = value;
} else {
combinedValue = getCombinator().combine(currentValue, value);
}
return combinedValue;
}
@Override
public CloseableIterator<KeyValue<T>> iterator() {
final RocksIterator rocksIterator = db.newIterator();
rocksIterator.seekToFirst();
return new CloseableIterator<KeyValue<T>>() {
@Override
protected void closeInt() {
rocksIterator.dispose();
}
@Override
public boolean hasNext() {
return rocksIterator.isValid();
}
@Override
public KeyValue<T> next() {
long key = SerializationUtils.bytesToLong(rocksIterator.key());
T value = SerializationUtils.bytesToObject(rocksIterator.value(), getObjectClass());
rocksIterator.next();
return new KeyValue<>(key, value);
}
};
}
@Override
public void optimizeForReading() {
//do nothing?
}
@Override
public T read(long key) {
writeLock.lockRead(key);
try {
T result = SerializationUtils.bytesToObject(db.get(SerializationUtils.longToBytes(key)), getObjectClass());
return result;
} catch (RocksDBException e) {
throw new RuntimeException("Received exception while reading single value", e);
} finally {
writeLock.unlockRead(key);
}
}
@Override
public synchronized void dropAllData() {
writeLock.lockWriteAll();
db.close();
try {
FileUtils.deleteDirectory(dbDirectory);
openDatabase();
} catch (Exception exp) {
throw new RuntimeException("Failed to drop all data", exp);
}
writeLock.unlockWriteAll();
}
@Override
public void flush() {
//OK
}
@Override
public synchronized void doClose() {
db.close();
db = null;
}
@Override
public long apprSize() {
return exactSize();
}
}