package be.bagofwords.db.experimental.lmdb;
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.fusesource.lmdbjni.*;
import java.util.Arrays;
import java.util.Iterator;
/**
* Created by Koen Deschacht (koendeschacht@gmail.com) on 9/16/14.
*/
public class LMDBDataInterface<T> extends CoreDataInterface<T> {
private final Env env;
private final DataLock dataLock;
private Database db;
public LMDBDataInterface(String name, Class<T> objectClass, Combinator<T> combinator, Env env, boolean isTemporaryDataInterface) {
super(name, objectClass, combinator, isTemporaryDataInterface);
this.db = env.openDatabase(name);
this.env = env;
this.dataLock = new DataLock(false);
}
@Override
public void write(Iterator<KeyValue<T>> entries) {
Transaction transaction = env.createTransaction();
long numberOfValuesWritten = 0;
while (entries.hasNext()) {
KeyValue<T> next = entries.next();
try {
writeWithTransaction(next.getKey(), next.getValue(), transaction);
} catch (Exception exp) {
throw new RuntimeException("Failed to write multiple entries after " + numberOfValuesWritten + " values", exp);
}
numberOfValuesWritten++;
if (numberOfValuesWritten > 1000) {
transaction.commit();
transaction = env.createTransaction();
// UI.write("Wrote " + numberOfValuesWritten + " values");
numberOfValuesWritten = 0;
}
}
transaction.commit();
// UI.write("Wrote " + numberOfValuesWritten + " values");
}
@Override
public void write(long key, T value) {
Transaction transaction = env.createTransaction();
writeWithTransaction(key, value, transaction);
transaction.commit();
}
private void writeWithTransaction(long key, T value, Transaction transaction) {
dataLock.lockWrite(key);
byte[] keysAsBytes = SerializationUtils.longToBytes(key);
try {
T currentValue = SerializationUtils.bytesToObject(db.get(transaction, keysAsBytes), getObjectClass());
T combinedValue;
if (currentValue == null || value == null) {
combinedValue = value;
} else {
combinedValue = getCombinator().combine(currentValue, value);
}
if (combinedValue == null) {
db.delete(transaction, keysAsBytes);
} else {
db.put(transaction, keysAsBytes, SerializationUtils.objectToBytes(combinedValue, getObjectClass()));
}
} catch (Exception exp) {
throw new RuntimeException("Error while trying to write key " + key + "(=" + Arrays.toString(keysAsBytes) + ") with value " + value, exp);
} finally {
dataLock.unlockWrite(key);
}
}
@Override
public CloseableIterator<KeyValue<T>> iterator() {
final Transaction transaction = env.createTransaction();
final Cursor cursor = db.openCursor(transaction);
return new CloseableIterator<KeyValue<T>>() {
private Entry next;
{
next = cursor.get(GetOp.FIRST);
}
@Override
protected void closeInt() {
cursor.close();
transaction.commit();
}
@Override
public boolean hasNext() {
return next != null;
}
@Override
public KeyValue<T> next() {
KeyValue<T> result = new KeyValue<>(SerializationUtils.bytesToLong(next.getKey()), SerializationUtils.bytesToObject(next.getValue(), getObjectClass()));
next = cursor.get(GetOp.NEXT);
return result;
}
};
}
@Override
public void optimizeForReading() {
//do nothing?
}
@Override
public T read(long key) {
byte[] resultAsBytes = db.get(SerializationUtils.longToBytes(key));
return SerializationUtils.bytesToObject(resultAsBytes, getObjectClass());
}
@Override
public void dropAllData() {
dataLock.lockWriteAll();
db.drop(false);
dataLock.unlockWriteAll();
}
@Override
public void flush() {
//always flushed
}
@Override
public synchronized void doClose() {
dataLock.lockWriteAll();
db.close();
dataLock.unlockWriteAll();
}
@Override
public long apprSize() {
//this does not seem to work? long approxSize= db.stat().ms_entries;
return exactSize();
}
}