Optimistic transactions, inline codecs
This commit is contained in:
parent
aad5f8c96c
commit
80d0ced888
|
@ -19,15 +19,17 @@ public class MappedSerializer<A, B> implements Serializer<B> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull DeserializationResult<B> deserialize(@NotNull Send<Buffer> serialized) throws SerializationException {
|
public @NotNull B deserialize(@NotNull Buffer serialized) throws SerializationException {
|
||||||
try (serialized) {
|
return keyMapper.map(serializer.deserialize(serialized));
|
||||||
var deserialized = serializer.deserialize(serialized);
|
|
||||||
return new DeserializationResult<>(keyMapper.map(deserialized.deserializedData()), deserialized.bytesRead());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Send<Buffer> serialize(@NotNull B deserialized) throws SerializationException {
|
public void serialize(@NotNull B deserialized, Buffer output) throws SerializationException {
|
||||||
return serializer.serialize(keyMapper.unmap(deserialized));
|
serializer.serialize(keyMapper.unmap(deserialized), output);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSerializedSizeHint() {
|
||||||
|
return serializer.getSerializedSizeHint();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,16 +19,13 @@ public class MappedSerializerFixedLength<A, B> implements SerializerFixedBinaryL
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull DeserializationResult<B> deserialize(@NotNull Send<Buffer> serialized) throws SerializationException {
|
public @NotNull B deserialize(@NotNull Buffer serialized) throws SerializationException {
|
||||||
try (serialized) {
|
return keyMapper.map(fixedLengthSerializer.deserialize(serialized));
|
||||||
var deserialized = fixedLengthSerializer.deserialize(serialized);
|
|
||||||
return new DeserializationResult<>(keyMapper.map(deserialized.deserializedData()), deserialized.bytesRead());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Send<Buffer> serialize(@NotNull B deserialized) throws SerializationException {
|
public void serialize(@NotNull B deserialized, Buffer output) throws SerializationException {
|
||||||
return fixedLengthSerializer.serialize(keyMapper.unmap(deserialized));
|
fixedLengthSerializer.serialize(keyMapper.unmap(deserialized), output);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -63,7 +63,6 @@ import reactor.util.function.Tuple3;
|
||||||
public class LLUtils {
|
public class LLUtils {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(LLUtils.class);
|
private static final Logger logger = LoggerFactory.getLogger(LLUtils.class);
|
||||||
public static final Marker MARKER_DB_BUFFER = MarkerFactory.getMarker("DB_BUFFER");
|
|
||||||
public static final Marker MARKER_ROCKSDB = MarkerFactory.getMarker("ROCKSDB");
|
public static final Marker MARKER_ROCKSDB = MarkerFactory.getMarker("ROCKSDB");
|
||||||
public static final Marker MARKER_LUCENE = MarkerFactory.getMarker("LUCENE");
|
public static final Marker MARKER_LUCENE = MarkerFactory.getMarker("LUCENE");
|
||||||
|
|
||||||
|
@ -193,6 +192,18 @@ public class LLUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String toStringSafe(byte @Nullable[] key) {
|
||||||
|
try {
|
||||||
|
if (key == null) {
|
||||||
|
return toString(key);
|
||||||
|
} else {
|
||||||
|
return "(released)";
|
||||||
|
}
|
||||||
|
} catch (IllegalReferenceCountException ex) {
|
||||||
|
return "(released)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static String toStringSafe(@Nullable LLRange range) {
|
public static String toStringSafe(@Nullable LLRange range) {
|
||||||
try {
|
try {
|
||||||
if (range == null || range.isAccessible()) {
|
if (range == null || range.isAccessible()) {
|
||||||
|
@ -268,6 +279,53 @@ public class LLUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String toString(byte @Nullable[] key) {
|
||||||
|
if (key == null) {
|
||||||
|
return "null";
|
||||||
|
} else {
|
||||||
|
int startIndex = 0;
|
||||||
|
int iMax = key.length - 1;
|
||||||
|
int iLimit = 128;
|
||||||
|
if (iMax <= -1) {
|
||||||
|
return "[]";
|
||||||
|
} else {
|
||||||
|
StringBuilder arraySB = new StringBuilder();
|
||||||
|
StringBuilder asciiSB = new StringBuilder();
|
||||||
|
boolean isAscii = true;
|
||||||
|
arraySB.append('[');
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
var byteVal = (int) key[startIndex + i];
|
||||||
|
arraySB.append(byteVal);
|
||||||
|
if (isAscii) {
|
||||||
|
if (byteVal >= 32 && byteVal < 127) {
|
||||||
|
asciiSB.append((char) byteVal);
|
||||||
|
} else if (byteVal == 0) {
|
||||||
|
asciiSB.append('␀');
|
||||||
|
} else {
|
||||||
|
isAscii = false;
|
||||||
|
asciiSB = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i == iLimit) {
|
||||||
|
arraySB.append("…");
|
||||||
|
}
|
||||||
|
if (i == iMax || i == iLimit) {
|
||||||
|
if (isAscii) {
|
||||||
|
return asciiSB.insert(0, "\"").append("\"").toString();
|
||||||
|
} else {
|
||||||
|
return arraySB.append(']').toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
arraySB.append(", ");
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static boolean equals(Buffer a, Buffer b) {
|
public static boolean equals(Buffer a, Buffer b) {
|
||||||
if (a == null && b == null) {
|
if (a == null && b == null) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -1005,6 +1063,12 @@ public class LLUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String deserializeString(@NotNull Buffer buffer, int readerOffset, int length, Charset charset) {
|
||||||
|
byte[] bytes = new byte[Math.min(length, buffer.readableBytes())];
|
||||||
|
buffer.copyInto(readerOffset, bytes, 0, length);
|
||||||
|
return new String(bytes, charset);
|
||||||
|
}
|
||||||
|
|
||||||
public static int utf8MaxBytes(String deserialized) {
|
public static int utf8MaxBytes(String deserialized) {
|
||||||
return deserialized.length() * 3;
|
return deserialized.length() * 3;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,8 @@ import io.net5.buffer.api.Drop;
|
||||||
import io.net5.buffer.api.Send;
|
import io.net5.buffer.api.Send;
|
||||||
import it.cavallium.dbengine.database.LLDictionary;
|
import it.cavallium.dbengine.database.LLDictionary;
|
||||||
import it.cavallium.dbengine.database.LLUtils;
|
import it.cavallium.dbengine.database.LLUtils;
|
||||||
|
import it.cavallium.dbengine.database.serialization.SerializationException;
|
||||||
import it.cavallium.dbengine.database.serialization.Serializer;
|
import it.cavallium.dbengine.database.serialization.Serializer;
|
||||||
import it.cavallium.dbengine.database.serialization.Serializer.DeserializationResult;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
@ -15,20 +15,23 @@ public class DatabaseEmpty {
|
||||||
|
|
||||||
@SuppressWarnings({"unused", "InstantiationOfUtilityClass"})
|
@SuppressWarnings({"unused", "InstantiationOfUtilityClass"})
|
||||||
public static final Nothing NOTHING = new Nothing();
|
public static final Nothing NOTHING = new Nothing();
|
||||||
public static final DeserializationResult<Nothing> NOTHING_RESULT = new DeserializationResult<>(NOTHING, 0);
|
|
||||||
|
|
||||||
public static Serializer<Nothing> nothingSerializer(BufferAllocator bufferAllocator) {
|
public static Serializer<Nothing> nothingSerializer(BufferAllocator bufferAllocator) {
|
||||||
return new Serializer<>() {
|
return new Serializer<>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull DeserializationResult<Nothing> deserialize(@Nullable Send<Buffer> serialized) {
|
public @NotNull Nothing deserialize(@NotNull Buffer serialized) {
|
||||||
try (serialized) {
|
return NOTHING;
|
||||||
return NOTHING_RESULT;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Send<Buffer> serialize(@NotNull Nothing deserialized) {
|
public void serialize(@NotNull Nothing deserialized, Buffer output) {
|
||||||
return LLUtils.empty(bufferAllocator);
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSerializedSizeHint() {
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package it.cavallium.dbengine.database.collections;
|
package it.cavallium.dbengine.database.collections;
|
||||||
|
|
||||||
import io.net5.buffer.api.Buffer;
|
import io.net5.buffer.api.Buffer;
|
||||||
import io.net5.buffer.api.Drop;
|
|
||||||
import io.net5.buffer.api.Send;
|
import io.net5.buffer.api.Send;
|
||||||
import io.net5.buffer.api.internal.ResourceSupport;
|
import io.net5.buffer.api.internal.ResourceSupport;
|
||||||
import it.cavallium.dbengine.client.CompositeSnapshot;
|
import it.cavallium.dbengine.client.CompositeSnapshot;
|
||||||
|
@ -66,9 +65,36 @@ public class DatabaseMapDictionary<T, U> extends DatabaseMapDictionaryDeep<T, U,
|
||||||
return new DatabaseMapDictionary<>(dictionary, prefixKey, keySuffixSerializer, valueSerializer, onClose);
|
return new DatabaseMapDictionary<>(dictionary, prefixKey, keySuffixSerializer, valueSerializer, onClose);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void deserializeValue(Send<Buffer> valueToReceive, SynchronousSink<U> sink) {
|
||||||
|
try (var value = valueToReceive.receive()) {
|
||||||
|
sink.next(valueSerializer.deserialize(value));
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
sink.error(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Send<Buffer> serializeValue(U value) throws SerializationException {
|
||||||
|
var valSizeHint = valueSerializer.getSerializedSizeHint();
|
||||||
|
if (valSizeHint == -1) valSizeHint = 128;
|
||||||
|
try (var valBuf = dictionary.getAllocator().allocate(valSizeHint)) {
|
||||||
|
valueSerializer.serialize(value, valBuf);
|
||||||
|
return valBuf.send();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Send<Buffer> serializeKeySuffixToKey(T keySuffix) throws SerializationException {
|
||||||
|
try (var keyBuf = keyPrefix.copy()) {
|
||||||
|
assert keyBuf.readableBytes() == keyPrefixLength;
|
||||||
|
keyBuf.ensureWritable(keySuffixLength + keyExtLength);
|
||||||
|
serializeSuffix(keySuffix, keyBuf);
|
||||||
|
assert keyBuf.readableBytes() == keyPrefixLength + keySuffixLength + keyExtLength;
|
||||||
|
return keyBuf.send();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Send<Buffer> toKey(Send<Buffer> suffixKeyToSend) {
|
private Send<Buffer> toKey(Send<Buffer> suffixKeyToSend) {
|
||||||
try (var suffixKey = suffixKeyToSend.receive()) {
|
try (var suffixKey = suffixKeyToSend.receive()) {
|
||||||
assert suffixKeyConsistency(suffixKey.readableBytes());
|
assert suffixKeyLengthConsistency(suffixKey.readableBytes());
|
||||||
if (keyPrefix.readableBytes() > 0) {
|
if (keyPrefix.readableBytes() > 0) {
|
||||||
try (var result = LLUtils.compositeBuffer(dictionary.getAllocator(),
|
try (var result = LLUtils.compositeBuffer(dictionary.getAllocator(),
|
||||||
LLUtils.copy(dictionary.getAllocator(), keyPrefix),
|
LLUtils.copy(dictionary.getAllocator(), keyPrefix),
|
||||||
|
@ -84,29 +110,28 @@ public class DatabaseMapDictionary<T, U> extends DatabaseMapDictionaryDeep<T, U,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deserializeValue(Send<Buffer> value, SynchronousSink<U> sink) {
|
|
||||||
try {
|
|
||||||
sink.next(valueSerializer.deserialize(value).deserializedData());
|
|
||||||
} catch (SerializationException ex) {
|
|
||||||
sink.error(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<Map<T, U>> get(@Nullable CompositeSnapshot snapshot, boolean existsAlmostCertainly) {
|
public Mono<Map<T, U>> get(@Nullable CompositeSnapshot snapshot, boolean existsAlmostCertainly) {
|
||||||
return dictionary
|
return dictionary
|
||||||
.getRange(resolveSnapshot(snapshot), rangeMono, existsAlmostCertainly)
|
.getRange(resolveSnapshot(snapshot), rangeMono, existsAlmostCertainly)
|
||||||
.<Entry<T, U>>handle((entrySend, sink) -> {
|
.<Entry<T, U>>handle((entrySend, sink) -> {
|
||||||
try (var entry = entrySend.receive()) {
|
Entry<T, U> deserializedEntry;
|
||||||
T key;
|
try {
|
||||||
try (var serializedKey = entry.getKey().receive()) {
|
try (var entry = entrySend.receive()) {
|
||||||
removePrefix(serializedKey);
|
T key;
|
||||||
suffixKeyConsistency(serializedKey.readableBytes());
|
try (var serializedKey = entry.getKey().receive()) {
|
||||||
key = deserializeSuffix(serializedKey.send());
|
splitPrefix(serializedKey).close();
|
||||||
|
suffixKeyLengthConsistency(serializedKey.readableBytes());
|
||||||
|
key = deserializeSuffix(serializedKey);
|
||||||
|
}
|
||||||
|
U value;
|
||||||
|
try (var valueBuf = entry.getValue().receive()) {
|
||||||
|
value = valueSerializer.deserialize(valueBuf);
|
||||||
|
}
|
||||||
|
deserializedEntry = Map.entry(key, value);
|
||||||
}
|
}
|
||||||
var value = valueSerializer.deserialize(entry.getValue()).deserializedData();
|
sink.next(deserializedEntry);
|
||||||
sink.next(Map.entry(key, value));
|
} catch (Throwable ex) {
|
||||||
} catch (SerializationException ex) {
|
|
||||||
sink.error(ex);
|
sink.error(ex);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -120,14 +145,7 @@ public class DatabaseMapDictionary<T, U> extends DatabaseMapDictionaryDeep<T, U,
|
||||||
.get(null, false)
|
.get(null, false)
|
||||||
.concatWith(dictionary.setRange(rangeMono, Flux
|
.concatWith(dictionary.setRange(rangeMono, Flux
|
||||||
.fromIterable(Collections.unmodifiableMap(value).entrySet())
|
.fromIterable(Collections.unmodifiableMap(value).entrySet())
|
||||||
.handle((entry, sink) -> {
|
.handle(this::serializeEntrySink)
|
||||||
try {
|
|
||||||
sink.next(LLEntry.of(this.toKey(serializeSuffix(entry.getKey())),
|
|
||||||
valueSerializer.serialize(entry.getValue())).send());
|
|
||||||
} catch (SerializationException e) {
|
|
||||||
sink.error(e);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
).then(Mono.empty()))
|
).then(Mono.empty()))
|
||||||
.singleOrEmpty()
|
.singleOrEmpty()
|
||||||
.transform(LLUtils::handleDiscard);
|
.transform(LLUtils::handleDiscard);
|
||||||
|
@ -152,14 +170,14 @@ public class DatabaseMapDictionary<T, U> extends DatabaseMapDictionaryDeep<T, U,
|
||||||
@Override
|
@Override
|
||||||
public Mono<DatabaseStageEntry<U>> at(@Nullable CompositeSnapshot snapshot, T keySuffix) {
|
public Mono<DatabaseStageEntry<U>> at(@Nullable CompositeSnapshot snapshot, T keySuffix) {
|
||||||
return Mono.fromCallable(() ->
|
return Mono.fromCallable(() ->
|
||||||
new DatabaseSingle<>(dictionary, toKey(serializeSuffix(keySuffix)), valueSerializer, null));
|
new DatabaseSingle<>(dictionary, serializeKeySuffixToKey(keySuffix), valueSerializer, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<U> getValue(@Nullable CompositeSnapshot snapshot, T keySuffix, boolean existsAlmostCertainly) {
|
public Mono<U> getValue(@Nullable CompositeSnapshot snapshot, T keySuffix, boolean existsAlmostCertainly) {
|
||||||
return dictionary
|
return dictionary
|
||||||
.get(resolveSnapshot(snapshot),
|
.get(resolveSnapshot(snapshot),
|
||||||
Mono.fromCallable(() -> toKey(serializeSuffix(keySuffix))),
|
Mono.fromCallable(() -> serializeKeySuffixToKey(keySuffix)),
|
||||||
existsAlmostCertainly
|
existsAlmostCertainly
|
||||||
)
|
)
|
||||||
.handle(this::deserializeValue);
|
.handle(this::deserializeValue);
|
||||||
|
@ -167,8 +185,8 @@ public class DatabaseMapDictionary<T, U> extends DatabaseMapDictionaryDeep<T, U,
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<Void> putValue(T keySuffix, U value) {
|
public Mono<Void> putValue(T keySuffix, U value) {
|
||||||
var keyMono = Mono.fromCallable(() -> toKey(serializeSuffix(keySuffix))).single();
|
var keyMono = Mono.fromCallable(() -> serializeKeySuffixToKey(keySuffix)).single();
|
||||||
var valueMono = Mono.fromCallable(() -> valueSerializer.serialize(value)).single();
|
var valueMono = Mono.fromCallable(() -> serializeValue(value)).single();
|
||||||
return dictionary
|
return dictionary
|
||||||
.put(keyMono, valueMono, LLDictionaryResultType.VOID)
|
.put(keyMono, valueMono, LLDictionaryResultType.VOID)
|
||||||
.doOnNext(Send::close)
|
.doOnNext(Send::close)
|
||||||
|
@ -185,7 +203,7 @@ public class DatabaseMapDictionary<T, U> extends DatabaseMapDictionaryDeep<T, U,
|
||||||
UpdateReturnMode updateReturnMode,
|
UpdateReturnMode updateReturnMode,
|
||||||
boolean existsAlmostCertainly,
|
boolean existsAlmostCertainly,
|
||||||
SerializationFunction<@Nullable U, @Nullable U> updater) {
|
SerializationFunction<@Nullable U, @Nullable U> updater) {
|
||||||
var keyMono = Mono.fromCallable(() -> toKey(serializeSuffix(keySuffix)));
|
var keyMono = Mono.fromCallable(() -> serializeKeySuffixToKey(keySuffix));
|
||||||
return dictionary
|
return dictionary
|
||||||
.update(keyMono, getSerializedUpdater(updater), updateReturnMode, existsAlmostCertainly)
|
.update(keyMono, getSerializedUpdater(updater), updateReturnMode, existsAlmostCertainly)
|
||||||
.handle(this::deserializeValue);
|
.handle(this::deserializeValue);
|
||||||
|
@ -195,12 +213,14 @@ public class DatabaseMapDictionary<T, U> extends DatabaseMapDictionaryDeep<T, U,
|
||||||
public Mono<Delta<U>> updateValueAndGetDelta(T keySuffix,
|
public Mono<Delta<U>> updateValueAndGetDelta(T keySuffix,
|
||||||
boolean existsAlmostCertainly,
|
boolean existsAlmostCertainly,
|
||||||
SerializationFunction<@Nullable U, @Nullable U> updater) {
|
SerializationFunction<@Nullable U, @Nullable U> updater) {
|
||||||
var keyMono = Mono.fromCallable(() -> toKey(serializeSuffix(keySuffix)));
|
var keyMono = Mono.fromCallable(() -> serializeKeySuffixToKey(keySuffix));
|
||||||
return dictionary
|
return dictionary
|
||||||
.updateAndGetDelta(keyMono, getSerializedUpdater(updater), existsAlmostCertainly)
|
.updateAndGetDelta(keyMono, getSerializedUpdater(updater), existsAlmostCertainly)
|
||||||
.transform(mono -> LLUtils.mapLLDelta(mono,
|
.transform(mono -> LLUtils.mapLLDelta(mono, serializedToReceive -> {
|
||||||
serialized -> valueSerializer.deserialize(serialized).deserializedData()
|
try (var serialized = serializedToReceive.receive()) {
|
||||||
));
|
return valueSerializer.deserialize(serialized);
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
public SerializationFunction<@Nullable Send<Buffer>, @Nullable Send<Buffer>> getSerializedUpdater(
|
public SerializationFunction<@Nullable Send<Buffer>, @Nullable Send<Buffer>> getSerializedUpdater(
|
||||||
|
@ -211,12 +231,14 @@ public class DatabaseMapDictionary<T, U> extends DatabaseMapDictionaryDeep<T, U,
|
||||||
if (oldSerialized == null) {
|
if (oldSerialized == null) {
|
||||||
result = updater.apply(null);
|
result = updater.apply(null);
|
||||||
} else {
|
} else {
|
||||||
result = updater.apply(valueSerializer.deserialize(oldSerialized).deserializedData());
|
try (var oldSerializedReceived = oldSerialized.receive()) {
|
||||||
|
result = updater.apply(valueSerializer.deserialize(oldSerializedReceived));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
return valueSerializer.serialize(result);
|
return serializeValue(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -230,12 +252,14 @@ public class DatabaseMapDictionary<T, U> extends DatabaseMapDictionaryDeep<T, U,
|
||||||
if (oldSerialized == null) {
|
if (oldSerialized == null) {
|
||||||
result = updater.apply(null, extra);
|
result = updater.apply(null, extra);
|
||||||
} else {
|
} else {
|
||||||
result = updater.apply(valueSerializer.deserialize(oldSerialized).deserializedData(), extra);
|
try (var oldSerializedReceived = oldSerialized.receive()) {
|
||||||
|
result = updater.apply(valueSerializer.deserialize(oldSerializedReceived), extra);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
return valueSerializer.serialize(result);
|
return serializeValue(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -243,19 +267,15 @@ public class DatabaseMapDictionary<T, U> extends DatabaseMapDictionaryDeep<T, U,
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<U> putValueAndGetPrevious(T keySuffix, U value) {
|
public Mono<U> putValueAndGetPrevious(T keySuffix, U value) {
|
||||||
var keyMono = Mono.fromCallable(() -> toKey(serializeSuffix(keySuffix)));
|
var keyMono = Mono.fromCallable(() -> serializeKeySuffixToKey(keySuffix));
|
||||||
var valueMono = Mono.fromCallable(() -> valueSerializer.serialize(value));
|
var valueMono = Mono.fromCallable(() -> serializeValue(value));
|
||||||
return dictionary
|
return dictionary.put(keyMono, valueMono, LLDictionaryResultType.PREVIOUS_VALUE).handle(this::deserializeValue);
|
||||||
.put(keyMono,
|
|
||||||
valueMono,
|
|
||||||
LLDictionaryResultType.PREVIOUS_VALUE)
|
|
||||||
.handle(this::deserializeValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<Boolean> putValueAndGetChanged(T keySuffix, U value) {
|
public Mono<Boolean> putValueAndGetChanged(T keySuffix, U value) {
|
||||||
var keyMono = Mono.fromCallable(() -> toKey(serializeSuffix(keySuffix)));
|
var keyMono = Mono.fromCallable(() -> serializeKeySuffixToKey(keySuffix));
|
||||||
var valueMono = Mono.fromCallable(() -> valueSerializer.serialize(value));
|
var valueMono = Mono.fromCallable(() -> serializeValue(value));
|
||||||
return dictionary
|
return dictionary
|
||||||
.put(keyMono, valueMono, LLDictionaryResultType.PREVIOUS_VALUE)
|
.put(keyMono, valueMono, LLDictionaryResultType.PREVIOUS_VALUE)
|
||||||
.handle(this::deserializeValue)
|
.handle(this::deserializeValue)
|
||||||
|
@ -265,7 +285,7 @@ public class DatabaseMapDictionary<T, U> extends DatabaseMapDictionaryDeep<T, U,
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<Void> remove(T keySuffix) {
|
public Mono<Void> remove(T keySuffix) {
|
||||||
var keyMono = Mono.fromCallable(() -> toKey(serializeSuffix(keySuffix)));
|
var keyMono = Mono.fromCallable(() -> serializeKeySuffixToKey(keySuffix));
|
||||||
return dictionary
|
return dictionary
|
||||||
.remove(keyMono, LLDictionaryResultType.VOID)
|
.remove(keyMono, LLDictionaryResultType.VOID)
|
||||||
.doOnNext(Send::close)
|
.doOnNext(Send::close)
|
||||||
|
@ -274,15 +294,13 @@ public class DatabaseMapDictionary<T, U> extends DatabaseMapDictionaryDeep<T, U,
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<U> removeAndGetPrevious(T keySuffix) {
|
public Mono<U> removeAndGetPrevious(T keySuffix) {
|
||||||
var keyMono = Mono.fromCallable(() -> toKey(serializeSuffix(keySuffix)));
|
var keyMono = Mono.fromCallable(() -> serializeKeySuffixToKey(keySuffix));
|
||||||
return dictionary
|
return dictionary.remove(keyMono, LLDictionaryResultType.PREVIOUS_VALUE).handle(this::deserializeValue);
|
||||||
.remove(keyMono, LLDictionaryResultType.PREVIOUS_VALUE)
|
|
||||||
.handle(this::deserializeValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<Boolean> removeAndGetStatus(T keySuffix) {
|
public Mono<Boolean> removeAndGetStatus(T keySuffix) {
|
||||||
var keyMono = Mono.fromCallable(() -> toKey(serializeSuffix(keySuffix)));
|
var keyMono = Mono.fromCallable(() -> serializeKeySuffixToKey(keySuffix));
|
||||||
return dictionary
|
return dictionary
|
||||||
.remove(keyMono, LLDictionaryResultType.PREVIOUS_VALUE_EXISTENCE)
|
.remove(keyMono, LLDictionaryResultType.PREVIOUS_VALUE_EXISTENCE)
|
||||||
.map(LLUtils::responseToBoolean);
|
.map(LLUtils::responseToBoolean);
|
||||||
|
@ -293,8 +311,9 @@ public class DatabaseMapDictionary<T, U> extends DatabaseMapDictionaryDeep<T, U,
|
||||||
var mappedKeys = keys
|
var mappedKeys = keys
|
||||||
.<Tuple2<T, Send<Buffer>>>handle((keySuffix, sink) -> {
|
.<Tuple2<T, Send<Buffer>>>handle((keySuffix, sink) -> {
|
||||||
try {
|
try {
|
||||||
sink.next(Tuples.of(keySuffix, toKey(serializeSuffix(keySuffix))));
|
Tuple2<T, Send<Buffer>> tuple = Tuples.of(keySuffix, serializeKeySuffixToKey(keySuffix));
|
||||||
} catch (SerializationException ex) {
|
sink.next(tuple);
|
||||||
|
} catch (Throwable ex) {
|
||||||
sink.error(ex);
|
sink.error(ex);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -304,14 +323,14 @@ public class DatabaseMapDictionary<T, U> extends DatabaseMapDictionaryDeep<T, U,
|
||||||
try {
|
try {
|
||||||
Optional<U> valueOpt;
|
Optional<U> valueOpt;
|
||||||
if (entry.getT3().isPresent()) {
|
if (entry.getT3().isPresent()) {
|
||||||
try (var buf = entry.getT3().get()) {
|
try (var buf = entry.getT3().get().receive()) {
|
||||||
valueOpt = Optional.of(valueSerializer.deserialize(buf).deserializedData());
|
valueOpt = Optional.of(valueSerializer.deserialize(buf));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
valueOpt = Optional.empty();
|
valueOpt = Optional.empty();
|
||||||
}
|
}
|
||||||
sink.next(Map.entry(entry.getT1(), valueOpt));
|
sink.next(Map.entry(entry.getT1(), valueOpt));
|
||||||
} catch (SerializationException ex) {
|
} catch (Throwable ex) {
|
||||||
sink.error(ex);
|
sink.error(ex);
|
||||||
} finally {
|
} finally {
|
||||||
entry.getT2().close();
|
entry.getT2().close();
|
||||||
|
@ -321,22 +340,29 @@ public class DatabaseMapDictionary<T, U> extends DatabaseMapDictionaryDeep<T, U,
|
||||||
.transform(LLUtils::handleDiscard);
|
.transform(LLUtils::handleDiscard);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Send<LLEntry> serializeEntry(T key, U value) throws SerializationException {
|
private Send<LLEntry> serializeEntry(T keySuffix, U value) throws SerializationException {
|
||||||
try (var serializedKey = toKey(serializeSuffix(key))) {
|
try (var key = serializeKeySuffixToKey(keySuffix)) {
|
||||||
var serializedValueToReceive = valueSerializer.serialize(value);
|
try (var serializedValue = serializeValue(value)) {
|
||||||
try (var serializedValue = serializedValueToReceive.receive()) {
|
return LLEntry.of(key, serializedValue).send();
|
||||||
return LLEntry.of(serializedKey, serializedValue.send()).send();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void serializeEntrySink(Entry<T,U> entry, SynchronousSink<Send<LLEntry>> sink) {
|
||||||
|
try {
|
||||||
|
sink.next(serializeEntry(entry.getKey(), entry.getValue()));
|
||||||
|
} catch (Throwable e) {
|
||||||
|
sink.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<Void> putMulti(Flux<Entry<T, U>> entries) {
|
public Mono<Void> putMulti(Flux<Entry<T, U>> entries) {
|
||||||
var serializedEntries = entries
|
var serializedEntries = entries
|
||||||
.<Send<LLEntry>>handle((entry, sink) -> {
|
.<Send<LLEntry>>handle((entry, sink) -> {
|
||||||
try {
|
try {
|
||||||
sink.next(serializeEntry(entry.getKey(), entry.getValue()));
|
sink.next(serializeEntry(entry.getKey(), entry.getValue()));
|
||||||
} catch (SerializationException e) {
|
} catch (Throwable e) {
|
||||||
sink.error(e);
|
sink.error(e);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -363,8 +389,9 @@ public class DatabaseMapDictionary<T, U> extends DatabaseMapDictionaryDeep<T, U,
|
||||||
var serializedEntries = entries
|
var serializedEntries = entries
|
||||||
.<Tuple2<Send<Buffer>, X>>handle((entry, sink) -> {
|
.<Tuple2<Send<Buffer>, X>>handle((entry, sink) -> {
|
||||||
try {
|
try {
|
||||||
sink.next(Tuples.of(serializeSuffix(entry.getT1()), entry.getT2()));
|
Send<Buffer> serializedKey = serializeKeySuffixToKey(entry.getT1());
|
||||||
} catch (SerializationException ex) {
|
sink.next(Tuples.of(serializedKey, entry.getT2()));
|
||||||
|
} catch (Throwable ex) {
|
||||||
sink.error(ex);
|
sink.error(ex);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -377,17 +404,17 @@ public class DatabaseMapDictionary<T, U> extends DatabaseMapDictionaryDeep<T, U,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
var serializedUpdater = getSerializedUpdater(updater);
|
var serializedUpdater = getSerializedUpdater(updater);
|
||||||
return dictionary.updateMulti(serializedEntries, serializedUpdater)
|
return dictionary.updateMulti(serializedEntries, serializedUpdater).handle((result, sink) -> {
|
||||||
.handle((result, sink) -> {
|
try {
|
||||||
try {
|
T keySuffix;
|
||||||
sink.next(new ExtraKeyOperationResult<>(deserializeSuffix(result.key()),
|
try (var keySuffixBuf = result.key().receive()) {
|
||||||
result.extra(),
|
keySuffix = deserializeSuffix(keySuffixBuf);
|
||||||
result.changed()
|
}
|
||||||
));
|
sink.next(new ExtraKeyOperationResult<>(keySuffix, result.extra(), result.changed()));
|
||||||
} catch (SerializationException ex) {
|
} catch (Throwable ex) {
|
||||||
sink.error(ex);
|
sink.error(ex);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -398,12 +425,15 @@ public class DatabaseMapDictionary<T, U> extends DatabaseMapDictionaryDeep<T, U,
|
||||||
try (var keyBuf = keyBufToReceive.receive()) {
|
try (var keyBuf = keyBufToReceive.receive()) {
|
||||||
assert keyBuf.readableBytes() == keyPrefixLength + keySuffixLength + keyExtLength;
|
assert keyBuf.readableBytes() == keyPrefixLength + keySuffixLength + keyExtLength;
|
||||||
// Remove prefix. Keep only the suffix and the ext
|
// Remove prefix. Keep only the suffix and the ext
|
||||||
removePrefix(keyBuf);
|
splitPrefix(keyBuf).close();
|
||||||
suffixKeyConsistency(keyBuf.readableBytes());
|
suffixKeyLengthConsistency(keyBuf.readableBytes());
|
||||||
sink.next(Map.entry(deserializeSuffix(keyBuf.copy().send()),
|
T keySuffix;
|
||||||
new DatabaseSingle<>(dictionary, toKey(keyBuf.send()), valueSerializer, null)
|
try (var keyBufCopy = keyBuf.copy()) {
|
||||||
));
|
keySuffix = deserializeSuffix(keyBufCopy);
|
||||||
} catch (SerializationException ex) {
|
}
|
||||||
|
var subStage = new DatabaseSingle<>(dictionary, toKey(keyBuf.send()), valueSerializer, null);
|
||||||
|
sink.next(Map.entry(keySuffix, subStage));
|
||||||
|
} catch (Throwable ex) {
|
||||||
sink.error(ex);
|
sink.error(ex);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -414,16 +444,25 @@ public class DatabaseMapDictionary<T, U> extends DatabaseMapDictionaryDeep<T, U,
|
||||||
return dictionary
|
return dictionary
|
||||||
.getRange(resolveSnapshot(snapshot), rangeMono)
|
.getRange(resolveSnapshot(snapshot), rangeMono)
|
||||||
.<Entry<T, U>>handle((serializedEntryToReceive, sink) -> {
|
.<Entry<T, U>>handle((serializedEntryToReceive, sink) -> {
|
||||||
try (var serializedEntry = serializedEntryToReceive.receive()) {
|
try {
|
||||||
try (var keyBuf = serializedEntry.getKey().receive()) {
|
Entry<T, U> entry;
|
||||||
assert keyBuf.readableBytes() == keyPrefixLength + keySuffixLength + keyExtLength;
|
try (var serializedEntry = serializedEntryToReceive.receive()) {
|
||||||
// Remove prefix. Keep only the suffix and the ext
|
try (var keyBuf = serializedEntry.getKey().receive()) {
|
||||||
removePrefix(keyBuf);
|
assert keyBuf.readableBytes() == keyPrefixLength + keySuffixLength + keyExtLength;
|
||||||
suffixKeyConsistency(keyBuf.readableBytes());
|
// Remove prefix. Keep only the suffix and the ext
|
||||||
sink.next(Map.entry(deserializeSuffix(keyBuf.send()),
|
splitPrefix(keyBuf).close();
|
||||||
valueSerializer.deserialize(serializedEntry.getValue()).deserializedData()));
|
suffixKeyLengthConsistency(keyBuf.readableBytes());
|
||||||
|
T keySuffix = deserializeSuffix(keyBuf);
|
||||||
|
|
||||||
|
U value;
|
||||||
|
try (var valueBuf = serializedEntry.getValue().receive()) {
|
||||||
|
value = valueSerializer.deserialize(valueBuf);
|
||||||
|
}
|
||||||
|
entry = Map.entry(keySuffix, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (SerializationException e) {
|
sink.next(entry);
|
||||||
|
} catch (Throwable e) {
|
||||||
sink.error(e);
|
sink.error(e);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -441,14 +480,7 @@ public class DatabaseMapDictionary<T, U> extends DatabaseMapDictionaryDeep<T, U,
|
||||||
public Flux<Entry<T, U>> setAllValuesAndGetPrevious(Flux<Entry<T, U>> entries) {
|
public Flux<Entry<T, U>> setAllValuesAndGetPrevious(Flux<Entry<T, U>> entries) {
|
||||||
return Flux.concat(
|
return Flux.concat(
|
||||||
this.getAllValues(null),
|
this.getAllValues(null),
|
||||||
dictionary.setRange(rangeMono, entries.handle((entry, sink) -> {
|
dictionary.setRange(rangeMono, entries.handle(this::serializeEntrySink)).then(Mono.empty())
|
||||||
try {
|
|
||||||
sink.next(LLEntry.of(toKey(serializeSuffix(entry.getKey())),
|
|
||||||
valueSerializer.serialize(entry.getValue())).send());
|
|
||||||
} catch (SerializationException e) {
|
|
||||||
sink.error(e);
|
|
||||||
}
|
|
||||||
})).then(Mono.empty())
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -269,7 +269,7 @@ public class DatabaseMapDictionaryDeep<T, U, US extends DatabaseStage<U>> extend
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
protected boolean suffixKeyConsistency(int keySuffixLength) {
|
protected boolean suffixKeyLengthConsistency(int keySuffixLength) {
|
||||||
return this.keySuffixLength == keySuffixLength;
|
return this.keySuffixLength == keySuffixLength;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -285,13 +285,15 @@ public class DatabaseMapDictionaryDeep<T, U, US extends DatabaseStage<U>> extend
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the prefix from the key
|
* Removes the prefix from the key
|
||||||
|
* @return the prefix
|
||||||
*/
|
*/
|
||||||
protected void removePrefix(Buffer key) {
|
protected Buffer splitPrefix(Buffer key) {
|
||||||
assert key.readableBytes() == keyPrefixLength + keySuffixLength + keyExtLength
|
assert key.readableBytes() == keyPrefixLength + keySuffixLength + keyExtLength
|
||||||
|| key.readableBytes() == keyPrefixLength + keySuffixLength;
|
|| key.readableBytes() == keyPrefixLength + keySuffixLength;
|
||||||
key.readerOffset(key.readerOffset() + this.keyPrefixLength);
|
var prefix = key.readSplit(this.keyPrefixLength);
|
||||||
assert key.readableBytes() == keySuffixLength + keyExtLength
|
assert key.readableBytes() == keySuffixLength + keyExtLength
|
||||||
|| key.readableBytes() == keySuffixLength;
|
|| key.readableBytes() == keySuffixLength;
|
||||||
|
return prefix;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -334,7 +336,13 @@ public class DatabaseMapDictionaryDeep<T, U, US extends DatabaseStage<U>> extend
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<US> at(@Nullable CompositeSnapshot snapshot, T keySuffix) {
|
public Mono<US> at(@Nullable CompositeSnapshot snapshot, T keySuffix) {
|
||||||
var suffixKeyWithoutExt = Mono.fromCallable(() -> toKeyWithoutExt(serializeSuffix(keySuffix)));
|
var suffixKeyWithoutExt = Mono.fromCallable(() -> {
|
||||||
|
try (var keyWithoutExtBuf = keyPrefix.copy()) {
|
||||||
|
keyWithoutExtBuf.ensureWritable(keySuffixLength + keyExtLength);
|
||||||
|
serializeSuffix(keySuffix, keyWithoutExtBuf);
|
||||||
|
return keyWithoutExtBuf.send();
|
||||||
|
}
|
||||||
|
});
|
||||||
return this.subStageGetter
|
return this.subStageGetter
|
||||||
.subStage(dictionary, snapshot, suffixKeyWithoutExt)
|
.subStage(dictionary, snapshot, suffixKeyWithoutExt)
|
||||||
.transform(LLUtils::handleDiscard)
|
.transform(LLUtils::handleDiscard)
|
||||||
|
@ -360,8 +368,10 @@ public class DatabaseMapDictionaryDeep<T, U, US extends DatabaseStage<U>> extend
|
||||||
groupKeyWithoutExtSend -> this.subStageGetter
|
groupKeyWithoutExtSend -> this.subStageGetter
|
||||||
.subStage(dictionary, snapshot, Mono.fromCallable(() -> groupKeyWithoutExtSend.copy().send()))
|
.subStage(dictionary, snapshot, Mono.fromCallable(() -> groupKeyWithoutExtSend.copy().send()))
|
||||||
.<Entry<T, US>>handle((us, sink) -> {
|
.<Entry<T, US>>handle((us, sink) -> {
|
||||||
|
T deserializedSuffix;
|
||||||
try {
|
try {
|
||||||
sink.next(Map.entry(this.deserializeSuffix(getGroupSuffix(groupKeyWithoutExtSend.send())), us));
|
deserializedSuffix = this.deserializeSuffix(splitGroupSuffix(groupKeyWithoutExtSend));
|
||||||
|
sink.next(Map.entry(deserializedSuffix, us));
|
||||||
} catch (SerializationException ex) {
|
} catch (SerializationException ex) {
|
||||||
sink.error(ex);
|
sink.error(ex);
|
||||||
}
|
}
|
||||||
|
@ -371,13 +381,18 @@ public class DatabaseMapDictionaryDeep<T, U, US extends DatabaseStage<U>> extend
|
||||||
.transform(LLUtils::handleDiscard);
|
.transform(LLUtils::handleDiscard);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Send<Buffer> getGroupSuffix(Send<Buffer> groupKeyWithoutExt) {
|
/**
|
||||||
try (var buffer = groupKeyWithoutExt.receive()) {
|
* Split the input. The input will become the ext, the returned data will be the group suffix
|
||||||
assert subStageKeysConsistency(buffer.readableBytes() + keyExtLength);
|
* @param groupKey group key, will become ext
|
||||||
this.removePrefix(buffer);
|
* @return group suffix
|
||||||
assert subStageKeysConsistency(keyPrefixLength + buffer.readableBytes() + keyExtLength);
|
*/
|
||||||
return buffer.send();
|
private Buffer splitGroupSuffix(@NotNull Buffer groupKey) {
|
||||||
}
|
assert subStageKeysConsistency(groupKey.readableBytes())
|
||||||
|
|| subStageKeysConsistency(groupKey.readableBytes() + keyExtLength);
|
||||||
|
this.splitPrefix(groupKey).close();
|
||||||
|
assert subStageKeysConsistency(keyPrefixLength + groupKey.readableBytes())
|
||||||
|
|| subStageKeysConsistency(keyPrefixLength + groupKey.readableBytes() + keyExtLength);
|
||||||
|
return groupKey.readSplit(keySuffixLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Send<Buffer> getGroupWithoutExt(Send<Buffer> groupKeyWithExtSend) {
|
private Send<Buffer> getGroupWithoutExt(Send<Buffer> groupKeyWithExtSend) {
|
||||||
|
@ -430,25 +445,21 @@ public class DatabaseMapDictionaryDeep<T, U, US extends DatabaseStage<U>> extend
|
||||||
}
|
}
|
||||||
|
|
||||||
//todo: temporary wrapper. convert the whole class to buffers
|
//todo: temporary wrapper. convert the whole class to buffers
|
||||||
protected T deserializeSuffix(@NotNull Send<Buffer> keySuffixToReceive) throws SerializationException {
|
protected T deserializeSuffix(@NotNull Buffer keySuffix) throws SerializationException {
|
||||||
try (var keySuffix = keySuffixToReceive.receive()) {
|
assert suffixKeyLengthConsistency(keySuffix.readableBytes());
|
||||||
assert suffixKeyConsistency(keySuffix.readableBytes());
|
var result = keySuffixSerializer.deserialize(keySuffix);
|
||||||
var result = keySuffixSerializer.deserialize(keySuffix.send());
|
assert keyPrefix.isAccessible();
|
||||||
assert keyPrefix.isAccessible();
|
return result;
|
||||||
return result.deserializedData();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//todo: temporary wrapper. convert the whole class to buffers
|
//todo: temporary wrapper. convert the whole class to buffers
|
||||||
@NotNull
|
protected void serializeSuffix(T keySuffix, Buffer output) throws SerializationException {
|
||||||
protected Send<Buffer> serializeSuffix(T keySuffix) throws SerializationException {
|
output.ensureWritable(keySuffixLength);
|
||||||
try (var suffixDataToReceive = keySuffixSerializer.serialize(keySuffix)) {
|
var beforeWriterOffset = output.writerOffset();
|
||||||
try (Buffer suffixData = suffixDataToReceive.receive()) {
|
keySuffixSerializer.serialize(keySuffix, output);
|
||||||
assert suffixKeyConsistency(suffixData.readableBytes());
|
var afterWriterOffset = output.writerOffset();
|
||||||
assert keyPrefix.isAccessible();
|
assert suffixKeyLengthConsistency(afterWriterOffset - beforeWriterOffset);
|
||||||
return suffixData.send();
|
assert keyPrefix.isAccessible();
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -77,9 +77,9 @@ public class DatabaseMapDictionaryHashed<T, U, TH> extends ResourceSupport<Datab
|
||||||
}
|
}
|
||||||
this.alloc = dictionary.getAllocator();
|
this.alloc = dictionary.getAllocator();
|
||||||
ValueWithHashSerializer<T, U> valueWithHashSerializer
|
ValueWithHashSerializer<T, U> valueWithHashSerializer
|
||||||
= new ValueWithHashSerializer<>(alloc, keySuffixSerializer, valueSerializer);
|
= new ValueWithHashSerializer<>(keySuffixSerializer, valueSerializer);
|
||||||
ValuesSetSerializer<Entry<T, U>> valuesSetSerializer
|
ValuesSetSerializer<Entry<T, U>> valuesSetSerializer
|
||||||
= new ValuesSetSerializer<>(alloc, valueWithHashSerializer);
|
= new ValuesSetSerializer<>(valueWithHashSerializer);
|
||||||
this.subDictionary = DatabaseMapDictionary.tail(dictionary, prefixKey, keySuffixHashSerializer,
|
this.subDictionary = DatabaseMapDictionary.tail(dictionary, prefixKey, keySuffixHashSerializer,
|
||||||
valuesSetSerializer, onClose);
|
valuesSetSerializer, onClose);
|
||||||
this.keySuffixHashFunction = keySuffixHashFunction;
|
this.keySuffixHashFunction = keySuffixHashFunction;
|
||||||
|
|
|
@ -84,13 +84,26 @@ public class DatabaseSingle<U> extends ResourceSupport<DatabaseStage<U>, Databas
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deserializeValue(Send<Buffer> value, SynchronousSink<U> sink) {
|
private void deserializeValue(Send<Buffer> value, SynchronousSink<U> sink) {
|
||||||
try (value) {
|
try {
|
||||||
sink.next(serializer.deserialize(value).deserializedData());
|
U deserializedValue;
|
||||||
|
try (var valueBuf = value.receive()) {
|
||||||
|
deserializedValue = serializer.deserialize(valueBuf);
|
||||||
|
}
|
||||||
|
sink.next(deserializedValue);
|
||||||
} catch (SerializationException ex) {
|
} catch (SerializationException ex) {
|
||||||
sink.error(ex);
|
sink.error(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Send<Buffer> serializeValue(U value) throws SerializationException {
|
||||||
|
var valSizeHint = serializer.getSerializedSizeHint();
|
||||||
|
if (valSizeHint == -1) valSizeHint = 128;
|
||||||
|
try (var valBuf = dictionary.getAllocator().allocate(valSizeHint)) {
|
||||||
|
serializer.serialize(value, valBuf);
|
||||||
|
return valBuf.send();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<U> get(@Nullable CompositeSnapshot snapshot, boolean existsAlmostCertainly) {
|
public Mono<U> get(@Nullable CompositeSnapshot snapshot, boolean existsAlmostCertainly) {
|
||||||
return dictionary
|
return dictionary
|
||||||
|
@ -101,7 +114,7 @@ public class DatabaseSingle<U> extends ResourceSupport<DatabaseStage<U>, Databas
|
||||||
@Override
|
@Override
|
||||||
public Mono<U> setAndGetPrevious(U value) {
|
public Mono<U> setAndGetPrevious(U value) {
|
||||||
return dictionary
|
return dictionary
|
||||||
.put(keyMono, Mono.fromCallable(() -> serializer.serialize(value)), LLDictionaryResultType.PREVIOUS_VALUE)
|
.put(keyMono, Mono.fromCallable(() -> serializeValue(value)), LLDictionaryResultType.PREVIOUS_VALUE)
|
||||||
.handle(this::deserializeValue);
|
.handle(this::deserializeValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,12 +125,20 @@ public class DatabaseSingle<U> extends ResourceSupport<DatabaseStage<U>, Databas
|
||||||
return dictionary
|
return dictionary
|
||||||
.update(keyMono, (oldValueSer) -> {
|
.update(keyMono, (oldValueSer) -> {
|
||||||
try (oldValueSer) {
|
try (oldValueSer) {
|
||||||
var result = updater.apply(oldValueSer == null ? null
|
U result;
|
||||||
: serializer.deserialize(oldValueSer).deserializedData());
|
if (oldValueSer == null) {
|
||||||
|
result = updater.apply(null);
|
||||||
|
} else {
|
||||||
|
U deserializedValue;
|
||||||
|
try (var valueBuf = oldValueSer.receive()) {
|
||||||
|
deserializedValue = serializer.deserialize(valueBuf);
|
||||||
|
}
|
||||||
|
result = updater.apply(deserializedValue);
|
||||||
|
}
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
return serializer.serialize(result);
|
return serializeValue(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, updateReturnMode, existsAlmostCertainly)
|
}, updateReturnMode, existsAlmostCertainly)
|
||||||
|
@ -130,17 +151,27 @@ public class DatabaseSingle<U> extends ResourceSupport<DatabaseStage<U>, Databas
|
||||||
return dictionary
|
return dictionary
|
||||||
.updateAndGetDelta(keyMono, (oldValueSer) -> {
|
.updateAndGetDelta(keyMono, (oldValueSer) -> {
|
||||||
try (oldValueSer) {
|
try (oldValueSer) {
|
||||||
var result = updater.apply(oldValueSer == null ? null
|
U result;
|
||||||
: serializer.deserialize(oldValueSer).deserializedData());
|
if (oldValueSer == null) {
|
||||||
|
result = updater.apply(null);
|
||||||
|
} else {
|
||||||
|
U deserializedValue;
|
||||||
|
try (var valueBuf = oldValueSer.receive()) {
|
||||||
|
deserializedValue = serializer.deserialize(valueBuf);
|
||||||
|
}
|
||||||
|
result = updater.apply(deserializedValue);
|
||||||
|
}
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
return null;
|
return null;
|
||||||
} else {
|
} else {
|
||||||
return serializer.serialize(result);
|
return serializeValue(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, existsAlmostCertainly).transform(mono -> LLUtils.mapLLDelta(mono,
|
}, existsAlmostCertainly).transform(mono -> LLUtils.mapLLDelta(mono, serialized -> {
|
||||||
serialized -> serializer.deserialize(serialized).deserializedData()
|
try (var valueBuf = serialized.receive()) {
|
||||||
));
|
return serializer.deserialize(valueBuf);
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -7,6 +7,6 @@ import it.cavallium.dbengine.database.serialization.Serializer;
|
||||||
public class SubStageGetterSingleBytes extends SubStageGetterSingle<Send<Buffer>> {
|
public class SubStageGetterSingleBytes extends SubStageGetterSingle<Send<Buffer>> {
|
||||||
|
|
||||||
public SubStageGetterSingleBytes() {
|
public SubStageGetterSingleBytes() {
|
||||||
super(Serializer.noop());
|
super(Serializer.NOOP_SEND_SERIALIZER);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,38 +15,40 @@ import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
class ValueWithHashSerializer<X, Y> implements Serializer<Entry<X, Y>> {
|
class ValueWithHashSerializer<X, Y> implements Serializer<Entry<X, Y>> {
|
||||||
|
|
||||||
private final BufferAllocator allocator;
|
|
||||||
private final Serializer<X> keySuffixSerializer;
|
private final Serializer<X> keySuffixSerializer;
|
||||||
private final Serializer<Y> valueSerializer;
|
private final Serializer<Y> valueSerializer;
|
||||||
|
|
||||||
ValueWithHashSerializer(BufferAllocator allocator,
|
ValueWithHashSerializer(
|
||||||
Serializer<X> keySuffixSerializer,
|
Serializer<X> keySuffixSerializer,
|
||||||
Serializer<Y> valueSerializer) {
|
Serializer<Y> valueSerializer) {
|
||||||
this.allocator = allocator;
|
|
||||||
this.keySuffixSerializer = keySuffixSerializer;
|
this.keySuffixSerializer = keySuffixSerializer;
|
||||||
this.valueSerializer = valueSerializer;
|
this.valueSerializer = valueSerializer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull DeserializationResult<Entry<X, Y>> deserialize(@Nullable Send<Buffer> serializedToReceive)
|
public @NotNull Entry<X, Y> deserialize(@NotNull Buffer serialized) throws SerializationException {
|
||||||
throws SerializationException {
|
Objects.requireNonNull(serialized);
|
||||||
Objects.requireNonNull(serializedToReceive);
|
X deserializedKey = keySuffixSerializer.deserialize(serialized);
|
||||||
try (var serialized = serializedToReceive.receive()) {
|
Y deserializedValue = valueSerializer.deserialize(serialized);
|
||||||
DeserializationResult<X> deserializedKey = keySuffixSerializer.deserialize(serialized.copy().send());
|
return Map.entry(deserializedKey, deserializedValue);
|
||||||
DeserializationResult<Y> deserializedValue = valueSerializer.deserialize(serialized
|
|
||||||
.copy(serialized.readerOffset() + deserializedKey.bytesRead(),
|
|
||||||
serialized.readableBytes() - deserializedKey.bytesRead()
|
|
||||||
)
|
|
||||||
.send());
|
|
||||||
return new DeserializationResult<>(Map.entry(deserializedKey.deserializedData(),
|
|
||||||
deserializedValue.deserializedData()), deserializedKey.bytesRead() + deserializedValue.bytesRead());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Send<Buffer> serialize(@NotNull Entry<X, Y> deserialized) throws SerializationException {
|
public void serialize(@NotNull Entry<X, Y> deserialized, Buffer output) throws SerializationException {
|
||||||
var keySuffix = keySuffixSerializer.serialize(deserialized.getKey());
|
keySuffixSerializer.serialize(deserialized.getKey(), output);
|
||||||
var value = valueSerializer.serialize(deserialized.getValue());
|
valueSerializer.serialize(deserialized.getValue(), output);
|
||||||
return LLUtils.compositeBuffer(allocator, keySuffix, value).send();
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSerializedSizeHint() {
|
||||||
|
var hint1 = keySuffixSerializer.getSerializedSizeHint();
|
||||||
|
var hint2 = valueSerializer.getSerializedSizeHint();
|
||||||
|
if (hint1 == -1 && hint2 == -1) {
|
||||||
|
return -1;
|
||||||
|
} else if (hint1 == -1) {
|
||||||
|
return hint2;
|
||||||
|
} else {
|
||||||
|
return hint1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,46 +13,34 @@ import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
class ValuesSetSerializer<X> implements Serializer<ObjectArraySet<X>> {
|
class ValuesSetSerializer<X> implements Serializer<ObjectArraySet<X>> {
|
||||||
|
|
||||||
private final BufferAllocator allocator;
|
|
||||||
private final Serializer<X> entrySerializer;
|
private final Serializer<X> entrySerializer;
|
||||||
|
|
||||||
ValuesSetSerializer(BufferAllocator allocator, Serializer<X> entrySerializer) {
|
ValuesSetSerializer(Serializer<X> entrySerializer) {
|
||||||
this.allocator = allocator;
|
|
||||||
this.entrySerializer = entrySerializer;
|
this.entrySerializer = entrySerializer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull DeserializationResult<ObjectArraySet<X>> deserialize(@Nullable Send<Buffer> serializedToReceive) throws SerializationException {
|
public @NotNull ObjectArraySet<X> deserialize(@NotNull Buffer serialized) throws SerializationException {
|
||||||
Objects.requireNonNull(serializedToReceive);
|
Objects.requireNonNull(serialized);
|
||||||
try (var serialized = serializedToReceive.receive()) {
|
int entriesLength = serialized.readInt();
|
||||||
int initialReaderOffset = serialized.readerOffset();
|
ArrayList<X> deserializedElements = new ArrayList<>(entriesLength);
|
||||||
int entriesLength = serialized.readInt();
|
for (int i = 0; i < entriesLength; i++) {
|
||||||
ArrayList<X> deserializedElements = new ArrayList<>(entriesLength);
|
var deserializationResult = entrySerializer.deserialize(serialized);
|
||||||
for (int i = 0; i < entriesLength; i++) {
|
deserializedElements.add(deserializationResult);
|
||||||
var deserializationResult = entrySerializer.deserialize(serialized
|
}
|
||||||
.copy(serialized.readerOffset(), serialized.readableBytes())
|
return new ObjectArraySet<>(deserializedElements);
|
||||||
.send());
|
}
|
||||||
deserializedElements.add(deserializationResult.deserializedData());
|
|
||||||
serialized.readerOffset(serialized.readerOffset() + deserializationResult.bytesRead());
|
@Override
|
||||||
}
|
public void serialize(@NotNull ObjectArraySet<X> deserialized, Buffer output) throws SerializationException {
|
||||||
return new DeserializationResult<>(new ObjectArraySet<>(deserializedElements), serialized.readerOffset() - initialReaderOffset);
|
output.writeInt(deserialized.size());
|
||||||
|
for (X entry : deserialized) {
|
||||||
|
entrySerializer.serialize(entry, output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Send<Buffer> serialize(@NotNull ObjectArraySet<X> deserialized) throws SerializationException {
|
public int getSerializedSizeHint() {
|
||||||
try (Buffer output = allocator.allocate(64)) {
|
return -1;
|
||||||
output.writeInt(deserialized.size());
|
|
||||||
for (X entry : deserialized) {
|
|
||||||
var serializedToReceive = entrySerializer.serialize(entry);
|
|
||||||
try (Buffer serialized = serializedToReceive.receive()) {
|
|
||||||
if (serialized.readableBytes() > 0) {
|
|
||||||
output.ensureWritable(serialized.readableBytes());
|
|
||||||
output.writeBytes(serialized);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return output.send();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,7 +92,6 @@ public class LLLocalDictionary implements LLDictionary {
|
||||||
static final long MAX_WRITE_BATCH_SIZE = 1024L * 1024L * 1024L; // 1GiB
|
static final long MAX_WRITE_BATCH_SIZE = 1024L * 1024L * 1024L; // 1GiB
|
||||||
static final int CAPPED_WRITE_BATCH_CAP = 50000; // 50K operations
|
static final int CAPPED_WRITE_BATCH_CAP = 50000; // 50K operations
|
||||||
static final int MULTI_GET_WINDOW = 16;
|
static final int MULTI_GET_WINDOW = 16;
|
||||||
static final Duration MULTI_GET_WINDOW_TIMEOUT = Duration.ofSeconds(1);
|
|
||||||
static final ReadOptions EMPTY_READ_OPTIONS = new UnreleasableReadOptions(new UnmodifiableReadOptions());
|
static final ReadOptions EMPTY_READ_OPTIONS = new UnreleasableReadOptions(new UnmodifiableReadOptions());
|
||||||
static final WriteOptions EMPTY_WRITE_OPTIONS = new UnreleasableWriteOptions(new UnmodifiableWriteOptions());
|
static final WriteOptions EMPTY_WRITE_OPTIONS = new UnreleasableWriteOptions(new UnmodifiableWriteOptions());
|
||||||
static final WriteOptions BATCH_WRITE_OPTIONS = new UnreleasableWriteOptions(new UnmodifiableWriteOptions());
|
static final WriteOptions BATCH_WRITE_OPTIONS = new UnreleasableWriteOptions(new UnmodifiableWriteOptions());
|
||||||
|
@ -125,7 +124,6 @@ public class LLLocalDictionary implements LLDictionary {
|
||||||
static final boolean USE_WRITE_BATCH_IN_SET_RANGE_DELETE = false;
|
static final boolean USE_WRITE_BATCH_IN_SET_RANGE_DELETE = false;
|
||||||
static final boolean PARALLEL_EXACT_SIZE = true;
|
static final boolean PARALLEL_EXACT_SIZE = true;
|
||||||
|
|
||||||
private static final int STRIPES = 512;
|
|
||||||
private static final byte[] FIRST_KEY = new byte[]{};
|
private static final byte[] FIRST_KEY = new byte[]{};
|
||||||
private static final byte[] NO_DATA = new byte[0];
|
private static final byte[] NO_DATA = new byte[0];
|
||||||
|
|
||||||
|
@ -158,10 +156,13 @@ public class LLLocalDictionary implements LLDictionary {
|
||||||
private final Function<LLSnapshot, Snapshot> snapshotResolver;
|
private final Function<LLSnapshot, Snapshot> snapshotResolver;
|
||||||
private final UpdateMode updateMode;
|
private final UpdateMode updateMode;
|
||||||
private final BufferAllocator alloc;
|
private final BufferAllocator alloc;
|
||||||
private final String getRangeMultiDebugName;
|
|
||||||
private final String getRangeKeysMultiDebugName;
|
|
||||||
private final DatabaseOptions databaseOptions;
|
private final DatabaseOptions databaseOptions;
|
||||||
|
|
||||||
|
private final String getRangeMultiDebugName;
|
||||||
|
private final String getRangeMultiGroupedDebugName;
|
||||||
|
private final String getRangeKeysDebugName;
|
||||||
|
private final String getRangeKeysGroupedDebugName;
|
||||||
|
|
||||||
public LLLocalDictionary(
|
public LLLocalDictionary(
|
||||||
BufferAllocator allocator,
|
BufferAllocator allocator,
|
||||||
@NotNull OptimisticTransactionDB db,
|
@NotNull OptimisticTransactionDB db,
|
||||||
|
@ -182,7 +183,9 @@ public class LLLocalDictionary implements LLDictionary {
|
||||||
this.snapshotResolver = snapshotResolver;
|
this.snapshotResolver = snapshotResolver;
|
||||||
this.updateMode = updateMode;
|
this.updateMode = updateMode;
|
||||||
this.getRangeMultiDebugName = databaseName + "(" + columnName + ")" + "::getRangeMulti";
|
this.getRangeMultiDebugName = databaseName + "(" + columnName + ")" + "::getRangeMulti";
|
||||||
this.getRangeKeysMultiDebugName = databaseName + "(" + columnName + ")" + "::getRangeKeysMulti";
|
this.getRangeMultiGroupedDebugName = databaseName + "(" + columnName + ")" + "::getRangeMultiGrouped";
|
||||||
|
this.getRangeKeysDebugName = databaseName + "(" + columnName + ")" + "::getRangeKeys";
|
||||||
|
this.getRangeKeysGroupedDebugName = databaseName + "(" + columnName + ")" + "::getRangeKeysGrouped";
|
||||||
this.databaseOptions = databaseOptions;
|
this.databaseOptions = databaseOptions;
|
||||||
alloc = allocator;
|
alloc = allocator;
|
||||||
}
|
}
|
||||||
|
@ -220,34 +223,6 @@ public class LLLocalDictionary implements LLDictionary {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getLockIndex(Buffer key) {
|
|
||||||
return Math.abs(LLUtils.hashCode(key) % STRIPES);
|
|
||||||
}
|
|
||||||
|
|
||||||
private IntArrayList getLockIndices(List<Buffer> keys) {
|
|
||||||
var list = new IntArrayList(keys.size());
|
|
||||||
for (Buffer key : keys) {
|
|
||||||
list.add(getLockIndex(key));
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IntArrayList getLockIndicesEntries(List<LLEntry> keys) {
|
|
||||||
var list = new IntArrayList(keys.size());
|
|
||||||
for (LLEntry key : keys) {
|
|
||||||
list.add(getLockIndex(key.getKeyUnsafe()));
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
private <X> IntArrayList getLockIndicesWithExtra(List<Tuple2<Buffer, X>> entries) {
|
|
||||||
var list = new IntArrayList(entries.size());
|
|
||||||
for (Tuple2<Buffer, X> key : entries) {
|
|
||||||
list.add(getLockIndex(key.getT1()));
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public BufferAllocator getAllocator() {
|
public BufferAllocator getAllocator() {
|
||||||
return alloc;
|
return alloc;
|
||||||
|
@ -275,7 +250,8 @@ public class LLLocalDictionary implements LLDictionary {
|
||||||
var result = dbGet(cfh, resolveSnapshot(snapshot), key.send(), existsAlmostCertainly);
|
var result = dbGet(cfh, resolveSnapshot(snapshot), key.send(), existsAlmostCertainly);
|
||||||
if (logger.isTraceEnabled(MARKER_ROCKSDB)) {
|
if (logger.isTraceEnabled(MARKER_ROCKSDB)) {
|
||||||
try (var result2 = result == null ? null : result.receive()) {
|
try (var result2 = result == null ? null : result.receive()) {
|
||||||
logger.trace(MARKER_ROCKSDB, "Reading {}: {}", LLUtils.toStringSafe(logKey), LLUtils.toString(result2));
|
logger.trace(MARKER_ROCKSDB, "Reading {}: {}", LLUtils.toStringSafe(logKey),
|
||||||
|
LLUtils.toString(result2));
|
||||||
return result2 == null ? null : result2.send();
|
return result2 == null ? null : result2.send();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -607,6 +583,23 @@ public class LLLocalDictionary implements LLDictionary {
|
||||||
return Mono.fromSupplier(() -> updateMode);
|
return Mono.fromSupplier(() -> updateMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return true if not committed successfully
|
||||||
|
*/
|
||||||
|
private boolean commitOptimistically(Transaction tx) throws RocksDBException {
|
||||||
|
try {
|
||||||
|
tx.commit();
|
||||||
|
return true;
|
||||||
|
} catch (RocksDBException ex) {
|
||||||
|
var status = ex.getStatus() != null ? ex.getStatus().getCode() : Code.Ok;
|
||||||
|
if (status == Code.Busy || status == Code.TryAgain) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Remember to change also updateAndGetDelta() if you are modifying this function
|
// Remember to change also updateAndGetDelta() if you are modifying this function
|
||||||
@SuppressWarnings("DuplicatedCode")
|
@SuppressWarnings("DuplicatedCode")
|
||||||
@Override
|
@Override
|
||||||
|
@ -624,85 +617,121 @@ public class LLLocalDictionary implements LLDictionary {
|
||||||
throw new UnsupportedOperationException("update() is disallowed");
|
throw new UnsupportedOperationException("update() is disallowed");
|
||||||
}
|
}
|
||||||
try (var tx = db.beginTransaction(EMPTY_WRITE_OPTIONS, DEFAULT_OPTIMISTIC_TX_OPTIONS)) {
|
try (var tx = db.beginTransaction(EMPTY_WRITE_OPTIONS, DEFAULT_OPTIMISTIC_TX_OPTIONS)) {
|
||||||
var prevDataArray = tx.getForUpdate(EMPTY_READ_OPTIONS, cfh, keyArray, true);
|
boolean committedSuccessfully;
|
||||||
Buffer prevData;
|
Send<Buffer> sentPrevData;
|
||||||
if (prevDataArray != null) {
|
Send<Buffer> sentCurData;
|
||||||
prevData = BYTE_ARRAY_BUFFERS.allocate(prevDataArray.length);
|
do {
|
||||||
prevData.writeBytes(prevDataArray);
|
var prevDataArray = tx.getForUpdate(EMPTY_READ_OPTIONS, cfh, keyArray, true);
|
||||||
} else {
|
if (logger.isTraceEnabled()) {
|
||||||
prevData = null;
|
logger.trace(MARKER_ROCKSDB,
|
||||||
}
|
"Reading {}: {} (before update)",
|
||||||
try (prevData) {
|
LLUtils.toStringSafe(key),
|
||||||
Buffer prevDataToSendToUpdater;
|
LLUtils.toStringSafe(prevDataArray)
|
||||||
if (prevData != null) {
|
);
|
||||||
prevDataToSendToUpdater = prevData.copy();
|
|
||||||
} else {
|
|
||||||
prevDataToSendToUpdater = null;
|
|
||||||
}
|
}
|
||||||
|
Buffer prevData;
|
||||||
|
if (prevDataArray != null) {
|
||||||
|
prevData = BYTE_ARRAY_BUFFERS.allocate(prevDataArray.length);
|
||||||
|
prevData.writeBytes(prevDataArray);
|
||||||
|
} else {
|
||||||
|
prevData = null;
|
||||||
|
}
|
||||||
|
try (prevData) {
|
||||||
|
Buffer prevDataToSendToUpdater;
|
||||||
|
if (prevData != null) {
|
||||||
|
prevDataToSendToUpdater = prevData.copy();
|
||||||
|
} else {
|
||||||
|
prevDataToSendToUpdater = null;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable Buffer newData;
|
@Nullable Buffer newData;
|
||||||
try (var sentData = prevDataToSendToUpdater == null ? null : prevDataToSendToUpdater.send()) {
|
try (var sentData = prevDataToSendToUpdater == null ? null : prevDataToSendToUpdater.send()) {
|
||||||
try (var newDataToReceive = updater.apply(sentData)) {
|
try (var newDataToReceive = updater.apply(sentData)) {
|
||||||
if (newDataToReceive != null) {
|
if (newDataToReceive != null) {
|
||||||
newData = newDataToReceive.receive();
|
newData = newDataToReceive.receive();
|
||||||
} else {
|
} else {
|
||||||
newData = null;
|
newData = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
try (newData) {
|
||||||
try (newData) {
|
var newDataArray = newData == null ? null : LLUtils.toArray(newData);
|
||||||
var newDataArray = newData == null ? null : LLUtils.toArray(newData);
|
|
||||||
if (prevData != null && newData == null) {
|
|
||||||
if (logger.isTraceEnabled()) {
|
|
||||||
logger.trace(MARKER_ROCKSDB, "Deleting {} (after update)", LLUtils.toStringSafe(key));
|
|
||||||
}
|
|
||||||
tx.delete(cfh, keyArray, true);
|
|
||||||
commitOptimistically(tx);
|
|
||||||
} else if (newData != null && (prevData == null || !LLUtils.equals(prevData, newData))) {
|
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.trace(MARKER_ROCKSDB,
|
logger.trace(MARKER_ROCKSDB,
|
||||||
"Writing {}: {} (after update)",
|
"Updating {}. previous data: {}, updated data: {}",
|
||||||
LLUtils.toStringSafe(key),
|
LLUtils.toStringSafe(key),
|
||||||
LLUtils.toStringSafe(newData)
|
LLUtils.toStringSafe(prevDataArray),
|
||||||
|
LLUtils.toStringSafe(newDataArray)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
tx.put(cfh, keyArray, newDataArray);
|
if (prevData != null && newData == null) {
|
||||||
commitOptimistically(tx);
|
if (logger.isTraceEnabled()) {
|
||||||
} else {
|
logger.trace(MARKER_ROCKSDB, "Deleting {} (after update)", LLUtils.toStringSafe(key));
|
||||||
tx.rollback();
|
}
|
||||||
|
tx.delete(cfh, keyArray, true);
|
||||||
|
committedSuccessfully = commitOptimistically(tx);
|
||||||
|
} else if (newData != null && (prevData == null || !LLUtils.equals(prevData, newData))) {
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace(MARKER_ROCKSDB,
|
||||||
|
"Writing {}: {} (after update)",
|
||||||
|
LLUtils.toStringSafe(key),
|
||||||
|
LLUtils.toStringSafe(newData)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
tx.put(cfh, keyArray, newDataArray);
|
||||||
|
committedSuccessfully = commitOptimistically(tx);
|
||||||
|
} else {
|
||||||
|
committedSuccessfully = true;
|
||||||
|
tx.rollback();
|
||||||
|
}
|
||||||
|
sentPrevData = prevData == null ? null : prevData.send();
|
||||||
|
sentCurData = newData == null ? null : newData.send();
|
||||||
|
if (!committedSuccessfully) {
|
||||||
|
if (sentPrevData != null) {
|
||||||
|
sentPrevData.close();
|
||||||
|
}
|
||||||
|
if (sentCurData != null) {
|
||||||
|
sentCurData.close();
|
||||||
|
}
|
||||||
|
logger.debug(MARKER_ROCKSDB, "Failed optimistic transaction {} (update):"
|
||||||
|
+ " waiting 5ms before retrying", LLUtils.toStringSafe(key));
|
||||||
|
// Wait for 5ms
|
||||||
|
LockSupport.parkNanos(5000000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return switch (updateReturnMode) {
|
|
||||||
case GET_NEW_VALUE -> newData != null ? newData.send() : null;
|
|
||||||
case GET_OLD_VALUE -> prevData != null ? prevData.send() : null;
|
|
||||||
case NOTHING -> null;
|
|
||||||
//noinspection UnnecessaryDefault
|
|
||||||
default -> throw new IllegalArgumentException();
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
} while (!committedSuccessfully);
|
||||||
|
return switch (updateReturnMode) {
|
||||||
|
case GET_NEW_VALUE -> {
|
||||||
|
if (sentPrevData != null) {
|
||||||
|
sentPrevData.close();
|
||||||
|
}
|
||||||
|
yield sentCurData;
|
||||||
|
}
|
||||||
|
case GET_OLD_VALUE -> {
|
||||||
|
if (sentCurData != null) {
|
||||||
|
sentCurData.close();
|
||||||
|
}
|
||||||
|
yield sentPrevData;
|
||||||
|
}
|
||||||
|
case NOTHING -> {
|
||||||
|
if (sentPrevData != null) {
|
||||||
|
sentPrevData.close();
|
||||||
|
}
|
||||||
|
if (sentCurData != null) {
|
||||||
|
sentCurData.close();
|
||||||
|
}
|
||||||
|
yield null;
|
||||||
|
}
|
||||||
|
//noinspection UnnecessaryDefault
|
||||||
|
default -> throw new IllegalArgumentException();
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).onErrorMap(cause -> new IOException("Failed to read or write", cause)),
|
}).onErrorMap(cause -> new IOException("Failed to read or write", cause)),
|
||||||
keySend -> Mono.fromRunnable(keySend::close));
|
keySend -> Mono.fromRunnable(keySend::close));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void commitOptimistically(Transaction tx) throws RocksDBException {
|
|
||||||
Code commitStatusCode = null;
|
|
||||||
do {
|
|
||||||
try {
|
|
||||||
tx.commit();
|
|
||||||
} catch (RocksDBException ex) {
|
|
||||||
if (ex.getStatus() != null && ex.getStatus().getCode() == Code.TryAgain) {
|
|
||||||
commitStatusCode = Code.TryAgain;
|
|
||||||
// Park for maximum 5ms
|
|
||||||
LockSupport.parkNanos(5000000);
|
|
||||||
} else {
|
|
||||||
throw ex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} while (commitStatusCode == Code.TryAgain);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remember to change also update() if you are modifying this function
|
// Remember to change also update() if you are modifying this function
|
||||||
@Override
|
@Override
|
||||||
public Mono<Send<LLDelta>> updateAndGetDelta(Mono<Send<Buffer>> keyMono,
|
public Mono<Send<LLDelta>> updateAndGetDelta(Mono<Send<Buffer>> keyMono,
|
||||||
|
@ -718,58 +747,91 @@ public class LLLocalDictionary implements LLDictionary {
|
||||||
throw new UnsupportedOperationException("update() is disallowed");
|
throw new UnsupportedOperationException("update() is disallowed");
|
||||||
}
|
}
|
||||||
try (var tx = db.beginTransaction(EMPTY_WRITE_OPTIONS, DEFAULT_OPTIMISTIC_TX_OPTIONS)) {
|
try (var tx = db.beginTransaction(EMPTY_WRITE_OPTIONS, DEFAULT_OPTIMISTIC_TX_OPTIONS)) {
|
||||||
var prevDataArray = tx.getForUpdate(EMPTY_READ_OPTIONS, cfh, keyArray, true);
|
boolean committedSuccessfully;
|
||||||
Buffer prevData;
|
Send<Buffer> sentPrevData;
|
||||||
if (prevDataArray != null) {
|
Send<Buffer> sentCurData;
|
||||||
prevData = BYTE_ARRAY_BUFFERS.allocate(prevDataArray.length);
|
do {
|
||||||
prevData.writeBytes(prevDataArray);
|
var prevDataArray = tx.getForUpdate(EMPTY_READ_OPTIONS, cfh, keyArray, true);
|
||||||
} else {
|
if (logger.isTraceEnabled()) {
|
||||||
prevData = null;
|
logger.trace(MARKER_ROCKSDB,
|
||||||
}
|
"Reading {}: {} (before update)",
|
||||||
try (prevData) {
|
LLUtils.toStringSafe(key),
|
||||||
Buffer prevDataToSendToUpdater;
|
LLUtils.toStringSafe(prevDataArray)
|
||||||
if (prevData != null) {
|
);
|
||||||
prevDataToSendToUpdater = prevData.copy();
|
|
||||||
} else {
|
|
||||||
prevDataToSendToUpdater = null;
|
|
||||||
}
|
}
|
||||||
|
Buffer prevData;
|
||||||
|
if (prevDataArray != null) {
|
||||||
|
prevData = BYTE_ARRAY_BUFFERS.allocate(prevDataArray.length);
|
||||||
|
prevData.writeBytes(prevDataArray);
|
||||||
|
} else {
|
||||||
|
prevData = null;
|
||||||
|
}
|
||||||
|
try (prevData) {
|
||||||
|
Buffer prevDataToSendToUpdater;
|
||||||
|
if (prevData != null) {
|
||||||
|
prevDataToSendToUpdater = prevData.copy();
|
||||||
|
} else {
|
||||||
|
prevDataToSendToUpdater = null;
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable Buffer newData;
|
@Nullable Buffer newData;
|
||||||
try (var sentData = prevDataToSendToUpdater == null ? null : prevDataToSendToUpdater.send()) {
|
try (var sentData = prevDataToSendToUpdater == null ? null : prevDataToSendToUpdater.send()) {
|
||||||
try (var newDataToReceive = updater.apply(sentData)) {
|
try (var newDataToReceive = updater.apply(sentData)) {
|
||||||
if (newDataToReceive != null) {
|
if (newDataToReceive != null) {
|
||||||
newData = newDataToReceive.receive();
|
newData = newDataToReceive.receive();
|
||||||
} else {
|
} else {
|
||||||
newData = null;
|
newData = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
try (newData) {
|
||||||
try (newData) {
|
var newDataArray = newData == null ? null : LLUtils.toArray(newData);
|
||||||
var newDataArray = newData == null ? null : LLUtils.toArray(newData);
|
|
||||||
if (prevData != null && newData == null) {
|
|
||||||
if (logger.isTraceEnabled()) {
|
|
||||||
logger.trace(MARKER_ROCKSDB, "Deleting {} (after update)", LLUtils.toStringSafe(key));
|
|
||||||
}
|
|
||||||
tx.delete(cfh, keyArray, true);
|
|
||||||
commitOptimistically(tx);
|
|
||||||
} else if (newData != null && (prevData == null || !LLUtils.equals(prevData, newData))) {
|
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.trace(MARKER_ROCKSDB,
|
logger.trace(MARKER_ROCKSDB,
|
||||||
"Writing {}: {} (after update)",
|
"Updating {}. previous data: {}, updated data: {}",
|
||||||
LLUtils.toStringSafe(key),
|
LLUtils.toStringSafe(key),
|
||||||
LLUtils.toStringSafe(newData)
|
LLUtils.toStringSafe(prevDataArray),
|
||||||
|
LLUtils.toStringSafe(newDataArray)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
tx.put(cfh, keyArray, newDataArray);
|
if (prevData != null && newData == null) {
|
||||||
commitOptimistically(tx);
|
if (logger.isTraceEnabled()) {
|
||||||
} else {
|
logger.trace(MARKER_ROCKSDB, "Deleting {} (after update)", LLUtils.toStringSafe(key));
|
||||||
tx.rollback();
|
}
|
||||||
|
tx.delete(cfh, keyArray, true);
|
||||||
|
committedSuccessfully = commitOptimistically(tx);
|
||||||
|
} else if (newData != null && (prevData == null || !LLUtils.equals(prevData, newData))) {
|
||||||
|
if (logger.isTraceEnabled()) {
|
||||||
|
logger.trace(MARKER_ROCKSDB,
|
||||||
|
"Writing {}: {} (after update)",
|
||||||
|
LLUtils.toStringSafe(key),
|
||||||
|
LLUtils.toStringSafe(newData)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
tx.put(cfh, keyArray, newDataArray);
|
||||||
|
committedSuccessfully = commitOptimistically(tx);
|
||||||
|
} else {
|
||||||
|
tx.rollback();
|
||||||
|
committedSuccessfully = true;
|
||||||
|
}
|
||||||
|
sentPrevData = prevData == null ? null : prevData.send();
|
||||||
|
sentCurData = newData == null ? null : newData.send();
|
||||||
|
if (!committedSuccessfully) {
|
||||||
|
if (sentPrevData != null) {
|
||||||
|
sentPrevData.close();
|
||||||
|
}
|
||||||
|
if (sentCurData != null) {
|
||||||
|
sentCurData.close();
|
||||||
|
}
|
||||||
|
logger.debug(MARKER_ROCKSDB, "Failed optimistic transaction {} (update):"
|
||||||
|
+ " waiting 5ms before retrying", LLUtils.toStringSafe(key));
|
||||||
|
// Wait for 5ms
|
||||||
|
LockSupport.parkNanos(5000000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return LLDelta
|
|
||||||
.of(prevData != null ? prevData.send() : null, newData != null ? newData.send() : null)
|
|
||||||
.send();
|
|
||||||
}
|
}
|
||||||
}
|
} while (!committedSuccessfully);
|
||||||
|
return LLDelta.of(sentPrevData, sentCurData).send();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).onErrorMap(cause -> new IOException("Failed to read or write", cause)),
|
}).onErrorMap(cause -> new IOException("Failed to read or write", cause)),
|
||||||
|
@ -1215,11 +1277,12 @@ public class LLLocalDictionary implements LLDictionary {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Flux<List<Send<LLEntry>>> getRangeMultiGrouped(LLSnapshot snapshot, Mono<Send<LLRange>> rangeMono, int prefixLength) {
|
private Flux<List<Send<LLEntry>>> getRangeMultiGrouped(LLSnapshot snapshot, Mono<Send<LLRange>> rangeMono,
|
||||||
|
int prefixLength) {
|
||||||
return Flux.usingWhen(rangeMono,
|
return Flux.usingWhen(rangeMono,
|
||||||
rangeSend -> Flux.using(
|
rangeSend -> Flux.using(
|
||||||
() -> new LLLocalGroupedEntryReactiveRocksIterator(db, alloc, cfh, prefixLength, rangeSend,
|
() -> new LLLocalGroupedEntryReactiveRocksIterator(db, alloc, cfh, prefixLength, rangeSend,
|
||||||
databaseOptions.allowNettyDirect(), resolveSnapshot(snapshot), "getRangeMultiGrouped"),
|
databaseOptions.allowNettyDirect(), resolveSnapshot(snapshot), getRangeMultiGroupedDebugName),
|
||||||
reactiveRocksIterator -> reactiveRocksIterator.flux().subscribeOn(dbScheduler),
|
reactiveRocksIterator -> reactiveRocksIterator.flux().subscribeOn(dbScheduler),
|
||||||
LLLocalGroupedReactiveRocksIterator::release
|
LLLocalGroupedReactiveRocksIterator::release
|
||||||
).transform(LLUtils::handleDiscard),
|
).transform(LLUtils::handleDiscard),
|
||||||
|
@ -1250,7 +1313,7 @@ public class LLLocalDictionary implements LLDictionary {
|
||||||
return Flux.usingWhen(rangeMono,
|
return Flux.usingWhen(rangeMono,
|
||||||
rangeSend -> Flux.using(
|
rangeSend -> Flux.using(
|
||||||
() -> new LLLocalGroupedKeyReactiveRocksIterator(db, alloc, cfh, prefixLength, rangeSend,
|
() -> new LLLocalGroupedKeyReactiveRocksIterator(db, alloc, cfh, prefixLength, rangeSend,
|
||||||
databaseOptions.allowNettyDirect(), resolveSnapshot(snapshot), "getRangeKeysGrouped"),
|
databaseOptions.allowNettyDirect(), resolveSnapshot(snapshot), getRangeKeysDebugName),
|
||||||
reactiveRocksIterator -> reactiveRocksIterator.flux().subscribeOn(dbScheduler),
|
reactiveRocksIterator -> reactiveRocksIterator.flux().subscribeOn(dbScheduler),
|
||||||
LLLocalGroupedReactiveRocksIterator::release
|
LLLocalGroupedReactiveRocksIterator::release
|
||||||
).transform(LLUtils::handleDiscard),
|
).transform(LLUtils::handleDiscard),
|
||||||
|
@ -1306,7 +1369,8 @@ public class LLLocalDictionary implements LLDictionary {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Flux<Send<Buffer>> getRangeKeyPrefixes(@Nullable LLSnapshot snapshot, Mono<Send<LLRange>> rangeMono, int prefixLength) {
|
public Flux<Send<Buffer>> getRangeKeyPrefixes(@Nullable LLSnapshot snapshot, Mono<Send<LLRange>> rangeMono,
|
||||||
|
int prefixLength) {
|
||||||
return Flux.usingWhen(rangeMono,
|
return Flux.usingWhen(rangeMono,
|
||||||
rangeSend -> Flux
|
rangeSend -> Flux
|
||||||
.using(
|
.using(
|
||||||
|
@ -1318,7 +1382,7 @@ public class LLLocalDictionary implements LLDictionary {
|
||||||
databaseOptions.allowNettyDirect(),
|
databaseOptions.allowNettyDirect(),
|
||||||
resolveSnapshot(snapshot),
|
resolveSnapshot(snapshot),
|
||||||
true,
|
true,
|
||||||
"getRangeKeysGrouped"
|
getRangeKeysGroupedDebugName
|
||||||
),
|
),
|
||||||
LLLocalKeyPrefixReactiveRocksIterator::flux,
|
LLLocalKeyPrefixReactiveRocksIterator::flux,
|
||||||
LLLocalKeyPrefixReactiveRocksIterator::release
|
LLLocalKeyPrefixReactiveRocksIterator::release
|
||||||
|
@ -1525,7 +1589,8 @@ public class LLLocalDictionary implements LLDictionary {
|
||||||
} else {
|
} else {
|
||||||
if (USE_WRITE_BATCHES_IN_SET_RANGE) {
|
if (USE_WRITE_BATCHES_IN_SET_RANGE) {
|
||||||
return Mono.fromCallable(() -> {
|
return Mono.fromCallable(() -> {
|
||||||
throw new UnsupportedOperationException("Can't use write batches in setRange without window. Please fix params");
|
throw new UnsupportedOperationException("Can't use write batches in setRange without window."
|
||||||
|
+ " Please fix the parameters");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return this
|
return this
|
||||||
|
@ -1562,14 +1627,16 @@ public class LLLocalDictionary implements LLDictionary {
|
||||||
readOpts.setFillCache(false);
|
readOpts.setFillCache(false);
|
||||||
ReleasableSlice minBound;
|
ReleasableSlice minBound;
|
||||||
if (range.hasMin()) {
|
if (range.hasMin()) {
|
||||||
minBound = setIterateBound(alloc, databaseOptions.allowNettyDirect(), readOpts, IterateBound.LOWER, range.getMin());
|
minBound = setIterateBound(alloc, databaseOptions.allowNettyDirect(), readOpts,
|
||||||
|
IterateBound.LOWER, range.getMin());
|
||||||
} else {
|
} else {
|
||||||
minBound = emptyReleasableSlice();
|
minBound = emptyReleasableSlice();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
ReleasableSlice maxBound;
|
ReleasableSlice maxBound;
|
||||||
if (range.hasMax()) {
|
if (range.hasMax()) {
|
||||||
maxBound = setIterateBound(alloc, databaseOptions.allowNettyDirect(), readOpts, IterateBound.UPPER, range.getMax());
|
maxBound = setIterateBound(alloc, databaseOptions.allowNettyDirect(), readOpts,
|
||||||
|
IterateBound.UPPER, range.getMax());
|
||||||
} else {
|
} else {
|
||||||
maxBound = emptyReleasableSlice();
|
maxBound = emptyReleasableSlice();
|
||||||
}
|
}
|
||||||
|
@ -1612,7 +1679,8 @@ public class LLLocalDictionary implements LLDictionary {
|
||||||
readOpts.setFillCache(false);
|
readOpts.setFillCache(false);
|
||||||
ReleasableSlice minBound;
|
ReleasableSlice minBound;
|
||||||
if (range.hasMin()) {
|
if (range.hasMin()) {
|
||||||
minBound = setIterateBound(alloc, databaseOptions.allowNettyDirect(), readOpts, IterateBound.LOWER, range.getMin());
|
minBound = setIterateBound(alloc, databaseOptions.allowNettyDirect(), readOpts,
|
||||||
|
IterateBound.LOWER, range.getMin());
|
||||||
} else {
|
} else {
|
||||||
minBound = emptyReleasableSlice();
|
minBound = emptyReleasableSlice();
|
||||||
}
|
}
|
||||||
|
@ -1655,8 +1723,8 @@ public class LLLocalDictionary implements LLDictionary {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static SafeCloseable rocksIterSeekTo(BufferAllocator alloc, boolean allowNettyDirect, RocksIterator rocksIterator,
|
private static SafeCloseable rocksIterSeekTo(BufferAllocator alloc, boolean allowNettyDirect,
|
||||||
Send<Buffer> bufferToReceive) {
|
RocksIterator rocksIterator, Send<Buffer> bufferToReceive) {
|
||||||
try (var buffer = bufferToReceive.receive()) {
|
try (var buffer = bufferToReceive.receive()) {
|
||||||
if (allowNettyDirect) {
|
if (allowNettyDirect) {
|
||||||
var direct = LLUtils.convertToReadableDirect(alloc, buffer.send());
|
var direct = LLUtils.convertToReadableDirect(alloc, buffer.send());
|
||||||
|
@ -1673,8 +1741,8 @@ public class LLLocalDictionary implements LLDictionary {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ReleasableSlice setIterateBound(BufferAllocator alloc, boolean allowNettyDirect, ReadOptions readOpts,
|
private static ReleasableSlice setIterateBound(BufferAllocator alloc, boolean allowNettyDirect,
|
||||||
IterateBound boundType, Send<Buffer> bufferToReceive) {
|
ReadOptions readOpts, IterateBound boundType, Send<Buffer> bufferToReceive) {
|
||||||
var buffer = bufferToReceive.receive();
|
var buffer = bufferToReceive.receive();
|
||||||
try {
|
try {
|
||||||
requireNonNull(buffer);
|
requireNonNull(buffer);
|
||||||
|
@ -1785,7 +1853,7 @@ public class LLLocalDictionary implements LLDictionary {
|
||||||
|
|
||||||
writeBatch.writeToDbAndClose();
|
writeBatch.writeToDbAndClose();
|
||||||
|
|
||||||
|
//noinspection ConstantConditions
|
||||||
if (shouldCompactLater) {
|
if (shouldCompactLater) {
|
||||||
// Compact range
|
// Compact range
|
||||||
db.suggestCompactRange(cfh);
|
db.suggestCompactRange(cfh);
|
||||||
|
@ -1814,76 +1882,72 @@ public class LLLocalDictionary implements LLDictionary {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<Long> sizeRange(@Nullable LLSnapshot snapshot, Mono<Send<LLRange>> rangeMono, boolean fast) {
|
public Mono<Long> sizeRange(@Nullable LLSnapshot snapshot, Mono<Send<LLRange>> rangeMono, boolean fast) {
|
||||||
return Mono.usingWhen(rangeMono,
|
return Mono.usingWhen(rangeMono, rangeSend -> runOnDb(() -> {
|
||||||
rangeSend -> {
|
try (var range = rangeSend.receive()) {
|
||||||
return runOnDb(() -> {
|
if (Schedulers.isInNonBlockingThread()) {
|
||||||
try (var range = rangeSend.receive()) {
|
throw new UnsupportedOperationException("Called sizeRange in a nonblocking thread");
|
||||||
if (Schedulers.isInNonBlockingThread()) {
|
}
|
||||||
throw new UnsupportedOperationException("Called sizeRange in a nonblocking thread");
|
if (range.isAll()) {
|
||||||
}
|
return fast ? fastSizeAll(snapshot) : exactSizeAll(snapshot);
|
||||||
if (range.isAll()) {
|
} else {
|
||||||
return fast ? fastSizeAll(snapshot) : exactSizeAll(snapshot);
|
try (var readOpts = new ReadOptions(resolveSnapshot(snapshot))) {
|
||||||
|
readOpts.setFillCache(false);
|
||||||
|
readOpts.setVerifyChecksums(VERIFY_CHECKSUMS_WHEN_NOT_NEEDED);
|
||||||
|
ReleasableSlice minBound;
|
||||||
|
if (range.hasMin()) {
|
||||||
|
minBound = setIterateBound(alloc, databaseOptions.allowNettyDirect(), readOpts,
|
||||||
|
IterateBound.LOWER, range.getMin());
|
||||||
|
} else {
|
||||||
|
minBound = emptyReleasableSlice();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ReleasableSlice maxBound;
|
||||||
|
if (range.hasMax()) {
|
||||||
|
maxBound = setIterateBound(alloc, databaseOptions.allowNettyDirect(), readOpts,
|
||||||
|
IterateBound.UPPER, range.getMax());
|
||||||
} else {
|
} else {
|
||||||
try (var readOpts = new ReadOptions(resolveSnapshot(snapshot))) {
|
maxBound = emptyReleasableSlice();
|
||||||
readOpts.setFillCache(false);
|
}
|
||||||
readOpts.setVerifyChecksums(VERIFY_CHECKSUMS_WHEN_NOT_NEEDED);
|
try {
|
||||||
ReleasableSlice minBound;
|
if (fast) {
|
||||||
if (range.hasMin()) {
|
readOpts.setIgnoreRangeDeletions(true);
|
||||||
minBound = setIterateBound(alloc, databaseOptions.allowNettyDirect(), readOpts, IterateBound.LOWER,
|
|
||||||
range.getMin());
|
}
|
||||||
|
try (var rocksIterator = db.newIterator(cfh, readOpts)) {
|
||||||
|
SafeCloseable seekTo;
|
||||||
|
if (!LLLocalDictionary.PREFER_SEEK_TO_FIRST && range.hasMin()) {
|
||||||
|
seekTo = rocksIterSeekTo(alloc, databaseOptions.allowNettyDirect(),
|
||||||
|
rocksIterator, range.getMin());
|
||||||
} else {
|
} else {
|
||||||
minBound = emptyReleasableSlice();
|
seekTo = null;
|
||||||
|
rocksIterator.seekToFirst();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
ReleasableSlice maxBound;
|
long i = 0;
|
||||||
if (range.hasMax()) {
|
rocksIterator.status();
|
||||||
maxBound = setIterateBound(alloc, databaseOptions.allowNettyDirect(), readOpts, IterateBound.UPPER,
|
while (rocksIterator.isValid()) {
|
||||||
range.getMax());
|
rocksIterator.next();
|
||||||
} else {
|
rocksIterator.status();
|
||||||
maxBound = emptyReleasableSlice();
|
i++;
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (fast) {
|
|
||||||
readOpts.setIgnoreRangeDeletions(true);
|
|
||||||
|
|
||||||
}
|
|
||||||
try (var rocksIterator = db.newIterator(cfh, readOpts)) {
|
|
||||||
SafeCloseable seekTo;
|
|
||||||
if (!LLLocalDictionary.PREFER_SEEK_TO_FIRST && range.hasMin()) {
|
|
||||||
seekTo = rocksIterSeekTo(alloc, databaseOptions.allowNettyDirect(), rocksIterator,
|
|
||||||
range.getMin());
|
|
||||||
} else {
|
|
||||||
seekTo = null;
|
|
||||||
rocksIterator.seekToFirst();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
long i = 0;
|
|
||||||
rocksIterator.status();
|
|
||||||
while (rocksIterator.isValid()) {
|
|
||||||
rocksIterator.next();
|
|
||||||
rocksIterator.status();
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
return i;
|
|
||||||
} finally {
|
|
||||||
if (seekTo != null) {
|
|
||||||
seekTo.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
maxBound.close();
|
|
||||||
}
|
}
|
||||||
|
return i;
|
||||||
} finally {
|
} finally {
|
||||||
minBound.close();
|
if (seekTo != null) {
|
||||||
|
seekTo.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
maxBound.close();
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
minBound.close();
|
||||||
}
|
}
|
||||||
}).onErrorMap(cause -> new IOException("Failed to get size of range", cause));
|
}
|
||||||
},
|
}
|
||||||
rangeSend -> Mono.fromRunnable(rangeSend::close)
|
}
|
||||||
);
|
}).onErrorMap(cause -> new IOException("Failed to get size of range", cause)),
|
||||||
|
rangeSend -> Mono.fromRunnable(rangeSend::close));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1897,23 +1961,24 @@ public class LLLocalDictionary implements LLDictionary {
|
||||||
try (var readOpts = new ReadOptions(resolveSnapshot(snapshot))) {
|
try (var readOpts = new ReadOptions(resolveSnapshot(snapshot))) {
|
||||||
ReleasableSlice minBound;
|
ReleasableSlice minBound;
|
||||||
if (range.hasMin()) {
|
if (range.hasMin()) {
|
||||||
minBound = setIterateBound(alloc, databaseOptions.allowNettyDirect(), readOpts, IterateBound.LOWER,
|
minBound = setIterateBound(alloc, databaseOptions.allowNettyDirect(), readOpts,
|
||||||
range.getMin());
|
IterateBound.LOWER, range.getMin());
|
||||||
} else {
|
} else {
|
||||||
minBound = emptyReleasableSlice();
|
minBound = emptyReleasableSlice();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
ReleasableSlice maxBound;
|
ReleasableSlice maxBound;
|
||||||
if (range.hasMax()) {
|
if (range.hasMax()) {
|
||||||
maxBound = setIterateBound(alloc, databaseOptions.allowNettyDirect(), readOpts, IterateBound.UPPER,
|
maxBound = setIterateBound(alloc, databaseOptions.allowNettyDirect(), readOpts,
|
||||||
range.getMax());
|
IterateBound.UPPER, range.getMax());
|
||||||
} else {
|
} else {
|
||||||
maxBound = emptyReleasableSlice();
|
maxBound = emptyReleasableSlice();
|
||||||
}
|
}
|
||||||
try (var rocksIterator = db.newIterator(cfh, readOpts)) {
|
try (var rocksIterator = db.newIterator(cfh, readOpts)) {
|
||||||
SafeCloseable seekTo;
|
SafeCloseable seekTo;
|
||||||
if (!LLLocalDictionary.PREFER_SEEK_TO_FIRST && range.hasMin()) {
|
if (!LLLocalDictionary.PREFER_SEEK_TO_FIRST && range.hasMin()) {
|
||||||
seekTo = rocksIterSeekTo(alloc, databaseOptions.allowNettyDirect(), rocksIterator, range.getMin());
|
seekTo = rocksIterSeekTo(alloc, databaseOptions.allowNettyDirect(),
|
||||||
|
rocksIterator, range.getMin());
|
||||||
} else {
|
} else {
|
||||||
seekTo = null;
|
seekTo = null;
|
||||||
rocksIterator.seekToFirst();
|
rocksIterator.seekToFirst();
|
||||||
|
@ -1958,23 +2023,24 @@ public class LLLocalDictionary implements LLDictionary {
|
||||||
try (var readOpts = new ReadOptions(resolveSnapshot(snapshot))) {
|
try (var readOpts = new ReadOptions(resolveSnapshot(snapshot))) {
|
||||||
ReleasableSlice minBound;
|
ReleasableSlice minBound;
|
||||||
if (range.hasMin()) {
|
if (range.hasMin()) {
|
||||||
minBound = setIterateBound(alloc, databaseOptions.allowNettyDirect(), readOpts, IterateBound.LOWER,
|
minBound = setIterateBound(alloc, databaseOptions.allowNettyDirect(), readOpts,
|
||||||
range.getMin());
|
IterateBound.LOWER, range.getMin());
|
||||||
} else {
|
} else {
|
||||||
minBound = emptyReleasableSlice();
|
minBound = emptyReleasableSlice();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
ReleasableSlice maxBound;
|
ReleasableSlice maxBound;
|
||||||
if (range.hasMax()) {
|
if (range.hasMax()) {
|
||||||
maxBound = setIterateBound(alloc, databaseOptions.allowNettyDirect(), readOpts, IterateBound.UPPER,
|
maxBound = setIterateBound(alloc, databaseOptions.allowNettyDirect(), readOpts,
|
||||||
range.getMax());
|
IterateBound.UPPER, range.getMax());
|
||||||
} else {
|
} else {
|
||||||
maxBound = emptyReleasableSlice();
|
maxBound = emptyReleasableSlice();
|
||||||
}
|
}
|
||||||
try (var rocksIterator = db.newIterator(cfh, readOpts)) {
|
try (var rocksIterator = db.newIterator(cfh, readOpts)) {
|
||||||
SafeCloseable seekTo;
|
SafeCloseable seekTo;
|
||||||
if (!LLLocalDictionary.PREFER_SEEK_TO_FIRST && range.hasMin()) {
|
if (!LLLocalDictionary.PREFER_SEEK_TO_FIRST && range.hasMin()) {
|
||||||
seekTo = rocksIterSeekTo(alloc, databaseOptions.allowNettyDirect(), rocksIterator, range.getMin());
|
seekTo = rocksIterSeekTo(alloc, databaseOptions.allowNettyDirect(),
|
||||||
|
rocksIterator, range.getMin());
|
||||||
} else {
|
} else {
|
||||||
seekTo = null;
|
seekTo = null;
|
||||||
rocksIterator.seekToFirst();
|
rocksIterator.seekToFirst();
|
||||||
|
@ -2126,23 +2192,24 @@ public class LLLocalDictionary implements LLDictionary {
|
||||||
try (var readOpts = new ReadOptions(getReadOptions(null))) {
|
try (var readOpts = new ReadOptions(getReadOptions(null))) {
|
||||||
ReleasableSlice minBound;
|
ReleasableSlice minBound;
|
||||||
if (range.hasMin()) {
|
if (range.hasMin()) {
|
||||||
minBound = setIterateBound(alloc, databaseOptions.allowNettyDirect(), readOpts, IterateBound.LOWER,
|
minBound = setIterateBound(alloc, databaseOptions.allowNettyDirect(), readOpts,
|
||||||
range.getMin());
|
IterateBound.LOWER, range.getMin());
|
||||||
} else {
|
} else {
|
||||||
minBound = emptyReleasableSlice();
|
minBound = emptyReleasableSlice();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
ReleasableSlice maxBound;
|
ReleasableSlice maxBound;
|
||||||
if (range.hasMax()) {
|
if (range.hasMax()) {
|
||||||
maxBound = setIterateBound(alloc, databaseOptions.allowNettyDirect(), readOpts, IterateBound.UPPER,
|
maxBound = setIterateBound(alloc, databaseOptions.allowNettyDirect(), readOpts,
|
||||||
range.getMax());
|
IterateBound.UPPER, range.getMax());
|
||||||
} else {
|
} else {
|
||||||
maxBound = emptyReleasableSlice();
|
maxBound = emptyReleasableSlice();
|
||||||
}
|
}
|
||||||
try (RocksIterator rocksIterator = db.newIterator(cfh, readOpts)) {
|
try (RocksIterator rocksIterator = db.newIterator(cfh, readOpts)) {
|
||||||
SafeCloseable seekTo;
|
SafeCloseable seekTo;
|
||||||
if (!LLLocalDictionary.PREFER_SEEK_TO_FIRST && range.hasMin()) {
|
if (!LLLocalDictionary.PREFER_SEEK_TO_FIRST && range.hasMin()) {
|
||||||
seekTo = rocksIterSeekTo(alloc, databaseOptions.allowNettyDirect(), rocksIterator, range.getMin());
|
seekTo = rocksIterSeekTo(alloc, databaseOptions.allowNettyDirect(),
|
||||||
|
rocksIterator, range.getMin());
|
||||||
} else {
|
} else {
|
||||||
seekTo = null;
|
seekTo = null;
|
||||||
rocksIterator.seekToFirst();
|
rocksIterator.seekToFirst();
|
||||||
|
@ -2177,7 +2244,8 @@ public class LLLocalDictionary implements LLDictionary {
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
public static Tuple4<RocksIterator, ReleasableSlice, ReleasableSlice, SafeCloseable> getRocksIterator(BufferAllocator alloc,
|
public static Tuple4<RocksIterator, ReleasableSlice, ReleasableSlice, SafeCloseable> getRocksIterator(
|
||||||
|
BufferAllocator alloc,
|
||||||
boolean allowNettyDirect,
|
boolean allowNettyDirect,
|
||||||
ReadOptions readOptions,
|
ReadOptions readOptions,
|
||||||
Send<LLRange> rangeToReceive,
|
Send<LLRange> rangeToReceive,
|
||||||
|
@ -2202,8 +2270,8 @@ public class LLLocalDictionary implements LLDictionary {
|
||||||
var rocksIterator = db.newIterator(cfh, readOptions);
|
var rocksIterator = db.newIterator(cfh, readOptions);
|
||||||
SafeCloseable seekTo;
|
SafeCloseable seekTo;
|
||||||
if (!PREFER_SEEK_TO_FIRST && range.hasMin()) {
|
if (!PREFER_SEEK_TO_FIRST && range.hasMin()) {
|
||||||
seekTo = Objects.requireNonNullElseGet(rocksIterSeekTo(alloc, allowNettyDirect, rocksIterator, range.getMin()),
|
seekTo = Objects.requireNonNullElseGet(rocksIterSeekTo(alloc, allowNettyDirect,
|
||||||
() -> ((SafeCloseable) () -> {})
|
rocksIterator, range.getMin()), () -> ((SafeCloseable) () -> {})
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
seekTo = () -> {};
|
seekTo = () -> {};
|
||||||
|
|
|
@ -89,6 +89,7 @@ public class LLLocalKeyPrefixReactiveRocksIterator {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (firstGroupKey != null) {
|
if (firstGroupKey != null) {
|
||||||
|
assert firstGroupKey.isAccessible();
|
||||||
var groupKeyPrefix = firstGroupKey.copy(firstGroupKey.readerOffset(), prefixLength);
|
var groupKeyPrefix = firstGroupKey.copy(firstGroupKey.readerOffset(), prefixLength);
|
||||||
assert groupKeyPrefix.isAccessible();
|
assert groupKeyPrefix.isAccessible();
|
||||||
|
|
||||||
|
@ -126,7 +127,7 @@ public class LLLocalKeyPrefixReactiveRocksIterator {
|
||||||
tuple.getT3().close();
|
tuple.getT3().close();
|
||||||
tuple.getT4().close();
|
tuple.getT4().close();
|
||||||
}),
|
}),
|
||||||
Send::close
|
resource -> resource.close()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,144 +1,55 @@
|
||||||
package it.cavallium.dbengine.database.serialization;
|
package it.cavallium.dbengine.database.serialization;
|
||||||
|
|
||||||
import io.net5.buffer.api.Buffer;
|
|
||||||
import io.net5.buffer.api.Send;
|
|
||||||
import it.cavallium.dbengine.database.SafeCloseable;
|
|
||||||
import java.io.DataInput;
|
import java.io.DataInput;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
public class BufferDataInput implements DataInput, SafeCloseable {
|
public interface BufferDataInput extends DataInput {
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private final Buffer buf;
|
|
||||||
private final int initialReaderOffset;
|
|
||||||
|
|
||||||
public BufferDataInput(@Nullable Send<Buffer> bufferSend) {
|
|
||||||
this.buf = bufferSend == null ? null : bufferSend.receive().makeReadOnly();
|
|
||||||
this.initialReaderOffset = buf == null ? 0 : buf.readerOffset();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readFully(byte @NotNull [] b) {
|
void readFully(byte @NotNull [] b);
|
||||||
this.readFully(b, 0, b.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readFully(byte @NotNull [] b, int off, int len) {
|
void readFully(byte @NotNull [] b, int off, int len);
|
||||||
if (buf == null) {
|
|
||||||
if (len != 0) {
|
|
||||||
throw new IndexOutOfBoundsException();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
buf.copyInto(buf.readerOffset(), b, off, len);
|
|
||||||
buf.readerOffset(buf.readerOffset() + len);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int skipBytes(int n) {
|
int skipBytes(int n);
|
||||||
if (buf == null) {
|
|
||||||
if (n != 0) {
|
|
||||||
throw new IndexOutOfBoundsException();
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
n = Math.min(n, buf.readerOffset() - buf.writerOffset());
|
|
||||||
buf.readerOffset(buf.readerOffset() + n);
|
|
||||||
return n;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean readBoolean() {
|
boolean readBoolean();
|
||||||
if (buf == null) throw new IndexOutOfBoundsException();
|
|
||||||
return buf.readUnsignedByte() != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte readByte() {
|
byte readByte();
|
||||||
if (buf == null) throw new IndexOutOfBoundsException();
|
|
||||||
return buf.readByte();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int readUnsignedByte() {
|
int readUnsignedByte();
|
||||||
if (buf == null) throw new IndexOutOfBoundsException();
|
|
||||||
return buf.readUnsignedByte();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public short readShort() {
|
short readShort();
|
||||||
if (buf == null) throw new IndexOutOfBoundsException();
|
|
||||||
return buf.readShort();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int readUnsignedShort() {
|
int readUnsignedShort();
|
||||||
if (buf == null) throw new IndexOutOfBoundsException();
|
|
||||||
return buf.readUnsignedShort();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public char readChar() {
|
char readChar();
|
||||||
if (buf == null) throw new IndexOutOfBoundsException();
|
|
||||||
return buf.readChar();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int readInt() {
|
int readInt();
|
||||||
if (buf == null) throw new IndexOutOfBoundsException();
|
|
||||||
return buf.readInt();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long readLong() {
|
long readLong();
|
||||||
if (buf == null) throw new IndexOutOfBoundsException();
|
|
||||||
return buf.readLong();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float readFloat() {
|
float readFloat();
|
||||||
if (buf == null) throw new IndexOutOfBoundsException();
|
|
||||||
return buf.readFloat();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public double readDouble() {
|
double readDouble();
|
||||||
if (buf == null) throw new IndexOutOfBoundsException();
|
|
||||||
return buf.readDouble();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String readLine() {
|
String readLine();
|
||||||
if (buf == null) throw new IndexOutOfBoundsException();
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
@Override
|
@Override
|
||||||
public String readUTF() {
|
String readUTF();
|
||||||
if (buf == null) throw new IndexOutOfBoundsException();
|
|
||||||
var len = buf.readUnsignedShort();
|
|
||||||
byte[] bytes = new byte[len];
|
|
||||||
buf.copyInto(buf.readerOffset(), bytes, 0, len);
|
|
||||||
buf.readerOffset(buf.readerOffset() + len);
|
|
||||||
return new String(bytes, StandardCharsets.UTF_8);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
int getReadBytesCount();
|
||||||
public void close() {
|
|
||||||
if (buf != null) {
|
|
||||||
buf.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getReadBytesCount() {
|
|
||||||
if (buf == null) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return buf.readerOffset() - initialReaderOffset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
package it.cavallium.dbengine.database.serialization;
|
||||||
|
|
||||||
|
import io.net5.buffer.api.Buffer;
|
||||||
|
import io.net5.buffer.api.Send;
|
||||||
|
import it.cavallium.dbengine.database.SafeCloseable;
|
||||||
|
import java.io.DataInput;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public class BufferDataInputOwned implements SafeCloseable, BufferDataInput {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final Buffer buf;
|
||||||
|
private final int initialReaderOffset;
|
||||||
|
|
||||||
|
public BufferDataInputOwned(@Nullable Send<Buffer> bufferSend) {
|
||||||
|
this.buf = bufferSend == null ? null : bufferSend.receive().makeReadOnly();
|
||||||
|
this.initialReaderOffset = buf == null ? 0 : buf.readerOffset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readFully(byte @NotNull [] b) {
|
||||||
|
this.readFully(b, 0, b.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readFully(byte @NotNull [] b, int off, int len) {
|
||||||
|
if (buf == null) {
|
||||||
|
if (len != 0) {
|
||||||
|
throw new IndexOutOfBoundsException();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buf.copyInto(buf.readerOffset(), b, off, len);
|
||||||
|
buf.readerOffset(buf.readerOffset() + len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int skipBytes(int n) {
|
||||||
|
if (buf == null) {
|
||||||
|
if (n != 0) {
|
||||||
|
throw new IndexOutOfBoundsException();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
n = Math.min(n, buf.readerOffset() - buf.writerOffset());
|
||||||
|
buf.readerOffset(buf.readerOffset() + n);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean readBoolean() {
|
||||||
|
if (buf == null) throw new IndexOutOfBoundsException();
|
||||||
|
return buf.readUnsignedByte() != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte readByte() {
|
||||||
|
if (buf == null) throw new IndexOutOfBoundsException();
|
||||||
|
return buf.readByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int readUnsignedByte() {
|
||||||
|
if (buf == null) throw new IndexOutOfBoundsException();
|
||||||
|
return buf.readUnsignedByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public short readShort() {
|
||||||
|
if (buf == null) throw new IndexOutOfBoundsException();
|
||||||
|
return buf.readShort();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int readUnsignedShort() {
|
||||||
|
if (buf == null) throw new IndexOutOfBoundsException();
|
||||||
|
return buf.readUnsignedShort();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char readChar() {
|
||||||
|
if (buf == null) throw new IndexOutOfBoundsException();
|
||||||
|
return buf.readChar();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int readInt() {
|
||||||
|
if (buf == null) throw new IndexOutOfBoundsException();
|
||||||
|
return buf.readInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long readLong() {
|
||||||
|
if (buf == null) throw new IndexOutOfBoundsException();
|
||||||
|
return buf.readLong();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float readFloat() {
|
||||||
|
if (buf == null) throw new IndexOutOfBoundsException();
|
||||||
|
return buf.readFloat();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double readDouble() {
|
||||||
|
if (buf == null) throw new IndexOutOfBoundsException();
|
||||||
|
return buf.readDouble();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String readLine() {
|
||||||
|
if (buf == null) throw new IndexOutOfBoundsException();
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public String readUTF() {
|
||||||
|
if (buf == null) throw new IndexOutOfBoundsException();
|
||||||
|
var len = buf.readUnsignedShort();
|
||||||
|
byte[] bytes = new byte[len];
|
||||||
|
buf.copyInto(buf.readerOffset(), bytes, 0, len);
|
||||||
|
buf.readerOffset(buf.readerOffset() + len);
|
||||||
|
return new String(bytes, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (buf != null) {
|
||||||
|
buf.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getReadBytesCount() {
|
||||||
|
if (buf == null) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return buf.readerOffset() - initialReaderOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,137 @@
|
||||||
|
package it.cavallium.dbengine.database.serialization;
|
||||||
|
|
||||||
|
import io.net5.buffer.api.Buffer;
|
||||||
|
import io.net5.buffer.api.Send;
|
||||||
|
import it.cavallium.dbengine.database.SafeCloseable;
|
||||||
|
import java.io.DataInput;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public class BufferDataInputShared implements BufferDataInput {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final Buffer buf;
|
||||||
|
private final int initialReaderOffset;
|
||||||
|
|
||||||
|
public BufferDataInputShared(@Nullable Buffer buffer) {
|
||||||
|
this.buf = buffer;
|
||||||
|
this.initialReaderOffset = buf == null ? 0 : buf.readerOffset();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readFully(byte @NotNull [] b) {
|
||||||
|
this.readFully(b, 0, b.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readFully(byte @NotNull [] b, int off, int len) {
|
||||||
|
if (buf == null) {
|
||||||
|
if (len != 0) {
|
||||||
|
throw new IndexOutOfBoundsException();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
buf.copyInto(buf.readerOffset(), b, off, len);
|
||||||
|
buf.readerOffset(buf.readerOffset() + len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int skipBytes(int n) {
|
||||||
|
if (buf == null) {
|
||||||
|
if (n != 0) {
|
||||||
|
throw new IndexOutOfBoundsException();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
n = Math.min(n, buf.readerOffset() - buf.writerOffset());
|
||||||
|
buf.readerOffset(buf.readerOffset() + n);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean readBoolean() {
|
||||||
|
if (buf == null) throw new IndexOutOfBoundsException();
|
||||||
|
return buf.readUnsignedByte() != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte readByte() {
|
||||||
|
if (buf == null) throw new IndexOutOfBoundsException();
|
||||||
|
return buf.readByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int readUnsignedByte() {
|
||||||
|
if (buf == null) throw new IndexOutOfBoundsException();
|
||||||
|
return buf.readUnsignedByte();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public short readShort() {
|
||||||
|
if (buf == null) throw new IndexOutOfBoundsException();
|
||||||
|
return buf.readShort();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int readUnsignedShort() {
|
||||||
|
if (buf == null) throw new IndexOutOfBoundsException();
|
||||||
|
return buf.readUnsignedShort();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char readChar() {
|
||||||
|
if (buf == null) throw new IndexOutOfBoundsException();
|
||||||
|
return buf.readChar();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int readInt() {
|
||||||
|
if (buf == null) throw new IndexOutOfBoundsException();
|
||||||
|
return buf.readInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long readLong() {
|
||||||
|
if (buf == null) throw new IndexOutOfBoundsException();
|
||||||
|
return buf.readLong();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float readFloat() {
|
||||||
|
if (buf == null) throw new IndexOutOfBoundsException();
|
||||||
|
return buf.readFloat();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double readDouble() {
|
||||||
|
if (buf == null) throw new IndexOutOfBoundsException();
|
||||||
|
return buf.readDouble();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String readLine() {
|
||||||
|
if (buf == null) throw new IndexOutOfBoundsException();
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public String readUTF() {
|
||||||
|
if (buf == null) throw new IndexOutOfBoundsException();
|
||||||
|
var len = buf.readUnsignedShort();
|
||||||
|
byte[] bytes = new byte[len];
|
||||||
|
buf.copyInto(buf.readerOffset(), bytes, 0, len);
|
||||||
|
buf.readerOffset(buf.readerOffset() + len);
|
||||||
|
return new String(bytes, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getReadBytesCount() {
|
||||||
|
if (buf == null) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return buf.readerOffset() - initialReaderOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,5 @@
|
||||||
package it.cavallium.dbengine.database.serialization;
|
package it.cavallium.dbengine.database.serialization;
|
||||||
|
|
||||||
import java.io.DataInput;
|
|
||||||
import java.io.DataOutput;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
|
|
@ -11,23 +11,23 @@ import org.warp.commonutils.error.IndexOutOfBoundsException;
|
||||||
|
|
||||||
public class CodecSerializer<A> implements Serializer<A> {
|
public class CodecSerializer<A> implements Serializer<A> {
|
||||||
|
|
||||||
private final BufferAllocator allocator;
|
|
||||||
private final Codecs<A> deserializationCodecs;
|
private final Codecs<A> deserializationCodecs;
|
||||||
private final Codec<A> serializationCodec;
|
private final Codec<A> serializationCodec;
|
||||||
private final int serializationCodecId;
|
private final int serializationCodecId;
|
||||||
private final boolean microCodecs;
|
private final boolean microCodecs;
|
||||||
|
private final int serializedSizeHint;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param microCodecs if true, allow only codecs with a value from 0 to 255 to save disk space
|
* @param microCodecs if true, allow only codecs with a value from 0 to 255 to save disk space
|
||||||
|
* @param serializedSizeHint suggested default buffer size, -1 if unknown
|
||||||
*/
|
*/
|
||||||
public CodecSerializer(
|
public CodecSerializer(
|
||||||
BufferAllocator allocator,
|
|
||||||
Codecs<A> deserializationCodecs,
|
Codecs<A> deserializationCodecs,
|
||||||
Codec<A> serializationCodec,
|
Codec<A> serializationCodec,
|
||||||
int serializationCodecId,
|
int serializationCodecId,
|
||||||
boolean microCodecs) {
|
boolean microCodecs,
|
||||||
this.allocator = allocator;
|
int serializedSizeHint) {
|
||||||
this.deserializationCodecs = deserializationCodecs;
|
this.deserializationCodecs = deserializationCodecs;
|
||||||
this.serializationCodec = serializationCodec;
|
this.serializationCodec = serializationCodec;
|
||||||
this.serializationCodecId = serializationCodecId;
|
this.serializationCodecId = serializationCodecId;
|
||||||
|
@ -35,11 +35,22 @@ public class CodecSerializer<A> implements Serializer<A> {
|
||||||
if (microCodecs && (serializationCodecId > 255 || serializationCodecId < 0)) {
|
if (microCodecs && (serializationCodecId > 255 || serializationCodecId < 0)) {
|
||||||
throw new IndexOutOfBoundsException(serializationCodecId, 0, 255);
|
throw new IndexOutOfBoundsException(serializationCodecId, 0, 255);
|
||||||
}
|
}
|
||||||
|
if (serializedSizeHint != -1) {
|
||||||
|
this.serializedSizeHint = (microCodecs ? Byte.BYTES : Integer.BYTES) + serializedSizeHint;
|
||||||
|
} else {
|
||||||
|
this.serializedSizeHint = -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull DeserializationResult<A> deserialize(@Nullable Send<Buffer> serializedToReceive) {
|
public int getSerializedSizeHint() {
|
||||||
try (var is = new BufferDataInput(serializedToReceive)) {
|
return serializedSizeHint;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull A deserialize(@NotNull Buffer serializedBuf) throws SerializationException {
|
||||||
|
try {
|
||||||
|
var is = new BufferDataInputShared(serializedBuf);
|
||||||
int codecId;
|
int codecId;
|
||||||
if (microCodecs) {
|
if (microCodecs) {
|
||||||
codecId = is.readUnsignedByte();
|
codecId = is.readUnsignedByte();
|
||||||
|
@ -47,7 +58,7 @@ public class CodecSerializer<A> implements Serializer<A> {
|
||||||
codecId = is.readInt();
|
codecId = is.readInt();
|
||||||
}
|
}
|
||||||
var serializer = deserializationCodecs.getCodec(codecId);
|
var serializer = deserializationCodecs.getCodec(codecId);
|
||||||
return new DeserializationResult<>(serializer.deserialize(is), is.getReadBytesCount());
|
return serializer.deserialize(is);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
// This shouldn't happen
|
// This shouldn't happen
|
||||||
throw new IOError(ex);
|
throw new IOError(ex);
|
||||||
|
@ -55,16 +66,15 @@ public class CodecSerializer<A> implements Serializer<A> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Send<Buffer> serialize(@NotNull A deserialized) {
|
public void serialize(@NotNull A deserialized, Buffer output) throws SerializationException {
|
||||||
try (Buffer buf = allocator.allocate(64)) {
|
try {
|
||||||
var os = new BufferDataOutput(buf);
|
var os = new BufferDataOutput(output);
|
||||||
if (microCodecs) {
|
if (microCodecs) {
|
||||||
os.writeByte(serializationCodecId);
|
os.writeByte(serializationCodecId);
|
||||||
} else {
|
} else {
|
||||||
os.writeInt(serializationCodecId);
|
os.writeInt(serializationCodecId);
|
||||||
}
|
}
|
||||||
serializationCodec.serialize(os, deserialized);
|
serializationCodec.serialize(os, deserialized);
|
||||||
return buf.send();
|
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
// This shouldn't happen
|
// This shouldn't happen
|
||||||
throw new IOError(ex);
|
throw new IOError(ex);
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package it.cavallium.dbengine.database.serialization;
|
package it.cavallium.dbengine.database.serialization;
|
||||||
|
|
||||||
|
import io.net5.buffer.ByteBufUtil;
|
||||||
import io.net5.buffer.api.Buffer;
|
import io.net5.buffer.api.Buffer;
|
||||||
import io.net5.buffer.api.BufferAllocator;
|
import io.net5.buffer.api.BufferAllocator;
|
||||||
import io.net5.buffer.api.Send;
|
import io.net5.buffer.api.Send;
|
||||||
|
import io.net5.util.internal.StringUtil;
|
||||||
import it.cavallium.dbengine.database.LLUtils;
|
import it.cavallium.dbengine.database.LLUtils;
|
||||||
import it.cavallium.dbengine.netty.NullableBuffer;
|
import it.cavallium.dbengine.netty.NullableBuffer;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
@ -12,56 +14,82 @@ import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
public interface Serializer<A> {
|
public interface Serializer<A> {
|
||||||
|
|
||||||
record DeserializationResult<T>(T deserializedData, int bytesRead) {}
|
/**
|
||||||
|
*
|
||||||
|
* @param serialized the serialized data should be split!
|
||||||
|
*/
|
||||||
|
@NotNull A deserialize(@NotNull Buffer serialized) throws SerializationException;
|
||||||
|
|
||||||
@NotNull DeserializationResult<A> deserialize(@NotNull Send<Buffer> serialized) throws SerializationException;
|
/**
|
||||||
|
* @param output its writable size will be at least equal to the size hint
|
||||||
|
*/
|
||||||
|
void serialize(@NotNull A deserialized, Buffer output) throws SerializationException;
|
||||||
|
|
||||||
@NotNull Send<Buffer> serialize(@NotNull A deserialized) throws SerializationException;
|
/**
|
||||||
|
* @return suggested default buffer size, -1 if unknown
|
||||||
|
*/
|
||||||
|
int getSerializedSizeHint();
|
||||||
|
|
||||||
Serializer<Send<Buffer>> NOOP_SERIALIZER = new Serializer<>() {
|
Serializer<Buffer> NOOP_SERIALIZER = new Serializer<>() {
|
||||||
@Override
|
@Override
|
||||||
public @NotNull DeserializationResult<Send<Buffer>> deserialize(@NotNull Send<Buffer> serialized) {
|
public @NotNull Buffer deserialize(@NotNull Buffer serialized) {
|
||||||
try (var serializedBuf = serialized.receive()) {
|
return serialized.split();
|
||||||
var readableBytes = serializedBuf.readableBytes();
|
|
||||||
return new DeserializationResult<>(serializedBuf.send(), readableBytes);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Send<Buffer> serialize(@NotNull Send<Buffer> deserialized) {
|
public void serialize(@NotNull Buffer deserialized, @NotNull Buffer deserializedToReceive) {
|
||||||
return deserialized;
|
deserializedToReceive.ensureWritable(deserialized.readableBytes());
|
||||||
|
deserializedToReceive.writeBytes(deserialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSerializedSizeHint() {
|
||||||
|
return -1;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static Serializer<Send<Buffer>> noop() {
|
Serializer<Send<Buffer>> NOOP_SEND_SERIALIZER = new Serializer<>() {
|
||||||
return NOOP_SERIALIZER;
|
@Override
|
||||||
}
|
public @NotNull Send<Buffer> deserialize(@NotNull Buffer serialized) {
|
||||||
|
return serialized.split().send();
|
||||||
|
}
|
||||||
|
|
||||||
static Serializer<String> utf8(BufferAllocator allocator) {
|
@Override
|
||||||
return new Serializer<>() {
|
public void serialize(@NotNull Send<Buffer> deserialized, @NotNull Buffer deserializedToReceive) {
|
||||||
@Override
|
try (var received = deserialized.receive()) {
|
||||||
public @NotNull DeserializationResult<String> deserialize(@Nullable Send<Buffer> serializedToReceive) {
|
deserializedToReceive.ensureWritable(received.readableBytes());
|
||||||
Objects.requireNonNull(serializedToReceive);
|
deserializedToReceive.writeBytes(received);
|
||||||
try (Buffer serialized = serializedToReceive.receive()) {
|
|
||||||
assert serialized.isAccessible();
|
|
||||||
int length = serialized.readInt();
|
|
||||||
var readerOffset = serialized.readerOffset();
|
|
||||||
return new DeserializationResult<>(LLUtils.deserializeString(serialized.send(),
|
|
||||||
readerOffset, length, StandardCharsets.UTF_8), Integer.BYTES + length);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Send<Buffer> serialize(@NotNull String deserialized) {
|
public int getSerializedSizeHint() {
|
||||||
var bytes = deserialized.getBytes(StandardCharsets.UTF_8);
|
return -1;
|
||||||
try (Buffer buf = allocator.allocate(Integer.BYTES + bytes.length)) {
|
}
|
||||||
assert buf.isAccessible();
|
};
|
||||||
buf.writeInt(bytes.length);
|
|
||||||
buf.writeBytes(bytes);
|
|
||||||
assert buf.isAccessible();
|
Serializer<String> UTF8_SERIALIZER = new Serializer<>() {
|
||||||
return buf.send();
|
@Override
|
||||||
}
|
public @NotNull String deserialize(@NotNull Buffer serialized) {
|
||||||
|
assert serialized.isAccessible();
|
||||||
|
int length = serialized.readInt();
|
||||||
|
try (var strBuf = serialized.readSplit(length)) {
|
||||||
|
return LLUtils.deserializeString(strBuf, strBuf.readerOffset(), length, StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
}
|
|
||||||
|
@Override
|
||||||
|
public void serialize(@NotNull String deserialized, Buffer output) {
|
||||||
|
var bytes = deserialized.getBytes(StandardCharsets.UTF_8);
|
||||||
|
output.ensureWritable(Integer.BYTES + bytes.length);
|
||||||
|
output.writeInt(bytes.length);
|
||||||
|
output.writeBytes(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSerializedSizeHint() {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,31 +14,33 @@ public interface SerializerFixedBinaryLength<A> extends Serializer<A> {
|
||||||
|
|
||||||
int getSerializedBinaryLength();
|
int getSerializedBinaryLength();
|
||||||
|
|
||||||
static SerializerFixedBinaryLength<Send<Buffer>> noop(int length) {
|
@Override
|
||||||
|
default int getSerializedSizeHint() {
|
||||||
|
return getSerializedBinaryLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
static SerializerFixedBinaryLength<Buffer> noop(int length) {
|
||||||
return new SerializerFixedBinaryLength<>() {
|
return new SerializerFixedBinaryLength<>() {
|
||||||
@Override
|
@Override
|
||||||
public @NotNull DeserializationResult<Send<Buffer>> deserialize(@NotNull Send<Buffer> serialized) {
|
public @NotNull Buffer deserialize(@NotNull Buffer serialized) {
|
||||||
Objects.requireNonNull(serialized);
|
Objects.requireNonNull(serialized);
|
||||||
try (var buf = serialized.receive()) {
|
if (serialized.readableBytes() < getSerializedBinaryLength()) {
|
||||||
if (buf.readableBytes() != getSerializedBinaryLength()) {
|
throw new IllegalArgumentException(
|
||||||
throw new IllegalArgumentException(
|
"Fixed serializer with " + getSerializedBinaryLength() + " bytes has tried to deserialize an element with "
|
||||||
"Fixed serializer with " + getSerializedBinaryLength() + " bytes has tried to deserialize an element with "
|
+ serialized.readableBytes() + " bytes instead");
|
||||||
+ buf.readableBytes() + " bytes instead");
|
|
||||||
}
|
|
||||||
var readableBytes = buf.readableBytes();
|
|
||||||
return new DeserializationResult<>(buf.send(), readableBytes);
|
|
||||||
}
|
}
|
||||||
|
return serialized.readSplit(getSerializedBinaryLength());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Send<Buffer> serialize(@NotNull Send<Buffer> deserialized) {
|
public void serialize(@NotNull Buffer deserialized, Buffer output) {
|
||||||
try (Buffer buf = deserialized.receive()) {
|
try (deserialized) {
|
||||||
if (buf.readableBytes() != getSerializedBinaryLength()) {
|
if (deserialized.readableBytes() != getSerializedBinaryLength()) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Fixed serializer with " + getSerializedBinaryLength() + " bytes has tried to serialize an element with "
|
"Fixed serializer with " + getSerializedBinaryLength() + " bytes has tried to serialize an element with "
|
||||||
+ buf.readableBytes() + " bytes instead");
|
+ deserialized.readableBytes() + " bytes instead");
|
||||||
}
|
}
|
||||||
return buf.send();
|
output.writeBytes(deserialized);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,39 +51,32 @@ public interface SerializerFixedBinaryLength<A> extends Serializer<A> {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static SerializerFixedBinaryLength<String> utf8(BufferAllocator allocator, int length) {
|
static SerializerFixedBinaryLength<String> utf8(int length) {
|
||||||
return new SerializerFixedBinaryLength<>() {
|
return new SerializerFixedBinaryLength<>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull DeserializationResult<String> deserialize(@NotNull Send<Buffer> serializedToReceive)
|
public @NotNull String deserialize(@NotNull Buffer serialized) throws SerializationException {
|
||||||
throws SerializationException {
|
if (serialized.readableBytes() < getSerializedBinaryLength()) {
|
||||||
try (var serialized = serializedToReceive.receive()) {
|
throw new SerializationException(
|
||||||
if (serialized.readableBytes() != getSerializedBinaryLength()) {
|
"Fixed serializer with " + getSerializedBinaryLength() + " bytes has tried to deserialize an element with "
|
||||||
throw new SerializationException(
|
+ serialized.readableBytes() + " bytes instead");
|
||||||
"Fixed serializer with " + getSerializedBinaryLength() + " bytes has tried to deserialize an element with "
|
|
||||||
+ serialized.readableBytes() + " bytes instead");
|
|
||||||
}
|
|
||||||
var readerOffset = serialized.readerOffset();
|
|
||||||
return new DeserializationResult<>(LLUtils.deserializeString(serialized.send(),
|
|
||||||
readerOffset, length, StandardCharsets.UTF_8), length);
|
|
||||||
}
|
}
|
||||||
|
var readerOffset = serialized.readerOffset();
|
||||||
|
return LLUtils.deserializeString(serialized.send(), readerOffset, length, StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Send<Buffer> serialize(@NotNull String deserialized) throws SerializationException {
|
public void serialize(@NotNull String deserialized, Buffer output) throws SerializationException {
|
||||||
// UTF-8 uses max. 3 bytes per char, so calculate the worst case.
|
assert output.isAccessible();
|
||||||
try (Buffer buf = allocator.allocate(LLUtils.utf8MaxBytes(deserialized))) {
|
var bytes = deserialized.getBytes(StandardCharsets.UTF_8);
|
||||||
assert buf.isAccessible();
|
output.ensureWritable(bytes.length);
|
||||||
var bytes = deserialized.getBytes(StandardCharsets.UTF_8);
|
output.writeBytes(bytes);
|
||||||
buf.ensureWritable(bytes.length);
|
if (output.readableBytes() < getSerializedBinaryLength()) {
|
||||||
buf.writeBytes(bytes);
|
throw new SerializationException("Fixed serializer with " + getSerializedBinaryLength()
|
||||||
if (buf.readableBytes() != getSerializedBinaryLength()) {
|
+ " bytes has tried to serialize an element with "
|
||||||
throw new SerializationException("Fixed serializer with " + getSerializedBinaryLength()
|
+ output.readableBytes() + " bytes instead");
|
||||||
+ " bytes has tried to serialize an element with "
|
|
||||||
+ buf.readableBytes() + " bytes instead");
|
|
||||||
}
|
|
||||||
assert buf.isAccessible();
|
|
||||||
return buf.send();
|
|
||||||
}
|
}
|
||||||
|
assert output.isAccessible();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -93,24 +88,21 @@ public interface SerializerFixedBinaryLength<A> extends Serializer<A> {
|
||||||
|
|
||||||
static SerializerFixedBinaryLength<Integer> intSerializer(BufferAllocator allocator) {
|
static SerializerFixedBinaryLength<Integer> intSerializer(BufferAllocator allocator) {
|
||||||
return new SerializerFixedBinaryLength<>() {
|
return new SerializerFixedBinaryLength<>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull DeserializationResult<Integer> deserialize(@NotNull Send<Buffer> serializedToReceive) {
|
public @NotNull Integer deserialize(@NotNull Buffer serialized) {
|
||||||
Objects.requireNonNull(serializedToReceive);
|
Objects.requireNonNull(serialized);
|
||||||
try (var serialized = serializedToReceive.receive()) {
|
if (serialized.readableBytes() < getSerializedBinaryLength()) {
|
||||||
if (serialized.readableBytes() != getSerializedBinaryLength()) {
|
throw new IllegalArgumentException(
|
||||||
throw new IllegalArgumentException(
|
"Fixed serializer with " + getSerializedBinaryLength() + " bytes has tried to deserialize an element with "
|
||||||
"Fixed serializer with " + getSerializedBinaryLength() + " bytes has tried to deserialize an element with "
|
+ serialized.readableBytes() + " bytes instead");
|
||||||
+ serialized.readableBytes() + " bytes instead");
|
|
||||||
}
|
|
||||||
return new DeserializationResult<>(serialized.readInt(), Integer.BYTES);
|
|
||||||
}
|
}
|
||||||
|
return serialized.readInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Send<Buffer> serialize(@NotNull Integer deserialized) {
|
public void serialize(@NotNull Integer deserialized, Buffer output) {
|
||||||
try (Buffer buf = allocator.allocate(Integer.BYTES)) {
|
output.writeInt(deserialized);
|
||||||
return buf.writeInt(deserialized).send();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -122,25 +114,21 @@ public interface SerializerFixedBinaryLength<A> extends Serializer<A> {
|
||||||
|
|
||||||
static SerializerFixedBinaryLength<Long> longSerializer(BufferAllocator allocator) {
|
static SerializerFixedBinaryLength<Long> longSerializer(BufferAllocator allocator) {
|
||||||
return new SerializerFixedBinaryLength<>() {
|
return new SerializerFixedBinaryLength<>() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull DeserializationResult<Long> deserialize(@NotNull Send<Buffer> serializedToReceive) {
|
public @NotNull Long deserialize(@NotNull Buffer serialized) {
|
||||||
Objects.requireNonNull(serializedToReceive);
|
Objects.requireNonNull(serialized);
|
||||||
try (var serialized = serializedToReceive.receive()) {
|
if (serialized.readableBytes() < getSerializedBinaryLength()) {
|
||||||
if (serialized.readableBytes() != getSerializedBinaryLength()) {
|
throw new IllegalArgumentException(
|
||||||
throw new IllegalArgumentException(
|
"Fixed serializer with " + getSerializedBinaryLength() + " bytes has tried to deserialize an element with "
|
||||||
"Fixed serializer with " + getSerializedBinaryLength() + " bytes has tried to deserialize an element with "
|
+ serialized.readableBytes() + " bytes instead");
|
||||||
+ serialized.readableBytes() + " bytes instead");
|
|
||||||
}
|
|
||||||
var readableBytes = serialized.readableBytes();
|
|
||||||
return new DeserializationResult<>(serialized.readLong(), Long.BYTES);
|
|
||||||
}
|
}
|
||||||
|
return serialized.readLong();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Send<Buffer> serialize(@NotNull Long deserialized) {
|
public void serialize(@NotNull Long deserialized, Buffer output) {
|
||||||
try (Buffer buf = allocator.allocate(Long.BYTES)) {
|
output.writeLong(deserialized);
|
||||||
return buf.writeLong(deserialized).send();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -24,6 +24,7 @@ import it.cavallium.dbengine.database.collections.DatabaseStageMap;
|
||||||
import it.cavallium.dbengine.database.collections.SubStageGetterHashMap;
|
import it.cavallium.dbengine.database.collections.SubStageGetterHashMap;
|
||||||
import it.cavallium.dbengine.database.collections.SubStageGetterMap;
|
import it.cavallium.dbengine.database.collections.SubStageGetterMap;
|
||||||
import it.cavallium.dbengine.database.disk.MemorySegmentUtils;
|
import it.cavallium.dbengine.database.disk.MemorySegmentUtils;
|
||||||
|
import it.cavallium.dbengine.database.serialization.SerializationException;
|
||||||
import it.cavallium.dbengine.database.serialization.Serializer;
|
import it.cavallium.dbengine.database.serialization.Serializer;
|
||||||
import it.cavallium.dbengine.database.serialization.SerializerFixedBinaryLength;
|
import it.cavallium.dbengine.database.serialization.SerializerFixedBinaryLength;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
@ -154,14 +155,14 @@ public class DbTestUtils {
|
||||||
int keyBytes) {
|
int keyBytes) {
|
||||||
if (mapType == MapType.MAP) {
|
if (mapType == MapType.MAP) {
|
||||||
return DatabaseMapDictionary.simple(dictionary,
|
return DatabaseMapDictionary.simple(dictionary,
|
||||||
SerializerFixedBinaryLength.utf8(dictionary.getAllocator(), keyBytes),
|
SerializerFixedBinaryLength.utf8(keyBytes),
|
||||||
Serializer.utf8(dictionary.getAllocator()),
|
Serializer.UTF8_SERIALIZER,
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return DatabaseMapDictionaryHashed.simple(dictionary,
|
return DatabaseMapDictionaryHashed.simple(dictionary,
|
||||||
Serializer.utf8(dictionary.getAllocator()),
|
Serializer.UTF8_SERIALIZER,
|
||||||
Serializer.utf8(dictionary.getAllocator()),
|
Serializer.UTF8_SERIALIZER,
|
||||||
s -> (short) s.hashCode(),
|
s -> (short) s.hashCode(),
|
||||||
new SerializerFixedBinaryLength<>() {
|
new SerializerFixedBinaryLength<>() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -170,21 +171,15 @@ public class DbTestUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull DeserializationResult<Short> deserialize(@Nullable Send<Buffer> serializedToReceive) {
|
public @NotNull Short deserialize(@NotNull Buffer serialized) throws SerializationException {
|
||||||
Objects.requireNonNull(serializedToReceive);
|
Objects.requireNonNull(serialized);
|
||||||
try (var serialized = serializedToReceive.receive()) {
|
var val = serialized.readShort();
|
||||||
var val = serialized.readShort();
|
return val;
|
||||||
return new DeserializationResult<>(val, Short.BYTES);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NotNull Send<Buffer> serialize(@NotNull Short deserialized) {
|
public void serialize(@NotNull Short deserialized, Buffer output) throws SerializationException {
|
||||||
try (var out = dictionary.getAllocator().allocate(Short.BYTES)) {
|
output.writeShort(deserialized);
|
||||||
out.writeShort(deserialized);
|
|
||||||
out.writerOffset(Short.BYTES);
|
|
||||||
return out.send();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
null
|
null
|
||||||
|
@ -198,10 +193,10 @@ public class DbTestUtils {
|
||||||
int key1Bytes,
|
int key1Bytes,
|
||||||
int key2Bytes) {
|
int key2Bytes) {
|
||||||
return DatabaseMapDictionaryDeep.deepTail(dictionary,
|
return DatabaseMapDictionaryDeep.deepTail(dictionary,
|
||||||
SerializerFixedBinaryLength.utf8(dictionary.getAllocator(), key1Bytes),
|
SerializerFixedBinaryLength.utf8(key1Bytes),
|
||||||
key2Bytes,
|
key2Bytes,
|
||||||
new SubStageGetterMap<>(SerializerFixedBinaryLength.utf8(dictionary.getAllocator(), key2Bytes),
|
new SubStageGetterMap<>(SerializerFixedBinaryLength.utf8(key2Bytes),
|
||||||
Serializer.utf8(dictionary.getAllocator())
|
Serializer.UTF8_SERIALIZER
|
||||||
),
|
),
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
|
@ -212,10 +207,10 @@ public class DbTestUtils {
|
||||||
LLDictionary dictionary,
|
LLDictionary dictionary,
|
||||||
int key1Bytes) {
|
int key1Bytes) {
|
||||||
return DatabaseMapDictionaryDeep.deepTail(dictionary,
|
return DatabaseMapDictionaryDeep.deepTail(dictionary,
|
||||||
SerializerFixedBinaryLength.utf8(dictionary.getAllocator(), key1Bytes),
|
SerializerFixedBinaryLength.utf8(key1Bytes),
|
||||||
Integer.BYTES,
|
Integer.BYTES,
|
||||||
new SubStageGetterHashMap<>(Serializer.utf8(dictionary.getAllocator()),
|
new SubStageGetterHashMap<>(Serializer.UTF8_SERIALIZER,
|
||||||
Serializer.utf8(dictionary.getAllocator()),
|
Serializer.UTF8_SERIALIZER,
|
||||||
String::hashCode,
|
String::hashCode,
|
||||||
SerializerFixedBinaryLength.intSerializer(dictionary.getAllocator())
|
SerializerFixedBinaryLength.intSerializer(dictionary.getAllocator())
|
||||||
),
|
),
|
||||||
|
@ -226,8 +221,8 @@ public class DbTestUtils {
|
||||||
public static <T, U> DatabaseMapDictionaryHashed<String, String, Integer> tempDatabaseMapDictionaryHashMap(
|
public static <T, U> DatabaseMapDictionaryHashed<String, String, Integer> tempDatabaseMapDictionaryHashMap(
|
||||||
LLDictionary dictionary) {
|
LLDictionary dictionary) {
|
||||||
return DatabaseMapDictionaryHashed.simple(dictionary,
|
return DatabaseMapDictionaryHashed.simple(dictionary,
|
||||||
Serializer.utf8(dictionary.getAllocator()),
|
Serializer.UTF8_SERIALIZER,
|
||||||
Serializer.utf8(dictionary.getAllocator()),
|
Serializer.UTF8_SERIALIZER,
|
||||||
String::hashCode,
|
String::hashCode,
|
||||||
SerializerFixedBinaryLength.intSerializer(dictionary.getAllocator()),
|
SerializerFixedBinaryLength.intSerializer(dictionary.getAllocator()),
|
||||||
null
|
null
|
||||||
|
|
|
@ -224,7 +224,7 @@ public abstract class TestDictionaryMap {
|
||||||
.flatMapMany(map -> Flux
|
.flatMapMany(map -> Flux
|
||||||
.concat(
|
.concat(
|
||||||
map.updateValue(key, old -> {
|
map.updateValue(key, old -> {
|
||||||
assert old == null;
|
Assertions.assertNull(old);
|
||||||
return "error?";
|
return "error?";
|
||||||
}),
|
}),
|
||||||
map.updateValue(key, false, old -> {
|
map.updateValue(key, false, old -> {
|
||||||
|
@ -624,8 +624,8 @@ public abstract class TestDictionaryMap {
|
||||||
.concat(
|
.concat(
|
||||||
map.putMulti(Flux.fromIterable(entries.entrySet())).then(Mono.empty()),
|
map.putMulti(Flux.fromIterable(entries.entrySet())).then(Mono.empty()),
|
||||||
map.get(null)
|
map.get(null)
|
||||||
.map(Map::entrySet)
|
.map(Map::entrySet)
|
||||||
.flatMapIterable(list -> list)
|
.flatMapIterable(list -> list)
|
||||||
)
|
)
|
||||||
.doFinally(s -> map.close())
|
.doFinally(s -> map.close())
|
||||||
)
|
)
|
||||||
|
@ -707,27 +707,37 @@ public abstract class TestDictionaryMap {
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@MethodSource("provideArgumentsPutMulti")
|
@MethodSource("provideArgumentsPutMulti")
|
||||||
public void testPutMultiClear(MapType mapType, UpdateMode updateMode, Map<String, String> entries, boolean shouldFail) {
|
public void testPutMultiClear(MapType mapType, UpdateMode updateMode, Map<String, String> entries, boolean shouldFail) {
|
||||||
Step<Boolean> stpVer = StepVerifier
|
List<Boolean> result;
|
||||||
.create(tempDb(getTempDbGenerator(), allocator, db -> tempDictionary(db, updateMode)
|
try {
|
||||||
.map(dict -> tempDatabaseMapDictionaryMap(dict, mapType, 5))
|
result = SyncUtils.run(DbTestUtils.tempDb(getTempDbGenerator(), allocator, db -> tempDictionary(db, updateMode)
|
||||||
.flatMapMany(map -> Flux
|
.map(dict -> tempDatabaseMapDictionaryMap(dict, mapType, 5))
|
||||||
.concat(
|
.flatMapMany(map -> Flux
|
||||||
map.isEmpty(null),
|
.concat(
|
||||||
map.putMulti(Flux.fromIterable(entries.entrySet())).then(Mono.empty()),
|
map.isEmpty(null),
|
||||||
map.isEmpty(null),
|
map.putMulti(Flux.fromIterable(entries.entrySet())).then(Mono.empty()),
|
||||||
map.clear().then(Mono.empty()),
|
map.isEmpty(null),
|
||||||
map.isEmpty(null)
|
map.clear().then(Mono.empty()),
|
||||||
)
|
map.isEmpty(null)
|
||||||
.doFinally(s -> map.close())
|
)
|
||||||
)
|
.doFinally(s -> map.close())
|
||||||
.flatMap(val -> shouldFail ? Mono.empty() : Mono.just(val))
|
)
|
||||||
.transform(LLUtils::handleDiscard)
|
.flatMap(val -> shouldFail ? Mono.empty() : Mono.just(val))
|
||||||
));
|
.transform(LLUtils::handleDiscard)
|
||||||
if (shouldFail) {
|
.collectList()
|
||||||
this.checkLeaks = false;
|
).singleOrEmpty());
|
||||||
stpVer.verifyError();
|
} catch (Exception ex) {
|
||||||
} else {
|
if (shouldFail) {
|
||||||
stpVer.expectNext(true, entries.isEmpty(), true).verifyComplete();
|
this.checkLeaks = false;
|
||||||
|
} else {
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Assertions.assertEquals(true, result.get(0));
|
||||||
|
|
||||||
|
Assertions.assertEquals(entries.isEmpty(), result.get(1));
|
||||||
|
|
||||||
|
Assertions.assertEquals(true, result.get(2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue