package ezdb.leveldb;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.util.Comparator;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import org.iq80.leveldb.DB;
import org.iq80.leveldb.DBIterator;
import org.iq80.leveldb.Options;
import com.google.common.base.Function;
import ezdb.DbException;
import ezdb.RangeTable;
import ezdb.RawTableRow;
import ezdb.TableIterator;
import ezdb.TableRow;
import ezdb.batch.Batch;
import ezdb.batch.RangeBatch;
import ezdb.serde.Serde;
import ezdb.util.Util;
public class EzLevelDbTable<H, R, V> implements RangeTable<H, R, V> {
private final DB db;
private final Serde<H> hashKeySerde;
private final Serde<R> rangeKeySerde;
private final Serde<V> valueSerde;
private final Comparator<byte[]> hashKeyComparator;
private final Comparator<byte[]> rangeKeyComparator;
public EzLevelDbTable(File path, EzLevelDbFactory factory,
Serde<H> hashKeySerde, Serde<R> rangeKeySerde, Serde<V> valueSerde,
Comparator<byte[]> hashKeyComparator,
Comparator<byte[]> rangeKeyComparator) {
this.hashKeySerde = hashKeySerde;
this.rangeKeySerde = rangeKeySerde;
this.valueSerde = valueSerde;
this.hashKeyComparator = hashKeyComparator;
this.rangeKeyComparator = rangeKeyComparator;
Options options = new Options();
options.createIfMissing(true);
options.comparator(new EzLevelDbComparator(hashKeyComparator,
rangeKeyComparator));
try {
this.db = factory.open(path, options);
} catch (IOException e) {
throw new DbException(e);
}
}
@Override
public void put(H hashKey, V value) {
put(hashKey, null, value);
}
@Override
public void put(H hashKey, R rangeKey, V value) {
db.put(Util.combine(hashKeySerde, rangeKeySerde, hashKey, rangeKey),
valueSerde.toBytes(value));
}
@Override
public V get(H hashKey) {
return get(hashKey, null);
}
@Override
public V get(H hashKey, R rangeKey) {
byte[] valueBytes = db.get(Util.combine(hashKeySerde, rangeKeySerde,
hashKey, rangeKey));
if (valueBytes == null) {
return null;
}
return valueSerde.fromBytes(valueBytes);
}
@Override
public TableIterator<H, R, V> range(H hashKey) {
final DBIterator iterator = db.iterator();
final byte[] keyBytesFrom = Util.combine(hashKeySerde, rangeKeySerde,
hashKey, null);
iterator.seek(keyBytesFrom);
return new AutoClosingTableIterator<H, R, V>(
new TableIterator<H, R, V>() {
@Override
public boolean hasNext() {
return iterator.hasNext()
&& Util.compareKeys(hashKeyComparator, null,
keyBytesFrom, iterator.peekNext()
.getKey()) == 0;
}
@Override
public TableRow<H, R, V> next() {
return new RawTableRow<H, R, V>(iterator.next(),
hashKeySerde, rangeKeySerde, valueSerde);
}
@Override
public void remove() {
iterator.remove();
}
@Override
public void close() {
try {
iterator.close();
} catch (Exception e) {
throw new DbException(e);
}
}
});
}
@Override
public TableIterator<H, R, V> range(H hashKey, R fromRangeKey) {
if (fromRangeKey == null) {
return range(hashKey);
}
final DBIterator iterator = db.iterator();
final byte[] keyBytesFrom = Util.combine(hashKeySerde, rangeKeySerde,
hashKey, fromRangeKey);
iterator.seek(keyBytesFrom);
return new AutoClosingTableIterator<H, R, V>(
new TableIterator<H, R, V>() {
@Override
public boolean hasNext() {
return iterator.hasNext()
&& Util.compareKeys(hashKeyComparator, null,
keyBytesFrom, iterator.peekNext()
.getKey()) == 0;
}
@Override
public TableRow<H, R, V> next() {
return new RawTableRow<H, R, V>(iterator.next(),
hashKeySerde, rangeKeySerde, valueSerde);
}
@Override
public void remove() {
iterator.remove();
}
@Override
public void close() {
try {
iterator.close();
} catch (Exception e) {
throw new DbException(e);
}
}
});
}
@Override
public TableIterator<H, R, V> range(H hashKey, R fromRangeKey, R toRangeKey) {
if (toRangeKey == null) {
return range(hashKey, fromRangeKey);
}
final DBIterator iterator = db.iterator();
final byte[] keyBytesFrom = Util.combine(hashKeySerde, rangeKeySerde,
hashKey, fromRangeKey);
final byte[] keyBytesTo = Util.combine(hashKeySerde, rangeKeySerde,
hashKey, toRangeKey);
iterator.seek(keyBytesFrom);
return new AutoClosingTableIterator<H, R, V>(
new TableIterator<H, R, V>() {
@Override
public boolean hasNext() {
return iterator.hasNext()
&& Util.compareKeys(hashKeyComparator,
rangeKeyComparator, keyBytesTo,
iterator.peekNext().getKey()) >= 0;
}
@Override
public TableRow<H, R, V> next() {
return new RawTableRow<H, R, V>(iterator.next(),
hashKeySerde, rangeKeySerde, valueSerde);
}
@Override
public void remove() {
iterator.remove();
}
@Override
public void close() {
try {
iterator.close();
} catch (Exception e) {
throw new DbException(e);
}
}
});
}
public TableIterator<H, R, V> rangeReverse(final H hashKey) {
final DBIterator iterator = db.iterator();
final Function<CheckKeysRequest, Boolean> checkKeys = new Function<CheckKeysRequest, Boolean>() {
@Override
public Boolean apply(CheckKeysRequest input) {
return Util.compareKeys(hashKeyComparator, null,
input.getKeyBytesFrom(), input.getPeekKey()) == 0;
}
};
final byte[] keyBytesFrom = Util.combine(hashKeySerde, rangeKeySerde,
hashKey, null);
TableIterator<H, R, V> emptyIterator = reverseSeekToLast(hashKey, null,
null, keyBytesFrom, null, iterator, checkKeys);
if (emptyIterator != null) {
return emptyIterator;
}
return new AutoClosingTableIterator<H, R, V>(
new TableIterator<H, R, V>() {
private boolean fixFirst = true;;
@Override
public boolean hasNext() {
if (useFixFirst()) {
return true;
}
return iterator.hasPrev()
&& checkKeys.apply(new CheckKeysRequest(
hashKey, null, null, keyBytesFrom,
null, iterator.peekPrev()));
}
private boolean useFixFirst() {
if (fixFirst && iterator.hasNext()) {
final Entry<byte[], byte[]> peekNext = iterator
.peekNext();
if (peekNext != null) {
if (checkKeys.apply(new CheckKeysRequest(
hashKey, null, null, keyBytesFrom,
null, peekNext))) {
return true;
} else {
fixFirst = false;
}
}
}
return false;
}
@Override
public TableRow<H, R, V> next() {
if (useFixFirst()) {
fixFirst = false;
return new RawTableRow<H, R, V>(
iterator.peekNext(), hashKeySerde,
rangeKeySerde, valueSerde);
}
return new RawTableRow<H, R, V>(iterator.prev(),
hashKeySerde, rangeKeySerde, valueSerde);
}
@Override
public void remove() {
if (useFixFirst()) {
throw new UnsupportedOperationException(
"Not possible on first result for now...");
}
iterator.remove();
}
@Override
public void close() {
try {
iterator.close();
} catch (final Exception e) {
throw new DbException(e);
}
}
});
}
private TableIterator<H, R, V> reverseSeekToLast(H hashKey, R fromRangeKey,
R toRangeKey, final byte[] keyBytesFrom, byte[] keyBytesTo,
final DBIterator iterator,
Function<CheckKeysRequest, Boolean> checkKeys) {
iterator.seek(keyBytesFrom);
Entry<byte[], byte[]> last = null;
while (iterator.hasNext()
&& checkKeys.apply(new CheckKeysRequest(hashKey, fromRangeKey,
toRangeKey, keyBytesFrom, keyBytesTo, iterator
.peekNext()))) {
last = iterator.next();
}
// if there is no last one, there is nothing at all in the table
if (last == null) {
return new TableIterator<H, R, V>() {
@Override
public boolean hasNext() {
return false;
}
@Override
public TableRow<H, R, V> next() {
throw new NoSuchElementException();
}
@Override
public void remove() {
throw new NoSuchElementException();
}
@Override
public void close() {
}
};
}
// since last has been found, seek again for that one
iterator.seek(last.getKey());
return null;
}
public TableIterator<H, R, V> rangeReverse(final H hashKey,
final R fromRangeKey) {
if (fromRangeKey == null) {
return rangeReverse(hashKey);
}
final DBIterator iterator = db.iterator();
final Function<CheckKeysRequest, Boolean> checkKeys = new Function<CheckKeysRequest, Boolean>() {
@Override
public Boolean apply(CheckKeysRequest input) {
return Util.compareKeys(hashKeyComparator, null,
input.getKeyBytesFrom(), input.getPeekKey()) == 0
&& (input.getFromRangeKey() == null || Util
.compareKeys(hashKeyComparator,
rangeKeyComparator,
input.getKeyBytesFrom(),
input.getPeekKey()) >= 0);
}
};
final byte[] keyBytesFrom = Util.combine(hashKeySerde, rangeKeySerde,
hashKey, fromRangeKey);
iterator.seek(keyBytesFrom);
if (!iterator.hasNext() || fromRangeKey == null) {
byte[] keyBytesFromForSeekLast = Util.combine(hashKeySerde,
rangeKeySerde, hashKey, null);
TableIterator<H, R, V> emptyIterator = reverseSeekToLast(hashKey,
null, null, keyBytesFromForSeekLast, null, iterator,
checkKeys);
if (emptyIterator != null) {
return emptyIterator;
}
}
return new AutoClosingTableIterator<H, R, V>(
new TableIterator<H, R, V>() {
private boolean fixFirst = true;
@Override
public boolean hasNext() {
if (useFixFirst()) {
return true;
}
return iterator.hasPrev()
&& checkKeys.apply(new CheckKeysRequest(
hashKey, fromRangeKey, null,
keyBytesFrom, null, iterator.peekPrev()));
}
private boolean useFixFirst() {
if (fixFirst && iterator.hasNext()) {
final Entry<byte[], byte[]> peekNext = iterator
.peekNext();
if (peekNext != null) {
if (checkKeys.apply(new CheckKeysRequest(
hashKey, fromRangeKey, null,
keyBytesFrom, null, peekNext))) {
return true;
} else {
fixFirst = false;
}
}
}
return false;
}
@Override
public TableRow<H, R, V> next() {
if (useFixFirst()) {
fixFirst = false;
return new RawTableRow<H, R, V>(
iterator.peekNext(), hashKeySerde,
rangeKeySerde, valueSerde);
}
return new RawTableRow<H, R, V>(iterator.prev(),
hashKeySerde, rangeKeySerde, valueSerde);
}
@Override
public void remove() {
if (useFixFirst()) {
throw new UnsupportedOperationException(
"Not possible on first result for now...");
}
iterator.remove();
}
@Override
public void close() {
try {
iterator.close();
} catch (final Exception e) {
throw new DbException(e);
}
}
});
}
public TableIterator<H, R, V> rangeReverse(final H hashKey,
final R fromRangeKey, final R toRangeKey) {
if (toRangeKey == null) {
return rangeReverse(hashKey, fromRangeKey);
}
final DBIterator iterator = db.iterator();
final Function<CheckKeysRequest, Boolean> checkKeys = new Function<CheckKeysRequest, Boolean>() {
@Override
public Boolean apply(CheckKeysRequest input) {
return Util.compareKeys(hashKeyComparator, null,
input.getKeyBytesFrom(), input.getPeekKey()) == 0
&& (input.getFromRangeKey() == null || Util
.compareKeys(hashKeyComparator,
rangeKeyComparator,
input.getKeyBytesFrom(),
input.getPeekKey()) >= 0)
&& (input.getToRangeKey() == null || Util.compareKeys(
hashKeyComparator, rangeKeyComparator,
input.getKeyBytesTo(), input.getPeekKey()) <= 0);
}
};
final byte[] keyBytesFrom = Util.combine(hashKeySerde, rangeKeySerde,
hashKey, fromRangeKey);
final byte[] keyBytesTo = Util.combine(hashKeySerde, rangeKeySerde,
hashKey, toRangeKey);
iterator.seek(keyBytesFrom);
if (!iterator.hasNext() || fromRangeKey == null) {
byte[] keyBytesFromForSeekLast = Util.combine(hashKeySerde,
rangeKeySerde, hashKey, toRangeKey);
TableIterator<H, R, V> emptyIterator = reverseSeekToLast(hashKey,
null, toRangeKey, keyBytesFromForSeekLast, keyBytesTo,
iterator, checkKeys);
if (emptyIterator != null) {
return emptyIterator;
}
}
return new AutoClosingTableIterator<H, R, V>(
new TableIterator<H, R, V>() {
private boolean fixFirst = true;
@Override
public boolean hasNext() {
if (useFixFirst()) {
return true;
}
return iterator.hasPrev()
&& checkKeys.apply(new CheckKeysRequest(
hashKey, fromRangeKey, toRangeKey,
keyBytesFrom, keyBytesTo, iterator
.peekPrev()));
}
private boolean useFixFirst() {
if (fixFirst && iterator.hasNext()) {
final Entry<byte[], byte[]> peekNext = iterator
.peekNext();
if (peekNext != null) {
if (checkKeys.apply(new CheckKeysRequest(
hashKey, fromRangeKey, toRangeKey,
keyBytesFrom, keyBytesTo, peekNext))) {
return true;
} else {
fixFirst = false;
}
}
}
return false;
}
@Override
public TableRow<H, R, V> next() {
if (useFixFirst()) {
fixFirst = false;
return new RawTableRow<H, R, V>(
iterator.peekNext(), hashKeySerde,
rangeKeySerde, valueSerde);
}
return new RawTableRow<H, R, V>(iterator.prev(),
hashKeySerde, rangeKeySerde, valueSerde);
}
@Override
public void remove() {
if (useFixFirst()) {
throw new UnsupportedOperationException(
"Not possible on first result for now...");
}
iterator.remove();
}
@Override
public void close() {
try {
iterator.close();
} catch (final Exception e) {
throw new DbException(e);
}
}
});
}
@Override
public void delete(H hashKey) {
delete(hashKey, null);
}
@Override
public void delete(H hashKey, R rangeKey) {
this.db.delete(Util.combine(hashKeySerde, rangeKeySerde, hashKey,
rangeKey));
}
@Override
public void close() {
try {
this.db.close();
} catch (Exception e) {
throw new DbException(e);
}
}
private static class AutoClosingTableIterator<_H, _R, _V> implements
TableIterator<_H, _R, _V> {
private final TableIterator<_H, _R, _V> delegate;
private boolean closed;
public AutoClosingTableIterator(final TableIterator<_H, _R, _V> delegate) {
this.delegate = delegate;
}
@Override
public boolean hasNext() {
if (closed) {
return false;
}
final boolean hasNext = delegate.hasNext();
if (!hasNext) {
close();
}
return hasNext;
}
@Override
public TableRow<_H, _R, _V> next() {
if (closed) {
throw new NoSuchElementException();
}
return delegate.next();
}
@Override
public void remove() {
delegate.remove();
}
@Override
protected void finalize() throws Throwable {
super.finalize();
close();
}
@Override
public void close() {
if (!closed) {
closed = true;
delegate.close();
}
}
}
private class CheckKeysRequest {
private final byte[] keyBytesFrom;
private final byte[] keyBytesTo;
private final Entry<byte[], byte[]> peek;
private final H hashKey;
private final R fromRangeKey;
private final R toRangeKey;
public CheckKeysRequest(H hashKey, R fromRangeKey, R toRangeKey,
byte[] keyBytesFrom, byte[] keyBytesTo,
Entry<byte[], byte[]> peek) {
this.hashKey = hashKey;
this.fromRangeKey = fromRangeKey;
this.toRangeKey = toRangeKey;
this.keyBytesFrom = keyBytesFrom;
this.keyBytesTo = keyBytesTo;
this.peek = peek;
}
public byte[] getKeyBytesFrom() {
return keyBytesFrom;
}
public byte[] getKeyBytesTo() {
return keyBytesTo;
}
public byte[] getPeekKey() {
return peek.getKey();
}
public H getHashKey() {
return hashKey;
}
public R getFromRangeKey() {
return fromRangeKey;
}
public R getToRangeKey() {
return toRangeKey;
}
@Override
public String toString() {
return "CheckKeysRequest [hashKey="
+ getHashKey()
+ ", fromRangeKey="
+ getFromRangeKey()
+ ", toRangeKey="
+ getToRangeKey()
+ "] -> "
+ new RawTableRow<H, R, V>(peek, hashKeySerde,
rangeKeySerde, valueSerde).toString();
}
}
@Override
public TableRow<H, R, V> getLatest(H hashKey) {
final TableIterator<H, R, V> rangeReverse = rangeReverse(hashKey);
try {
if (rangeReverse.hasNext()) {
return rangeReverse.next();
} else {
final TableIterator<H, R, V> range = range(hashKey);
try {
if (range.hasNext()) {
return range.next();
} else {
return null;
}
} finally {
range.close();
}
}
} finally {
rangeReverse.close();
}
}
@Override
public TableRow<H, R, V> getLatest(H hashKey, R rangeKey) {
if (rangeKey == null) {
return getLatest(hashKey);
}
final TableIterator<H, R, V> rangeReverse = rangeReverse(hashKey,
rangeKey);
try {
if (rangeReverse.hasNext()) {
return rangeReverse.next();
} else {
final TableIterator<H, R, V> range = range(hashKey, rangeKey);
try {
if (range.hasNext()) {
return range.next();
} else {
return null;
}
} finally {
range.close();
}
}
} finally {
rangeReverse.close();
}
}
@Override
public TableRow<H, R, V> getNext(H hashKey, R rangeKey) {
final TableIterator<H, R, V> range = range(hashKey, rangeKey);
try {
if (range.hasNext()) {
return range.next();
} else {
return null;
}
} finally {
range.close();
}
}
@Override
public TableRow<H, R, V> getPrev(H hashKey, R rangeKey) {
final TableIterator<H, R, V> rangeReverse = rangeReverse(hashKey,
rangeKey);
try {
if (rangeReverse.hasNext()) {
return rangeReverse.next();
} else {
return null;
}
} finally {
rangeReverse.close();
}
}
@Override
public Batch<H, V> newBatch() {
return newRangeBatch();
}
@Override
public RangeBatch<H, R, V> newRangeBatch() {
return new EzLevelDbBatch<H, R, V>(db, hashKeySerde, rangeKeySerde, valueSerde);
}
@Override
public void deleteRange(H hashKey) {
TableIterator<H, R, V> range = range(hashKey);
internalDeleteRange(range);
}
@Override
public void deleteRange(H hashKey, R fromRangeKey) {
TableIterator<H, R, V> range = range(hashKey, fromRangeKey);
internalDeleteRange(range);
}
@Override
public void deleteRange(H hashKey, R fromRangeKey, R toRangeKey) {
TableIterator<H, R, V> range = range(hashKey, fromRangeKey, toRangeKey);
internalDeleteRange(range);
}
private void internalDeleteRange(TableIterator<H, R, V> range) {
RangeBatch<H, R, V> batch = newRangeBatch();
try {
while (range.hasNext()) {
TableRow<H, R, V> next = range.next();
batch.delete(next.getHashKey(), next.getRangeKey());
}
batch.flush();
} finally {
try {
batch.close();
} catch (IOException e) {
throw new DbException(e);
}
}
}
}