Converted everything to netty direct buffers
This commit is contained in:
parent
df84562bb9
commit
2e6aceafe6
16
pom.xml
16
pom.xml
@ -107,7 +107,7 @@
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-api</artifactId>
|
||||
<version>5.7.0</version>
|
||||
<version>5.8.0-M1</version>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
@ -116,6 +116,18 @@
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-engine</artifactId>
|
||||
<version>5.8.0-M1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.junit.jupiter</groupId>
|
||||
<artifactId>junit-jupiter-params</artifactId>
|
||||
<version>5.8.0-M1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<!-- This will get hamcrest-core automatically -->
|
||||
<dependency>
|
||||
<groupId>org.hamcrest</groupId>
|
||||
@ -138,7 +150,7 @@
|
||||
<dependency>
|
||||
<groupId>org.rocksdb</groupId>
|
||||
<artifactId>rocksdbjni</artifactId>
|
||||
<version>6.16.4</version>
|
||||
<version>6.19.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.lucene</groupId>
|
||||
|
@ -11,7 +11,7 @@ public interface LLDatabaseConnection {
|
||||
|
||||
Mono<? extends LLDatabaseConnection> connect();
|
||||
|
||||
Mono<? extends LLKeyValueDatabase> getDatabase(String name, List<Column> columns, boolean lowMemory);
|
||||
Mono<? extends LLKeyValueDatabase> getDatabase(String name, List<Column> columns, boolean lowMemory, boolean inMemory);
|
||||
|
||||
Mono<? extends LLLuceneIndex> getLuceneIndex(String name,
|
||||
int instancesCount,
|
||||
@ -19,7 +19,8 @@ public interface LLDatabaseConnection {
|
||||
TextFieldsSimilarity textFieldsSimilarity,
|
||||
Duration queryRefreshDebounceTime,
|
||||
Duration commitDebounceTime,
|
||||
boolean lowMemory);
|
||||
boolean lowMemory,
|
||||
boolean inMemory);
|
||||
|
||||
Mono<Void> disconnect();
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
package it.cavallium.dbengine.database;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import java.util.List;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.warp.commonutils.concurrency.atomicity.NotAtomic;
|
||||
@ -13,60 +14,62 @@ import reactor.core.publisher.Mono;
|
||||
@NotAtomic
|
||||
public interface LLDictionary extends LLKeyValueDatabaseStructure {
|
||||
|
||||
Mono<byte[]> get(@Nullable LLSnapshot snapshot, byte[] key, boolean existsAlmostCertainly);
|
||||
ByteBufAllocator getAllocator();
|
||||
|
||||
default Mono<byte[]> get(@Nullable LLSnapshot snapshot, byte[] key) {
|
||||
Mono<ByteBuf> get(@Nullable LLSnapshot snapshot, ByteBuf key, boolean existsAlmostCertainly);
|
||||
|
||||
default Mono<ByteBuf> get(@Nullable LLSnapshot snapshot, ByteBuf key) {
|
||||
return get(snapshot, key, false);
|
||||
}
|
||||
|
||||
Mono<byte[]> put(byte[] key, byte[] value, LLDictionaryResultType resultType);
|
||||
Mono<ByteBuf> put(ByteBuf key, ByteBuf value, LLDictionaryResultType resultType);
|
||||
|
||||
Mono<Boolean> update(byte[] key, Function<Optional<byte[]>, Optional<byte[]>> updater, boolean existsAlmostCertainly);
|
||||
Mono<Boolean> update(ByteBuf key, Function<@Nullable ByteBuf, @Nullable ByteBuf> updater, boolean existsAlmostCertainly);
|
||||
|
||||
default Mono<Boolean> update(byte[] key, Function<Optional<byte[]>, Optional<byte[]>> updater) {
|
||||
default Mono<Boolean> update(ByteBuf key, Function<@Nullable ByteBuf, @Nullable ByteBuf> updater) {
|
||||
return update(key, updater, false);
|
||||
}
|
||||
|
||||
Mono<Void> clear();
|
||||
|
||||
Mono<byte[]> remove(byte[] key, LLDictionaryResultType resultType);
|
||||
Mono<ByteBuf> remove(ByteBuf key, LLDictionaryResultType resultType);
|
||||
|
||||
Flux<Entry<byte[], byte[]>> getMulti(@Nullable LLSnapshot snapshot, Flux<byte[]> keys, boolean existsAlmostCertainly);
|
||||
Flux<Entry<ByteBuf, ByteBuf>> getMulti(@Nullable LLSnapshot snapshot, Flux<ByteBuf> keys, boolean existsAlmostCertainly);
|
||||
|
||||
default Flux<Entry<byte[], byte[]>> getMulti(@Nullable LLSnapshot snapshot, Flux<byte[]> keys) {
|
||||
default Flux<Entry<ByteBuf, ByteBuf>> getMulti(@Nullable LLSnapshot snapshot, Flux<ByteBuf> keys) {
|
||||
return getMulti(snapshot, keys, false);
|
||||
}
|
||||
|
||||
Flux<Entry<byte[], byte[]>> putMulti(Flux<Entry<byte[], byte[]>> entries, boolean getOldValues);
|
||||
Flux<Entry<ByteBuf, ByteBuf>> putMulti(Flux<Entry<ByteBuf, ByteBuf>> entries, boolean getOldValues);
|
||||
|
||||
Flux<Entry<byte[], byte[]>> getRange(@Nullable LLSnapshot snapshot, LLRange range, boolean existsAlmostCertainly);
|
||||
Flux<Entry<ByteBuf, ByteBuf>> getRange(@Nullable LLSnapshot snapshot, LLRange range, boolean existsAlmostCertainly);
|
||||
|
||||
default Flux<Entry<byte[], byte[]>> getRange(@Nullable LLSnapshot snapshot, LLRange range) {
|
||||
default Flux<Entry<ByteBuf, ByteBuf>> getRange(@Nullable LLSnapshot snapshot, LLRange range) {
|
||||
return getRange(snapshot, range, false);
|
||||
}
|
||||
|
||||
Flux<List<Entry<byte[], byte[]>>> getRangeGrouped(@Nullable LLSnapshot snapshot,
|
||||
Flux<List<Entry<ByteBuf, ByteBuf>>> getRangeGrouped(@Nullable LLSnapshot snapshot,
|
||||
LLRange range,
|
||||
int prefixLength,
|
||||
boolean existsAlmostCertainly);
|
||||
|
||||
default Flux<List<Entry<byte[], byte[]>>> getRangeGrouped(@Nullable LLSnapshot snapshot,
|
||||
default Flux<List<Entry<ByteBuf, ByteBuf>>> getRangeGrouped(@Nullable LLSnapshot snapshot,
|
||||
LLRange range,
|
||||
int prefixLength) {
|
||||
return getRangeGrouped(snapshot, range, prefixLength, false);
|
||||
}
|
||||
|
||||
Flux<byte[]> getRangeKeys(@Nullable LLSnapshot snapshot, LLRange range);
|
||||
Flux<ByteBuf> getRangeKeys(@Nullable LLSnapshot snapshot, LLRange range);
|
||||
|
||||
Flux<List<byte[]>> getRangeKeysGrouped(@Nullable LLSnapshot snapshot, LLRange range, int prefixLength);
|
||||
Flux<List<ByteBuf>> getRangeKeysGrouped(@Nullable LLSnapshot snapshot, LLRange range, int prefixLength);
|
||||
|
||||
Flux<byte[]> getRangeKeyPrefixes(@Nullable LLSnapshot snapshot, LLRange range, int prefixLength);
|
||||
Flux<ByteBuf> getRangeKeyPrefixes(@Nullable LLSnapshot snapshot, LLRange range, int prefixLength);
|
||||
|
||||
Flux<Entry<byte[], byte[]>> setRange(LLRange range, Flux<Entry<byte[], byte[]>> entries, boolean getOldValues);
|
||||
Flux<Entry<ByteBuf, ByteBuf>> setRange(LLRange range, Flux<Entry<ByteBuf, ByteBuf>> entries, boolean getOldValues);
|
||||
|
||||
default Mono<Void> replaceRange(LLRange range,
|
||||
boolean canKeysChange,
|
||||
Function<Entry<byte[], byte[]>, Mono<Entry<byte[], byte[]>>> entriesReplacer,
|
||||
Function<Entry<ByteBuf, ByteBuf>, Mono<Entry<ByteBuf, ByteBuf>>> entriesReplacer,
|
||||
boolean existsAlmostCertainly) {
|
||||
return Mono.defer(() -> {
|
||||
if (canKeysChange) {
|
||||
@ -87,7 +90,7 @@ public interface LLDictionary extends LLKeyValueDatabaseStructure {
|
||||
|
||||
default Mono<Void> replaceRange(LLRange range,
|
||||
boolean canKeysChange,
|
||||
Function<Entry<byte[], byte[]>, Mono<Entry<byte[], byte[]>>> entriesReplacer) {
|
||||
Function<Entry<ByteBuf, ByteBuf>, Mono<Entry<ByteBuf, ByteBuf>>> entriesReplacer) {
|
||||
return replaceRange(range, canKeysChange, entriesReplacer, false);
|
||||
}
|
||||
|
||||
@ -95,9 +98,9 @@ public interface LLDictionary extends LLKeyValueDatabaseStructure {
|
||||
|
||||
Mono<Long> sizeRange(@Nullable LLSnapshot snapshot, LLRange range, boolean fast);
|
||||
|
||||
Mono<Entry<byte[], byte[]>> getOne(@Nullable LLSnapshot snapshot, LLRange range);
|
||||
Mono<Entry<ByteBuf, ByteBuf>> getOne(@Nullable LLSnapshot snapshot, LLRange range);
|
||||
|
||||
Mono<byte[]> getOneKey(@Nullable LLSnapshot snapshot, LLRange range);
|
||||
Mono<ByteBuf> getOneKey(@Nullable LLSnapshot snapshot, LLRange range);
|
||||
|
||||
Mono<Entry<byte[], byte[]>> removeOne(LLRange range);
|
||||
Mono<Entry<ByteBuf, ByteBuf>> removeOne(LLRange range);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
package it.cavallium.dbengine.database;
|
||||
|
||||
public enum LLDictionaryResultType {
|
||||
VOID, VALUE_CHANGED, PREVIOUS_VALUE
|
||||
VOID, PREVIOUS_VALUE_EXISTENCE, PREVIOUS_VALUE
|
||||
}
|
||||
|
@ -1,5 +1,10 @@
|
||||
package it.cavallium.dbengine.database;
|
||||
|
||||
import static io.netty.buffer.Unpooled.wrappedBuffer;
|
||||
import static io.netty.buffer.Unpooled.wrappedUnmodifiableBuffer;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import java.util.Arrays;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
@ -9,10 +14,18 @@ import java.util.StringJoiner;
|
||||
public class LLRange {
|
||||
|
||||
private static final LLRange RANGE_ALL = new LLRange(null, null);
|
||||
private final byte[] min;
|
||||
private final byte[] max;
|
||||
private final ByteBuf min;
|
||||
private final ByteBuf max;
|
||||
|
||||
private LLRange(byte[] min, byte[] max) {
|
||||
private LLRange(ByteBuf min, ByteBuf max) {
|
||||
assert min == null || min.refCnt() > 0;
|
||||
assert max == null || max.refCnt() > 0;
|
||||
if (min != null && !min.isDirect()) {
|
||||
throw new IllegalArgumentException("Min buffer must be direct");
|
||||
}
|
||||
if (max != null && !max.isDirect()) {
|
||||
throw new IllegalArgumentException("Min buffer must be direct");
|
||||
}
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
@ -21,50 +34,64 @@ public class LLRange {
|
||||
return RANGE_ALL;
|
||||
}
|
||||
|
||||
public static LLRange from(byte[] min) {
|
||||
public static LLRange from(ByteBuf min) {
|
||||
return new LLRange(min, null);
|
||||
}
|
||||
|
||||
public static LLRange to(byte[] max) {
|
||||
public static LLRange to(ByteBuf max) {
|
||||
return new LLRange(null, max);
|
||||
}
|
||||
|
||||
public static LLRange single(byte[] single) {
|
||||
public static LLRange single(ByteBuf single) {
|
||||
return new LLRange(single, single);
|
||||
}
|
||||
|
||||
public static LLRange of(byte[] min, byte[] max) {
|
||||
public static LLRange of(ByteBuf min, ByteBuf max) {
|
||||
return new LLRange(min, max);
|
||||
}
|
||||
|
||||
public boolean isAll() {
|
||||
assert min == null || min.refCnt() > 0;
|
||||
assert max == null || max.refCnt() > 0;
|
||||
return min == null && max == null;
|
||||
}
|
||||
|
||||
public boolean isSingle() {
|
||||
assert min == null || min.refCnt() > 0;
|
||||
assert max == null || max.refCnt() > 0;
|
||||
if (min == null || max == null) return false;
|
||||
return Arrays.equals(min, max);
|
||||
return LLUtils.equals(min, max);
|
||||
}
|
||||
|
||||
public boolean hasMin() {
|
||||
assert min == null || min.refCnt() > 0;
|
||||
assert max == null || max.refCnt() > 0;
|
||||
return min != null;
|
||||
}
|
||||
|
||||
public byte[] getMin() {
|
||||
public ByteBuf getMin() {
|
||||
assert min == null || min.refCnt() > 0;
|
||||
assert max == null || max.refCnt() > 0;
|
||||
assert min != null;
|
||||
return min;
|
||||
}
|
||||
|
||||
public boolean hasMax() {
|
||||
assert min == null || min.refCnt() > 0;
|
||||
assert max == null || max.refCnt() > 0;
|
||||
return max != null;
|
||||
}
|
||||
|
||||
public byte[] getMax() {
|
||||
public ByteBuf getMax() {
|
||||
assert min == null || min.refCnt() > 0;
|
||||
assert max == null || max.refCnt() > 0;
|
||||
assert max != null;
|
||||
return max;
|
||||
}
|
||||
|
||||
public byte[] getSingle() {
|
||||
public ByteBuf getSingle() {
|
||||
assert min == null || min.refCnt() > 0;
|
||||
assert max == null || max.refCnt() > 0;
|
||||
assert isSingle();
|
||||
return min;
|
||||
}
|
||||
@ -78,21 +105,40 @@ public class LLRange {
|
||||
return false;
|
||||
}
|
||||
LLRange llRange = (LLRange) o;
|
||||
return Arrays.equals(min, llRange.min) && Arrays.equals(max, llRange.max);
|
||||
return LLUtils.equals(min, llRange.min) && LLUtils.equals(max, llRange.max);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = Arrays.hashCode(min);
|
||||
result = 31 * result + Arrays.hashCode(max);
|
||||
int result = LLUtils.hashCode(min);
|
||||
result = 31 * result + LLUtils.hashCode(max);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringJoiner(", ", LLRange.class.getSimpleName() + "[", "]")
|
||||
.add("min=" + Arrays.toString(min))
|
||||
.add("max=" + Arrays.toString(max))
|
||||
.add("min=" + LLUtils.toString(min))
|
||||
.add("max=" + LLUtils.toString(max))
|
||||
.toString();
|
||||
}
|
||||
|
||||
public LLRange retain() {
|
||||
if (min != null) {
|
||||
min.retain();
|
||||
}
|
||||
if (max != null) {
|
||||
max.retain();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public void release() {
|
||||
if (min != null) {
|
||||
min.release();
|
||||
}
|
||||
if (max != null) {
|
||||
max.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,18 @@ package it.cavallium.dbengine.database;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.common.primitives.Longs;
|
||||
import io.netty.buffer.AbstractByteBufAllocator;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.CompositeByteBuf;
|
||||
import io.netty.buffer.PooledByteBufAllocator;
|
||||
import it.cavallium.dbengine.lucene.RandomSortField;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.function.ToIntFunction;
|
||||
import org.apache.lucene.document.Document;
|
||||
import org.apache.lucene.document.Field;
|
||||
import org.apache.lucene.document.FloatPoint;
|
||||
@ -20,13 +28,20 @@ import org.apache.lucene.search.ScoreMode;
|
||||
import org.apache.lucene.search.Sort;
|
||||
import org.apache.lucene.search.SortField;
|
||||
import org.apache.lucene.search.SortedNumericSortField;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.rocksdb.RocksDB;
|
||||
|
||||
import static io.netty.buffer.Unpooled.EMPTY_BUFFER;
|
||||
import static io.netty.buffer.Unpooled.wrappedBuffer;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class LLUtils {
|
||||
|
||||
private static final byte[] RESPONSE_TRUE = new byte[]{1};
|
||||
private static final byte[] RESPONSE_FALSE = new byte[]{0};
|
||||
private static final byte[] RESPONSE_TRUE_BUF = new byte[]{1};
|
||||
private static final byte[] RESPONSE_FALSE_BUF = new byte[]{0};
|
||||
public static final byte[][] LEXICONOGRAPHIC_ITERATION_SEEKS = new byte[256][1];
|
||||
|
||||
static {
|
||||
@ -40,10 +55,23 @@ public class LLUtils {
|
||||
return response[0] == 1;
|
||||
}
|
||||
|
||||
public static boolean responseToBoolean(ByteBuf response) {
|
||||
try {
|
||||
assert response.readableBytes() == 1;
|
||||
return response.getByte(response.readerIndex()) == 1;
|
||||
} finally {
|
||||
response.release();
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] booleanToResponse(boolean bool) {
|
||||
return bool ? RESPONSE_TRUE : RESPONSE_FALSE;
|
||||
}
|
||||
|
||||
public static ByteBuf booleanToResponseByteBuffer(boolean bool) {
|
||||
return wrappedBuffer(booleanToResponse(bool));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Sort toSort(@Nullable LLSort sort) {
|
||||
if (sort == null) {
|
||||
@ -127,4 +155,228 @@ public class LLUtils {
|
||||
public static it.cavallium.dbengine.database.LLKeyScore toKeyScore(LLKeyScore hit) {
|
||||
return new it.cavallium.dbengine.database.LLKeyScore(hit.getKey(), hit.getScore());
|
||||
}
|
||||
|
||||
public static String toString(ByteBuf key) {
|
||||
if (key == null) {
|
||||
return "null";
|
||||
} else {
|
||||
int startIndex = key.readerIndex();
|
||||
int iMax = key.readableBytes() - 1;
|
||||
int iLimit = 128;
|
||||
if (iMax <= -1) {
|
||||
return "[]";
|
||||
} else {
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append('[');
|
||||
int i = 0;
|
||||
|
||||
while(true) {
|
||||
b.append(key.getByte(startIndex + i));
|
||||
if (i == iLimit) {
|
||||
b.append("…");
|
||||
}
|
||||
if (i == iMax || i == iLimit) {
|
||||
return b.append(']').toString();
|
||||
}
|
||||
|
||||
b.append(", ");
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean equals(ByteBuf a, ByteBuf b) {
|
||||
if (a == null && b == null) {
|
||||
return true;
|
||||
} else if (a != null && b != null) {
|
||||
return ByteBufUtil.equals(a, b);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] toArray(ByteBuf key) {
|
||||
byte[] keyBytes = new byte[key.readableBytes()];
|
||||
key.getBytes(key.readerIndex(), keyBytes, 0, key.readableBytes());
|
||||
return keyBytes;
|
||||
}
|
||||
|
||||
public static List<byte[]> toArray(List<ByteBuf> input) {
|
||||
List<byte[]> result = new ArrayList<>(input.size());
|
||||
for (ByteBuf byteBuf : input) {
|
||||
result.add(toArray(byteBuf));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static int hashCode(ByteBuf buf) {
|
||||
return buf == null ? 0 : buf.hashCode();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static ByteBuf readNullableDirectNioBuffer(ByteBufAllocator alloc, ToIntFunction<ByteBuffer> reader) {
|
||||
ByteBuf buffer = alloc.directBuffer();
|
||||
try {
|
||||
ByteBuf directBuffer = null;
|
||||
ByteBuffer nioBuffer;
|
||||
int size;
|
||||
Boolean mustBeCopied = null;
|
||||
do {
|
||||
if (mustBeCopied == null || !mustBeCopied) {
|
||||
nioBuffer = LLUtils.toDirectFast(buffer.retain());
|
||||
if (nioBuffer != null) {
|
||||
nioBuffer.limit(nioBuffer.capacity());
|
||||
}
|
||||
} else {
|
||||
nioBuffer = null;
|
||||
}
|
||||
if ((mustBeCopied != null && mustBeCopied) || nioBuffer == null) {
|
||||
directBuffer = LLUtils.toDirectCopy(buffer.retain());
|
||||
nioBuffer = directBuffer.nioBuffer(0, directBuffer.capacity());
|
||||
mustBeCopied = true;
|
||||
} else {
|
||||
mustBeCopied = false;
|
||||
}
|
||||
try {
|
||||
assert nioBuffer.isDirect();
|
||||
size = reader.applyAsInt(nioBuffer);
|
||||
if (size != RocksDB.NOT_FOUND) {
|
||||
if (mustBeCopied) {
|
||||
buffer.writerIndex(0).writeBytes(nioBuffer);
|
||||
}
|
||||
if (size == nioBuffer.limit()) {
|
||||
buffer.setIndex(0, size);
|
||||
return buffer;
|
||||
} else {
|
||||
assert size > nioBuffer.limit();
|
||||
assert nioBuffer.limit() > 0;
|
||||
buffer.capacity(size);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (nioBuffer != null) {
|
||||
nioBuffer = null;
|
||||
}
|
||||
if(directBuffer != null) {
|
||||
directBuffer.release();
|
||||
directBuffer = null;
|
||||
}
|
||||
}
|
||||
} while (size != RocksDB.NOT_FOUND);
|
||||
} catch (Throwable t) {
|
||||
buffer.release();
|
||||
throw t;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static ByteBuffer toDirectFast(ByteBuf buffer) {
|
||||
try {
|
||||
ByteBuffer result = buffer.nioBuffer(0, buffer.capacity());
|
||||
if (result.isDirect()) {
|
||||
result.limit(buffer.writerIndex());
|
||||
|
||||
assert result.isDirect();
|
||||
assert result.capacity() == buffer.capacity();
|
||||
assert buffer.readerIndex() == result.position();
|
||||
assert result.limit() - result.position() == buffer.readableBytes();
|
||||
|
||||
return result;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} finally {
|
||||
buffer.release();
|
||||
}
|
||||
}
|
||||
|
||||
public static ByteBuf toDirectCopy(ByteBuf buffer) {
|
||||
try {
|
||||
ByteBuf directCopyBuf = buffer.alloc().directBuffer(buffer.capacity(), buffer.maxCapacity());
|
||||
directCopyBuf.writeBytes(buffer, 0, buffer.writerIndex());
|
||||
return directCopyBuf;
|
||||
} finally {
|
||||
buffer.release();
|
||||
}
|
||||
}
|
||||
|
||||
public static ByteBuf convertToDirectByteBuf(AbstractByteBufAllocator alloc, ByteBuf buffer) {
|
||||
ByteBuf result;
|
||||
ByteBuf directCopyBuf = alloc.directBuffer(buffer.capacity(), buffer.maxCapacity());
|
||||
directCopyBuf.writeBytes(buffer, 0, buffer.writerIndex());
|
||||
directCopyBuf.readerIndex(buffer.readerIndex());
|
||||
result = directCopyBuf;
|
||||
assert result.isDirect();
|
||||
assert result.capacity() == buffer.capacity();
|
||||
assert buffer.readerIndex() == result.readerIndex();
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static ByteBuf readDirectNioBuffer(ByteBufAllocator alloc, ToIntFunction<ByteBuffer> reader) {
|
||||
var buffer = readNullableDirectNioBuffer(alloc, reader);
|
||||
if (buffer == null) {
|
||||
throw new IllegalStateException("A non-nullable buffer read operation tried to return a \"not found\" element");
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
public static ByteBuf directCompositeBuffer(ByteBufAllocator alloc, ByteBuf buffer) {
|
||||
return wrappedBuffer(buffer);
|
||||
}
|
||||
|
||||
public static ByteBuf directCompositeBuffer(ByteBufAllocator alloc, ByteBuf buffer1, ByteBuf buffer2) {
|
||||
assert buffer1.isDirect();
|
||||
assert buffer1.nioBuffer().isDirect();
|
||||
assert buffer2.isDirect();
|
||||
assert buffer2.nioBuffer().isDirect();
|
||||
if (buffer1.readableBytes() == 0) {
|
||||
return wrappedBuffer(buffer2);
|
||||
} else if (buffer2.readableBytes() == 0) {
|
||||
return wrappedBuffer(buffer1);
|
||||
}
|
||||
CompositeByteBuf compositeBuffer = alloc.compositeDirectBuffer(2);
|
||||
compositeBuffer.addComponent(true, buffer1);
|
||||
compositeBuffer.addComponent(true, buffer2);
|
||||
compositeBuffer.consolidate();
|
||||
assert compositeBuffer.isDirect();
|
||||
assert compositeBuffer.nioBuffer().isDirect();
|
||||
return compositeBuffer;
|
||||
}
|
||||
|
||||
public static ByteBuf directCompositeBuffer(ByteBufAllocator alloc, ByteBuf buffer1, ByteBuf buffer2, ByteBuf buffer3) {
|
||||
if (buffer1.readableBytes() == 0) {
|
||||
return directCompositeBuffer(alloc, buffer2, buffer3);
|
||||
} else if (buffer2.readableBytes() == 0) {
|
||||
return directCompositeBuffer(alloc, buffer1, buffer3);
|
||||
} else if (buffer3.readableBytes() == 0) {
|
||||
return directCompositeBuffer(alloc, buffer1, buffer2);
|
||||
}
|
||||
CompositeByteBuf compositeBuffer = alloc.compositeDirectBuffer(3);
|
||||
compositeBuffer.addComponent(true, buffer1);
|
||||
compositeBuffer.addComponent(true, buffer2);
|
||||
compositeBuffer.addComponent(true, buffer3);
|
||||
compositeBuffer.consolidate();
|
||||
return compositeBuffer;
|
||||
}
|
||||
|
||||
public static ByteBuf directCompositeBuffer(ByteBufAllocator alloc, ByteBuf... buffers) {
|
||||
switch (buffers.length) {
|
||||
case 0:
|
||||
return EMPTY_BUFFER;
|
||||
case 1:
|
||||
return directCompositeBuffer(alloc, buffers[0]);
|
||||
case 2:
|
||||
return directCompositeBuffer(alloc, buffers[0], buffers[1]);
|
||||
case 3:
|
||||
return directCompositeBuffer(alloc, buffers[0], buffers[1], buffers[2]);
|
||||
default:
|
||||
CompositeByteBuf compositeBuffer = alloc.compositeDirectBuffer(buffers.length);
|
||||
compositeBuffer.addComponents(true, buffers);
|
||||
compositeBuffer.consolidate();
|
||||
return compositeBuffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,25 @@
|
||||
package it.cavallium.dbengine.database.collections;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import it.cavallium.dbengine.database.LLDictionary;
|
||||
import it.cavallium.dbengine.database.serialization.Serializer;
|
||||
import java.util.function.Function;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import static io.netty.buffer.Unpooled.*;
|
||||
|
||||
public class DatabaseEmpty {
|
||||
|
||||
@SuppressWarnings({"unused", "InstantiationOfUtilityClass"})
|
||||
public static final Nothing NOTHING = new Nothing();
|
||||
private static final byte[] NOTHING_BYTES = new byte[0];
|
||||
public static final Serializer<Nothing, byte[]> NOTHING_SERIALIZER = new Serializer<>() {
|
||||
public static final Serializer<Nothing, ByteBuf> NOTHING_SERIALIZER = new Serializer<>() {
|
||||
@Override
|
||||
public @NotNull Nothing deserialize(byte @NotNull [] serialized) {
|
||||
public @NotNull Nothing deserialize(@NotNull ByteBuf serialized) {
|
||||
return NOTHING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte @NotNull [] serialize(@NotNull Nothing deserialized) {
|
||||
return NOTHING_BYTES;
|
||||
public @NotNull ByteBuf serialize(@NotNull Nothing deserialized) {
|
||||
return EMPTY_BUFFER;
|
||||
}
|
||||
};
|
||||
public static final Function<Nothing, Nothing> NOTHING_HASH_FUNCTION = nothing -> nothing;
|
||||
@ -28,7 +29,7 @@ public class DatabaseEmpty {
|
||||
private DatabaseEmpty() {
|
||||
}
|
||||
|
||||
public static DatabaseStageEntry<Nothing> create(LLDictionary dictionary, byte[] key) {
|
||||
public static DatabaseStageEntry<Nothing> create(LLDictionary dictionary, ByteBuf key) {
|
||||
return new DatabaseSingle<>(dictionary, key, NOTHING_SERIALIZER);
|
||||
}
|
||||
|
||||
|
@ -1,18 +1,19 @@
|
||||
package it.cavallium.dbengine.database.collections;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import it.cavallium.dbengine.client.CompositeSnapshot;
|
||||
import it.cavallium.dbengine.database.LLDictionary;
|
||||
import it.cavallium.dbengine.database.LLDictionaryResultType;
|
||||
import it.cavallium.dbengine.database.LLUtils;
|
||||
import it.cavallium.dbengine.database.serialization.Serializer;
|
||||
import it.cavallium.dbengine.database.serialization.SerializerFixedBinaryLength;
|
||||
import java.util.Arrays;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.rocksdb.RocksDBException;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@ -21,40 +22,44 @@ import reactor.core.publisher.Mono;
|
||||
*/
|
||||
public class DatabaseMapDictionary<T, U> extends DatabaseMapDictionaryDeep<T, U, DatabaseStageEntry<U>> {
|
||||
|
||||
private final Serializer<U, byte[]> valueSerializer;
|
||||
private final Serializer<U, ByteBuf> valueSerializer;
|
||||
|
||||
protected DatabaseMapDictionary(LLDictionary dictionary,
|
||||
byte[] prefixKey,
|
||||
SerializerFixedBinaryLength<T, byte[]> keySuffixSerializer,
|
||||
Serializer<U, byte[]> valueSerializer) {
|
||||
ByteBuf prefixKey,
|
||||
SerializerFixedBinaryLength<T, ByteBuf> keySuffixSerializer,
|
||||
Serializer<U, ByteBuf> valueSerializer) {
|
||||
// Do not retain or release or use the prefixKey here
|
||||
super(dictionary, prefixKey, keySuffixSerializer, new SubStageGetterSingle<>(valueSerializer), 0);
|
||||
prefixKey = null;
|
||||
this.valueSerializer = valueSerializer;
|
||||
}
|
||||
|
||||
public static <T, U> DatabaseMapDictionary<T, U> simple(LLDictionary dictionary,
|
||||
SerializerFixedBinaryLength<T, byte[]> keySerializer,
|
||||
Serializer<U, byte[]> valueSerializer) {
|
||||
SerializerFixedBinaryLength<T, ByteBuf> keySerializer,
|
||||
Serializer<U, ByteBuf> valueSerializer) {
|
||||
return new DatabaseMapDictionary<>(dictionary, EMPTY_BYTES, keySerializer, valueSerializer);
|
||||
}
|
||||
|
||||
public static <T, U> DatabaseMapDictionary<T, U> tail(LLDictionary dictionary,
|
||||
byte[] prefixKey,
|
||||
SerializerFixedBinaryLength<T, byte[]> keySuffixSerializer,
|
||||
Serializer<U, byte[]> valueSerializer) {
|
||||
ByteBuf prefixKey,
|
||||
SerializerFixedBinaryLength<T, ByteBuf> keySuffixSerializer,
|
||||
Serializer<U, ByteBuf> valueSerializer) {
|
||||
return new DatabaseMapDictionary<>(dictionary, prefixKey, keySuffixSerializer, valueSerializer);
|
||||
}
|
||||
|
||||
private byte[] toKey(byte[] suffixKey) {
|
||||
assert suffixKeyConsistency(suffixKey.length);
|
||||
byte[] key = Arrays.copyOf(keyPrefix, keyPrefix.length + suffixKey.length);
|
||||
System.arraycopy(suffixKey, 0, key, keyPrefix.length, suffixKey.length);
|
||||
return key;
|
||||
private ByteBuf toKey(ByteBuf suffixKey) {
|
||||
assert suffixKeyConsistency(suffixKey.readableBytes());
|
||||
try {
|
||||
return LLUtils.directCompositeBuffer(dictionary.getAllocator(), keyPrefix.retain(), suffixKey.retain());
|
||||
} finally {
|
||||
suffixKey.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Map<T, U>> get(@Nullable CompositeSnapshot snapshot, boolean existsAlmostCertainly) {
|
||||
return dictionary
|
||||
.getRange(resolveSnapshot(snapshot), range, existsAlmostCertainly)
|
||||
.getRange(resolveSnapshot(snapshot), range.retain(), existsAlmostCertainly)
|
||||
.collectMap(
|
||||
entry -> deserializeSuffix(stripPrefix(entry.getKey())),
|
||||
entry -> deserialize(entry.getValue()),
|
||||
@ -64,7 +69,7 @@ public class DatabaseMapDictionary<T, U> extends DatabaseMapDictionaryDeep<T, U,
|
||||
@Override
|
||||
public Mono<Map<T, U>> setAndGetPrevious(Map<T, U> value) {
|
||||
return dictionary
|
||||
.setRange(range,
|
||||
.setRange(range.retain(),
|
||||
Flux
|
||||
.fromIterable(value.entrySet())
|
||||
.map(entry -> Map.entry(serializeSuffix(entry.getKey()), serialize(entry.getValue()))),
|
||||
@ -79,7 +84,7 @@ public class DatabaseMapDictionary<T, U> extends DatabaseMapDictionaryDeep<T, U,
|
||||
@Override
|
||||
public Mono<Map<T, U>> clearAndGetPrevious() {
|
||||
return dictionary
|
||||
.setRange(range, Flux.empty(), true)
|
||||
.setRange(range.retain(), Flux.empty(), true)
|
||||
.collectMap(
|
||||
entry -> deserializeSuffix(stripPrefix(entry.getKey())),
|
||||
entry -> deserialize(entry.getValue()),
|
||||
@ -88,96 +93,170 @@ public class DatabaseMapDictionary<T, U> extends DatabaseMapDictionaryDeep<T, U,
|
||||
|
||||
@Override
|
||||
public Mono<Long> leavesCount(@Nullable CompositeSnapshot snapshot, boolean fast) {
|
||||
return dictionary.sizeRange(resolveSnapshot(snapshot), range, fast);
|
||||
return dictionary.sizeRange(resolveSnapshot(snapshot), range.retain(), fast);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> isEmpty(@Nullable CompositeSnapshot snapshot) {
|
||||
return dictionary.isRangeEmpty(resolveSnapshot(snapshot), range);
|
||||
return dictionary.isRangeEmpty(resolveSnapshot(snapshot), range.retain());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<DatabaseStageEntry<U>> at(@Nullable CompositeSnapshot snapshot, T keySuffix) {
|
||||
ByteBuf keySuffixBuf = serializeSuffix(keySuffix);
|
||||
ByteBuf keyBuf = toKey(keySuffixBuf.retain());
|
||||
return Mono
|
||||
.just(new DatabaseSingle<>(dictionary, toKey(serializeSuffix(keySuffix)), Serializer.noop()))
|
||||
.map(entry -> new DatabaseSingleMapped<>(entry, valueSerializer));
|
||||
.fromSupplier(() -> new DatabaseSingle<>(dictionary, keyBuf.retain(), Serializer.noop()))
|
||||
.<DatabaseStageEntry<U>>map(entry -> new DatabaseSingleMapped<>(entry, valueSerializer))
|
||||
.doFinally(s -> {
|
||||
keyBuf.release();
|
||||
keySuffixBuf.release();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<U> getValue(@Nullable CompositeSnapshot snapshot, T keySuffix, boolean existsAlmostCertainly) {
|
||||
ByteBuf keySuffixBuf = serializeSuffix(keySuffix);
|
||||
ByteBuf keyBuf = toKey(keySuffixBuf.retain());
|
||||
return dictionary
|
||||
.get(resolveSnapshot(snapshot), toKey(serializeSuffix(keySuffix)), existsAlmostCertainly)
|
||||
.map(this::deserialize);
|
||||
.get(resolveSnapshot(snapshot), keyBuf.retain(), existsAlmostCertainly)
|
||||
.map(this::deserialize)
|
||||
.doFinally(s -> {
|
||||
keyBuf.release();
|
||||
keySuffixBuf.release();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> putValue(T keySuffix, U value) {
|
||||
return dictionary.put(toKey(serializeSuffix(keySuffix)), serialize(value), LLDictionaryResultType.VOID).then();
|
||||
ByteBuf keySuffixBuf = serializeSuffix(keySuffix);
|
||||
ByteBuf keyBuf = toKey(keySuffixBuf.retain());
|
||||
ByteBuf valueBuf = serialize(value);
|
||||
return dictionary.put(keyBuf.retain(), valueBuf.retain(), LLDictionaryResultType.VOID).doFinally(s -> {
|
||||
keyBuf.release();
|
||||
keySuffixBuf.release();
|
||||
valueBuf.release();
|
||||
}).then();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> updateValue(T keySuffix,
|
||||
boolean existsAlmostCertainly,
|
||||
Function<Optional<U>, Optional<U>> updater) {
|
||||
return dictionary.update(toKey(serializeSuffix(keySuffix)),
|
||||
oldSerialized -> updater.apply(oldSerialized.map(this::deserialize)).map(this::serialize),
|
||||
existsAlmostCertainly
|
||||
);
|
||||
Function<@Nullable U, @Nullable U> updater) {
|
||||
ByteBuf keySuffixBuf = serializeSuffix(keySuffix);
|
||||
ByteBuf keyBuf = toKey(keySuffixBuf.retain());
|
||||
return dictionary.update(keyBuf.retain(), oldSerialized -> {
|
||||
try {
|
||||
var result = updater.apply(oldSerialized == null ? null : this.deserialize(oldSerialized.retain()));
|
||||
if (result == null) {
|
||||
return null;
|
||||
} else {
|
||||
return this.serialize(result);
|
||||
}
|
||||
} finally {
|
||||
if (oldSerialized != null) {
|
||||
oldSerialized.release();
|
||||
}
|
||||
}
|
||||
}, existsAlmostCertainly).doFinally(s -> {
|
||||
keyBuf.release();
|
||||
keySuffixBuf.release();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<U> putValueAndGetPrevious(T keySuffix, U value) {
|
||||
ByteBuf keySuffixBuf = serializeSuffix(keySuffix);
|
||||
ByteBuf keyBuf = toKey(keySuffixBuf.retain());
|
||||
ByteBuf valueBuf = serialize(value);
|
||||
return dictionary
|
||||
.put(toKey(serializeSuffix(keySuffix)), serialize(value), LLDictionaryResultType.PREVIOUS_VALUE)
|
||||
.map(this::deserialize);
|
||||
.put(keyBuf.retain(), valueBuf.retain(), LLDictionaryResultType.PREVIOUS_VALUE)
|
||||
.map(this::deserialize)
|
||||
.doFinally(s -> {
|
||||
keyBuf.release();
|
||||
keySuffixBuf.release();
|
||||
valueBuf.release();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> putValueAndGetStatus(T keySuffix, U value) {
|
||||
ByteBuf keySuffixBuf = serializeSuffix(keySuffix);
|
||||
ByteBuf keyBuf = toKey(keySuffixBuf.retain());
|
||||
ByteBuf valueBuf = serialize(value);
|
||||
return dictionary
|
||||
.put(toKey(serializeSuffix(keySuffix)), serialize(value), LLDictionaryResultType.VALUE_CHANGED)
|
||||
.map(LLUtils::responseToBoolean);
|
||||
.put(keyBuf.retain(), valueBuf.retain(), LLDictionaryResultType.PREVIOUS_VALUE_EXISTENCE)
|
||||
.map(LLUtils::responseToBoolean)
|
||||
.doFinally(s -> {
|
||||
keyBuf.release();
|
||||
keySuffixBuf.release();
|
||||
valueBuf.release();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> remove(T keySuffix) {
|
||||
return dictionary.remove(toKey(serializeSuffix(keySuffix)), LLDictionaryResultType.VOID).then();
|
||||
ByteBuf keySuffixBuf = serializeSuffix(keySuffix);
|
||||
ByteBuf keyBuf = toKey(keySuffixBuf.retain());
|
||||
return dictionary.remove(keyBuf.retain(), LLDictionaryResultType.VOID).doFinally(s -> {
|
||||
keyBuf.release();
|
||||
keySuffixBuf.release();
|
||||
}).then();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<U> removeAndGetPrevious(T keySuffix) {
|
||||
ByteBuf keySuffixBuf = serializeSuffix(keySuffix);
|
||||
ByteBuf keyBuf = toKey(keySuffixBuf.retain());
|
||||
return dictionary
|
||||
.remove(toKey(serializeSuffix(keySuffix)), LLDictionaryResultType.PREVIOUS_VALUE)
|
||||
.map(this::deserialize);
|
||||
.remove(keyBuf.retain(), LLDictionaryResultType.PREVIOUS_VALUE)
|
||||
.map(this::deserialize)
|
||||
.doFinally(s -> {
|
||||
keyBuf.release();
|
||||
keySuffixBuf.release();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> removeAndGetStatus(T keySuffix) {
|
||||
ByteBuf keySuffixBuf = serializeSuffix(keySuffix);
|
||||
ByteBuf keyBuf = toKey(keySuffixBuf.retain());
|
||||
return dictionary
|
||||
.remove(toKey(serializeSuffix(keySuffix)), LLDictionaryResultType.VALUE_CHANGED)
|
||||
.map(LLUtils::responseToBoolean);
|
||||
.remove(keyBuf.retain(), LLDictionaryResultType.PREVIOUS_VALUE_EXISTENCE)
|
||||
.map(LLUtils::responseToBoolean)
|
||||
.doFinally(s -> {
|
||||
keyBuf.release();
|
||||
keySuffixBuf.release();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Entry<T, U>> getMulti(@Nullable CompositeSnapshot snapshot, Flux<T> keys, boolean existsAlmostCertainly) {
|
||||
return dictionary
|
||||
.getMulti(resolveSnapshot(snapshot), keys.map(keySuffix -> toKey(serializeSuffix(keySuffix))), existsAlmostCertainly)
|
||||
.map(entry -> Map.entry(deserializeSuffix(stripPrefix(entry.getKey())), deserialize(entry.getValue())));
|
||||
.getMulti(resolveSnapshot(snapshot), keys.flatMap(keySuffix -> Mono.fromCallable(() -> {
|
||||
ByteBuf keySuffixBuf = serializeSuffix(keySuffix);
|
||||
try {
|
||||
return toKey(keySuffixBuf.retain());
|
||||
} finally {
|
||||
keySuffixBuf.release();
|
||||
}
|
||||
})), existsAlmostCertainly)
|
||||
.flatMap(entry -> Mono.fromCallable(() -> Map.entry(deserializeSuffix(stripPrefix(entry.getKey())), deserialize(entry.getValue()))));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> putMulti(Flux<Entry<T, U>> entries) {
|
||||
return dictionary
|
||||
.putMulti(entries
|
||||
.map(entry -> Map
|
||||
.entry(toKey(serializeSuffix(entry.getKey())), serialize(entry.getValue()))), false)
|
||||
.putMulti(entries.flatMap(entry -> Mono.fromCallable(() -> Map.entry(toKey(serializeSuffix(entry.getKey())),
|
||||
serialize(entry.getValue())
|
||||
))), false)
|
||||
.then();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Entry<T, DatabaseStageEntry<U>>> getAllStages(@Nullable CompositeSnapshot snapshot) {
|
||||
return dictionary
|
||||
.getRangeKeys(resolveSnapshot(snapshot), range)
|
||||
.getRangeKeys(resolveSnapshot(snapshot), range.retain())
|
||||
.map(key -> Map.entry(deserializeSuffix(stripPrefix(key)),
|
||||
new DatabaseSingleMapped<>(
|
||||
new DatabaseSingle<>(dictionary,
|
||||
@ -191,7 +270,7 @@ public class DatabaseMapDictionary<T, U> extends DatabaseMapDictionaryDeep<T, U,
|
||||
@Override
|
||||
public Flux<Entry<T, U>> getAllValues(@Nullable CompositeSnapshot snapshot) {
|
||||
return dictionary
|
||||
.getRange(resolveSnapshot(snapshot), range)
|
||||
.getRange(resolveSnapshot(snapshot), range.retain())
|
||||
.map(serializedEntry -> Map.entry(
|
||||
deserializeSuffix(stripPrefix(serializedEntry.getKey())),
|
||||
valueSerializer.deserialize(serializedEntry.getValue())
|
||||
@ -201,7 +280,7 @@ public class DatabaseMapDictionary<T, U> extends DatabaseMapDictionaryDeep<T, U,
|
||||
@Override
|
||||
public Flux<Entry<T, U>> setAllValuesAndGetPrevious(Flux<Entry<T, U>> entries) {
|
||||
return dictionary
|
||||
.setRange(range,
|
||||
.setRange(range.retain(),
|
||||
entries.map(entry ->
|
||||
Map.entry(toKey(serializeSuffix(entry.getKey())), serialize(entry.getValue()))), true)
|
||||
.map(entry -> Map.entry(deserializeSuffix(stripPrefix(entry.getKey())), deserialize(entry.getValue())));
|
||||
@ -214,22 +293,31 @@ public class DatabaseMapDictionary<T, U> extends DatabaseMapDictionaryDeep<T, U,
|
||||
.clear();
|
||||
} else if (range.isSingle()) {
|
||||
return dictionary
|
||||
.remove(range.getSingle(), LLDictionaryResultType.VOID)
|
||||
.remove(range.getSingle().retain(), LLDictionaryResultType.VOID)
|
||||
.then();
|
||||
} else {
|
||||
return dictionary
|
||||
.setRange(range, Flux.empty(), false)
|
||||
.setRange(range.retain(), Flux.empty(), false)
|
||||
.then();
|
||||
}
|
||||
}
|
||||
|
||||
//todo: temporary wrapper. convert the whole class to buffers
|
||||
private U deserialize(byte[] bytes) {
|
||||
/**
|
||||
* This method is just a shorter version than valueSerializer::deserialize
|
||||
*/
|
||||
private U deserialize(ByteBuf bytes) {
|
||||
return valueSerializer.deserialize(bytes);
|
||||
}
|
||||
|
||||
//todo: temporary wrapper. convert the whole class to buffers
|
||||
private byte[] serialize(U bytes) {
|
||||
/**
|
||||
* This method is just a shorter version than valueSerializer::serialize
|
||||
*/
|
||||
private ByteBuf serialize(U bytes) {
|
||||
return valueSerializer.serialize(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
super.release();
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,15 @@
|
||||
package it.cavallium.dbengine.database.collections;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.PooledByteBufAllocator;
|
||||
import it.cavallium.dbengine.client.CompositeSnapshot;
|
||||
import it.cavallium.dbengine.database.LLDictionary;
|
||||
import it.cavallium.dbengine.database.LLDictionaryResultType;
|
||||
import it.cavallium.dbengine.database.LLRange;
|
||||
import it.cavallium.dbengine.database.LLSnapshot;
|
||||
import it.cavallium.dbengine.database.LLUtils;
|
||||
import it.cavallium.dbengine.database.disk.LLLocalDictionary;
|
||||
import it.cavallium.dbengine.database.serialization.SerializerFixedBinaryLength;
|
||||
import java.util.Arrays;
|
||||
@ -14,88 +19,162 @@ import org.jetbrains.annotations.Nullable;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.util.function.Tuples;
|
||||
import static io.netty.buffer.Unpooled.*;
|
||||
|
||||
// todo: implement optimized methods
|
||||
public class DatabaseMapDictionaryDeep<T, U, US extends DatabaseStage<U>> implements DatabaseStageMap<T, U, US> {
|
||||
|
||||
public static final byte[] EMPTY_BYTES = new byte[0];
|
||||
public static final ByteBuf EMPTY_BYTES = unreleasableBuffer(directBuffer(0, 0));
|
||||
protected final LLDictionary dictionary;
|
||||
private final ByteBufAllocator alloc;
|
||||
protected final SubStageGetter<U, US> subStageGetter;
|
||||
protected final SerializerFixedBinaryLength<T, byte[]> keySuffixSerializer;
|
||||
protected final byte[] keyPrefix;
|
||||
protected final SerializerFixedBinaryLength<T, ByteBuf> keySuffixSerializer;
|
||||
protected final ByteBuf keyPrefix;
|
||||
protected final int keyPrefixLength;
|
||||
protected final int keySuffixLength;
|
||||
protected final int keyExtLength;
|
||||
protected final LLRange range;
|
||||
|
||||
private static byte[] incrementPrefix(byte[] key, int prefixLength) {
|
||||
boolean remainder = true;
|
||||
final byte ff = (byte) 0xFF;
|
||||
for (int i = prefixLength - 1; i >= 0; i--) {
|
||||
if (key[i] != ff) {
|
||||
key[i]++;
|
||||
remainder = false;
|
||||
break;
|
||||
} else {
|
||||
key[i] = 0x00;
|
||||
remainder = true;
|
||||
private static ByteBuf incrementPrefix(ByteBufAllocator alloc, ByteBuf originalKey, int prefixLength) {
|
||||
try {
|
||||
assert originalKey.readableBytes() >= prefixLength;
|
||||
ByteBuf copiedBuf = alloc.directBuffer(originalKey.writerIndex(), originalKey.writerIndex() + 1);
|
||||
try {
|
||||
boolean overflowed = true;
|
||||
final int ff = 0xFF;
|
||||
int writtenBytes = 0;
|
||||
copiedBuf.writerIndex(prefixLength);
|
||||
for (int i = prefixLength - 1; i >= 0; i--) {
|
||||
int iByte = originalKey.getUnsignedByte(i);
|
||||
if (iByte != ff) {
|
||||
copiedBuf.setByte(i, iByte + 1);
|
||||
writtenBytes++;
|
||||
overflowed = false;
|
||||
break;
|
||||
} else {
|
||||
copiedBuf.setByte(i, 0x00);
|
||||
writtenBytes++;
|
||||
overflowed = true;
|
||||
}
|
||||
}
|
||||
assert prefixLength - writtenBytes >= 0;
|
||||
if (prefixLength - writtenBytes > 0) {
|
||||
copiedBuf.setBytes(0, originalKey, 0, (prefixLength - writtenBytes));
|
||||
}
|
||||
|
||||
copiedBuf.writerIndex(copiedBuf.capacity());
|
||||
|
||||
if (originalKey.writerIndex() - prefixLength > 0) {
|
||||
copiedBuf.setBytes(prefixLength, originalKey, prefixLength, originalKey.writerIndex() - prefixLength);
|
||||
}
|
||||
|
||||
if (overflowed) {
|
||||
for (int i = 0; i < copiedBuf.writerIndex(); i++) {
|
||||
copiedBuf.setByte(i, 0xFF);
|
||||
}
|
||||
copiedBuf.writeZero(1);
|
||||
}
|
||||
return copiedBuf.retain();
|
||||
} finally {
|
||||
copiedBuf.release();
|
||||
}
|
||||
}
|
||||
|
||||
if (remainder) {
|
||||
Arrays.fill(key, 0, prefixLength, (byte) 0xFF);
|
||||
return Arrays.copyOf(key, key.length + 1);
|
||||
} else {
|
||||
return key;
|
||||
} finally {
|
||||
originalKey.release();
|
||||
}
|
||||
}
|
||||
|
||||
static byte[] firstRangeKey(byte[] prefixKey, int prefixLength, int suffixLength, int extLength) {
|
||||
return zeroFillKeySuffixAndExt(prefixKey, prefixLength, suffixLength, extLength);
|
||||
static ByteBuf firstRangeKey(ByteBufAllocator alloc, ByteBuf prefixKey, int prefixLength, int suffixLength, int extLength) {
|
||||
return zeroFillKeySuffixAndExt(alloc, prefixKey, prefixLength, suffixLength, extLength);
|
||||
}
|
||||
|
||||
static byte[] nextRangeKey(byte[] prefixKey, int prefixLength, int suffixLength, int extLength) {
|
||||
byte[] nonIncremented = zeroFillKeySuffixAndExt(prefixKey, prefixLength, suffixLength, extLength);
|
||||
return incrementPrefix(nonIncremented, prefixLength);
|
||||
static ByteBuf nextRangeKey(ByteBufAllocator alloc, ByteBuf prefixKey, int prefixLength, int suffixLength, int extLength) {
|
||||
try {
|
||||
ByteBuf nonIncremented = zeroFillKeySuffixAndExt(alloc, prefixKey.retain(), prefixLength, suffixLength, extLength);
|
||||
try {
|
||||
return incrementPrefix(alloc, nonIncremented.retain(), prefixLength);
|
||||
} finally {
|
||||
nonIncremented.release();
|
||||
}
|
||||
} finally {
|
||||
prefixKey.release();
|
||||
}
|
||||
}
|
||||
|
||||
protected static byte[] zeroFillKeySuffixAndExt(byte[] prefixKey, int prefixLength, int suffixLength, int extLength) {
|
||||
assert prefixKey.length == prefixLength;
|
||||
protected static ByteBuf zeroFillKeySuffixAndExt(ByteBufAllocator alloc, ByteBuf prefixKey, int prefixLength, int suffixLength, int extLength) {
|
||||
try {
|
||||
assert prefixKey.readableBytes() == prefixLength;
|
||||
assert suffixLength > 0;
|
||||
assert extLength >= 0;
|
||||
if (!prefixKey.isDirect()) {
|
||||
throw new IllegalArgumentException("Prefix key must be a direct buffer");
|
||||
}
|
||||
assert prefixKey.nioBuffer().isDirect();
|
||||
ByteBuf zeroSuffixAndExt = alloc.directBuffer(suffixLength + extLength, suffixLength + extLength);
|
||||
try {
|
||||
assert zeroSuffixAndExt.isDirect();
|
||||
assert zeroSuffixAndExt.nioBuffer().isDirect();
|
||||
zeroSuffixAndExt.writeZero(suffixLength + extLength);
|
||||
ByteBuf result = LLUtils.directCompositeBuffer(alloc, prefixKey.retain(), zeroSuffixAndExt.retain());
|
||||
assert result.isDirect();
|
||||
assert result.nioBuffer().isDirect();
|
||||
return result;
|
||||
} finally {
|
||||
zeroSuffixAndExt.release();
|
||||
}
|
||||
} finally {
|
||||
prefixKey.release();
|
||||
}
|
||||
}
|
||||
|
||||
static ByteBuf firstRangeKey(
|
||||
ByteBufAllocator alloc,
|
||||
ByteBuf prefixKey,
|
||||
ByteBuf suffixKey,
|
||||
int prefixLength,
|
||||
int suffixLength,
|
||||
int extLength) {
|
||||
return zeroFillKeyExt(alloc, prefixKey, suffixKey, prefixLength, suffixLength, extLength);
|
||||
}
|
||||
|
||||
static ByteBuf nextRangeKey(
|
||||
ByteBufAllocator alloc,
|
||||
ByteBuf prefixKey,
|
||||
ByteBuf suffixKey,
|
||||
int prefixLength,
|
||||
int suffixLength,
|
||||
int extLength) {
|
||||
try {
|
||||
ByteBuf nonIncremented = zeroFillKeyExt(alloc,
|
||||
prefixKey.retain(),
|
||||
suffixKey.retain(),
|
||||
prefixLength,
|
||||
suffixLength,
|
||||
extLength
|
||||
);
|
||||
try {
|
||||
return incrementPrefix(alloc, nonIncremented.retain(), prefixLength + suffixLength);
|
||||
} finally {
|
||||
nonIncremented.release();
|
||||
}
|
||||
} finally {
|
||||
prefixKey.release();
|
||||
suffixKey.release();
|
||||
}
|
||||
}
|
||||
|
||||
protected static ByteBuf zeroFillKeyExt(
|
||||
ByteBufAllocator alloc,
|
||||
ByteBuf prefixKey,
|
||||
ByteBuf suffixKey,
|
||||
int prefixLength,
|
||||
int suffixLength,
|
||||
int extLength) {
|
||||
assert prefixKey.readableBytes() == prefixLength;
|
||||
assert suffixKey.readableBytes() == suffixLength;
|
||||
assert suffixLength > 0;
|
||||
assert extLength >= 0;
|
||||
byte[] result = Arrays.copyOf(prefixKey, prefixLength + suffixLength + extLength);
|
||||
Arrays.fill(result, prefixLength, result.length, (byte) 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
static byte[] firstRangeKey(byte[] prefixKey,
|
||||
byte[] suffixKey,
|
||||
int prefixLength,
|
||||
int suffixLength,
|
||||
int extLength) {
|
||||
return zeroFillKeyExt(prefixKey, suffixKey, prefixLength, suffixLength, extLength);
|
||||
}
|
||||
|
||||
static byte[] nextRangeKey(byte[] prefixKey,
|
||||
byte[] suffixKey,
|
||||
int prefixLength,
|
||||
int suffixLength,
|
||||
int extLength) {
|
||||
byte[] nonIncremented = zeroFillKeyExt(prefixKey, suffixKey, prefixLength, suffixLength, extLength);
|
||||
return incrementPrefix(nonIncremented, prefixLength + suffixLength);
|
||||
}
|
||||
|
||||
protected static byte[] zeroFillKeyExt(byte[] prefixKey,
|
||||
byte[] suffixKey,
|
||||
int prefixLength,
|
||||
int suffixLength,
|
||||
int extLength) {
|
||||
assert prefixKey.length == prefixLength;
|
||||
assert suffixKey.length == suffixLength;
|
||||
assert suffixLength > 0;
|
||||
assert extLength >= 0;
|
||||
byte[] result = Arrays.copyOf(prefixKey, prefixLength + suffixLength + extLength);
|
||||
System.arraycopy(suffixKey, 0, result, prefixLength, suffixLength);
|
||||
Arrays.fill(result, prefixLength + suffixLength, result.length, (byte) 0);
|
||||
var result = LLUtils.directCompositeBuffer(alloc, prefixKey, suffixKey, alloc.buffer(extLength, extLength).writeZero(extLength));
|
||||
assert result.readableBytes() == prefixLength + suffixLength + extLength;
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -104,41 +183,73 @@ public class DatabaseMapDictionaryDeep<T, U, US extends DatabaseStage<U>> implem
|
||||
*/
|
||||
@Deprecated
|
||||
public static <T, U> DatabaseMapDictionaryDeep<T, U, DatabaseStageEntry<U>> simple(LLDictionary dictionary,
|
||||
SerializerFixedBinaryLength<T, byte[]> keySerializer,
|
||||
SerializerFixedBinaryLength<T, ByteBuf> keySerializer,
|
||||
SubStageGetterSingle<U> subStageGetter) {
|
||||
return new DatabaseMapDictionaryDeep<>(dictionary, EMPTY_BYTES, keySerializer, subStageGetter, 0);
|
||||
}
|
||||
|
||||
public static <T, U, US extends DatabaseStage<U>> DatabaseMapDictionaryDeep<T, U, US> deepTail(LLDictionary dictionary,
|
||||
SerializerFixedBinaryLength<T, byte[]> keySerializer,
|
||||
SerializerFixedBinaryLength<T, ByteBuf> keySerializer,
|
||||
int keyExtLength,
|
||||
SubStageGetter<U, US> subStageGetter) {
|
||||
return new DatabaseMapDictionaryDeep<>(dictionary, EMPTY_BYTES, keySerializer, subStageGetter, keyExtLength);
|
||||
}
|
||||
|
||||
public static <T, U, US extends DatabaseStage<U>> DatabaseMapDictionaryDeep<T, U, US> deepIntermediate(LLDictionary dictionary,
|
||||
byte[] prefixKey,
|
||||
SerializerFixedBinaryLength<T, byte[]> keySuffixSerializer,
|
||||
ByteBuf prefixKey,
|
||||
SerializerFixedBinaryLength<T, ByteBuf> keySuffixSerializer,
|
||||
SubStageGetter<U, US> subStageGetter,
|
||||
int keyExtLength) {
|
||||
return new DatabaseMapDictionaryDeep<>(dictionary, prefixKey, keySuffixSerializer, subStageGetter, keyExtLength);
|
||||
}
|
||||
|
||||
protected DatabaseMapDictionaryDeep(LLDictionary dictionary,
|
||||
byte[] prefixKey,
|
||||
SerializerFixedBinaryLength<T, byte[]> keySuffixSerializer,
|
||||
ByteBuf prefixKey,
|
||||
SerializerFixedBinaryLength<T, ByteBuf> keySuffixSerializer,
|
||||
SubStageGetter<U, US> subStageGetter,
|
||||
int keyExtLength) {
|
||||
this.dictionary = dictionary;
|
||||
this.subStageGetter = subStageGetter;
|
||||
this.keySuffixSerializer = keySuffixSerializer;
|
||||
this.keyPrefix = prefixKey;
|
||||
this.keySuffixLength = keySuffixSerializer.getSerializedBinaryLength();
|
||||
this.keyExtLength = keyExtLength;
|
||||
byte[] firstKey = firstRangeKey(keyPrefix, keyPrefix.length, keySuffixLength, keyExtLength);
|
||||
byte[] nextRangeKey = nextRangeKey(keyPrefix, keyPrefix.length, keySuffixLength, keyExtLength);
|
||||
this.range = keyPrefix.length == 0 ? LLRange.all() : LLRange.of(firstKey, nextRangeKey);
|
||||
assert subStageKeysConsistency(keyPrefix.length + keySuffixLength + keyExtLength);
|
||||
try {
|
||||
this.dictionary = dictionary;
|
||||
this.alloc = dictionary.getAllocator();
|
||||
this.subStageGetter = subStageGetter;
|
||||
this.keySuffixSerializer = keySuffixSerializer;
|
||||
this.keyPrefix = wrappedUnmodifiableBuffer(prefixKey).retain();
|
||||
this.keyPrefixLength = keyPrefix.readableBytes();
|
||||
this.keySuffixLength = keySuffixSerializer.getSerializedBinaryLength();
|
||||
this.keyExtLength = keyExtLength;
|
||||
if (!keyPrefix.isDirect()) {
|
||||
throw new IllegalArgumentException("KeyPrefix must be a direct buffer");
|
||||
}
|
||||
assert keyPrefix.isDirect();
|
||||
ByteBuf firstKey = wrappedUnmodifiableBuffer(firstRangeKey(alloc,
|
||||
keyPrefix.retain(),
|
||||
keyPrefixLength,
|
||||
keySuffixLength,
|
||||
keyExtLength
|
||||
));
|
||||
ByteBuf nextRangeKey = wrappedUnmodifiableBuffer(nextRangeKey(alloc,
|
||||
keyPrefix.retain(),
|
||||
keyPrefixLength,
|
||||
keySuffixLength,
|
||||
keyExtLength
|
||||
));
|
||||
try {
|
||||
assert keyPrefixLength == 0 || !LLUtils.equals(firstKey, nextRangeKey);
|
||||
assert firstKey.isDirect();
|
||||
assert nextRangeKey.isDirect();
|
||||
assert firstKey.nioBuffer().isDirect();
|
||||
assert nextRangeKey.nioBuffer().isDirect();
|
||||
this.range = keyPrefixLength == 0 ? LLRange.all() : LLRange.of(firstKey.retain(), nextRangeKey.retain());
|
||||
assert range == null || !range.hasMin() || range.getMin().isDirect();
|
||||
assert range == null || !range.hasMax() || range.getMax().isDirect();
|
||||
assert subStageKeysConsistency(keyPrefixLength + keySuffixLength + keyExtLength);
|
||||
} finally {
|
||||
firstKey.release();
|
||||
nextRangeKey.release();
|
||||
}
|
||||
} finally {
|
||||
prefixKey.release();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@ -159,26 +270,33 @@ public class DatabaseMapDictionaryDeep<T, U, US extends DatabaseStage<U>> implem
|
||||
/**
|
||||
* Keep only suffix and ext
|
||||
*/
|
||||
protected byte[] stripPrefix(byte[] key) {
|
||||
return Arrays.copyOfRange(key, this.keyPrefix.length, key.length);
|
||||
protected ByteBuf stripPrefix(ByteBuf key) {
|
||||
return key.slice(this.keyPrefixLength, key.readableBytes() - this.keyPrefixLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove ext from full key
|
||||
*/
|
||||
protected byte[] removeExtFromFullKey(byte[] key) {
|
||||
return Arrays.copyOf(key, keyPrefix.length + keySuffixLength);
|
||||
protected ByteBuf removeExtFromFullKey(ByteBuf key) {
|
||||
try {
|
||||
return key.slice(key.readerIndex(), keyPrefixLength + keySuffixLength).retain();
|
||||
} finally {
|
||||
key.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add prefix to suffix
|
||||
*/
|
||||
protected byte[] toKeyWithoutExt(byte[] suffixKey) {
|
||||
assert suffixKey.length == keySuffixLength;
|
||||
byte[] result = Arrays.copyOf(keyPrefix, keyPrefix.length + keySuffixLength);
|
||||
System.arraycopy(suffixKey, 0, result, keyPrefix.length, keySuffixLength);
|
||||
assert result.length == keyPrefix.length + keySuffixLength;
|
||||
return result;
|
||||
protected ByteBuf toKeyWithoutExt(ByteBuf suffixKey) {
|
||||
try {
|
||||
assert suffixKey.readableBytes() == keySuffixLength;
|
||||
ByteBuf result = LLUtils.directCompositeBuffer(alloc, keyPrefix.retain(), suffixKey.retain());
|
||||
assert result.readableBytes() == keyPrefixLength + keySuffixLength;
|
||||
return result;
|
||||
} finally {
|
||||
suffixKey.release();
|
||||
}
|
||||
}
|
||||
|
||||
protected LLSnapshot resolveSnapshot(@Nullable CompositeSnapshot snapshot) {
|
||||
@ -189,71 +307,89 @@ public class DatabaseMapDictionaryDeep<T, U, US extends DatabaseStage<U>> implem
|
||||
}
|
||||
}
|
||||
|
||||
protected LLRange toExtRange(byte[] keySuffix) {
|
||||
byte[] first = firstRangeKey(keyPrefix, keySuffix, keyPrefix.length, keySuffixLength, keyExtLength);
|
||||
byte[] end = nextRangeKey(keyPrefix, keySuffix, keyPrefix.length, keySuffixLength, keyExtLength);
|
||||
return LLRange.of(first, end);
|
||||
protected LLRange toExtRange(ByteBuf keySuffix) {
|
||||
try {
|
||||
ByteBuf first = firstRangeKey(alloc,
|
||||
keyPrefix.retain(),
|
||||
keySuffix.retain(),
|
||||
keyPrefixLength,
|
||||
keySuffixLength,
|
||||
keyExtLength
|
||||
);
|
||||
ByteBuf end = nextRangeKey(alloc,
|
||||
keyPrefix.retain(),
|
||||
keySuffix.retain(),
|
||||
keyPrefixLength,
|
||||
keySuffixLength,
|
||||
keyExtLength
|
||||
);
|
||||
return LLRange.of(first, end);
|
||||
} finally {
|
||||
keySuffix.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Long> leavesCount(@Nullable CompositeSnapshot snapshot, boolean fast) {
|
||||
return dictionary.sizeRange(resolveSnapshot(snapshot), range, fast);
|
||||
return dictionary.sizeRange(resolveSnapshot(snapshot), range.retain(), fast);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> isEmpty(@Nullable CompositeSnapshot snapshot) {
|
||||
return dictionary.isRangeEmpty(resolveSnapshot(snapshot), range);
|
||||
return dictionary.isRangeEmpty(resolveSnapshot(snapshot), range.retain());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<US> at(@Nullable CompositeSnapshot snapshot, T keySuffix) {
|
||||
byte[] keySuffixData = serializeSuffix(keySuffix);
|
||||
Flux<byte[]> keyFlux;
|
||||
ByteBuf keySuffixData = serializeSuffix(keySuffix);
|
||||
Flux<ByteBuf> keyFlux;
|
||||
if (LLLocalDictionary.DEBUG_PREFIXES_WHEN_ASSERTIONS_ARE_ENABLED && this.subStageGetter.needsDebuggingKeyFlux()) {
|
||||
keyFlux = this.dictionary.getRangeKeys(resolveSnapshot(snapshot), toExtRange(keySuffixData));
|
||||
keyFlux = this.dictionary.getRangeKeys(resolveSnapshot(snapshot), toExtRange(keySuffixData.retain()));
|
||||
} else {
|
||||
keyFlux = Flux.empty();
|
||||
}
|
||||
return this.subStageGetter
|
||||
.subStage(dictionary,
|
||||
snapshot,
|
||||
toKeyWithoutExt(keySuffixData),
|
||||
keyFlux
|
||||
);
|
||||
.subStage(dictionary, snapshot, toKeyWithoutExt(keySuffixData.retain()), keyFlux)
|
||||
.doFinally(s -> keySuffixData.release());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Entry<T, US>> getAllStages(@Nullable CompositeSnapshot snapshot) {
|
||||
if (LLLocalDictionary.DEBUG_PREFIXES_WHEN_ASSERTIONS_ARE_ENABLED && this.subStageGetter.needsDebuggingKeyFlux()) {
|
||||
return dictionary
|
||||
.getRangeKeysGrouped(resolveSnapshot(snapshot), range, keyPrefix.length + keySuffixLength)
|
||||
.getRangeKeysGrouped(resolveSnapshot(snapshot), range.retain(), keyPrefixLength + keySuffixLength)
|
||||
.flatMapSequential(rangeKeys -> {
|
||||
assert this.subStageGetter.isMultiKey() || rangeKeys.size() == 1;
|
||||
byte[] groupKeyWithExt = rangeKeys.get(0);
|
||||
byte[] groupKeyWithoutExt = removeExtFromFullKey(groupKeyWithExt);
|
||||
byte[] groupSuffix = this.stripPrefix(groupKeyWithoutExt);
|
||||
assert subStageKeysConsistency(groupKeyWithExt.length);
|
||||
ByteBuf groupKeyWithExt = rangeKeys.get(0).retain();
|
||||
ByteBuf groupKeyWithoutExt = removeExtFromFullKey(groupKeyWithExt.retain());
|
||||
ByteBuf groupSuffix = this.stripPrefix(groupKeyWithoutExt.retain());
|
||||
assert subStageKeysConsistency(groupKeyWithExt.readableBytes());
|
||||
return this.subStageGetter
|
||||
.subStage(dictionary,
|
||||
snapshot,
|
||||
groupKeyWithoutExt,
|
||||
Flux.fromIterable(rangeKeys)
|
||||
)
|
||||
.map(us -> Map.entry(this.deserializeSuffix(groupSuffix), us));
|
||||
.map(us -> Map.entry(this.deserializeSuffix(wrappedUnmodifiableBuffer(groupSuffix.retain())), us))
|
||||
.doFinally(s -> {
|
||||
groupSuffix.release();
|
||||
groupKeyWithoutExt.release();
|
||||
groupKeyWithExt.release();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return dictionary
|
||||
.getRangeKeyPrefixes(resolveSnapshot(snapshot), range, keyPrefix.length + keySuffixLength)
|
||||
.getRangeKeyPrefixes(resolveSnapshot(snapshot), range, keyPrefixLength + keySuffixLength)
|
||||
.flatMapSequential(groupKeyWithoutExt -> {
|
||||
byte[] groupSuffix = this.stripPrefix(groupKeyWithoutExt);
|
||||
assert subStageKeysConsistency(groupKeyWithoutExt.length + keyExtLength);
|
||||
ByteBuf groupSuffix = this.stripPrefix(groupKeyWithoutExt);
|
||||
assert subStageKeysConsistency(groupKeyWithoutExt.readableBytes() + keyExtLength);
|
||||
return this.subStageGetter
|
||||
.subStage(dictionary,
|
||||
snapshot,
|
||||
groupKeyWithoutExt,
|
||||
Flux.empty()
|
||||
)
|
||||
.map(us -> Map.entry(this.deserializeSuffix(groupSuffix), us));
|
||||
.map(us -> Map.entry(this.deserializeSuffix(wrappedUnmodifiableBuffer(groupSuffix)), us));
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -261,10 +397,10 @@ public class DatabaseMapDictionaryDeep<T, U, US extends DatabaseStage<U>> implem
|
||||
private boolean subStageKeysConsistency(int totalKeyLength) {
|
||||
if (subStageGetter instanceof SubStageGetterMapDeep) {
|
||||
return totalKeyLength
|
||||
== keyPrefix.length + keySuffixLength + ((SubStageGetterMapDeep<?, ?, ?>) subStageGetter).getKeyBinaryLength();
|
||||
== keyPrefixLength + keySuffixLength + ((SubStageGetterMapDeep<?, ?, ?>) subStageGetter).getKeyBinaryLength();
|
||||
} else if (subStageGetter instanceof SubStageGetterMap) {
|
||||
return totalKeyLength
|
||||
== keyPrefix.length + keySuffixLength + ((SubStageGetterMap<?, ?>) subStageGetter).getKeyBinaryLength();
|
||||
== keyPrefixLength + keySuffixLength + ((SubStageGetterMap<?, ?>) subStageGetter).getKeyBinaryLength();
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
@ -287,25 +423,37 @@ public class DatabaseMapDictionaryDeep<T, U, US extends DatabaseStage<U>> implem
|
||||
.clear();
|
||||
} else if (range.isSingle()) {
|
||||
return dictionary
|
||||
.remove(range.getSingle(), LLDictionaryResultType.VOID)
|
||||
.remove(range.getSingle().retain(), LLDictionaryResultType.VOID)
|
||||
.then();
|
||||
} else {
|
||||
return dictionary
|
||||
.setRange(range, Flux.empty(), false)
|
||||
.setRange(range.retain(), Flux.empty(), false)
|
||||
.then();
|
||||
}
|
||||
}
|
||||
|
||||
//todo: temporary wrapper. convert the whole class to buffers
|
||||
protected T deserializeSuffix(byte[] keySuffix) {
|
||||
assert suffixKeyConsistency(keySuffix.length);
|
||||
protected T deserializeSuffix(ByteBuf keySuffix) {
|
||||
assert suffixKeyConsistency(keySuffix.readableBytes());
|
||||
return keySuffixSerializer.deserialize(keySuffix);
|
||||
}
|
||||
|
||||
//todo: temporary wrapper. convert the whole class to buffers
|
||||
protected byte[] serializeSuffix(T keySuffix) {
|
||||
byte[] suffixData = keySuffixSerializer.serialize(keySuffix);
|
||||
assert suffixKeyConsistency(suffixData.length);
|
||||
protected ByteBuf serializeSuffix(T keySuffix) {
|
||||
ByteBuf suffixData = keySuffixSerializer.serialize(keySuffix);
|
||||
assert suffixKeyConsistency(suffixData.readableBytes());
|
||||
return suffixData;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
range.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
this.range.release();
|
||||
this.keyPrefix.release();
|
||||
}
|
||||
}
|
||||
|
@ -2,18 +2,18 @@ package it.cavallium.dbengine.database.collections;
|
||||
|
||||
import static it.cavallium.dbengine.database.collections.DatabaseMapDictionaryDeep.EMPTY_BYTES;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import it.cavallium.dbengine.client.CompositeSnapshot;
|
||||
import it.cavallium.dbengine.database.LLDictionary;
|
||||
import it.cavallium.dbengine.database.LLUtils;
|
||||
import it.cavallium.dbengine.database.collections.Joiner.ValueGetter;
|
||||
import it.cavallium.dbengine.database.collections.JoinerBlocking.ValueGetterBlocking;
|
||||
import it.cavallium.dbengine.database.serialization.Serializer;
|
||||
import it.cavallium.dbengine.database.serialization.SerializerFixedBinaryLength;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@ -23,19 +23,21 @@ import reactor.core.publisher.Mono;
|
||||
@SuppressWarnings("unused")
|
||||
public class DatabaseMapDictionaryHashed<T, U, TH> implements DatabaseStageMap<T, U, DatabaseStageEntry<U>> {
|
||||
|
||||
private final ByteBufAllocator alloc;
|
||||
private final DatabaseMapDictionary<TH, Entry<T, U>> subDictionary;
|
||||
private final Function<T, TH> keySuffixHashFunction;
|
||||
private final Function<T, ValueMapper<T, U>> valueMapper;
|
||||
|
||||
protected DatabaseMapDictionaryHashed(LLDictionary dictionary,
|
||||
byte[] prefixKey,
|
||||
Serializer<T, byte[]> keySuffixSerializer,
|
||||
Serializer<U, byte[]> valueSerializer,
|
||||
ByteBuf prefixKey,
|
||||
Serializer<T, ByteBuf> keySuffixSerializer,
|
||||
Serializer<U, ByteBuf> valueSerializer,
|
||||
Function<T, TH> keySuffixHashFunction,
|
||||
SerializerFixedBinaryLength<TH, byte[]> keySuffixHashSerializer) {
|
||||
SerializerFixedBinaryLength<TH, ByteBuf> keySuffixHashSerializer) {
|
||||
ValueWithHashSerializer<T, U> valueWithHashSerializer = new ValueWithHashSerializer<>(keySuffixSerializer,
|
||||
valueSerializer
|
||||
);
|
||||
this.alloc = dictionary.getAllocator();
|
||||
this.valueMapper = ValueMapper::new;
|
||||
this.subDictionary = DatabaseMapDictionary.tail(dictionary,
|
||||
prefixKey,
|
||||
@ -44,40 +46,35 @@ public class DatabaseMapDictionaryHashed<T, U, TH> implements DatabaseStageMap<T
|
||||
this.keySuffixHashFunction = keySuffixHashFunction;
|
||||
}
|
||||
|
||||
private static class ValueWithHashSerializer<T, U> implements Serializer<Entry<T, U>, byte[]> {
|
||||
private class ValueWithHashSerializer<T, U> implements Serializer<Entry<T, U>, ByteBuf> {
|
||||
|
||||
private final Serializer<T, byte[]> keySuffixSerializer;
|
||||
private final Serializer<U, byte[]> valueSerializer;
|
||||
private final Serializer<T, ByteBuf> keySuffixSerializer;
|
||||
private final Serializer<U, ByteBuf> valueSerializer;
|
||||
|
||||
private ValueWithHashSerializer(Serializer<T, byte[]> keySuffixSerializer, Serializer<U, byte[]> valueSerializer) {
|
||||
private ValueWithHashSerializer(Serializer<T, ByteBuf> keySuffixSerializer, Serializer<U, ByteBuf> valueSerializer) {
|
||||
this.keySuffixSerializer = keySuffixSerializer;
|
||||
this.valueSerializer = valueSerializer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull Entry<T, U> deserialize(byte @NotNull [] serialized) {
|
||||
int keySuffixLength = Ints.fromBytes(serialized[0], serialized[1], serialized[2], serialized[3]);
|
||||
T keySuffix = keySuffixSerializer.deserialize(Arrays.copyOfRange(serialized,
|
||||
Integer.BYTES,
|
||||
Integer.BYTES + keySuffixLength
|
||||
));
|
||||
U value = valueSerializer.deserialize(Arrays.copyOfRange(serialized,
|
||||
Integer.BYTES + keySuffixLength,
|
||||
serialized.length
|
||||
));
|
||||
return Map.entry(keySuffix, value);
|
||||
public @NotNull Entry<T, U> deserialize(@NotNull ByteBuf serialized) {
|
||||
try {
|
||||
int keySuffixLength = serialized.readInt();
|
||||
T keySuffix = keySuffixSerializer.deserialize(serialized.retainedSlice(serialized.readerIndex(), keySuffixLength));
|
||||
U value = valueSerializer.deserialize(serialized.retain());
|
||||
return Map.entry(keySuffix, value);
|
||||
} finally {
|
||||
serialized.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte @NotNull [] serialize(@NotNull Entry<T, U> deserialized) {
|
||||
byte[] keySuffix = keySuffixSerializer.serialize(deserialized.getKey());
|
||||
byte[] value = valueSerializer.serialize(deserialized.getValue());
|
||||
byte[] result = new byte[Integer.BYTES + keySuffix.length + value.length];
|
||||
byte[] keySuffixLen = Ints.toByteArray(keySuffix.length);
|
||||
System.arraycopy(keySuffixLen, 0, result, 0, Integer.BYTES);
|
||||
System.arraycopy(keySuffix, 0, result, Integer.BYTES, keySuffix.length);
|
||||
System.arraycopy(value, 0, result, Integer.BYTES + keySuffix.length, value.length);
|
||||
return result;
|
||||
public @NotNull ByteBuf serialize(@NotNull Entry<T, U> deserialized) {
|
||||
ByteBuf keySuffix = keySuffixSerializer.serialize(deserialized.getKey());
|
||||
ByteBuf value = valueSerializer.serialize(deserialized.getValue());
|
||||
ByteBuf keySuffixLen = alloc.buffer(Integer.BYTES, Integer.BYTES);
|
||||
keySuffixLen.writeInt(keySuffix.readableBytes());
|
||||
return LLUtils.directCompositeBuffer(alloc, keySuffixLen, keySuffix, value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,10 +98,10 @@ public class DatabaseMapDictionaryHashed<T, U, TH> implements DatabaseStageMap<T
|
||||
}
|
||||
|
||||
public static <T, U, UH> DatabaseMapDictionaryHashed<T, U, UH> simple(LLDictionary dictionary,
|
||||
Serializer<T, byte[]> keySerializer,
|
||||
Serializer<U, byte[]> valueSerializer,
|
||||
Serializer<T, ByteBuf> keySerializer,
|
||||
Serializer<U, ByteBuf> valueSerializer,
|
||||
Function<T, UH> keyHashFunction,
|
||||
SerializerFixedBinaryLength<UH, byte[]> keyHashSerializer) {
|
||||
SerializerFixedBinaryLength<UH, ByteBuf> keyHashSerializer) {
|
||||
return new DatabaseMapDictionaryHashed<>(dictionary,
|
||||
EMPTY_BYTES,
|
||||
keySerializer,
|
||||
@ -115,11 +112,11 @@ public class DatabaseMapDictionaryHashed<T, U, TH> implements DatabaseStageMap<T
|
||||
}
|
||||
|
||||
public static <T, U, UH> DatabaseMapDictionaryHashed<T, U, UH> tail(LLDictionary dictionary,
|
||||
byte[] prefixKey,
|
||||
Serializer<T, byte[]> keySuffixSerializer,
|
||||
Serializer<U, byte[]> valueSerializer,
|
||||
ByteBuf prefixKey,
|
||||
Serializer<T, ByteBuf> keySuffixSerializer,
|
||||
Serializer<U, ByteBuf> valueSerializer,
|
||||
Function<T, UH> keySuffixHashFunction,
|
||||
SerializerFixedBinaryLength<UH, byte[]> keySuffixHashSerializer) {
|
||||
SerializerFixedBinaryLength<UH, ByteBuf> keySuffixHashSerializer) {
|
||||
return new DatabaseMapDictionaryHashed<>(dictionary,
|
||||
prefixKey,
|
||||
keySuffixSerializer,
|
||||
@ -157,13 +154,20 @@ public class DatabaseMapDictionaryHashed<T, U, TH> implements DatabaseStageMap<T
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> setAndGetStatus(Map<T, U> map) {
|
||||
return Mono.fromSupplier(() -> this.serializeMap(map)).flatMap(subDictionary::setAndGetStatus);
|
||||
public Mono<Boolean> setAndGetChanged(Map<T, U> map) {
|
||||
return Mono.fromSupplier(() -> this.serializeMap(map)).flatMap(subDictionary::setAndGetChanged).single();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> update(Function<Optional<Map<T, U>>, Optional<Map<T, U>>> updater) {
|
||||
return subDictionary.update(old -> updater.apply(old.map(this::deserializeMap)).map(this::serializeMap));
|
||||
public Mono<Boolean> update(Function<@Nullable Map<T, U>, @Nullable Map<T, U>> updater) {
|
||||
return subDictionary.update(old -> {
|
||||
var result = updater.apply(old == null ? null : this.deserializeMap(old));
|
||||
if (result == null) {
|
||||
return null;
|
||||
} else {
|
||||
return this.serializeMap(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -186,6 +190,11 @@ public class DatabaseMapDictionaryHashed<T, U, TH> implements DatabaseStageMap<T
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
this.subDictionary.release();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<DatabaseStageEntry<U>> at(@Nullable CompositeSnapshot snapshot, T key) {
|
||||
return subDictionary
|
||||
@ -218,18 +227,27 @@ public class DatabaseMapDictionaryHashed<T, U, TH> implements DatabaseStageMap<T
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> updateValue(T key, boolean existsAlmostCertainly, Function<Optional<U>, Optional<U>> updater) {
|
||||
return subDictionary.updateValue(keySuffixHashFunction.apply(key),
|
||||
existsAlmostCertainly,
|
||||
old -> updater.apply(old.map(Entry::getValue)).map(newV -> Map.entry(key, newV))
|
||||
);
|
||||
public Mono<Boolean> updateValue(T key, boolean existsAlmostCertainly, Function<@Nullable U, @Nullable U> updater) {
|
||||
return subDictionary.updateValue(keySuffixHashFunction.apply(key), existsAlmostCertainly, old -> {
|
||||
var result = updater.apply(old == null ? null : old.getValue());
|
||||
if (result == null) {
|
||||
return null;
|
||||
} else {
|
||||
return Map.entry(key, result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> updateValue(T key, Function<Optional<U>, Optional<U>> updater) {
|
||||
return subDictionary.updateValue(keySuffixHashFunction.apply(key),
|
||||
old -> updater.apply(old.map(Entry::getValue)).map(newV -> Map.entry(key, newV))
|
||||
);
|
||||
public Mono<Boolean> updateValue(T key, Function<@Nullable U, @Nullable U> updater) {
|
||||
return subDictionary.updateValue(keySuffixHashFunction.apply(key), old -> {
|
||||
var result = updater.apply(old == null ? null : old.getValue());
|
||||
if (result == null) {
|
||||
return null;
|
||||
} else {
|
||||
return Map.entry(key, result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -346,10 +364,16 @@ public class DatabaseMapDictionaryHashed<T, U, TH> implements DatabaseStageMap<T
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> update(Function<Optional<Map<T, U>>, Optional<Map<T, U>>> updater,
|
||||
public Mono<Boolean> update(Function<@Nullable Map<T, U>, @Nullable Map<T, U>> updater,
|
||||
boolean existsAlmostCertainly) {
|
||||
return subDictionary
|
||||
.update(item -> updater.apply(item.map(this::deserializeMap)).map(this::serializeMap), existsAlmostCertainly);
|
||||
return subDictionary.update(item -> {
|
||||
var result = updater.apply(item == null ? null : this.deserializeMap(item));
|
||||
if (result == null) {
|
||||
return null;
|
||||
} else {
|
||||
return this.serializeMap(result);
|
||||
}
|
||||
}, existsAlmostCertainly);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,5 +1,6 @@
|
||||
package it.cavallium.dbengine.database.collections;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import it.cavallium.dbengine.client.CompositeSnapshot;
|
||||
import it.cavallium.dbengine.database.LLDictionary;
|
||||
import it.cavallium.dbengine.database.collections.DatabaseEmpty.Nothing;
|
||||
@ -14,19 +15,19 @@ import reactor.core.publisher.Mono;
|
||||
public class DatabaseSetDictionary<T> extends DatabaseMapDictionaryDeep<T, Nothing, DatabaseStageEntry<Nothing>> {
|
||||
|
||||
protected DatabaseSetDictionary(LLDictionary dictionary,
|
||||
byte[] prefixKey,
|
||||
SerializerFixedBinaryLength<T, byte[]> keySuffixSerializer) {
|
||||
ByteBuf prefixKey,
|
||||
SerializerFixedBinaryLength<T, ByteBuf> keySuffixSerializer) {
|
||||
super(dictionary, prefixKey, keySuffixSerializer, DatabaseEmpty.createSubStageGetter(), 0);
|
||||
}
|
||||
|
||||
public static <T> DatabaseSetDictionary<T> simple(LLDictionary dictionary,
|
||||
SerializerFixedBinaryLength<T, byte[]> keySerializer) {
|
||||
SerializerFixedBinaryLength<T, ByteBuf> keySerializer) {
|
||||
return new DatabaseSetDictionary<>(dictionary, EMPTY_BYTES, keySerializer);
|
||||
}
|
||||
|
||||
public static <T> DatabaseSetDictionary<T> tail(LLDictionary dictionary,
|
||||
byte[] prefixKey,
|
||||
SerializerFixedBinaryLength<T, byte[]> keySuffixSerializer) {
|
||||
ByteBuf prefixKey,
|
||||
SerializerFixedBinaryLength<T, ByteBuf> keySuffixSerializer) {
|
||||
return new DatabaseSetDictionary<>(dictionary, prefixKey, keySuffixSerializer);
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ package it.cavallium.dbengine.database.collections;
|
||||
|
||||
import static it.cavallium.dbengine.database.collections.DatabaseMapDictionaryDeep.EMPTY_BYTES;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import it.cavallium.dbengine.client.CompositeSnapshot;
|
||||
import it.cavallium.dbengine.database.LLDictionary;
|
||||
import it.cavallium.dbengine.database.collections.DatabaseEmpty.Nothing;
|
||||
@ -18,10 +19,10 @@ import reactor.core.publisher.Mono;
|
||||
public class DatabaseSetDictionaryHashed<T, TH> extends DatabaseMapDictionaryHashed<T, Nothing, TH> {
|
||||
|
||||
protected DatabaseSetDictionaryHashed(LLDictionary dictionary,
|
||||
byte[] prefixKey,
|
||||
Serializer<T, byte[]> keySuffixSerializer,
|
||||
ByteBuf prefixKey,
|
||||
Serializer<T, ByteBuf> keySuffixSerializer,
|
||||
Function<T, TH> keySuffixHashFunction,
|
||||
SerializerFixedBinaryLength<TH, byte[]> keySuffixHashSerializer) {
|
||||
SerializerFixedBinaryLength<TH, ByteBuf> keySuffixHashSerializer) {
|
||||
super(dictionary,
|
||||
prefixKey,
|
||||
keySuffixSerializer,
|
||||
@ -32,9 +33,9 @@ public class DatabaseSetDictionaryHashed<T, TH> extends DatabaseMapDictionaryHas
|
||||
}
|
||||
|
||||
public static <T, TH> DatabaseSetDictionaryHashed<T, TH> simple(LLDictionary dictionary,
|
||||
Serializer<T, byte[]> keySerializer,
|
||||
Serializer<T, ByteBuf> keySerializer,
|
||||
Function<T, TH> keyHashFunction,
|
||||
SerializerFixedBinaryLength<TH, byte[]> keyHashSerializer) {
|
||||
SerializerFixedBinaryLength<TH, ByteBuf> keyHashSerializer) {
|
||||
return new DatabaseSetDictionaryHashed<>(dictionary,
|
||||
EMPTY_BYTES,
|
||||
keySerializer,
|
||||
@ -44,10 +45,10 @@ public class DatabaseSetDictionaryHashed<T, TH> extends DatabaseMapDictionaryHas
|
||||
}
|
||||
|
||||
public static <T, TH> DatabaseSetDictionaryHashed<T, TH> tail(LLDictionary dictionary,
|
||||
byte[] prefixKey,
|
||||
Serializer<T, byte[]> keySuffixSerializer,
|
||||
ByteBuf prefixKey,
|
||||
Serializer<T, ByteBuf> keySuffixSerializer,
|
||||
Function<T, TH> keyHashFunction,
|
||||
SerializerFixedBinaryLength<TH, byte[]> keyHashSerializer) {
|
||||
SerializerFixedBinaryLength<TH, ByteBuf> keyHashSerializer) {
|
||||
return new DatabaseSetDictionaryHashed<>(dictionary,
|
||||
prefixKey,
|
||||
keySuffixSerializer,
|
||||
|
@ -1,5 +1,6 @@
|
||||
package it.cavallium.dbengine.database.collections;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import it.cavallium.dbengine.client.CompositeSnapshot;
|
||||
import it.cavallium.dbengine.database.LLDictionary;
|
||||
import it.cavallium.dbengine.database.LLDictionaryResultType;
|
||||
@ -10,15 +11,19 @@ import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import reactor.core.publisher.Mono;
|
||||
import static io.netty.buffer.Unpooled.*;
|
||||
|
||||
public class DatabaseSingle<U> implements DatabaseStageEntry<U> {
|
||||
|
||||
private final LLDictionary dictionary;
|
||||
private final byte[] key;
|
||||
private final Serializer<U, byte[]> serializer;
|
||||
private final ByteBuf key;
|
||||
private final Serializer<U, ByteBuf> serializer;
|
||||
|
||||
public DatabaseSingle(LLDictionary dictionary, byte[] key, Serializer<U, byte[]> serializer) {
|
||||
public DatabaseSingle(LLDictionary dictionary, ByteBuf key, Serializer<U, ByteBuf> serializer) {
|
||||
this.dictionary = dictionary;
|
||||
if (!key.isDirect()) {
|
||||
throw new IllegalArgumentException("Key must be direct");
|
||||
}
|
||||
this.key = key;
|
||||
this.serializer = serializer;
|
||||
}
|
||||
@ -33,47 +38,60 @@ public class DatabaseSingle<U> implements DatabaseStageEntry<U> {
|
||||
|
||||
@Override
|
||||
public Mono<U> get(@Nullable CompositeSnapshot snapshot, boolean existsAlmostCertainly) {
|
||||
return dictionary.get(resolveSnapshot(snapshot), key, existsAlmostCertainly).map(this::deserialize);
|
||||
return dictionary.get(resolveSnapshot(snapshot), key.retain(), existsAlmostCertainly).map(this::deserialize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<U> setAndGetPrevious(U value) {
|
||||
return dictionary.put(key, serialize(value), LLDictionaryResultType.PREVIOUS_VALUE).map(this::deserialize);
|
||||
ByteBuf valueByteBuf = serialize(value);
|
||||
return dictionary
|
||||
.put(key.retain(), valueByteBuf.retain(), LLDictionaryResultType.PREVIOUS_VALUE)
|
||||
.map(this::deserialize)
|
||||
.doFinally(s -> valueByteBuf.release());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> update(Function<Optional<U>, Optional<U>> updater, boolean existsAlmostCertainly) {
|
||||
return dictionary.update(key,
|
||||
(oldValueSer) -> updater.apply(oldValueSer.map(this::deserialize)).map(this::serialize),
|
||||
existsAlmostCertainly
|
||||
);
|
||||
public Mono<Boolean> update(Function<@Nullable U, @Nullable U> updater, boolean existsAlmostCertainly) {
|
||||
return dictionary.update(key.retain(), (oldValueSer) -> {
|
||||
var result = updater.apply(oldValueSer == null ? null : this.deserialize(oldValueSer));
|
||||
if (result == null) {
|
||||
return null;
|
||||
} else {
|
||||
return this.serialize(result);
|
||||
}
|
||||
}, existsAlmostCertainly);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<U> clearAndGetPrevious() {
|
||||
return dictionary.remove(key, LLDictionaryResultType.PREVIOUS_VALUE).map(this::deserialize);
|
||||
return dictionary.remove(key.retain(), LLDictionaryResultType.PREVIOUS_VALUE).map(this::deserialize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Long> leavesCount(@Nullable CompositeSnapshot snapshot, boolean fast) {
|
||||
return dictionary
|
||||
.isRangeEmpty(resolveSnapshot(snapshot), LLRange.single(key))
|
||||
.isRangeEmpty(resolveSnapshot(snapshot), LLRange.single(key.retain()))
|
||||
.map(empty -> empty ? 0L : 1L);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> isEmpty(@Nullable CompositeSnapshot snapshot) {
|
||||
return dictionary
|
||||
.isRangeEmpty(resolveSnapshot(snapshot), LLRange.single(key));
|
||||
.isRangeEmpty(resolveSnapshot(snapshot), LLRange.single(key.retain()));
|
||||
}
|
||||
|
||||
//todo: temporary wrapper. convert the whole class to buffers
|
||||
private U deserialize(byte[] bytes) {
|
||||
private U deserialize(ByteBuf bytes) {
|
||||
return serializer.deserialize(bytes);
|
||||
}
|
||||
|
||||
//todo: temporary wrapper. convert the whole class to buffers
|
||||
private byte[] serialize(U bytes) {
|
||||
private ByteBuf serialize(U bytes) {
|
||||
return serializer.serialize(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
key.release();
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@ package it.cavallium.dbengine.database.collections;
|
||||
|
||||
import it.cavallium.dbengine.client.CompositeSnapshot;
|
||||
import it.cavallium.dbengine.database.serialization.Serializer;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import reactor.core.publisher.Mono;
|
||||
@ -39,14 +38,20 @@ public class DatabaseSingleMapped<A, B> implements DatabaseStageEntry<A> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> setAndGetStatus(A value) {
|
||||
return serializedSingle.setAndGetStatus(serialize(value));
|
||||
public Mono<Boolean> setAndGetChanged(A value) {
|
||||
return serializedSingle.setAndGetChanged(serialize(value)).single();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Boolean> update(Function<Optional<A>, Optional<A>> updater, boolean existsAlmostCertainly) {
|
||||
return serializedSingle
|
||||
.update(oldValue -> updater.apply(oldValue.map(this::deserialize)).map(this::serialize), existsAlmostCertainly);
|
||||
public Mono<Boolean> update(Function<@Nullable A, @Nullable A> updater, boolean existsAlmostCertainly) {
|
||||
return serializedSingle.update(oldValue -> {
|
||||
var result = updater.apply(oldValue == null ? null : this.deserialize(oldValue));
|
||||
if (result == null) {
|
||||
return null;
|
||||
} else {
|
||||
return this.serialize(result);
|
||||
}
|
||||
}, existsAlmostCertainly);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -84,6 +89,11 @@ public class DatabaseSingleMapped<A, B> implements DatabaseStageEntry<A> {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
serializedSingle.release();
|
||||
}
|
||||
|
||||
//todo: temporary wrapper. convert the whole class to buffers
|
||||
private A deserialize(B bytes) {
|
||||
return serializer.deserialize(bytes);
|
||||
|
@ -2,7 +2,6 @@ package it.cavallium.dbengine.database.collections;
|
||||
|
||||
import it.cavallium.dbengine.client.CompositeSnapshot;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import reactor.core.publisher.Mono;
|
||||
@ -26,18 +25,18 @@ public interface DatabaseStage<T> extends DatabaseStageWithEntry<T> {
|
||||
}
|
||||
|
||||
default Mono<Void> set(T value) {
|
||||
return setAndGetStatus(value).then();
|
||||
return setAndGetChanged(value).then();
|
||||
}
|
||||
|
||||
Mono<T> setAndGetPrevious(T value);
|
||||
|
||||
default Mono<Boolean> setAndGetStatus(T value) {
|
||||
return setAndGetPrevious(value).map(oldValue -> !Objects.equals(oldValue, value)).defaultIfEmpty(false);
|
||||
default Mono<Boolean> setAndGetChanged(T value) {
|
||||
return setAndGetPrevious(value).map(oldValue -> !Objects.equals(oldValue, value)).defaultIfEmpty(value != null);
|
||||
}
|
||||
|
||||
Mono<Boolean> update(Function<Optional<T>, Optional<T>> updater, boolean existsAlmostCertainly);
|
||||
Mono<Boolean> update(Function<@Nullable T, @Nullable T> updater, boolean existsAlmostCertainly);
|
||||
|
||||
default Mono<Boolean> update(Function<Optional<T>, Optional<T>> updater) {
|
||||
default Mono<Boolean> update(Function<@Nullable T, @Nullable T> updater) {
|
||||
return update(updater, false);
|
||||
}
|
||||
|
||||
@ -51,6 +50,8 @@ public interface DatabaseStage<T> extends DatabaseStageWithEntry<T> {
|
||||
return clearAndGetPrevious().map(Objects::nonNull).defaultIfEmpty(false);
|
||||
}
|
||||
|
||||
void release();
|
||||
|
||||
default Mono<Void> close() {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import it.cavallium.dbengine.database.collections.JoinerBlocking.ValueGetterBloc
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import reactor.core.publisher.Flux;
|
||||
@ -18,7 +17,7 @@ public interface DatabaseStageMap<T, U, US extends DatabaseStage<U>> extends Dat
|
||||
Mono<US> at(@Nullable CompositeSnapshot snapshot, T key);
|
||||
|
||||
default Mono<U> getValue(@Nullable CompositeSnapshot snapshot, T key, boolean existsAlmostCertainly) {
|
||||
return this.at(snapshot, key).flatMap(v -> v.get(snapshot, existsAlmostCertainly));
|
||||
return this.at(snapshot, key).flatMap(v -> v.get(snapshot, existsAlmostCertainly).doFinally(s -> v.release()));
|
||||
}
|
||||
|
||||
default Mono<U> getValue(@Nullable CompositeSnapshot snapshot, T key) {
|
||||
@ -30,23 +29,29 @@ public interface DatabaseStageMap<T, U, US extends DatabaseStage<U>> extends Dat
|
||||
}
|
||||
|
||||
default Mono<Void> putValue(T key, U value) {
|
||||
return at(null, key).single().flatMap(v -> v.set(value));
|
||||
return at(null, key).single().flatMap(v -> v.set(value).doFinally(s -> v.release()));
|
||||
}
|
||||
|
||||
default Mono<Boolean> updateValue(T key, boolean existsAlmostCertainly, Function<Optional<U>, Optional<U>> updater) {
|
||||
return at(null, key).single().flatMap(v -> v.update(updater, existsAlmostCertainly));
|
||||
default Mono<Boolean> updateValue(T key, boolean existsAlmostCertainly, Function<@Nullable U, @Nullable U> updater) {
|
||||
return at(null, key).single().flatMap(v -> v.update(updater, existsAlmostCertainly).doFinally(s -> v.release()));
|
||||
}
|
||||
|
||||
default Mono<Boolean> updateValue(T key, Function<Optional<U>, Optional<U>> updater) {
|
||||
default Mono<Boolean> updateValue(T key, Function<@Nullable U, @Nullable U> updater) {
|
||||
return updateValue(key, false, updater);
|
||||
}
|
||||
|
||||
default Mono<U> putValueAndGetPrevious(T key, U value) {
|
||||
return at(null, key).single().flatMap(v -> v.setAndGetPrevious(value));
|
||||
return at(null, key).single().flatMap(v -> v.setAndGetPrevious(value).doFinally(s -> v.release()));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param key
|
||||
* @param value
|
||||
* @return true if the key was associated with any value, false if the key didn't exist.
|
||||
*/
|
||||
default Mono<Boolean> putValueAndGetStatus(T key, U value) {
|
||||
return at(null, key).single().flatMap(v -> v.setAndGetStatus(value));
|
||||
return at(null, key).single().flatMap(v -> v.setAndGetChanged(value).doFinally(s -> v.release())).single();
|
||||
}
|
||||
|
||||
default Mono<Void> remove(T key) {
|
||||
@ -54,7 +59,7 @@ public interface DatabaseStageMap<T, U, US extends DatabaseStage<U>> extends Dat
|
||||
}
|
||||
|
||||
default Mono<U> removeAndGetPrevious(T key) {
|
||||
return at(null, key).flatMap(DatabaseStage::clearAndGetPrevious);
|
||||
return at(null, key).flatMap(v -> v.clearAndGetPrevious().doFinally(s -> v.release()));
|
||||
}
|
||||
|
||||
default Mono<Boolean> removeAndGetStatus(T key) {
|
||||
@ -106,7 +111,7 @@ public interface DatabaseStageMap<T, U, US extends DatabaseStage<U>> extends Dat
|
||||
.flatMap(entriesReplacer)
|
||||
.flatMap(replacedEntry -> this
|
||||
.at(null, replacedEntry.getKey())
|
||||
.map(entry -> entry.set(replacedEntry.getValue())))
|
||||
.map(v -> v.set(replacedEntry.getValue()).doFinally(s -> v.release())))
|
||||
.then();
|
||||
}
|
||||
}
|
||||
@ -126,15 +131,23 @@ public interface DatabaseStageMap<T, U, US extends DatabaseStage<U>> extends Dat
|
||||
}
|
||||
|
||||
@Override
|
||||
default Mono<Boolean> update(Function<Optional<Map<T, U>>, Optional<Map<T, U>>> updater, boolean existsAlmostCertainly) {
|
||||
default Mono<Boolean> update(Function<@Nullable Map<T, U>, @Nullable Map<T, U>> updater, boolean existsAlmostCertainly) {
|
||||
return this
|
||||
.getAllValues(null)
|
||||
.collectMap(Entry::getKey, Entry::getValue, HashMap::new)
|
||||
.single()
|
||||
.map(v -> v.isEmpty() ? Optional.<Map<T, U>>empty() : Optional.of(v))
|
||||
.map(updater)
|
||||
.filter(Optional::isPresent)
|
||||
.map(Optional::get)
|
||||
.<Map<T, U>>handle((v, sink) -> {
|
||||
if (v == null || v.isEmpty()) {
|
||||
sink.complete();
|
||||
} else {
|
||||
var result = updater.apply(v);
|
||||
if (result == null) {
|
||||
sink.complete();
|
||||
} else {
|
||||
sink.next(result);
|
||||
}
|
||||
}
|
||||
})
|
||||
.flatMap(values -> this.setAllValues(Flux.fromIterable(values.entrySet())))
|
||||
//todo: can be optimized by calculating the correct return value
|
||||
.thenReturn(true);
|
||||
|
@ -1,5 +1,6 @@
|
||||
package it.cavallium.dbengine.database.collections;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import it.cavallium.dbengine.client.CompositeSnapshot;
|
||||
import it.cavallium.dbengine.database.LLDictionary;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@ -10,8 +11,8 @@ public interface SubStageGetter<U, US extends DatabaseStage<U>> {
|
||||
|
||||
Mono<US> subStage(LLDictionary dictionary,
|
||||
@Nullable CompositeSnapshot snapshot,
|
||||
byte[] prefixKey,
|
||||
Flux<byte[]> debuggingKeyFlux);
|
||||
ByteBuf prefixKey,
|
||||
Flux<ByteBuf> debuggingKeyFlux);
|
||||
|
||||
boolean isMultiKey();
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package it.cavallium.dbengine.database.collections;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import it.cavallium.dbengine.client.CompositeSnapshot;
|
||||
import it.cavallium.dbengine.database.LLDictionary;
|
||||
import it.cavallium.dbengine.database.serialization.Serializer;
|
||||
@ -23,15 +24,15 @@ public class SubStageGetterHashMap<T, U, TH> implements
|
||||
assertsEnabled = assertsEnabledTmp;
|
||||
}
|
||||
|
||||
private final Serializer<T, byte[]> keySerializer;
|
||||
private final Serializer<U, byte[]> valueSerializer;
|
||||
private final Serializer<T, ByteBuf> keySerializer;
|
||||
private final Serializer<U, ByteBuf> valueSerializer;
|
||||
private final Function<T, TH> keyHashFunction;
|
||||
private final SerializerFixedBinaryLength<TH, byte[]> keyHashSerializer;
|
||||
private final SerializerFixedBinaryLength<TH, ByteBuf> keyHashSerializer;
|
||||
|
||||
public SubStageGetterHashMap(Serializer<T, byte[]> keySerializer,
|
||||
Serializer<U, byte[]> valueSerializer,
|
||||
public SubStageGetterHashMap(Serializer<T, ByteBuf> keySerializer,
|
||||
Serializer<U, ByteBuf> valueSerializer,
|
||||
Function<T, TH> keyHashFunction,
|
||||
SerializerFixedBinaryLength<TH, byte[]> keyHashSerializer) {
|
||||
SerializerFixedBinaryLength<TH, ByteBuf> keyHashSerializer) {
|
||||
this.keySerializer = keySerializer;
|
||||
this.valueSerializer = valueSerializer;
|
||||
this.keyHashFunction = keyHashFunction;
|
||||
@ -41,8 +42,8 @@ public class SubStageGetterHashMap<T, U, TH> implements
|
||||
@Override
|
||||
public Mono<DatabaseMapDictionaryHashed<T, U, TH>> subStage(LLDictionary dictionary,
|
||||
@Nullable CompositeSnapshot snapshot,
|
||||
byte[] prefixKey,
|
||||
Flux<byte[]> debuggingKeyFlux) {
|
||||
ByteBuf prefixKey,
|
||||
Flux<ByteBuf> debuggingKeyFlux) {
|
||||
Mono<DatabaseMapDictionaryHashed<T, U, TH>> result = Mono.just(DatabaseMapDictionaryHashed.tail(dictionary,
|
||||
prefixKey,
|
||||
keySerializer,
|
||||
@ -67,9 +68,9 @@ public class SubStageGetterHashMap<T, U, TH> implements
|
||||
return assertsEnabled;
|
||||
}
|
||||
|
||||
private Mono<Void> checkKeyFluxConsistency(byte[] prefixKey, Flux<byte[]> keyFlux) {
|
||||
private Mono<Void> checkKeyFluxConsistency(ByteBuf prefixKey, Flux<ByteBuf> keyFlux) {
|
||||
return keyFlux.doOnNext(key -> {
|
||||
assert key.length == prefixKey.length + getKeyHashBinaryLength();
|
||||
assert key.readableBytes() == prefixKey.readableBytes() + getKeyHashBinaryLength();
|
||||
}).then();
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package it.cavallium.dbengine.database.collections;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import it.cavallium.dbengine.client.CompositeSnapshot;
|
||||
import it.cavallium.dbengine.database.LLDictionary;
|
||||
import it.cavallium.dbengine.database.collections.DatabaseEmpty.Nothing;
|
||||
@ -24,13 +25,13 @@ public class SubStageGetterHashSet<T, TH> implements
|
||||
assertsEnabled = assertsEnabledTmp;
|
||||
}
|
||||
|
||||
private final Serializer<T, byte[]> keySerializer;
|
||||
private final Serializer<T, ByteBuf> keySerializer;
|
||||
private final Function<T, TH> keyHashFunction;
|
||||
private final SerializerFixedBinaryLength<TH, byte[]> keyHashSerializer;
|
||||
private final SerializerFixedBinaryLength<TH, ByteBuf> keyHashSerializer;
|
||||
|
||||
public SubStageGetterHashSet(Serializer<T, byte[]> keySerializer,
|
||||
public SubStageGetterHashSet(Serializer<T, ByteBuf> keySerializer,
|
||||
Function<T, TH> keyHashFunction,
|
||||
SerializerFixedBinaryLength<TH, byte[]> keyHashSerializer) {
|
||||
SerializerFixedBinaryLength<TH, ByteBuf> keyHashSerializer) {
|
||||
this.keySerializer = keySerializer;
|
||||
this.keyHashFunction = keyHashFunction;
|
||||
this.keyHashSerializer = keyHashSerializer;
|
||||
@ -39,8 +40,8 @@ public class SubStageGetterHashSet<T, TH> implements
|
||||
@Override
|
||||
public Mono<DatabaseSetDictionaryHashed<T, TH>> subStage(LLDictionary dictionary,
|
||||
@Nullable CompositeSnapshot snapshot,
|
||||
byte[] prefixKey,
|
||||
Flux<byte[]> debuggingKeyFlux) {
|
||||
ByteBuf prefixKey,
|
||||
Flux<ByteBuf> debuggingKeyFlux) {
|
||||
Mono<DatabaseSetDictionaryHashed<T, TH>> result = Mono.just(DatabaseSetDictionaryHashed.tail(dictionary,
|
||||
prefixKey,
|
||||
keySerializer,
|
||||
@ -64,9 +65,9 @@ public class SubStageGetterHashSet<T, TH> implements
|
||||
return assertsEnabled;
|
||||
}
|
||||
|
||||
private Mono<Void> checkKeyFluxConsistency(byte[] prefixKey, Flux<byte[]> keyFlux) {
|
||||
private Mono<Void> checkKeyFluxConsistency(ByteBuf prefixKey, Flux<ByteBuf> keyFlux) {
|
||||
return keyFlux.doOnNext(key -> {
|
||||
assert key.length == prefixKey.length + getKeyHashBinaryLength();
|
||||
assert key.readableBytes() == prefixKey.readableBytes() + getKeyHashBinaryLength();
|
||||
}).then();
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package it.cavallium.dbengine.database.collections;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import it.cavallium.dbengine.client.CompositeSnapshot;
|
||||
import it.cavallium.dbengine.database.LLDictionary;
|
||||
import it.cavallium.dbengine.database.serialization.Serializer;
|
||||
@ -20,11 +21,11 @@ public class SubStageGetterMap<T, U> implements SubStageGetter<Map<T, U>, Databa
|
||||
assertsEnabled = assertsEnabledTmp;
|
||||
}
|
||||
|
||||
private final SerializerFixedBinaryLength<T, byte[]> keySerializer;
|
||||
private final Serializer<U, byte[]> valueSerializer;
|
||||
private final SerializerFixedBinaryLength<T, ByteBuf> keySerializer;
|
||||
private final Serializer<U, ByteBuf> valueSerializer;
|
||||
|
||||
public SubStageGetterMap(SerializerFixedBinaryLength<T, byte[]> keySerializer,
|
||||
Serializer<U, byte[]> valueSerializer) {
|
||||
public SubStageGetterMap(SerializerFixedBinaryLength<T, ByteBuf> keySerializer,
|
||||
Serializer<U, ByteBuf> valueSerializer) {
|
||||
this.keySerializer = keySerializer;
|
||||
this.valueSerializer = valueSerializer;
|
||||
}
|
||||
@ -32,8 +33,8 @@ public class SubStageGetterMap<T, U> implements SubStageGetter<Map<T, U>, Databa
|
||||
@Override
|
||||
public Mono<DatabaseMapDictionary<T, U>> subStage(LLDictionary dictionary,
|
||||
@Nullable CompositeSnapshot snapshot,
|
||||
byte[] prefixKey,
|
||||
Flux<byte[]> debuggingKeyFlux) {
|
||||
ByteBuf prefixKey,
|
||||
Flux<ByteBuf> debuggingKeyFlux) {
|
||||
Mono<DatabaseMapDictionary<T, U>> result = Mono.just(DatabaseMapDictionary.tail(dictionary, prefixKey, keySerializer,
|
||||
valueSerializer
|
||||
));
|
||||
@ -54,9 +55,9 @@ public class SubStageGetterMap<T, U> implements SubStageGetter<Map<T, U>, Databa
|
||||
return assertsEnabled;
|
||||
}
|
||||
|
||||
private Mono<Void> checkKeyFluxConsistency(byte[] prefixKey, Flux<byte[]> keyFlux) {
|
||||
private Mono<Void> checkKeyFluxConsistency(ByteBuf prefixKey, Flux<ByteBuf> keyFlux) {
|
||||
return keyFlux.doOnNext(key -> {
|
||||
assert key.length == prefixKey.length + getKeyBinaryLength();
|
||||
assert key.readableBytes() == prefixKey.readableBytes() + getKeyBinaryLength();
|
||||
}).then();
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package it.cavallium.dbengine.database.collections;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import it.cavallium.dbengine.client.CompositeSnapshot;
|
||||
import it.cavallium.dbengine.database.LLDictionary;
|
||||
import it.cavallium.dbengine.database.serialization.SerializerFixedBinaryLength;
|
||||
@ -20,11 +21,11 @@ public class SubStageGetterMapDeep<T, U, US extends DatabaseStage<U>> implements
|
||||
}
|
||||
|
||||
private final SubStageGetter<U, US> subStageGetter;
|
||||
private final SerializerFixedBinaryLength<T, byte[]> keySerializer;
|
||||
private final SerializerFixedBinaryLength<T, ByteBuf> keySerializer;
|
||||
private final int keyExtLength;
|
||||
|
||||
public SubStageGetterMapDeep(SubStageGetter<U, US> subStageGetter,
|
||||
SerializerFixedBinaryLength<T, byte[]> keySerializer,
|
||||
SerializerFixedBinaryLength<T, ByteBuf> keySerializer,
|
||||
int keyExtLength) {
|
||||
this.subStageGetter = subStageGetter;
|
||||
this.keySerializer = keySerializer;
|
||||
@ -46,8 +47,8 @@ public class SubStageGetterMapDeep<T, U, US extends DatabaseStage<U>> implements
|
||||
@Override
|
||||
public Mono<DatabaseMapDictionaryDeep<T, U, US>> subStage(LLDictionary dictionary,
|
||||
@Nullable CompositeSnapshot snapshot,
|
||||
byte[] prefixKey,
|
||||
Flux<byte[]> debuggingKeyFlux) {
|
||||
ByteBuf prefixKey,
|
||||
Flux<ByteBuf> debuggingKeyFlux) {
|
||||
Mono<DatabaseMapDictionaryDeep<T, U, US>> result = Mono.just(DatabaseMapDictionaryDeep.deepIntermediate(dictionary,
|
||||
prefixKey,
|
||||
keySerializer,
|
||||
@ -71,9 +72,9 @@ public class SubStageGetterMapDeep<T, U, US extends DatabaseStage<U>> implements
|
||||
return assertsEnabled;
|
||||
}
|
||||
|
||||
private Mono<Void> checkKeyFluxConsistency(byte[] prefixKey, Flux<byte[]> keyFlux) {
|
||||
private Mono<Void> checkKeyFluxConsistency(ByteBuf prefixKey, Flux<ByteBuf> keyFlux) {
|
||||
return keyFlux.doOnNext(key -> {
|
||||
assert key.length == prefixKey.length + getKeyBinaryLength();
|
||||
assert key.readableBytes() == prefixKey.readableBytes() + getKeyBinaryLength();
|
||||
}).then();
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package it.cavallium.dbengine.database.collections;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import it.cavallium.dbengine.client.CompositeSnapshot;
|
||||
import it.cavallium.dbengine.database.LLDictionary;
|
||||
import it.cavallium.dbengine.database.collections.DatabaseEmpty.Nothing;
|
||||
@ -20,22 +21,27 @@ public class SubStageGetterSet<T> implements SubStageGetter<Map<T, Nothing>, Dat
|
||||
assertsEnabled = assertsEnabledTmp;
|
||||
}
|
||||
|
||||
private final SerializerFixedBinaryLength<T, byte[]> keySerializer;
|
||||
private final SerializerFixedBinaryLength<T, ByteBuf> keySerializer;
|
||||
|
||||
public SubStageGetterSet(SerializerFixedBinaryLength<T, byte[]> keySerializer) {
|
||||
public SubStageGetterSet(SerializerFixedBinaryLength<T, ByteBuf> keySerializer) {
|
||||
this.keySerializer = keySerializer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<DatabaseSetDictionary<T>> subStage(LLDictionary dictionary,
|
||||
@Nullable CompositeSnapshot snapshot,
|
||||
byte[] prefixKey,
|
||||
Flux<byte[]> debuggingKeyFlux) {
|
||||
Mono<DatabaseSetDictionary<T>> result = Mono.just(DatabaseSetDictionary.tail(dictionary, prefixKey, keySerializer));
|
||||
if (assertsEnabled) {
|
||||
return checkKeyFluxConsistency(prefixKey, debuggingKeyFlux).then(result);
|
||||
} else {
|
||||
return result;
|
||||
ByteBuf prefixKey,
|
||||
Flux<ByteBuf> debuggingKeyFlux) {
|
||||
try {
|
||||
Mono<DatabaseSetDictionary<T>> result = Mono
|
||||
.fromSupplier(() -> DatabaseSetDictionary.tail(dictionary, prefixKey.retain(), keySerializer));
|
||||
if (assertsEnabled) {
|
||||
return checkKeyFluxConsistency(prefixKey.retain(), debuggingKeyFlux).then(result);
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
} finally {
|
||||
prefixKey.release();
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,10 +55,10 @@ public class SubStageGetterSet<T> implements SubStageGetter<Map<T, Nothing>, Dat
|
||||
return assertsEnabled;
|
||||
}
|
||||
|
||||
private Mono<Void> checkKeyFluxConsistency(byte[] prefixKey, Flux<byte[]> keyFlux) {
|
||||
private Mono<Void> checkKeyFluxConsistency(ByteBuf prefixKey, Flux<ByteBuf> keyFlux) {
|
||||
return keyFlux.doOnNext(key -> {
|
||||
assert key.length == prefixKey.length + getKeyBinaryLength();
|
||||
}).then();
|
||||
assert key.readableBytes() == prefixKey.readableBytes() + getKeyBinaryLength();
|
||||
}).doFinally(s -> prefixKey.release()).then();
|
||||
}
|
||||
|
||||
public int getKeyBinaryLength() {
|
||||
|
@ -1,7 +1,9 @@
|
||||
package it.cavallium.dbengine.database.collections;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import it.cavallium.dbengine.client.CompositeSnapshot;
|
||||
import it.cavallium.dbengine.database.LLDictionary;
|
||||
import it.cavallium.dbengine.database.LLUtils;
|
||||
import it.cavallium.dbengine.database.serialization.Serializer;
|
||||
import java.util.Arrays;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@ -19,22 +21,22 @@ public class SubStageGetterSingle<T> implements SubStageGetter<T, DatabaseStageE
|
||||
assertsEnabled = assertsEnabledTmp;
|
||||
}
|
||||
|
||||
private final Serializer<T, byte[]> serializer;
|
||||
private final Serializer<T, ByteBuf> serializer;
|
||||
|
||||
public SubStageGetterSingle(Serializer<T, byte[]> serializer) {
|
||||
public SubStageGetterSingle(Serializer<T, ByteBuf> serializer) {
|
||||
this.serializer = serializer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<DatabaseStageEntry<T>> subStage(LLDictionary dictionary,
|
||||
@Nullable CompositeSnapshot snapshot,
|
||||
byte[] keyPrefix,
|
||||
Flux<byte[]> debuggingKeyFlux) {
|
||||
ByteBuf keyPrefix,
|
||||
Flux<ByteBuf> debuggingKeyFlux) {
|
||||
return debuggingKeyFlux
|
||||
.singleOrEmpty()
|
||||
.flatMap(key -> Mono
|
||||
.<DatabaseStageEntry<T>>fromCallable(() -> {
|
||||
if (!Arrays.equals(keyPrefix, key)) {
|
||||
if (!LLUtils.equals(keyPrefix, key)) {
|
||||
throw new IndexOutOfBoundsException("Found more than one element!");
|
||||
}
|
||||
return null;
|
||||
|
@ -1,8 +1,9 @@
|
||||
package it.cavallium.dbengine.database.collections;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import it.cavallium.dbengine.database.serialization.Serializer;
|
||||
|
||||
public class SubStageGetterSingleBytes extends SubStageGetterSingle<byte[]> {
|
||||
public class SubStageGetterSingleBytes extends SubStageGetterSingle<ByteBuf> {
|
||||
|
||||
public SubStageGetterSingleBytes() {
|
||||
super(Serializer.noop());
|
||||
|
@ -0,0 +1,6 @@
|
||||
package it.cavallium.dbengine.database.disk;
|
||||
|
||||
public enum IterateBound {
|
||||
LOWER,
|
||||
UPPER
|
||||
}
|
@ -36,14 +36,18 @@ public class LLLocalDatabaseConnection implements LLDatabaseConnection {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<LLLocalKeyValueDatabase> getDatabase(String name, List<Column> columns, boolean lowMemory) {
|
||||
public Mono<LLLocalKeyValueDatabase> getDatabase(String name,
|
||||
List<Column> columns,
|
||||
boolean lowMemory,
|
||||
boolean inMemory) {
|
||||
return Mono
|
||||
.fromCallable(() -> new LLLocalKeyValueDatabase(name,
|
||||
basePath.resolve("database_" + name),
|
||||
columns,
|
||||
new LinkedList<>(),
|
||||
crashIfWalError,
|
||||
lowMemory
|
||||
lowMemory,
|
||||
inMemory
|
||||
))
|
||||
.subscribeOn(Schedulers.boundedElastic());
|
||||
}
|
||||
@ -55,7 +59,8 @@ public class LLLocalDatabaseConnection implements LLDatabaseConnection {
|
||||
TextFieldsSimilarity textFieldsSimilarity,
|
||||
Duration queryRefreshDebounceTime,
|
||||
Duration commitDebounceTime,
|
||||
boolean lowMemory) {
|
||||
boolean lowMemory,
|
||||
boolean inMemory) {
|
||||
return Mono
|
||||
.fromCallable(() -> {
|
||||
if (instancesCount != 1) {
|
||||
@ -66,7 +71,8 @@ public class LLLocalDatabaseConnection implements LLDatabaseConnection {
|
||||
textFieldsSimilarity,
|
||||
queryRefreshDebounceTime,
|
||||
commitDebounceTime,
|
||||
lowMemory
|
||||
lowMemory,
|
||||
inMemory
|
||||
);
|
||||
} else {
|
||||
return new LLLocalLuceneIndex(basePath.resolve("lucene"),
|
||||
@ -76,6 +82,7 @@ public class LLLocalDatabaseConnection implements LLDatabaseConnection {
|
||||
queryRefreshDebounceTime,
|
||||
commitDebounceTime,
|
||||
lowMemory,
|
||||
inMemory,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,7 @@
|
||||
package it.cavallium.dbengine.database.disk;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import it.cavallium.dbengine.database.LLRange;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
@ -7,17 +9,18 @@ import org.rocksdb.ColumnFamilyHandle;
|
||||
import org.rocksdb.ReadOptions;
|
||||
import org.rocksdb.RocksDB;
|
||||
|
||||
public class LLLocalEntryReactiveRocksIterator extends LLLocalReactiveRocksIterator<Entry<byte[], byte[]>> {
|
||||
public class LLLocalEntryReactiveRocksIterator extends LLLocalReactiveRocksIterator<Entry<ByteBuf, ByteBuf>> {
|
||||
|
||||
public LLLocalEntryReactiveRocksIterator(RocksDB db,
|
||||
ByteBufAllocator alloc,
|
||||
ColumnFamilyHandle cfh,
|
||||
LLRange range,
|
||||
ReadOptions readOptions) {
|
||||
super(db, cfh, range, readOptions, true);
|
||||
super(db, alloc, cfh, range, readOptions, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<byte[], byte[]> getEntry(byte[] key, byte[] value) {
|
||||
public Entry<ByteBuf, ByteBuf> getEntry(ByteBuf key, ByteBuf value) {
|
||||
return Map.entry(key, value);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package it.cavallium.dbengine.database.disk;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import it.cavallium.dbengine.database.LLRange;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
@ -8,19 +10,18 @@ import org.rocksdb.ReadOptions;
|
||||
import org.rocksdb.RocksDB;
|
||||
|
||||
public class LLLocalGroupedEntryReactiveRocksIterator extends
|
||||
LLLocalGroupedReactiveRocksIterator<Entry<byte[], byte[]>> {
|
||||
LLLocalGroupedReactiveRocksIterator<Entry<ByteBuf, ByteBuf>> {
|
||||
|
||||
public LLLocalGroupedEntryReactiveRocksIterator(RocksDB db,
|
||||
ColumnFamilyHandle cfh,
|
||||
public LLLocalGroupedEntryReactiveRocksIterator(RocksDB db, ByteBufAllocator alloc, ColumnFamilyHandle cfh,
|
||||
int prefixLength,
|
||||
LLRange range,
|
||||
ReadOptions readOptions,
|
||||
String debugName) {
|
||||
super(db, cfh, prefixLength, range, readOptions, false, true);
|
||||
super(db, alloc, cfh, prefixLength, range, readOptions, false, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Entry<byte[], byte[]> getEntry(byte[] key, byte[] value) {
|
||||
public Entry<ByteBuf, ByteBuf> getEntry(ByteBuf key, ByteBuf value) {
|
||||
return Map.entry(key, value);
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,26 @@
|
||||
package it.cavallium.dbengine.database.disk;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import it.cavallium.dbengine.database.LLRange;
|
||||
import org.rocksdb.ColumnFamilyHandle;
|
||||
import org.rocksdb.ReadOptions;
|
||||
import org.rocksdb.RocksDB;
|
||||
|
||||
public class LLLocalGroupedKeyReactiveRocksIterator extends LLLocalGroupedReactiveRocksIterator<byte[]> {
|
||||
public class LLLocalGroupedKeyReactiveRocksIterator extends LLLocalGroupedReactiveRocksIterator<ByteBuf> {
|
||||
|
||||
public LLLocalGroupedKeyReactiveRocksIterator(RocksDB db,
|
||||
ByteBufAllocator alloc,
|
||||
ColumnFamilyHandle cfh,
|
||||
int prefixLength,
|
||||
LLRange range,
|
||||
ReadOptions readOptions,
|
||||
String debugName) {
|
||||
super(db, cfh, prefixLength, range, readOptions, true, false);
|
||||
super(db, alloc, cfh, prefixLength, range, readOptions, true, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEntry(byte[] key, byte[] value) {
|
||||
public ByteBuf getEntry(ByteBuf key, ByteBuf value) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
@ -2,21 +2,24 @@ package it.cavallium.dbengine.database.disk;
|
||||
|
||||
import static it.cavallium.dbengine.database.disk.LLLocalDictionary.getRocksIterator;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import it.cavallium.dbengine.database.LLRange;
|
||||
import it.cavallium.dbengine.database.LLUtils;
|
||||
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import org.rocksdb.ColumnFamilyHandle;
|
||||
import org.rocksdb.ReadOptions;
|
||||
import org.rocksdb.RocksDB;
|
||||
import org.rocksdb.RocksMutableObject;
|
||||
import reactor.core.publisher.Flux;
|
||||
import static io.netty.buffer.Unpooled.*;
|
||||
|
||||
public abstract class LLLocalGroupedReactiveRocksIterator<T> {
|
||||
|
||||
private static final byte[] EMPTY = new byte[0];
|
||||
|
||||
private final RocksDB db;
|
||||
private final ByteBufAllocator alloc;
|
||||
private final ColumnFamilyHandle cfh;
|
||||
private final int prefixLength;
|
||||
private final LLRange range;
|
||||
@ -24,14 +27,14 @@ public abstract class LLLocalGroupedReactiveRocksIterator<T> {
|
||||
private final boolean canFillCache;
|
||||
private final boolean readValues;
|
||||
|
||||
public LLLocalGroupedReactiveRocksIterator(RocksDB db,
|
||||
ColumnFamilyHandle cfh,
|
||||
public LLLocalGroupedReactiveRocksIterator(RocksDB db, ByteBufAllocator alloc, ColumnFamilyHandle cfh,
|
||||
int prefixLength,
|
||||
LLRange range,
|
||||
ReadOptions readOptions,
|
||||
boolean canFillCache,
|
||||
boolean readValues) {
|
||||
this.db = db;
|
||||
this.alloc = alloc;
|
||||
this.cfh = cfh;
|
||||
this.prefixLength = prefixLength;
|
||||
this.range = range;
|
||||
@ -50,18 +53,33 @@ public abstract class LLLocalGroupedReactiveRocksIterator<T> {
|
||||
}, (tuple, sink) -> {
|
||||
var rocksIterator = tuple.getT1();
|
||||
ObjectArrayList<T> values = new ObjectArrayList<>();
|
||||
byte[] firstGroupKey = null;
|
||||
ByteBuf firstGroupKey = null;
|
||||
|
||||
while (rocksIterator.isValid()) {
|
||||
byte[] key = rocksIterator.key();
|
||||
if (firstGroupKey == null) {
|
||||
firstGroupKey = key;
|
||||
} else if (!Arrays.equals(firstGroupKey, 0, prefixLength, key, 0, prefixLength)) {
|
||||
break;
|
||||
try {
|
||||
while (rocksIterator.isValid()) {
|
||||
ByteBuf key = LLUtils.readDirectNioBuffer(alloc, rocksIterator::key);
|
||||
try {
|
||||
if (firstGroupKey == null) {
|
||||
firstGroupKey = key.retainedSlice();
|
||||
} else if (!ByteBufUtil.equals(firstGroupKey, 0, key, 0, prefixLength)) {
|
||||
break;
|
||||
}
|
||||
ByteBuf value = readValues ? LLUtils.readDirectNioBuffer(alloc, rocksIterator::value) : EMPTY_BUFFER;
|
||||
try {
|
||||
rocksIterator.next();
|
||||
T entry = getEntry(key.retain(), value.retain());
|
||||
values.add(entry);
|
||||
} finally {
|
||||
value.release();
|
||||
}
|
||||
} finally {
|
||||
key.release();
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (firstGroupKey != null) {
|
||||
firstGroupKey.release();
|
||||
}
|
||||
byte[] value = readValues ? rocksIterator.value() : EMPTY;
|
||||
rocksIterator.next();
|
||||
values.add(getEntry(key, value));
|
||||
}
|
||||
if (!values.isEmpty()) {
|
||||
sink.next(values);
|
||||
@ -72,10 +90,11 @@ public abstract class LLLocalGroupedReactiveRocksIterator<T> {
|
||||
}, tuple -> {
|
||||
var rocksIterator = tuple.getT1();
|
||||
rocksIterator.close();
|
||||
tuple.getT2().ifPresent(RocksMutableObject::close);
|
||||
tuple.getT3().ifPresent(RocksMutableObject::close);
|
||||
tuple.getT2().release();
|
||||
tuple.getT3().release();
|
||||
range.release();
|
||||
});
|
||||
}
|
||||
|
||||
public abstract T getEntry(byte[] key, byte[] value);
|
||||
public abstract T getEntry(ByteBuf key, ByteBuf value);
|
||||
}
|
||||
|
@ -1,18 +1,22 @@
|
||||
package it.cavallium.dbengine.database.disk;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import it.cavallium.dbengine.database.LLRange;
|
||||
import it.cavallium.dbengine.database.LLUtils;
|
||||
import java.util.Arrays;
|
||||
import org.rocksdb.ColumnFamilyHandle;
|
||||
import org.rocksdb.ReadOptions;
|
||||
import org.rocksdb.RocksDB;
|
||||
import org.rocksdb.RocksMutableObject;
|
||||
import reactor.core.publisher.Flux;
|
||||
import static io.netty.buffer.Unpooled.*;
|
||||
|
||||
public class LLLocalKeyPrefixReactiveRocksIterator {
|
||||
|
||||
private static final byte[] EMPTY = new byte[0];
|
||||
|
||||
private final RocksDB db;
|
||||
private final ByteBufAllocator alloc;
|
||||
private final ColumnFamilyHandle cfh;
|
||||
private final int prefixLength;
|
||||
private final LLRange range;
|
||||
@ -20,14 +24,14 @@ public class LLLocalKeyPrefixReactiveRocksIterator {
|
||||
private final boolean canFillCache;
|
||||
private final String debugName;
|
||||
|
||||
public LLLocalKeyPrefixReactiveRocksIterator(RocksDB db,
|
||||
ColumnFamilyHandle cfh,
|
||||
public LLLocalKeyPrefixReactiveRocksIterator(RocksDB db, ByteBufAllocator alloc, ColumnFamilyHandle cfh,
|
||||
int prefixLength,
|
||||
LLRange range,
|
||||
ReadOptions readOptions,
|
||||
boolean canFillCache,
|
||||
String debugName) {
|
||||
this.db = db;
|
||||
this.alloc = alloc;
|
||||
this.cfh = cfh;
|
||||
this.prefixLength = prefixLength;
|
||||
this.range = range;
|
||||
@ -37,7 +41,7 @@ public class LLLocalKeyPrefixReactiveRocksIterator {
|
||||
}
|
||||
|
||||
|
||||
public Flux<byte[]> flux() {
|
||||
public Flux<ByteBuf> flux() {
|
||||
return Flux
|
||||
.generate(() -> {
|
||||
var readOptions = new ReadOptions(this.readOptions);
|
||||
@ -45,32 +49,42 @@ public class LLLocalKeyPrefixReactiveRocksIterator {
|
||||
//readOptions.setReadaheadSize(2 * 1024 * 1024);
|
||||
readOptions.setFillCache(canFillCache);
|
||||
}
|
||||
return LLLocalDictionary.getRocksIterator(readOptions, range, db, cfh);
|
||||
return LLLocalDictionary.getRocksIterator(readOptions, range.retain(), db, cfh);
|
||||
}, (tuple, sink) -> {
|
||||
var rocksIterator = tuple.getT1();
|
||||
byte[] firstGroupKey = null;
|
||||
|
||||
while (rocksIterator.isValid()) {
|
||||
byte[] key = rocksIterator.key();
|
||||
if (firstGroupKey == null) {
|
||||
firstGroupKey = key;
|
||||
} else if (!Arrays.equals(firstGroupKey, 0, prefixLength, key, 0, prefixLength)) {
|
||||
break;
|
||||
ByteBuf firstGroupKey = null;
|
||||
try {
|
||||
while (rocksIterator.isValid()) {
|
||||
ByteBuf key = LLUtils.readDirectNioBuffer(alloc, rocksIterator::key);
|
||||
try {
|
||||
if (firstGroupKey == null) {
|
||||
firstGroupKey = key.retain();
|
||||
} else if (!ByteBufUtil.equals(firstGroupKey, 0, key, 0, prefixLength)) {
|
||||
break;
|
||||
}
|
||||
rocksIterator.next();
|
||||
} finally {
|
||||
key.release();
|
||||
}
|
||||
}
|
||||
if (firstGroupKey != null) {
|
||||
var groupKeyPrefix = firstGroupKey.slice(0, prefixLength);
|
||||
sink.next(groupKeyPrefix.retain());
|
||||
} else {
|
||||
sink.complete();
|
||||
}
|
||||
} finally {
|
||||
if (firstGroupKey != null) {
|
||||
firstGroupKey.release();
|
||||
}
|
||||
rocksIterator.next();
|
||||
}
|
||||
if (firstGroupKey != null) {
|
||||
var groupKeyPrefix = Arrays.copyOf(firstGroupKey, prefixLength);
|
||||
sink.next(groupKeyPrefix);
|
||||
} else {
|
||||
sink.complete();
|
||||
}
|
||||
return tuple;
|
||||
}, tuple -> {
|
||||
var rocksIterator = tuple.getT1();
|
||||
rocksIterator.close();
|
||||
tuple.getT2().ifPresent(RocksMutableObject::close);
|
||||
tuple.getT3().ifPresent(RocksMutableObject::close);
|
||||
tuple.getT2().release();
|
||||
tuple.getT3().release();
|
||||
range.release();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,21 +1,24 @@
|
||||
package it.cavallium.dbengine.database.disk;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import it.cavallium.dbengine.database.LLRange;
|
||||
import org.rocksdb.ColumnFamilyHandle;
|
||||
import org.rocksdb.ReadOptions;
|
||||
import org.rocksdb.RocksDB;
|
||||
|
||||
public class LLLocalKeyReactiveRocksIterator extends LLLocalReactiveRocksIterator<byte[]> {
|
||||
public class LLLocalKeyReactiveRocksIterator extends LLLocalReactiveRocksIterator<ByteBuf> {
|
||||
|
||||
public LLLocalKeyReactiveRocksIterator(RocksDB db,
|
||||
ByteBufAllocator alloc,
|
||||
ColumnFamilyHandle cfh,
|
||||
LLRange range,
|
||||
ReadOptions readOptions) {
|
||||
super(db, cfh, range, readOptions, false);
|
||||
super(db, alloc, cfh, range, readOptions, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getEntry(byte[] key, byte[] value) {
|
||||
public ByteBuf getEntry(ByteBuf key, ByteBuf value) {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
@ -59,6 +59,7 @@ public class LLLocalKeyValueDatabase implements LLKeyValueDatabase {
|
||||
|
||||
private final Scheduler dbScheduler;
|
||||
private final Path dbPath;
|
||||
private final boolean inMemory;
|
||||
private final String name;
|
||||
private RocksDB db;
|
||||
private final Map<Column, ColumnFamilyHandle> handles;
|
||||
@ -66,7 +67,7 @@ public class LLLocalKeyValueDatabase implements LLKeyValueDatabase {
|
||||
private final AtomicLong nextSnapshotNumbers = new AtomicLong(1);
|
||||
|
||||
public LLLocalKeyValueDatabase(String name, Path path, List<Column> columns, List<ColumnFamilyHandle> handles,
|
||||
boolean crashIfWalError, boolean lowMemory) throws IOException {
|
||||
boolean crashIfWalError, boolean lowMemory, boolean inMemory) throws IOException {
|
||||
Options options = openRocksDb(path, crashIfWalError, lowMemory);
|
||||
try {
|
||||
List<ColumnFamilyDescriptor> descriptors = new LinkedList<>();
|
||||
@ -83,6 +84,7 @@ public class LLLocalKeyValueDatabase implements LLKeyValueDatabase {
|
||||
String dbPathString = databasesDirPath.toString() + File.separatorChar + path.getFileName();
|
||||
Path dbPath = Paths.get(dbPathString);
|
||||
this.dbPath = dbPath;
|
||||
this.inMemory = inMemory;
|
||||
this.name = name;
|
||||
this.dbScheduler = Schedulers.newBoundedElastic(lowMemory ? Runtime.getRuntime().availableProcessors()
|
||||
: Math.max(8, Runtime.getRuntime().availableProcessors()),
|
||||
@ -92,12 +94,17 @@ public class LLLocalKeyValueDatabase implements LLKeyValueDatabase {
|
||||
true
|
||||
);
|
||||
|
||||
createIfNotExists(descriptors, options, dbPath, dbPathString);
|
||||
createIfNotExists(descriptors, options, inMemory, this.dbPath, dbPathString);
|
||||
// Create all column families that don't exist
|
||||
createAllColumns(descriptors, options, dbPathString);
|
||||
createAllColumns(descriptors, options, inMemory, dbPathString);
|
||||
|
||||
// a factory method that returns a RocksDB instance
|
||||
this.db = RocksDB.open(new DBOptions(options), dbPathString, descriptors, handles);
|
||||
this.db = RocksDB.open(new DBOptions(options),
|
||||
dbPathString,
|
||||
inMemory ? List.of(DEFAULT_COLUMN_FAMILY) : descriptors,
|
||||
handles
|
||||
);
|
||||
createInMemoryColumns(descriptors, inMemory, handles);
|
||||
this.handles = new HashMap<>();
|
||||
for (int i = 0; i < columns.size(); i++) {
|
||||
this.handles.put(columns.get(i), handles.get(i));
|
||||
@ -252,8 +259,10 @@ public class LLLocalKeyValueDatabase implements LLKeyValueDatabase {
|
||||
return options;
|
||||
}
|
||||
|
||||
private void createAllColumns(List<ColumnFamilyDescriptor> totalDescriptors, Options options,
|
||||
String dbPathString) throws RocksDBException {
|
||||
private void createAllColumns(List<ColumnFamilyDescriptor> totalDescriptors, Options options, boolean inMemory, String dbPathString) throws RocksDBException {
|
||||
if (inMemory) {
|
||||
return;
|
||||
}
|
||||
List<byte[]> columnFamiliesToCreate = new LinkedList<>();
|
||||
|
||||
for (ColumnFamilyDescriptor descriptor : totalDescriptors) {
|
||||
@ -293,8 +302,35 @@ public class LLLocalKeyValueDatabase implements LLKeyValueDatabase {
|
||||
flushAndCloseDb(db, handles);
|
||||
}
|
||||
|
||||
private void createIfNotExists(List<ColumnFamilyDescriptor> descriptors, Options options,
|
||||
Path dbPath, String dbPathString) throws RocksDBException {
|
||||
private void createInMemoryColumns(List<ColumnFamilyDescriptor> totalDescriptors,
|
||||
boolean inMemory,
|
||||
List<ColumnFamilyHandle> handles)
|
||||
throws RocksDBException {
|
||||
if (!inMemory) {
|
||||
return;
|
||||
}
|
||||
List<byte[]> columnFamiliesToCreate = new LinkedList<>();
|
||||
|
||||
for (ColumnFamilyDescriptor descriptor : totalDescriptors) {
|
||||
columnFamiliesToCreate.add(descriptor.getName());
|
||||
}
|
||||
|
||||
for (byte[] name : columnFamiliesToCreate) {
|
||||
if (!Arrays.equals(name, DEFAULT_COLUMN_FAMILY.getName())) {
|
||||
var descriptor = new ColumnFamilyDescriptor(name);
|
||||
handles.add(db.createColumnFamily(descriptor));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void createIfNotExists(List<ColumnFamilyDescriptor> descriptors,
|
||||
Options options,
|
||||
boolean inMemory,
|
||||
Path dbPath,
|
||||
String dbPathString) throws RocksDBException {
|
||||
if (inMemory) {
|
||||
return;
|
||||
}
|
||||
if (Files.notExists(dbPath)) {
|
||||
// Check if handles are all different
|
||||
var descriptorsSet = new HashSet<>(descriptors);
|
||||
@ -318,7 +354,9 @@ public class LLLocalKeyValueDatabase implements LLKeyValueDatabase {
|
||||
handles.add(db.createColumnFamily(columnFamilyDescriptor));
|
||||
}
|
||||
|
||||
flushAndCloseDb(db, handles);
|
||||
if (!inMemory) {
|
||||
flushAndCloseDb(db, handles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -53,6 +53,9 @@ import org.apache.lucene.search.similarities.Similarity;
|
||||
import org.apache.lucene.search.similarities.TFIDFSimilarity;
|
||||
import org.apache.lucene.store.Directory;
|
||||
import org.apache.lucene.store.FSDirectory;
|
||||
import org.apache.lucene.store.MMapDirectory;
|
||||
import org.apache.lucene.store.RAMDirectory;
|
||||
import org.apache.solr.core.RAMDirectoryFactory;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.warp.commonutils.log.Logger;
|
||||
import org.warp.commonutils.log.LoggerFactory;
|
||||
@ -113,13 +116,12 @@ public class LLLocalLuceneIndex implements LLLuceneIndex {
|
||||
TextFieldsSimilarity similarity,
|
||||
Duration queryRefreshDebounceTime,
|
||||
Duration commitDebounceTime,
|
||||
boolean lowMemory,
|
||||
@Nullable LLSearchCollectionStatisticsGetter distributedCollectionStatisticsGetter) throws IOException {
|
||||
boolean lowMemory, boolean inMemory, @Nullable LLSearchCollectionStatisticsGetter distributedCollectionStatisticsGetter) throws IOException {
|
||||
if (name.length() == 0) {
|
||||
throw new IOException("Empty lucene database name");
|
||||
}
|
||||
Path directoryPath = luceneBasePath.resolve(name + ".lucene.db");
|
||||
this.directory = FSDirectory.open(directoryPath);
|
||||
this.directory = inMemory ? new RAMDirectory() : FSDirectory.open(directoryPath);
|
||||
this.luceneIndexName = name;
|
||||
this.snapshotter = new SnapshotDeletionPolicy(new KeepOnlyLastCommitDeletionPolicy());
|
||||
this.lowMemory = lowMemory;
|
||||
|
@ -58,7 +58,7 @@ public class LLLocalMultiLuceneIndex implements LLLuceneIndex {
|
||||
TextFieldsSimilarity textFieldsSimilarity,
|
||||
Duration queryRefreshDebounceTime,
|
||||
Duration commitDebounceTime,
|
||||
boolean lowMemory) throws IOException {
|
||||
boolean lowMemory, boolean inMemory) throws IOException {
|
||||
|
||||
if (instancesCount <= 1 || instancesCount > 100) {
|
||||
throw new IOException("Unsupported instances count: " + instancesCount);
|
||||
@ -79,8 +79,7 @@ public class LLLocalMultiLuceneIndex implements LLLuceneIndex {
|
||||
textFieldsSimilarity,
|
||||
queryRefreshDebounceTime,
|
||||
commitDebounceTime,
|
||||
lowMemory,
|
||||
(indexSearcher, field, distributedPre, actionId) -> distributedCustomCollectionStatistics(finalI,
|
||||
lowMemory, inMemory, (indexSearcher, field, distributedPre, actionId) -> distributedCustomCollectionStatistics(finalI,
|
||||
indexSearcher,
|
||||
field,
|
||||
distributedPre,
|
||||
|
@ -2,29 +2,34 @@ package it.cavallium.dbengine.database.disk;
|
||||
|
||||
import static it.cavallium.dbengine.database.disk.LLLocalDictionary.getRocksIterator;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import it.cavallium.dbengine.database.LLRange;
|
||||
import it.cavallium.dbengine.database.LLUtils;
|
||||
import org.rocksdb.ColumnFamilyHandle;
|
||||
import org.rocksdb.ReadOptions;
|
||||
import org.rocksdb.RocksDB;
|
||||
import org.rocksdb.RocksMutableObject;
|
||||
import reactor.core.publisher.Flux;
|
||||
import static io.netty.buffer.Unpooled.*;
|
||||
|
||||
public abstract class LLLocalReactiveRocksIterator<T> {
|
||||
|
||||
private static final byte[] EMPTY = new byte[0];
|
||||
|
||||
private final RocksDB db;
|
||||
private final ByteBufAllocator alloc;
|
||||
private final ColumnFamilyHandle cfh;
|
||||
private final LLRange range;
|
||||
private final ReadOptions readOptions;
|
||||
private final boolean readValues;
|
||||
|
||||
public LLLocalReactiveRocksIterator(RocksDB db,
|
||||
ByteBufAllocator alloc,
|
||||
ColumnFamilyHandle cfh,
|
||||
LLRange range,
|
||||
ReadOptions readOptions,
|
||||
boolean readValues) {
|
||||
this.db = db;
|
||||
this.alloc = alloc;
|
||||
this.cfh = cfh;
|
||||
this.range = range;
|
||||
this.readOptions = readOptions;
|
||||
@ -39,14 +44,22 @@ public abstract class LLLocalReactiveRocksIterator<T> {
|
||||
readOptions.setReadaheadSize(2 * 1024 * 1024);
|
||||
readOptions.setFillCache(false);
|
||||
}
|
||||
return getRocksIterator(readOptions, range, db, cfh);
|
||||
return getRocksIterator(readOptions, range.retain(), db, cfh);
|
||||
}, (tuple, sink) -> {
|
||||
var rocksIterator = tuple.getT1();
|
||||
if (rocksIterator.isValid()) {
|
||||
byte[] key = rocksIterator.key();
|
||||
byte[] value = readValues ? rocksIterator.value() : EMPTY;
|
||||
rocksIterator.next();
|
||||
sink.next(getEntry(key, value));
|
||||
ByteBuf key = LLUtils.readDirectNioBuffer(alloc, rocksIterator::key);
|
||||
try {
|
||||
ByteBuf value = readValues ? LLUtils.readDirectNioBuffer(alloc, rocksIterator::value) : EMPTY_BUFFER;
|
||||
try {
|
||||
rocksIterator.next();
|
||||
sink.next(getEntry(key.retain(), value.retain()));
|
||||
} finally {
|
||||
value.release();
|
||||
}
|
||||
} finally {
|
||||
key.release();
|
||||
}
|
||||
} else {
|
||||
sink.complete();
|
||||
}
|
||||
@ -54,10 +67,10 @@ public abstract class LLLocalReactiveRocksIterator<T> {
|
||||
}, tuple -> {
|
||||
var rocksIterator = tuple.getT1();
|
||||
rocksIterator.close();
|
||||
tuple.getT2().ifPresent(RocksMutableObject::close);
|
||||
tuple.getT3().ifPresent(RocksMutableObject::close);
|
||||
tuple.getT2().release();
|
||||
tuple.getT3().release();
|
||||
});
|
||||
}
|
||||
|
||||
public abstract T getEntry(byte[] key, byte[] value);
|
||||
public abstract T getEntry(ByteBuf key, ByteBuf value);
|
||||
}
|
||||
|
@ -3,13 +3,14 @@ package it.cavallium.dbengine.database.serialization;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufInputStream;
|
||||
import io.netty.buffer.ByteBufOutputStream;
|
||||
import io.netty.buffer.PooledByteBufAllocator;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import java.io.IOError;
|
||||
import java.io.IOException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.warp.commonutils.error.IndexOutOfBoundsException;
|
||||
|
||||
public class CodecSerializer<A> implements Serializer<A, byte[]> {
|
||||
public class CodecSerializer<A> implements Serializer<A, ByteBuf> {
|
||||
|
||||
private final Codecs<A> deserializationCodecs;
|
||||
private final Codec<A> serializationCodec;
|
||||
@ -34,9 +35,8 @@ public class CodecSerializer<A> implements Serializer<A, byte[]> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull A deserialize(byte @NotNull [] serialized) {
|
||||
ByteBuf buf = Unpooled.wrappedBuffer(serialized);
|
||||
try (var is = new ByteBufInputStream(buf)) {
|
||||
public @NotNull A deserialize(@NotNull ByteBuf serialized) {
|
||||
try (var is = new ByteBufInputStream(serialized)) {
|
||||
int codecId;
|
||||
if (microCodecs) {
|
||||
codecId = is.readUnsignedByte();
|
||||
@ -48,12 +48,14 @@ public class CodecSerializer<A> implements Serializer<A, byte[]> {
|
||||
} catch (IOException ex) {
|
||||
// This shouldn't happen
|
||||
throw new IOError(ex);
|
||||
} finally {
|
||||
serialized.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte @NotNull [] serialize(@NotNull A deserialized) {
|
||||
ByteBuf buf = Unpooled.buffer(256);
|
||||
public @NotNull ByteBuf serialize(@NotNull A deserialized) {
|
||||
ByteBuf buf = PooledByteBufAllocator.DEFAULT.directBuffer();
|
||||
try (var os = new ByteBufOutputStream(buf)) {
|
||||
if (microCodecs) {
|
||||
os.writeByte(serializationCodecId);
|
||||
@ -61,14 +63,11 @@ public class CodecSerializer<A> implements Serializer<A, byte[]> {
|
||||
os.writeInt(serializationCodecId);
|
||||
}
|
||||
serializationCodec.serialize(os, deserialized);
|
||||
os.flush();
|
||||
var bytes = new byte[buf.readableBytes()];
|
||||
buf.readBytes(bytes);
|
||||
return bytes;
|
||||
} catch (IOException ex) {
|
||||
// This shouldn't happen
|
||||
throw new IOError(ex);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
|
@ -1,5 +1,9 @@
|
||||
package it.cavallium.dbengine.database.serialization;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.PooledByteBufAllocator;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public interface Serializer<A, B> {
|
||||
@ -8,19 +12,50 @@ public interface Serializer<A, B> {
|
||||
|
||||
@NotNull B serialize(@NotNull A deserialized);
|
||||
|
||||
Serializer<byte[], byte[]> NOOP_SERIALIZER = new Serializer<>() {
|
||||
Serializer<ByteBuf, ByteBuf> NOOP_SERIALIZER = new Serializer<>() {
|
||||
@Override
|
||||
public byte @NotNull [] deserialize(byte @NotNull [] serialized) {
|
||||
return serialized;
|
||||
public @NotNull ByteBuf deserialize(@NotNull ByteBuf serialized) {
|
||||
try {
|
||||
return serialized.retainedSlice();
|
||||
} finally {
|
||||
serialized.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte @NotNull [] serialize(byte @NotNull [] deserialized) {
|
||||
return deserialized;
|
||||
public @NotNull ByteBuf serialize(@NotNull ByteBuf deserialized) {
|
||||
try {
|
||||
return deserialized.retainedSlice();
|
||||
} finally {
|
||||
deserialized.release();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static Serializer<byte[], byte[]> noop() {
|
||||
Serializer<String, ByteBuf> UTF8_SERIALIZER = new Serializer<>() {
|
||||
@Override
|
||||
public @NotNull String deserialize(@NotNull ByteBuf serialized) {
|
||||
try {
|
||||
return serialized.toString(StandardCharsets.UTF_8);
|
||||
} finally {
|
||||
serialized.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ByteBuf serialize(@NotNull String deserialized) {
|
||||
// UTF-8 uses max. 3 bytes per char, so calculate the worst case.
|
||||
ByteBuf buf = PooledByteBufAllocator.DEFAULT.directBuffer(ByteBufUtil.utf8MaxBytes(deserialized));
|
||||
ByteBufUtil.writeUtf8(buf, deserialized);
|
||||
return buf;
|
||||
}
|
||||
};
|
||||
|
||||
static Serializer<ByteBuf, ByteBuf> noop() {
|
||||
return NOOP_SERIALIZER;
|
||||
}
|
||||
|
||||
static Serializer<String, ByteBuf> utf8() {
|
||||
return UTF8_SERIALIZER;
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,12 @@ package it.cavallium.dbengine.database.serialization;
|
||||
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.common.primitives.Longs;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.PooledByteBufAllocator;
|
||||
import java.io.NotSerializableException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import org.apache.commons.lang3.SerializationException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@ -9,18 +15,31 @@ public interface SerializerFixedBinaryLength<A, B> extends Serializer<A, B> {
|
||||
|
||||
int getSerializedBinaryLength();
|
||||
|
||||
static SerializerFixedBinaryLength<byte[], byte[]> noop(int length) {
|
||||
static SerializerFixedBinaryLength<ByteBuf, ByteBuf> noop(int length) {
|
||||
return new SerializerFixedBinaryLength<>() {
|
||||
@Override
|
||||
public byte @NotNull [] deserialize(byte @NotNull [] serialized) {
|
||||
assert serialized.length == getSerializedBinaryLength();
|
||||
return serialized;
|
||||
public @NotNull ByteBuf deserialize(@NotNull ByteBuf serialized) {
|
||||
try {
|
||||
if (serialized.readableBytes() != getSerializedBinaryLength()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Fixed serializer with " + getSerializedBinaryLength() + " bytes has tried to deserialize an element with "
|
||||
+ serialized.readableBytes() + " bytes instead");
|
||||
}
|
||||
return serialized.retain();
|
||||
} finally {
|
||||
serialized.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte @NotNull [] serialize(byte @NotNull [] deserialized) {
|
||||
assert deserialized.length == getSerializedBinaryLength();
|
||||
return deserialized;
|
||||
public @NotNull ByteBuf serialize(@NotNull ByteBuf deserialized) {
|
||||
ByteBuf buf = deserialized.retain();
|
||||
if (buf.readableBytes() != getSerializedBinaryLength()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Fixed serializer with " + getSerializedBinaryLength() + " bytes has tried to serialize an element with "
|
||||
+ buf.readableBytes() + " bytes instead");
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -30,17 +49,65 @@ public interface SerializerFixedBinaryLength<A, B> extends Serializer<A, B> {
|
||||
};
|
||||
}
|
||||
|
||||
static SerializerFixedBinaryLength<Integer, byte[]> intSerializer() {
|
||||
static SerializerFixedBinaryLength<String, ByteBuf> utf8(int length) {
|
||||
return new SerializerFixedBinaryLength<>() {
|
||||
@Override
|
||||
public @NotNull Integer deserialize(byte @NotNull [] serialized) {
|
||||
assert serialized.length == getSerializedBinaryLength();
|
||||
return Ints.fromByteArray(serialized);
|
||||
public @NotNull String deserialize(@NotNull ByteBuf serialized) {
|
||||
try {
|
||||
if (serialized.readableBytes() != getSerializedBinaryLength()) {
|
||||
throw new SerializationException(
|
||||
"Fixed serializer with " + getSerializedBinaryLength() + " bytes has tried to deserialize an element with "
|
||||
+ serialized.readableBytes() + " bytes instead");
|
||||
}
|
||||
return serialized.toString(StandardCharsets.UTF_8);
|
||||
} finally {
|
||||
serialized.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte @NotNull [] serialize(@NotNull Integer deserialized) {
|
||||
return Ints.toByteArray(deserialized);
|
||||
public @NotNull ByteBuf serialize(@NotNull String deserialized) {
|
||||
// UTF-8 uses max. 3 bytes per char, so calculate the worst case.
|
||||
ByteBuf buf = PooledByteBufAllocator.DEFAULT.directBuffer(ByteBufUtil.utf8MaxBytes(deserialized));
|
||||
try {
|
||||
ByteBufUtil.writeUtf8(buf, deserialized);
|
||||
if (buf.readableBytes() != getSerializedBinaryLength()) {
|
||||
throw new SerializationException("Fixed serializer with " + getSerializedBinaryLength() + " bytes has tried to serialize an element with "
|
||||
+ buf.readableBytes() + " bytes instead");
|
||||
}
|
||||
return buf.retain();
|
||||
} finally {
|
||||
buf.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSerializedBinaryLength() {
|
||||
return length;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static SerializerFixedBinaryLength<Integer, ByteBuf> intSerializer() {
|
||||
return new SerializerFixedBinaryLength<>() {
|
||||
@Override
|
||||
public @NotNull Integer deserialize(@NotNull ByteBuf serialized) {
|
||||
try {
|
||||
if (serialized.readableBytes() != getSerializedBinaryLength()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Fixed serializer with " + getSerializedBinaryLength() + " bytes has tried to deserialize an element with "
|
||||
+ serialized.readableBytes() + " bytes instead");
|
||||
}
|
||||
return serialized.readInt();
|
||||
} finally {
|
||||
serialized.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ByteBuf serialize(@NotNull Integer deserialized) {
|
||||
ByteBuf buf = PooledByteBufAllocator.DEFAULT.directBuffer(Integer.BYTES, Integer.BYTES);
|
||||
return buf.writeInt(deserialized);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -50,17 +117,26 @@ public interface SerializerFixedBinaryLength<A, B> extends Serializer<A, B> {
|
||||
};
|
||||
}
|
||||
|
||||
static SerializerFixedBinaryLength<Long, byte[]> longSerializer() {
|
||||
static SerializerFixedBinaryLength<Long, ByteBuf> longSerializer() {
|
||||
return new SerializerFixedBinaryLength<>() {
|
||||
@Override
|
||||
public @NotNull Long deserialize(byte @NotNull [] serialized) {
|
||||
assert serialized.length == getSerializedBinaryLength();
|
||||
return Longs.fromByteArray(serialized);
|
||||
public @NotNull Long deserialize(@NotNull ByteBuf serialized) {
|
||||
try {
|
||||
if (serialized.readableBytes() != getSerializedBinaryLength()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Fixed serializer with " + getSerializedBinaryLength() + " bytes has tried to deserialize an element with "
|
||||
+ serialized.readableBytes() + " bytes instead");
|
||||
}
|
||||
return serialized.readLong();
|
||||
} finally {
|
||||
serialized.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte @NotNull [] serialize(@NotNull Long deserialized) {
|
||||
return Longs.toByteArray(deserialized);
|
||||
public @NotNull ByteBuf serialize(@NotNull Long deserialized) {
|
||||
ByteBuf buf = PooledByteBufAllocator.DEFAULT.directBuffer(Integer.BYTES, Integer.BYTES);
|
||||
return buf.writeLong(deserialized);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,6 +1,11 @@
|
||||
package it.cavallium.dbengine.database.disk;
|
||||
package org.rocksdb;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import it.cavallium.dbengine.database.LLUtils;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.rocksdb.AbstractWriteBatch;
|
||||
import org.rocksdb.ColumnFamilyHandle;
|
||||
import org.rocksdb.RocksDB;
|
||||
import org.rocksdb.RocksDBException;
|
||||
@ -10,13 +15,13 @@ import org.rocksdb.WriteOptions;
|
||||
import org.warp.commonutils.concurrency.atomicity.NotAtomic;
|
||||
|
||||
@NotAtomic
|
||||
public class CappedWriteBatch implements WriteBatchInterface, AutoCloseable {
|
||||
public class CappedWriteBatch extends WriteBatch {
|
||||
|
||||
private final RocksDB db;
|
||||
private final int cap;
|
||||
private final WriteOptions writeOptions;
|
||||
|
||||
private final WriteBatch writeBatch;
|
||||
|
||||
private final List<ByteBuf> buffersToRelease;
|
||||
|
||||
/**
|
||||
* @param cap The limit of operations
|
||||
@ -26,158 +31,224 @@ public class CappedWriteBatch implements WriteBatchInterface, AutoCloseable {
|
||||
int reservedWriteBatchSize,
|
||||
long maxWriteBatchSize,
|
||||
WriteOptions writeOptions) {
|
||||
super(reservedWriteBatchSize);
|
||||
this.db = db;
|
||||
this.cap = cap;
|
||||
this.writeOptions = writeOptions;
|
||||
this.writeBatch = new WriteBatch(reservedWriteBatchSize);
|
||||
this.writeBatch.setMaxBytes(maxWriteBatchSize);
|
||||
this.setMaxBytes(maxWriteBatchSize);
|
||||
this.buffersToRelease = new ArrayList<>();
|
||||
}
|
||||
|
||||
private synchronized void flushIfNeeded(boolean force) throws RocksDBException {
|
||||
if (this.writeBatch.count() >= (force ? 1 : cap)) {
|
||||
db.write(writeOptions, this.writeBatch);
|
||||
this.writeBatch.clear();
|
||||
if (this.count() >= (force ? 1 : cap)) {
|
||||
db.write(writeOptions, this);
|
||||
this.clear();
|
||||
releaseAllBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void releaseAllBuffers() {
|
||||
for (ByteBuf byteBuffer : buffersToRelease) {
|
||||
byteBuffer.release();
|
||||
}
|
||||
buffersToRelease.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int count() {
|
||||
return writeBatch.count();
|
||||
return super.count();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void put(byte[] key, byte[] value) throws RocksDBException {
|
||||
writeBatch.put(key, value);
|
||||
super.put(key, value);
|
||||
flushIfNeeded(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void put(ColumnFamilyHandle columnFamilyHandle, byte[] key, byte[] value) throws RocksDBException {
|
||||
writeBatch.put(columnFamilyHandle, key, value);
|
||||
super.put(columnFamilyHandle, key, value);
|
||||
flushIfNeeded(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void put(ByteBuffer key, ByteBuffer value) throws RocksDBException {
|
||||
writeBatch.put(key, value);
|
||||
super.put(key, value);
|
||||
flushIfNeeded(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void put(ColumnFamilyHandle columnFamilyHandle, ByteBuffer key, ByteBuffer value) throws RocksDBException {
|
||||
writeBatch.put(columnFamilyHandle, key, value);
|
||||
super.put(columnFamilyHandle, key, value);
|
||||
flushIfNeeded(false);
|
||||
}
|
||||
|
||||
public synchronized void put(ColumnFamilyHandle columnFamilyHandle, ByteBuf key, ByteBuf value) throws RocksDBException {
|
||||
buffersToRelease.add(key);
|
||||
buffersToRelease.add(value);
|
||||
ByteBuf keyDirectBuf = key.retain();
|
||||
ByteBuffer keyNioBuffer = LLUtils.toDirectFast(keyDirectBuf.retain());
|
||||
if (keyNioBuffer == null) {
|
||||
keyDirectBuf.release();
|
||||
keyDirectBuf = LLUtils.toDirectCopy(key.retain());
|
||||
keyNioBuffer = keyDirectBuf.nioBuffer();
|
||||
}
|
||||
try {
|
||||
assert keyNioBuffer.isDirect();
|
||||
|
||||
ByteBuf valueDirectBuf = value.retain();
|
||||
ByteBuffer valueNioBuffer = LLUtils.toDirectFast(valueDirectBuf.retain());
|
||||
if (valueNioBuffer == null) {
|
||||
valueDirectBuf.release();
|
||||
valueDirectBuf = LLUtils.toDirectCopy(value.retain());
|
||||
valueNioBuffer = valueDirectBuf.nioBuffer();
|
||||
}
|
||||
try {
|
||||
assert valueNioBuffer.isDirect();
|
||||
super.put(columnFamilyHandle, keyNioBuffer, valueNioBuffer);
|
||||
} finally {
|
||||
buffersToRelease.add(valueDirectBuf);
|
||||
}
|
||||
} finally {
|
||||
buffersToRelease.add(keyDirectBuf);
|
||||
}
|
||||
flushIfNeeded(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void merge(byte[] key, byte[] value) throws RocksDBException {
|
||||
writeBatch.merge(key, value);
|
||||
super.merge(key, value);
|
||||
flushIfNeeded(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void merge(ColumnFamilyHandle columnFamilyHandle, byte[] key, byte[] value) throws RocksDBException {
|
||||
writeBatch.merge(columnFamilyHandle, key, value);
|
||||
super.merge(columnFamilyHandle, key, value);
|
||||
flushIfNeeded(false);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public synchronized void remove(byte[] key) throws RocksDBException {
|
||||
writeBatch.remove(key);
|
||||
super.remove(key);
|
||||
flushIfNeeded(false);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
public synchronized void remove(ColumnFamilyHandle columnFamilyHandle, byte[] key) throws RocksDBException {
|
||||
writeBatch.remove(columnFamilyHandle, key);
|
||||
super.remove(columnFamilyHandle, key);
|
||||
flushIfNeeded(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void delete(byte[] key) throws RocksDBException {
|
||||
writeBatch.delete(key);
|
||||
super.delete(key);
|
||||
flushIfNeeded(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void delete(ColumnFamilyHandle columnFamilyHandle, byte[] key) throws RocksDBException {
|
||||
writeBatch.delete(columnFamilyHandle, key);
|
||||
super.delete(columnFamilyHandle, key);
|
||||
flushIfNeeded(false);
|
||||
}
|
||||
|
||||
public synchronized void delete(ColumnFamilyHandle columnFamilyHandle, ByteBuf key) throws RocksDBException {
|
||||
buffersToRelease.add(key);
|
||||
ByteBuf keyDirectBuf = key.retain();
|
||||
ByteBuffer keyNioBuffer = LLUtils.toDirectFast(keyDirectBuf.retain());
|
||||
if (keyNioBuffer == null) {
|
||||
keyDirectBuf.release();
|
||||
keyDirectBuf = LLUtils.toDirectCopy(key.retain());
|
||||
keyNioBuffer = keyDirectBuf.nioBuffer();
|
||||
}
|
||||
try {
|
||||
assert keyNioBuffer.isDirect();
|
||||
removeDirect(nativeHandle_,
|
||||
keyNioBuffer,
|
||||
keyNioBuffer.position(),
|
||||
keyNioBuffer.remaining(),
|
||||
columnFamilyHandle.nativeHandle_
|
||||
);
|
||||
keyNioBuffer.position(keyNioBuffer.limit());
|
||||
} finally {
|
||||
buffersToRelease.add(keyDirectBuf);
|
||||
}
|
||||
flushIfNeeded(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void singleDelete(byte[] key) throws RocksDBException {
|
||||
writeBatch.singleDelete(key);
|
||||
super.singleDelete(key);
|
||||
flushIfNeeded(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void singleDelete(ColumnFamilyHandle columnFamilyHandle, byte[] key) throws RocksDBException {
|
||||
writeBatch.singleDelete(columnFamilyHandle, key);
|
||||
super.singleDelete(columnFamilyHandle, key);
|
||||
flushIfNeeded(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void remove(ByteBuffer key) throws RocksDBException {
|
||||
writeBatch.remove(key);
|
||||
super.remove(key);
|
||||
flushIfNeeded(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void remove(ColumnFamilyHandle columnFamilyHandle, ByteBuffer key) throws RocksDBException {
|
||||
writeBatch.remove(columnFamilyHandle, key);
|
||||
super.remove(columnFamilyHandle, key);
|
||||
flushIfNeeded(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void deleteRange(byte[] beginKey, byte[] endKey) throws RocksDBException {
|
||||
writeBatch.deleteRange(beginKey, endKey);
|
||||
super.deleteRange(beginKey, endKey);
|
||||
flushIfNeeded(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void deleteRange(ColumnFamilyHandle columnFamilyHandle, byte[] beginKey, byte[] endKey)
|
||||
throws RocksDBException {
|
||||
writeBatch.deleteRange(columnFamilyHandle, beginKey, endKey);
|
||||
super.deleteRange(columnFamilyHandle, beginKey, endKey);
|
||||
flushIfNeeded(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void putLogData(byte[] blob) throws RocksDBException {
|
||||
writeBatch.putLogData(blob);
|
||||
super.putLogData(blob);
|
||||
flushIfNeeded(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void clear() {
|
||||
writeBatch.clear();
|
||||
super.clear();
|
||||
releaseAllBuffers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setSavePoint() {
|
||||
writeBatch.setSavePoint();
|
||||
super.setSavePoint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void rollbackToSavePoint() throws RocksDBException {
|
||||
writeBatch.rollbackToSavePoint();
|
||||
super.rollbackToSavePoint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void popSavePoint() throws RocksDBException {
|
||||
writeBatch.popSavePoint();
|
||||
super.popSavePoint();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setMaxBytes(long maxBytes) {
|
||||
writeBatch.setMaxBytes(maxBytes);
|
||||
super.setMaxBytes(maxBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized WriteBatch getWriteBatch() {
|
||||
return writeBatch;
|
||||
return this;
|
||||
}
|
||||
|
||||
public synchronized void writeToDbAndClose() throws RocksDBException {
|
||||
@ -186,6 +257,7 @@ public class CappedWriteBatch implements WriteBatchInterface, AutoCloseable {
|
||||
|
||||
@Override
|
||||
public synchronized void close() {
|
||||
writeBatch.close();
|
||||
super.close();
|
||||
releaseAllBuffers();
|
||||
}
|
||||
}
|
106
src/test/java/it/cavallium/dbengine/client/DbTestUtils.java
Normal file
106
src/test/java/it/cavallium/dbengine/client/DbTestUtils.java
Normal file
@ -0,0 +1,106 @@
|
||||
package it.cavallium.dbengine.client;
|
||||
|
||||
import it.cavallium.dbengine.database.Column;
|
||||
import it.cavallium.dbengine.database.LLDictionary;
|
||||
import it.cavallium.dbengine.database.LLKeyValueDatabase;
|
||||
import it.cavallium.dbengine.database.UpdateMode;
|
||||
import it.cavallium.dbengine.database.collections.DatabaseMapDictionary;
|
||||
import it.cavallium.dbengine.database.collections.DatabaseMapDictionaryDeep;
|
||||
import it.cavallium.dbengine.database.collections.DatabaseMapDictionaryHashed;
|
||||
import it.cavallium.dbengine.database.collections.SubStageGetterHashMap;
|
||||
import it.cavallium.dbengine.database.collections.SubStageGetterMap;
|
||||
import it.cavallium.dbengine.database.disk.LLLocalDatabaseConnection;
|
||||
import it.cavallium.dbengine.database.serialization.Serializer;
|
||||
import it.cavallium.dbengine.database.serialization.SerializerFixedBinaryLength;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
import org.reactivestreams.Publisher;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.core.scheduler.Schedulers;
|
||||
|
||||
public class DbTestUtils {
|
||||
|
||||
public static final AtomicInteger dbId = new AtomicInteger(0);
|
||||
|
||||
public static <U> Flux<U> tempDb(Function<LLKeyValueDatabase, Publisher<U>> action) {
|
||||
var wrkspcPath = Path.of("/tmp/.cache/tempdb-" + dbId.incrementAndGet() + "/");
|
||||
return Flux.usingWhen(Mono
|
||||
.<LLKeyValueDatabase>fromCallable(() -> {
|
||||
if (Files.exists(wrkspcPath)) {
|
||||
Files.walk(wrkspcPath).sorted(Comparator.reverseOrder()).forEach(file -> {
|
||||
try {
|
||||
Files.delete(file);
|
||||
} catch (IOException ex) {
|
||||
throw new CompletionException(ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
Files.createDirectories(wrkspcPath);
|
||||
return null;
|
||||
})
|
||||
.subscribeOn(Schedulers.boundedElastic())
|
||||
.then(new LLLocalDatabaseConnection(wrkspcPath, true).connect())
|
||||
.flatMap(conn -> conn.getDatabase("testdb",
|
||||
List.of(Column.dictionary("testmap"), Column.special("ints"), Column.special("longs")),
|
||||
false, true
|
||||
)),
|
||||
action,
|
||||
db -> db.close().then(Mono.fromCallable(() -> {
|
||||
if (Files.exists(wrkspcPath)) {
|
||||
Files.walk(wrkspcPath).sorted(Comparator.reverseOrder()).forEach(file -> {
|
||||
try {
|
||||
Files.delete(file);
|
||||
} catch (IOException ex) {
|
||||
throw new CompletionException(ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}).subscribeOn(Schedulers.boundedElastic()))
|
||||
);
|
||||
}
|
||||
public static Mono<? extends LLDictionary> tempDictionary(LLKeyValueDatabase database, UpdateMode updateMode) {
|
||||
return tempDictionary(database, "testmap", updateMode);
|
||||
}
|
||||
|
||||
public static Mono<? extends LLDictionary> tempDictionary(LLKeyValueDatabase database,
|
||||
String name,
|
||||
UpdateMode updateMode) {
|
||||
return database.getDictionary(name, updateMode);
|
||||
}
|
||||
|
||||
public static DatabaseMapDictionary<String, String> tempDatabaseMapDictionaryMap(
|
||||
LLDictionary dictionary,
|
||||
int keyBytes) {
|
||||
return DatabaseMapDictionary.simple(dictionary, SerializerFixedBinaryLength.utf8(keyBytes), Serializer.utf8());
|
||||
}
|
||||
|
||||
public static <T, U> DatabaseMapDictionaryDeep<String, Map<String, String>, DatabaseMapDictionary<String, String>> tempDatabaseMapDictionaryDeepMap(
|
||||
LLDictionary dictionary,
|
||||
int key1Bytes,
|
||||
int key2Bytes) {
|
||||
return DatabaseMapDictionaryDeep.deepTail(dictionary,
|
||||
SerializerFixedBinaryLength.utf8(key1Bytes),
|
||||
key2Bytes,
|
||||
new SubStageGetterMap<>(SerializerFixedBinaryLength.utf8(key2Bytes), Serializer.UTF8_SERIALIZER)
|
||||
);
|
||||
}
|
||||
|
||||
public static <T, U> DatabaseMapDictionaryHashed<String, String, Integer> tempDatabaseMapDictionaryHashMap(
|
||||
LLDictionary dictionary) {
|
||||
return DatabaseMapDictionaryHashed.simple(dictionary,
|
||||
Serializer.utf8(),
|
||||
Serializer.utf8(),
|
||||
String::hashCode,
|
||||
SerializerFixedBinaryLength.intSerializer()
|
||||
);
|
||||
}
|
||||
}
|
@ -2,15 +2,19 @@ package it.cavallium.dbengine.client;
|
||||
|
||||
import static it.cavallium.dbengine.client.CompositeDatabasePartLocation.CompositeDatabasePartType.KV_DATABASE;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import it.cavallium.dbengine.database.Column;
|
||||
import it.cavallium.dbengine.database.LLKeyValueDatabase;
|
||||
import it.cavallium.dbengine.database.UpdateMode;
|
||||
import it.cavallium.dbengine.database.collections.DatabaseMapDictionary;
|
||||
import it.cavallium.dbengine.database.collections.DatabaseMapDictionaryDeep;
|
||||
import it.cavallium.dbengine.database.collections.SubStageGetterMap;
|
||||
import it.cavallium.dbengine.database.disk.LLLocalDatabaseConnection;
|
||||
import it.cavallium.dbengine.database.serialization.Serializer;
|
||||
import it.cavallium.dbengine.database.serialization.SerializerFixedBinaryLength;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
@ -20,6 +24,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@ -29,7 +34,61 @@ import reactor.core.scheduler.Schedulers;
|
||||
import reactor.test.StepVerifier;
|
||||
import reactor.util.function.Tuples;
|
||||
|
||||
public class Database {
|
||||
public class OldDatabaseTests {
|
||||
|
||||
@Test
|
||||
public void testDatabaseAddKeysAndCheckSize() {
|
||||
LinkedHashSet<String> originalKeys = new LinkedHashSet<>(List.of("K1a", "K1b", "K1c"));
|
||||
|
||||
StepVerifier
|
||||
.create(
|
||||
tempDb()
|
||||
.flatMap(db -> db
|
||||
.getDictionary("testmap", UpdateMode.DISALLOW)
|
||||
.map(dictionary -> DatabaseMapDictionary.simple(dictionary,
|
||||
new FixedStringSerializer(3),
|
||||
Serializer.noop()
|
||||
))
|
||||
.flatMap(collection -> Flux
|
||||
.fromIterable(originalKeys)
|
||||
.flatMap(k1 -> collection.putValue(k1, DUMMY_VALUE))
|
||||
.then(collection.leavesCount(null, false))
|
||||
)
|
||||
)
|
||||
)
|
||||
.expectNext((long) originalKeys.size())
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeepDatabaseAddKeysAndCheckSize() {
|
||||
LinkedHashSet<String> originalSuperKeys = new LinkedHashSet<>(List.of("K1a", "K1b", "K1c"));
|
||||
LinkedHashSet<String> originalSubKeys = new LinkedHashSet<>(List.of("K2aa", "K2bb", "K2cc"));
|
||||
|
||||
StepVerifier
|
||||
.create(
|
||||
tempDb()
|
||||
.flatMap(db -> db
|
||||
.getDictionary("testmap", UpdateMode.DISALLOW)
|
||||
.map(dictionary -> DatabaseMapDictionaryDeep.deepTail(dictionary,
|
||||
new FixedStringSerializer(3),
|
||||
4,
|
||||
new SubStageGetterMap<>(new FixedStringSerializer(4), Serializer.noop())
|
||||
))
|
||||
.flatMap(collection -> Flux
|
||||
.fromIterable(originalSuperKeys)
|
||||
.flatMap(k1 -> collection.at(null, k1))
|
||||
.flatMap(k1at -> Flux
|
||||
.fromIterable(originalSubKeys)
|
||||
.flatMap(k2 -> k1at.putValue(k2, DUMMY_VALUE))
|
||||
)
|
||||
.then(collection.leavesCount(null, false))
|
||||
)
|
||||
)
|
||||
)
|
||||
.expectNext((long) originalSuperKeys.size() * originalSubKeys.size())
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeepDatabaseAddKeysAndConvertToLongerOnes() {
|
||||
@ -53,7 +112,7 @@ public class Database {
|
||||
}
|
||||
|
||||
public static <U> Mono<? extends LLKeyValueDatabase> tempDb() {
|
||||
var wrkspcPath = Path.of("/tmp/.cache/tempdb/");
|
||||
var wrkspcPath = Path.of("/tmp/.cache/tempdb-" + DbTestUtils.dbId.incrementAndGet() + "/");
|
||||
return Mono
|
||||
.fromCallable(() -> {
|
||||
if (Files.exists(wrkspcPath)) {
|
||||
@ -72,10 +131,16 @@ public class Database {
|
||||
})
|
||||
.subscribeOn(Schedulers.boundedElastic())
|
||||
.then(new LLLocalDatabaseConnection(wrkspcPath, true).connect())
|
||||
.flatMap(conn -> conn.getDatabase("testdb", List.of(Column.dictionary("testmap")), false));
|
||||
.flatMap(conn -> conn.getDatabase("testdb", List.of(Column.dictionary("testmap")), false, true));
|
||||
}
|
||||
|
||||
private static final byte[] DUMMY_VALUE = new byte[] {0x01, 0x03};
|
||||
private static final ByteBuf DUMMY_VALUE;
|
||||
static {
|
||||
ByteBuf buf = Unpooled.directBuffer(2, 2);
|
||||
buf.writeByte(0x01);
|
||||
buf.writeByte(0x03);
|
||||
DUMMY_VALUE = buf;
|
||||
}
|
||||
|
||||
private Flux<Entry<String, String>> addKeysAndConvertToLongerOnes(LLKeyValueDatabase db,
|
||||
LinkedHashSet<String> originalSuperKeys,
|
||||
@ -157,7 +222,7 @@ public class Database {
|
||||
);
|
||||
}
|
||||
|
||||
private static class FixedStringSerializer implements SerializerFixedBinaryLength<String, byte[]> {
|
||||
private static class FixedStringSerializer implements SerializerFixedBinaryLength<String, ByteBuf> {
|
||||
|
||||
private final int size;
|
||||
|
||||
@ -171,13 +236,21 @@ public class Database {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull String deserialize(byte @NotNull [] serialized) {
|
||||
return new String(serialized, StandardCharsets.US_ASCII);
|
||||
public @NotNull String deserialize(ByteBuf serialized) {
|
||||
try {
|
||||
return serialized.toString(StandardCharsets.US_ASCII);
|
||||
} finally {
|
||||
serialized.release();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte @NotNull [] serialize(@NotNull String deserialized) {
|
||||
return deserialized.getBytes(StandardCharsets.US_ASCII);
|
||||
public ByteBuf serialize(@NotNull String deserialized) {
|
||||
var serialized = deserialized.getBytes(StandardCharsets.US_ASCII);
|
||||
var serializedBuf = Unpooled.directBuffer(serialized.length, serialized.length);
|
||||
serializedBuf.writeBytes(serialized);
|
||||
assert serializedBuf.isDirect();
|
||||
return serializedBuf;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,622 @@
|
||||
package it.cavallium.dbengine.client;
|
||||
|
||||
import static it.cavallium.dbengine.client.DbTestUtils.*;
|
||||
|
||||
import it.cavallium.dbengine.database.LLDictionary;
|
||||
import it.cavallium.dbengine.database.UpdateMode;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentSkipListSet;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import reactor.test.StepVerifier.FirstStep;
|
||||
import reactor.test.StepVerifier.Step;
|
||||
import reactor.util.function.Tuple2;
|
||||
import reactor.util.function.Tuple3;
|
||||
import reactor.util.function.Tuple4;
|
||||
import reactor.util.function.Tuples;
|
||||
|
||||
public class TestDictionaryMap {
|
||||
|
||||
private static Stream<Arguments> provideArgumentsCreate() {
|
||||
return Arrays.stream(UpdateMode.values()).map(Arguments::of);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideArgumentsCreate")
|
||||
public void testCreate(UpdateMode updateMode) {
|
||||
StepVerifier
|
||||
.create(tempDb(db -> tempDictionary(db, updateMode)
|
||||
.flatMap(LLDictionary::clear)
|
||||
.then()
|
||||
))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
private static Stream<Arguments> provideArgumentsPut() {
|
||||
var goodKeys = Set.of("12345", "zebra");
|
||||
var badKeys = Set.of("", "a", "aaaa", "aaaaaa");
|
||||
Set<Tuple2<String, Boolean>> keys = Stream.concat(
|
||||
goodKeys.stream().map(s -> Tuples.of(s, false)),
|
||||
badKeys.stream().map(s -> Tuples.of(s, true))
|
||||
).collect(Collectors.toSet());
|
||||
var values = Set.of("a", "", "\0", "\0\0", "z", "azzszgzczqz", "bzzazazqzeztzgzzhz!");
|
||||
|
||||
return keys
|
||||
.stream()
|
||||
.flatMap(keyTuple -> {
|
||||
Stream<String> strm;
|
||||
if (keyTuple.getT2()) {
|
||||
strm = values.stream().limit(1);
|
||||
} else {
|
||||
strm = values.stream();
|
||||
}
|
||||
return strm.map(val -> Tuples.of(keyTuple.getT1(), val, keyTuple.getT2()));
|
||||
})
|
||||
.flatMap(entryTuple -> Arrays.stream(UpdateMode.values()).map(updateMode -> Tuples.of(updateMode,
|
||||
entryTuple.getT1(),
|
||||
entryTuple.getT2(),
|
||||
entryTuple.getT3()
|
||||
)))
|
||||
.map(fullTuple -> Arguments.of(fullTuple.getT1(), fullTuple.getT2(), fullTuple.getT3(), fullTuple.getT4()));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideArgumentsPut")
|
||||
public void testPut(UpdateMode updateMode, String key, String value, boolean shouldFail) {
|
||||
var stpVer = StepVerifier
|
||||
.create(tempDb(db -> tempDictionary(db, updateMode)
|
||||
.map(dict -> tempDatabaseMapDictionaryMap(dict, 5))
|
||||
.flatMap(map -> map
|
||||
.putValue(key, value)
|
||||
.then(map.getValue(null, key))
|
||||
.doFinally(s -> map.release())
|
||||
)
|
||||
));
|
||||
if (shouldFail) {
|
||||
stpVer.verifyError();
|
||||
} else {
|
||||
stpVer.expectNext(value).verifyComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideArgumentsPut")
|
||||
public void testAtSetAtGet(UpdateMode updateMode, String key, String value, boolean shouldFail) {
|
||||
var stpVer = StepVerifier
|
||||
.create(tempDb(db -> tempDictionary(db, updateMode)
|
||||
.map(dict -> tempDatabaseMapDictionaryMap(dict, 5))
|
||||
.flatMap(map -> map
|
||||
.at(null, key).flatMap(v -> v.set(value).doFinally(s -> v.release()))
|
||||
.then(map.at(null, key).flatMap(v -> v.get(null).doFinally(s -> v.release())))
|
||||
.doFinally(s -> map.release())
|
||||
)
|
||||
));
|
||||
if (shouldFail) {
|
||||
stpVer.verifyError();
|
||||
} else {
|
||||
stpVer.expectNext(value).verifyComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideArgumentsPut")
|
||||
public void testPutAndGetPrevious(UpdateMode updateMode, String key, String value, boolean shouldFail) {
|
||||
var stpVer = StepVerifier
|
||||
.create(tempDb(db -> tempDictionary(db, updateMode)
|
||||
.map(dict -> tempDatabaseMapDictionaryMap(dict, 5))
|
||||
.flatMapMany(map -> Flux
|
||||
.concat(
|
||||
map.putValueAndGetPrevious(key, "error?"),
|
||||
map.putValueAndGetPrevious(key, value),
|
||||
map.putValueAndGetPrevious(key, value)
|
||||
)
|
||||
.doFinally(s -> map.release())
|
||||
)
|
||||
));
|
||||
if (shouldFail) {
|
||||
stpVer.verifyError();
|
||||
} else {
|
||||
stpVer.expectNext("error?").expectNext(value).verifyComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideArgumentsPut")
|
||||
public void testPutValueRemoveAndGetPrevious(UpdateMode updateMode, String key, String value, boolean shouldFail) {
|
||||
var stpVer = StepVerifier
|
||||
.create(tempDb(db -> tempDictionary(db, updateMode)
|
||||
.map(dict -> tempDatabaseMapDictionaryMap(dict, 5))
|
||||
.flatMapMany(map -> Flux
|
||||
.concat(
|
||||
map.removeAndGetPrevious(key),
|
||||
map.putValue(key, value).then(map.removeAndGetPrevious(key)),
|
||||
map.removeAndGetPrevious(key)
|
||||
)
|
||||
.doFinally(s -> map.release())
|
||||
)
|
||||
));
|
||||
if (shouldFail) {
|
||||
stpVer.verifyError();
|
||||
} else {
|
||||
stpVer.expectNext(value).verifyComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideArgumentsPut")
|
||||
public void testPutValueRemoveAndGetStatus(UpdateMode updateMode, String key, String value, boolean shouldFail) {
|
||||
var stpVer = StepVerifier
|
||||
.create(tempDb(db -> tempDictionary(db, updateMode)
|
||||
.map(dict -> tempDatabaseMapDictionaryMap(dict, 5))
|
||||
.flatMapMany(map -> Flux
|
||||
.concat(
|
||||
map.removeAndGetStatus(key),
|
||||
map.putValue(key, value).then(map.removeAndGetStatus(key)),
|
||||
map.removeAndGetStatus(key)
|
||||
)
|
||||
.doFinally(s -> map.release())
|
||||
)
|
||||
));
|
||||
if (shouldFail) {
|
||||
stpVer.verifyError();
|
||||
} else {
|
||||
stpVer.expectNext(false, true, false).verifyComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideArgumentsPut")
|
||||
public void testUpdate(UpdateMode updateMode, String key, String value, boolean shouldFail) {
|
||||
var stpVer = StepVerifier
|
||||
.create(tempDb(db -> tempDictionary(db, updateMode)
|
||||
.map(dict -> tempDatabaseMapDictionaryMap(dict, 5))
|
||||
.flatMapMany(map -> Flux
|
||||
.concat(
|
||||
map.updateValue(key, old -> {
|
||||
assert old == null;
|
||||
return "error?";
|
||||
}),
|
||||
map.updateValue(key, false, old -> {
|
||||
assert Objects.equals(old, "error?");
|
||||
return "error?";
|
||||
}),
|
||||
map.updateValue(key, true, old -> {
|
||||
assert Objects.equals(old, "error?");
|
||||
return "error?";
|
||||
}),
|
||||
map.updateValue(key, true, old -> {
|
||||
assert Objects.equals(old, "error?");
|
||||
return value;
|
||||
}),
|
||||
map.updateValue(key, true, old -> {
|
||||
assert Objects.equals(old, value);
|
||||
return value;
|
||||
})
|
||||
)
|
||||
.doFinally(s -> map.release())
|
||||
)
|
||||
));
|
||||
if (updateMode == UpdateMode.DISALLOW || shouldFail) {
|
||||
stpVer.verifyError();
|
||||
} else {
|
||||
stpVer.expectNext(true, false, false, true, false).verifyComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideArgumentsPut")
|
||||
public void testUpdateGet(UpdateMode updateMode, String key, String value, boolean shouldFail) {
|
||||
var stpVer = StepVerifier
|
||||
.create(tempDb(db -> tempDictionary(db, updateMode)
|
||||
.map(dict -> tempDatabaseMapDictionaryMap(dict, 5))
|
||||
.flatMapMany(map -> Flux
|
||||
.concat(
|
||||
map.updateValue(key, old -> {
|
||||
assert old == null;
|
||||
return "error?";
|
||||
}).then(map.getValue(null, key)),
|
||||
map.updateValue(key, false, old -> {
|
||||
assert Objects.equals(old, "error?");
|
||||
return "error?";
|
||||
}).then(map.getValue(null, key)),
|
||||
map.updateValue(key, true, old -> {
|
||||
assert Objects.equals(old, "error?");
|
||||
return "error?";
|
||||
}).then(map.getValue(null, key)),
|
||||
map.updateValue(key, true, old -> {
|
||||
assert Objects.equals(old, "error?");
|
||||
return value;
|
||||
}).then(map.getValue(null, key)),
|
||||
map.updateValue(key, true, old -> {
|
||||
assert Objects.equals(old, value);
|
||||
return value;
|
||||
}).then(map.getValue(null, key))
|
||||
)
|
||||
.doFinally(s -> map.release())
|
||||
)
|
||||
));
|
||||
if (updateMode == UpdateMode.DISALLOW || shouldFail) {
|
||||
stpVer.verifyError();
|
||||
} else {
|
||||
stpVer.expectNext("error?", "error?", "error?", value, value).verifyComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideArgumentsPut")
|
||||
public void testPutAndGetStatus(UpdateMode updateMode, String key, String value, boolean shouldFail) {
|
||||
var stpVer = StepVerifier
|
||||
.create(tempDb(db -> tempDictionary(db, updateMode)
|
||||
.map(dict -> tempDatabaseMapDictionaryMap(dict, 5))
|
||||
.flatMapMany(map -> Flux
|
||||
.concat(
|
||||
map.putValueAndGetStatus(key, "error?").single(),
|
||||
map.putValueAndGetStatus(key, value).single(),
|
||||
map.putValueAndGetStatus(key, value).single(),
|
||||
map.remove(key),
|
||||
map.putValueAndGetStatus(key, "error?").single()
|
||||
)
|
||||
.doFinally(s -> map.release())
|
||||
)
|
||||
));
|
||||
if (shouldFail) {
|
||||
stpVer.verifyError();
|
||||
} else {
|
||||
stpVer.expectNext(false, true, true, false).verifyComplete();
|
||||
}
|
||||
}
|
||||
|
||||
private static Stream<Arguments> provideArgumentsPutMulti() {
|
||||
var goodKeys = Set.of(Set.of("12345", "67890"), Set.of("zebra"), Set.<String>of());
|
||||
var badKeys = Set.of(Set.of("", "12345"), Set.of("12345", "a"), Set.of("45678", "aaaa"), Set.of("aaaaaa", "capra"));
|
||||
Set<Tuple2<Set<String>, Boolean>> keys = Stream.concat(
|
||||
goodKeys.stream().map(s -> Tuples.of(s, false)),
|
||||
badKeys.stream().map(s -> Tuples.of(s, true))
|
||||
).collect(Collectors.toSet());
|
||||
var values = Set.of("a", "", "\0", "\0\0", "z", "azzszgzczqz", "bzzazazqzeztzgzzhz!");
|
||||
|
||||
return keys
|
||||
.stream()
|
||||
.map(keyTuple -> keyTuple.mapT1(ks -> Flux
|
||||
.zip(Flux.fromIterable(ks), Flux.fromIterable(values))
|
||||
.collectMap(Tuple2::getT1, Tuple2::getT2)
|
||||
.block()
|
||||
))
|
||||
.flatMap(entryTuple -> Arrays.stream(UpdateMode.values()).map(updateMode -> Tuples.of(updateMode,
|
||||
entryTuple.getT1(),
|
||||
entryTuple.getT2()
|
||||
)))
|
||||
.map(fullTuple -> Arguments.of(fullTuple.getT1(), fullTuple.getT2(), fullTuple.getT3()));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideArgumentsPutMulti")
|
||||
public void testPutMultiGetMulti(UpdateMode updateMode, Map<String, String> entries, boolean shouldFail) {
|
||||
var remainingEntries = new ConcurrentHashMap<Entry<String, String>, Boolean>().keySet(true);
|
||||
Step<Entry<String, String>> stpVer = StepVerifier
|
||||
.create(tempDb(db -> tempDictionary(db, updateMode)
|
||||
.map(dict -> tempDatabaseMapDictionaryMap(dict, 5))
|
||||
.flatMapMany(map -> Flux
|
||||
.concat(
|
||||
map.putMulti(Flux.fromIterable(entries.entrySet())).then(Mono.empty()),
|
||||
map.getMulti(null, Flux.fromIterable(entries.keySet()))
|
||||
)
|
||||
.doFinally(s -> map.release())
|
||||
)
|
||||
));
|
||||
if (shouldFail) {
|
||||
stpVer.verifyError();
|
||||
} else {
|
||||
entries.forEach((k, v) -> remainingEntries.add(Map.entry(k, v)));
|
||||
for (Entry<String, String> ignored : remainingEntries) {
|
||||
stpVer = stpVer.expectNextMatches(remainingEntries::remove);
|
||||
}
|
||||
stpVer.verifyComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideArgumentsPutMulti")
|
||||
public void testSetAllValuesGetMulti(UpdateMode updateMode, Map<String, String> entries, boolean shouldFail) {
|
||||
var remainingEntries = new ConcurrentHashMap<Entry<String, String>, Boolean>().keySet(true);
|
||||
Step<Entry<String, String>> stpVer = StepVerifier
|
||||
.create(tempDb(db -> tempDictionary(db, updateMode)
|
||||
.map(dict -> tempDatabaseMapDictionaryMap(dict, 5))
|
||||
.flatMapMany(map -> map
|
||||
.setAllValues(Flux.fromIterable(entries.entrySet()))
|
||||
.thenMany(map.getMulti(null, Flux.fromIterable(entries.keySet())))
|
||||
.doFinally(s -> map.release())
|
||||
)
|
||||
));
|
||||
if (shouldFail) {
|
||||
stpVer.verifyError();
|
||||
} else {
|
||||
entries.forEach((k, v) -> remainingEntries.add(Map.entry(k, v)));
|
||||
for (Entry<String, String> ignored : remainingEntries) {
|
||||
stpVer = stpVer.expectNextMatches(remainingEntries::remove);
|
||||
}
|
||||
stpVer.verifyComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideArgumentsPutMulti")
|
||||
public void testSetAllValuesAndGetPrevious(UpdateMode updateMode, Map<String, String> entries, boolean shouldFail) {
|
||||
var remainingEntries = new ConcurrentHashMap<Entry<String, String>, Boolean>().keySet(true);
|
||||
Step<Entry<String, String>> stpVer = StepVerifier
|
||||
.create(tempDb(db -> tempDictionary(db, updateMode)
|
||||
.map(dict -> tempDatabaseMapDictionaryMap(dict, 5))
|
||||
.flatMapMany(map -> Flux
|
||||
.concat(
|
||||
map.setAllValuesAndGetPrevious(Flux.fromIterable(entries.entrySet())),
|
||||
map.setAllValuesAndGetPrevious(Flux.fromIterable(entries.entrySet()))
|
||||
)
|
||||
.doFinally(s -> map.release())
|
||||
)
|
||||
));
|
||||
if (shouldFail) {
|
||||
stpVer.verifyError();
|
||||
} else {
|
||||
entries.forEach((k, v) -> remainingEntries.add(Map.entry(k, v)));
|
||||
for (Entry<String, String> ignored : remainingEntries) {
|
||||
stpVer = stpVer.expectNextMatches(remainingEntries::remove);
|
||||
}
|
||||
stpVer.verifyComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideArgumentsPutMulti")
|
||||
public void testSetGetMulti(UpdateMode updateMode, Map<String, String> entries, boolean shouldFail) {
|
||||
var remainingEntries = new ConcurrentHashMap<Entry<String, String>, Boolean>().keySet(true);
|
||||
Step<Entry<String, String>> stpVer = StepVerifier
|
||||
.create(tempDb(db -> tempDictionary(db, updateMode)
|
||||
.map(dict -> tempDatabaseMapDictionaryMap(dict, 5))
|
||||
.flatMapMany(map -> Flux
|
||||
.concat(
|
||||
map.set(entries).then(Mono.empty()),
|
||||
map.getMulti(null, Flux.fromIterable(entries.keySet()))
|
||||
)
|
||||
.doFinally(s -> map.release())
|
||||
)
|
||||
));
|
||||
if (shouldFail) {
|
||||
stpVer.verifyError();
|
||||
} else {
|
||||
entries.forEach((k, v) -> remainingEntries.add(Map.entry(k, v)));
|
||||
for (Entry<String, String> ignored : remainingEntries) {
|
||||
stpVer = stpVer.expectNextMatches(remainingEntries::remove);
|
||||
}
|
||||
stpVer.verifyComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideArgumentsPutMulti")
|
||||
public void testSetAndGetStatus(UpdateMode updateMode, Map<String, String> entries, boolean shouldFail) {
|
||||
var remainingEntries = new ConcurrentHashMap<Entry<String, String>, Boolean>().keySet(true);
|
||||
Step<Boolean> stpVer = StepVerifier
|
||||
.create(tempDb(db -> tempDictionary(db, updateMode)
|
||||
.map(dict -> tempDatabaseMapDictionaryMap(dict, 5))
|
||||
.flatMapMany(map -> {
|
||||
Mono<Void> removalMono;
|
||||
if (entries.isEmpty()) {
|
||||
removalMono = Mono.empty();
|
||||
} else {
|
||||
removalMono = map.remove(entries.keySet().stream().findAny().orElseThrow());
|
||||
}
|
||||
return Flux
|
||||
.concat(
|
||||
map.setAndGetChanged(entries).single(),
|
||||
map.setAndGetChanged(entries).single(),
|
||||
removalMono.then(Mono.empty()),
|
||||
map.setAndGetChanged(entries).single()
|
||||
)
|
||||
.doFinally(s -> map.release());
|
||||
})
|
||||
));
|
||||
if (shouldFail) {
|
||||
stpVer.verifyError();
|
||||
} else {
|
||||
stpVer.expectNext(!entries.isEmpty(), false, !entries.isEmpty()).verifyComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideArgumentsPutMulti")
|
||||
public void testSetAndGetPrevious(UpdateMode updateMode, Map<String, String> entries, boolean shouldFail) {
|
||||
var remainingEntries = new ConcurrentHashMap<Entry<String, String>, Boolean>().keySet(true);
|
||||
Step<Entry<String, String>> stpVer = StepVerifier
|
||||
.create(tempDb(db -> tempDictionary(db, updateMode)
|
||||
.map(dict -> tempDatabaseMapDictionaryMap(dict, 5))
|
||||
.flatMapMany(map -> Flux
|
||||
.concat(map.setAndGetPrevious(entries), map.setAndGetPrevious(entries))
|
||||
.map(Map::entrySet)
|
||||
.flatMap(Flux::fromIterable)
|
||||
.doFinally(s -> map.release())
|
||||
)
|
||||
));
|
||||
if (shouldFail) {
|
||||
stpVer.verifyError();
|
||||
} else {
|
||||
entries.forEach((k, v) -> remainingEntries.add(Map.entry(k, v)));
|
||||
for (Entry<String, String> ignored : remainingEntries) {
|
||||
stpVer = stpVer.expectNextMatches(remainingEntries::remove);
|
||||
}
|
||||
stpVer.verifyComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideArgumentsPutMulti")
|
||||
public void testSetClearAndGetPreviousGet(UpdateMode updateMode, Map<String, String> entries, boolean shouldFail) {
|
||||
var remainingEntries = new ConcurrentHashMap<Entry<String, String>, Boolean>().keySet(true);
|
||||
Step<Entry<String, String>> stpVer = StepVerifier
|
||||
.create(tempDb(db -> tempDictionary(db, updateMode)
|
||||
.map(dict -> tempDatabaseMapDictionaryMap(dict, 5))
|
||||
.flatMapMany(map -> Flux
|
||||
.concat(map.set(entries).then(Mono.empty()), map.clearAndGetPrevious(), map.get(null))
|
||||
.map(Map::entrySet)
|
||||
.flatMap(Flux::fromIterable)
|
||||
.doFinally(s -> map.release())
|
||||
)
|
||||
));
|
||||
if (shouldFail) {
|
||||
stpVer.verifyError();
|
||||
} else {
|
||||
entries.forEach((k, v) -> remainingEntries.add(Map.entry(k, v)));
|
||||
for (Entry<String, String> ignored : remainingEntries) {
|
||||
stpVer = stpVer.expectNextMatches(remainingEntries::remove);
|
||||
}
|
||||
stpVer.verifyComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideArgumentsPutMulti")
|
||||
public void testPutMultiGetAllValues(UpdateMode updateMode, Map<String, String> entries, boolean shouldFail) {
|
||||
var remainingEntries = new ConcurrentHashMap<Entry<String, String>, Boolean>().keySet(true);
|
||||
Step<Entry<String, String>> stpVer = StepVerifier
|
||||
.create(tempDb(db -> tempDictionary(db, updateMode)
|
||||
.map(dict -> tempDatabaseMapDictionaryMap(dict, 5))
|
||||
.flatMapMany(map -> Flux
|
||||
.concat(
|
||||
map.putMulti(Flux.fromIterable(entries.entrySet())).then(Mono.empty()),
|
||||
map.getAllValues(null)
|
||||
)
|
||||
.doFinally(s -> map.release())
|
||||
)
|
||||
));
|
||||
if (shouldFail) {
|
||||
stpVer.verifyError();
|
||||
} else {
|
||||
entries.forEach((k, v) -> remainingEntries.add(Map.entry(k, v)));
|
||||
for (Entry<String, String> ignored : remainingEntries) {
|
||||
stpVer = stpVer.expectNextMatches(remainingEntries::remove);
|
||||
}
|
||||
stpVer.verifyComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideArgumentsPutMulti")
|
||||
public void testPutMultiGet(UpdateMode updateMode, Map<String, String> entries, boolean shouldFail) {
|
||||
var remainingEntries = new ConcurrentHashMap<Entry<String, String>, Boolean>().keySet(true);
|
||||
Step<Entry<String, String>> stpVer = StepVerifier
|
||||
.create(tempDb(db -> tempDictionary(db, updateMode)
|
||||
.map(dict -> tempDatabaseMapDictionaryMap(dict, 5))
|
||||
.flatMapMany(map -> Flux
|
||||
.concat(
|
||||
map.putMulti(Flux.fromIterable(entries.entrySet())).then(Mono.empty()),
|
||||
map.get(null)
|
||||
.map(Map::entrySet)
|
||||
.flatMapMany(Flux::fromIterable)
|
||||
)
|
||||
.doFinally(s -> map.release())
|
||||
)
|
||||
));
|
||||
if (shouldFail) {
|
||||
stpVer.verifyError();
|
||||
} else {
|
||||
entries.forEach((k, v) -> remainingEntries.add(Map.entry(k, v)));
|
||||
for (Entry<String, String> ignored : remainingEntries) {
|
||||
stpVer = stpVer.expectNextMatches(remainingEntries::remove);
|
||||
}
|
||||
stpVer.verifyComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideArgumentsPutMulti")
|
||||
public void testPutMultiGetAllStagesGet(UpdateMode updateMode, Map<String, String> entries, boolean shouldFail) {
|
||||
var remainingEntries = new ConcurrentHashMap<Entry<String, String>, Boolean>().keySet(true);
|
||||
Step<Entry<String, String>> stpVer = StepVerifier
|
||||
.create(tempDb(db -> tempDictionary(db, updateMode)
|
||||
.map(dict -> tempDatabaseMapDictionaryMap(dict, 5))
|
||||
.flatMapMany(map -> Flux
|
||||
.concat(
|
||||
map.putMulti(Flux.fromIterable(entries.entrySet())).then(Mono.empty()),
|
||||
map
|
||||
.getAllStages(null)
|
||||
.flatMap(stage -> stage
|
||||
.getValue()
|
||||
.get(null)
|
||||
.map(val -> Map.entry(stage.getKey(), val))
|
||||
.doFinally(s -> stage.getValue().release())
|
||||
)
|
||||
)
|
||||
.doFinally(s -> map.release())
|
||||
)
|
||||
));
|
||||
if (shouldFail) {
|
||||
stpVer.verifyError();
|
||||
} else {
|
||||
entries.forEach((k, v) -> remainingEntries.add(Map.entry(k, v)));
|
||||
for (Entry<String, String> ignored : remainingEntries) {
|
||||
stpVer = stpVer.expectNextMatches(remainingEntries::remove);
|
||||
}
|
||||
stpVer.verifyComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideArgumentsPutMulti")
|
||||
public void testPutMultiIsEmpty(UpdateMode updateMode, Map<String, String> entries, boolean shouldFail) {
|
||||
var remainingEntries = new ConcurrentHashMap<Entry<String, String>, Boolean>().keySet(true);
|
||||
Step<Boolean> stpVer = StepVerifier
|
||||
.create(tempDb(db -> tempDictionary(db, updateMode)
|
||||
.map(dict -> tempDatabaseMapDictionaryMap(dict, 5))
|
||||
.flatMapMany(map -> Flux
|
||||
.concat(
|
||||
map.isEmpty(null),
|
||||
map.putMulti(Flux.fromIterable(entries.entrySet())).then(Mono.empty()),
|
||||
map.isEmpty(null)
|
||||
)
|
||||
.doFinally(s -> map.release())
|
||||
)
|
||||
));
|
||||
if (shouldFail) {
|
||||
stpVer.expectNext(true).verifyError();
|
||||
} else {
|
||||
stpVer.expectNext(true, entries.isEmpty()).verifyComplete();
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideArgumentsPutMulti")
|
||||
public void testPutMultiClear(UpdateMode updateMode, Map<String, String> entries, boolean shouldFail) {
|
||||
var remainingEntries = new ConcurrentHashMap<Entry<String, String>, Boolean>().keySet(true);
|
||||
Step<Boolean> stpVer = StepVerifier
|
||||
.create(tempDb(db -> tempDictionary(db, updateMode)
|
||||
.map(dict -> tempDatabaseMapDictionaryMap(dict, 5))
|
||||
.flatMapMany(map -> Flux
|
||||
.concat(
|
||||
map.isEmpty(null),
|
||||
map.putMulti(Flux.fromIterable(entries.entrySet())).then(Mono.empty()),
|
||||
map.isEmpty(null),
|
||||
map.clear().then(Mono.empty()),
|
||||
map.isEmpty(null)
|
||||
)
|
||||
.doFinally(s -> map.release())
|
||||
)
|
||||
));
|
||||
if (shouldFail) {
|
||||
stpVer.expectNext(true).verifyError();
|
||||
} else {
|
||||
stpVer.expectNext(true, entries.isEmpty(), true).verifyComplete();
|
||||
}
|
||||
}
|
||||
}
|
120
src/test/java/it/cavallium/dbengine/client/TestSingletons.java
Normal file
120
src/test/java/it/cavallium/dbengine/client/TestSingletons.java
Normal file
@ -0,0 +1,120 @@
|
||||
package it.cavallium.dbengine.client;
|
||||
|
||||
import static it.cavallium.dbengine.client.DbTestUtils.tempDb;
|
||||
|
||||
import it.cavallium.dbengine.database.LLKeyValueDatabase;
|
||||
import it.cavallium.dbengine.database.collections.DatabaseInt;
|
||||
import it.cavallium.dbengine.database.collections.DatabaseLong;
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.ArgumentsProvider;
|
||||
import org.junit.jupiter.params.provider.ArgumentsSource;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
public class TestSingletons {
|
||||
|
||||
private static Stream<Arguments> provideNumberWithRepeats() {
|
||||
return Stream.of(
|
||||
Arguments.of(Integer.MIN_VALUE, 2),
|
||||
Arguments.of(-11, 2),
|
||||
Arguments.of(0, 3),
|
||||
Arguments.of(102, 5)
|
||||
);
|
||||
}
|
||||
|
||||
private static Stream<Arguments> provideLongNumberWithRepeats() {
|
||||
return Stream.of(
|
||||
Arguments.of(Long.MIN_VALUE, 2),
|
||||
Arguments.of(-11L, 2),
|
||||
Arguments.of(0L, 3),
|
||||
Arguments.of(102L, 5)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateInteger() {
|
||||
StepVerifier
|
||||
.create(tempDb(db -> tempInt(db, "test", 0)
|
||||
.flatMap(dbInt -> dbInt.get(null))
|
||||
.then()
|
||||
))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateLong() {
|
||||
StepVerifier
|
||||
.create(tempDb(db -> tempLong(db, "test", 0)
|
||||
.flatMap(dbLong -> dbLong.get(null))
|
||||
.then()
|
||||
))
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = {Integer.MIN_VALUE, -192, -2, -1, 0, 1, 2, 1292, Integer.MAX_VALUE})
|
||||
public void testDefaultValueInteger(int i) {
|
||||
StepVerifier
|
||||
.create(tempDb(db -> tempInt(db, "test", i)
|
||||
.flatMap(dbInt -> dbInt.get(null))
|
||||
))
|
||||
.expectNext(i)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(longs = {Long.MIN_VALUE, -192, -2, -1, 0, 1, 2, 1292, Long.MAX_VALUE})
|
||||
public void testDefaultValueLong(long i) {
|
||||
StepVerifier
|
||||
.create(tempDb(db -> tempLong(db, "test", i)
|
||||
.flatMap(dbLong -> dbLong.get(null))
|
||||
))
|
||||
.expectNext(i)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideNumberWithRepeats")
|
||||
public void testSetInteger(Integer i, Integer repeats) {
|
||||
StepVerifier
|
||||
.create(tempDb(db -> tempInt(db, "test", 0)
|
||||
.flatMap(dbInt -> Mono
|
||||
.defer(() -> dbInt.set((int) System.currentTimeMillis()))
|
||||
.repeat(repeats)
|
||||
.then(dbInt.set(i))
|
||||
.then(dbInt.get(null)))
|
||||
))
|
||||
.expectNext(i)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideLongNumberWithRepeats")
|
||||
public void testSetLong(Long i, Integer repeats) {
|
||||
StepVerifier
|
||||
.create(tempDb(db -> tempLong(db, "test", 0)
|
||||
.flatMap(dbLong -> Mono
|
||||
.defer(() -> dbLong.set(System.currentTimeMillis()))
|
||||
.repeat(repeats)
|
||||
.then(dbLong.set(i))
|
||||
.then(dbLong.get(null)))
|
||||
))
|
||||
.expectNext(i)
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
public static Mono<DatabaseInt> tempInt(LLKeyValueDatabase database, String name, int defaultValue) {
|
||||
return database
|
||||
.getInteger("ints", name, defaultValue);
|
||||
}
|
||||
|
||||
public static Mono<DatabaseLong> tempLong(LLKeyValueDatabase database, String name, long defaultValue) {
|
||||
return database
|
||||
.getLong("longs", name, defaultValue);
|
||||
}
|
||||
}
|
@ -1,10 +1,21 @@
|
||||
package it.cavallium.dbengine.database.collections;
|
||||
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.buffer.PooledByteBufAllocator;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import it.cavallium.dbengine.database.LLUtils;
|
||||
import java.util.Arrays;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import static io.netty.buffer.Unpooled.*;
|
||||
|
||||
public class TestRanges {
|
||||
|
||||
@Test
|
||||
public void testDirectBuffer() {
|
||||
Assertions.assertTrue(wrappedBuffer(Unpooled.directBuffer(10, 10), Unpooled.buffer(10, 10)).isDirect());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNextRangeKey() {
|
||||
testNextRangeKey(new byte[] {0x00, 0x00, 0x00});
|
||||
@ -21,11 +32,21 @@ public class TestRanges {
|
||||
|
||||
public void testNextRangeKey(byte[] prefixKey) {
|
||||
|
||||
byte[] firstRangeKey = DatabaseMapDictionaryDeep.firstRangeKey(prefixKey, prefixKey.length, 7, 3);
|
||||
byte[] nextRangeKey = DatabaseMapDictionaryDeep.nextRangeKey(prefixKey, prefixKey.length, 7, 3);
|
||||
byte[] firstRangeKey = LLUtils.toArray(DatabaseMapDictionaryDeep.firstRangeKey(PooledByteBufAllocator.DEFAULT,
|
||||
LLUtils.convertToDirectByteBuf(PooledByteBufAllocator.DEFAULT, wrappedBuffer(prefixKey)),
|
||||
prefixKey.length,
|
||||
7,
|
||||
3
|
||||
));
|
||||
byte[] nextRangeKey = LLUtils.toArray(DatabaseMapDictionaryDeep.nextRangeKey(PooledByteBufAllocator.DEFAULT,
|
||||
LLUtils.convertToDirectByteBuf(PooledByteBufAllocator.DEFAULT, wrappedBuffer(prefixKey)),
|
||||
prefixKey.length,
|
||||
7,
|
||||
3
|
||||
));
|
||||
|
||||
if (Arrays.equals(prefixKey, new byte[] {(byte) 0xFF, (byte) 0xFF, (byte) 0xFF})) {
|
||||
Assertions.assertArrayEquals(new byte[] {(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, nextRangeKey);
|
||||
Assertions.assertArrayEquals(new byte[] {(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 0}, nextRangeKey);
|
||||
} else {
|
||||
long biPrefix = 0;
|
||||
var s = 0;
|
||||
@ -64,11 +85,23 @@ public class TestRanges {
|
||||
|
||||
public void testNextRangeKeyWithSuffix(byte[] prefixKey, byte[] suffixKey) {
|
||||
|
||||
byte[] firstRangeKey = DatabaseMapDictionaryDeep.firstRangeKey(prefixKey, suffixKey, prefixKey.length, 3, 7);
|
||||
byte[] nextRangeKey = DatabaseMapDictionaryDeep.nextRangeKey(prefixKey, suffixKey, prefixKey.length, 3, 7);
|
||||
byte[] firstRangeKey = LLUtils.toArray(DatabaseMapDictionaryDeep.firstRangeKey(ByteBufAllocator.DEFAULT,
|
||||
LLUtils.convertToDirectByteBuf(PooledByteBufAllocator.DEFAULT, wrappedBuffer(prefixKey)),
|
||||
LLUtils.convertToDirectByteBuf(PooledByteBufAllocator.DEFAULT, wrappedBuffer(suffixKey)),
|
||||
prefixKey.length,
|
||||
3,
|
||||
7
|
||||
));
|
||||
byte[] nextRangeKey = LLUtils.toArray(DatabaseMapDictionaryDeep.nextRangeKey(ByteBufAllocator.DEFAULT,
|
||||
LLUtils.convertToDirectByteBuf(PooledByteBufAllocator.DEFAULT, wrappedBuffer(prefixKey)),
|
||||
LLUtils.convertToDirectByteBuf(PooledByteBufAllocator.DEFAULT, wrappedBuffer(suffixKey)),
|
||||
prefixKey.length,
|
||||
3,
|
||||
7
|
||||
));
|
||||
|
||||
if (Arrays.equals(prefixKey, new byte[] {(byte) 0xFF, (byte) 0xFF, (byte) 0xFF}) && Arrays.equals(suffixKey, new byte[] {(byte) 0xFF, (byte) 0xFF, (byte) 0xFF})) {
|
||||
Assertions.assertArrayEquals(new byte[] {(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 0, 0, 0, 0, 0, 0, 0, 0}, nextRangeKey);
|
||||
Assertions.assertArrayEquals(new byte[] {(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 0}, nextRangeKey);
|
||||
} else {
|
||||
long biPrefix = 0;
|
||||
var s = 0;
|
||||
|
Loading…
Reference in New Issue
Block a user