Implement singletons
This commit is contained in:
parent
e866241ff1
commit
de5be6564e
@ -113,6 +113,9 @@ versions:
|
|||||||
javaClass: it.cavallium.dbengine.database.LLSnapshot
|
javaClass: it.cavallium.dbengine.database.LLSnapshot
|
||||||
serializer: it.cavallium.dbengine.database.remote.LLSnapshotSerializer
|
serializer: it.cavallium.dbengine.database.remote.LLSnapshotSerializer
|
||||||
|
|
||||||
|
Bytes:
|
||||||
|
javaClass: it.unimi.dsi.fastutil.bytes.ByteList
|
||||||
|
serializer: it.cavallium.dbengine.database.remote.ByteListSerializer
|
||||||
StringMap:
|
StringMap:
|
||||||
javaClass: java.util.Map<java.lang.String, java.lang.String>
|
javaClass: java.util.Map<java.lang.String, java.lang.String>
|
||||||
serializer: it.cavallium.dbengine.database.remote.StringMapSerializer
|
serializer: it.cavallium.dbengine.database.remote.StringMapSerializer
|
||||||
@ -150,7 +153,7 @@ versions:
|
|||||||
databaseId: long
|
databaseId: long
|
||||||
singletonListColumnName: byte[]
|
singletonListColumnName: byte[]
|
||||||
name: byte[]
|
name: byte[]
|
||||||
defaultValue: byte[]
|
defaultValue: -Bytes
|
||||||
SingletonGet:
|
SingletonGet:
|
||||||
data:
|
data:
|
||||||
singletonId: long
|
singletonId: long
|
||||||
@ -158,7 +161,7 @@ versions:
|
|||||||
SingletonSet:
|
SingletonSet:
|
||||||
data:
|
data:
|
||||||
singletonId: long
|
singletonId: long
|
||||||
value: byte[]
|
value: -Bytes
|
||||||
SingletonUpdateInit:
|
SingletonUpdateInit:
|
||||||
data:
|
data:
|
||||||
singletonId: long
|
singletonId: long
|
||||||
|
@ -7,13 +7,14 @@ import io.netty5.buffer.api.BufferAllocator;
|
|||||||
import it.cavallium.dbengine.client.MemoryStats;
|
import it.cavallium.dbengine.client.MemoryStats;
|
||||||
import it.cavallium.dbengine.database.collections.DatabaseInt;
|
import it.cavallium.dbengine.database.collections.DatabaseInt;
|
||||||
import it.cavallium.dbengine.database.collections.DatabaseLong;
|
import it.cavallium.dbengine.database.collections.DatabaseLong;
|
||||||
import it.cavallium.dbengine.rpc.current.data.Column;
|
import it.cavallium.dbengine.database.collections.DatabaseSingleton;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
public interface LLKeyValueDatabase extends LLSnapshottable, LLKeyValueDatabaseStructure {
|
public interface LLKeyValueDatabase extends LLSnapshottable, LLKeyValueDatabaseStructure {
|
||||||
|
|
||||||
Mono<? extends LLSingleton> getSingleton(byte[] singletonListColumnName, byte[] name, byte[] defaultValue);
|
Mono<? extends LLSingleton> getSingleton(byte[] singletonListColumnName, byte[] name, byte @Nullable[] defaultValue);
|
||||||
|
|
||||||
Mono<? extends LLDictionary> getDictionary(byte[] columnName, UpdateMode updateMode);
|
Mono<? extends LLDictionary> getDictionary(byte[] columnName, UpdateMode updateMode);
|
||||||
|
|
||||||
@ -26,6 +27,13 @@ public interface LLKeyValueDatabase extends LLSnapshottable, LLKeyValueDatabaseS
|
|||||||
return getDictionary(ColumnUtils.dictionary(name).name().getBytes(StandardCharsets.US_ASCII), updateMode);
|
return getDictionary(ColumnUtils.dictionary(name).name().getBytes(StandardCharsets.US_ASCII), updateMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default Mono<? extends LLSingleton> getSingleton(String singletonListName, String name) {
|
||||||
|
return getSingleton(ColumnUtils.special(singletonListName).name().getBytes(StandardCharsets.US_ASCII),
|
||||||
|
name.getBytes(StandardCharsets.US_ASCII),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
default Mono<DatabaseInt> getInteger(String singletonListName, String name, int defaultValue) {
|
default Mono<DatabaseInt> getInteger(String singletonListName, String name, int defaultValue) {
|
||||||
return this
|
return this
|
||||||
.getSingleton(ColumnUtils.special(singletonListName).name().getBytes(StandardCharsets.US_ASCII),
|
.getSingleton(ColumnUtils.special(singletonListName).name().getBytes(StandardCharsets.US_ASCII),
|
||||||
|
@ -14,10 +14,20 @@ public interface LLSingleton extends LLKeyValueDatabaseStructure {
|
|||||||
|
|
||||||
BufferAllocator getAllocator();
|
BufferAllocator getAllocator();
|
||||||
|
|
||||||
Mono<byte[]> get(@Nullable LLSnapshot snapshot);
|
Mono<Send<Buffer>> get(@Nullable LLSnapshot snapshot);
|
||||||
|
|
||||||
Mono<Void> set(byte[] value);
|
Mono<Void> set(Mono<Send<Buffer>> value);
|
||||||
|
|
||||||
Mono<Send<Buffer>> update(SerializationFunction<@Nullable Send<Buffer>, @Nullable Buffer> updater,
|
default Mono<Send<Buffer>> update(SerializationFunction<@Nullable Send<Buffer>, @Nullable Buffer> updater,
|
||||||
UpdateReturnMode updateReturnMode);
|
UpdateReturnMode updateReturnMode) {
|
||||||
|
return this
|
||||||
|
.updateAndGetDelta(updater)
|
||||||
|
.transform(prev -> LLUtils.resolveLLDelta(prev, updateReturnMode));
|
||||||
|
}
|
||||||
|
|
||||||
|
Mono<Send<LLDelta>> updateAndGetDelta(SerializationFunction<@Nullable Send<Buffer>, @Nullable Buffer> updater);
|
||||||
|
|
||||||
|
String getColumnName();
|
||||||
|
|
||||||
|
String getName();
|
||||||
}
|
}
|
||||||
|
@ -84,6 +84,7 @@ public class LLUtils {
|
|||||||
private static final byte[] RESPONSE_FALSE_BUF = new byte[]{0};
|
private static final byte[] RESPONSE_FALSE_BUF = new byte[]{0};
|
||||||
public static final byte[][] LEXICONOGRAPHIC_ITERATION_SEEKS = new byte[256][1];
|
public static final byte[][] LEXICONOGRAPHIC_ITERATION_SEEKS = new byte[256][1];
|
||||||
public static final AtomicBoolean hookRegistered = new AtomicBoolean();
|
public static final AtomicBoolean hookRegistered = new AtomicBoolean();
|
||||||
|
public static final boolean MANUAL_READAHEAD = false;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
for (int i1 = 0; i1 < 256; i1++) {
|
for (int i1 = 0; i1 < 256; i1++) {
|
||||||
@ -730,7 +731,9 @@ public class LLUtils {
|
|||||||
readOptions = new ReadOptions();
|
readOptions = new ReadOptions();
|
||||||
}
|
}
|
||||||
if (!closedRange) {
|
if (!closedRange) {
|
||||||
readOptions.setReadaheadSize(32 * 1024); // 32KiB
|
if (LLUtils.MANUAL_READAHEAD) {
|
||||||
|
readOptions.setReadaheadSize(32 * 1024); // 32KiB
|
||||||
|
}
|
||||||
readOptions.setFillCache(false);
|
readOptions.setFillCache(false);
|
||||||
readOptions.setVerifyChecksums(false);
|
readOptions.setVerifyChecksums(false);
|
||||||
} else {
|
} else {
|
||||||
|
@ -2,14 +2,9 @@ package it.cavallium.dbengine.database.collections;
|
|||||||
|
|
||||||
import io.netty5.buffer.api.Buffer;
|
import io.netty5.buffer.api.Buffer;
|
||||||
import io.netty5.buffer.api.BufferAllocator;
|
import io.netty5.buffer.api.BufferAllocator;
|
||||||
import io.netty5.buffer.api.Drop;
|
|
||||||
import io.netty5.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.serialization.SerializationException;
|
|
||||||
import it.cavallium.dbengine.database.serialization.Serializer;
|
import it.cavallium.dbengine.database.serialization.Serializer;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
public class DatabaseEmpty {
|
public class DatabaseEmpty {
|
||||||
|
|
||||||
@ -40,7 +35,7 @@ public class DatabaseEmpty {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static DatabaseStageEntry<Nothing> create(LLDictionary dictionary, Buffer key, Runnable onClose) {
|
public static DatabaseStageEntry<Nothing> create(LLDictionary dictionary, Buffer key, Runnable onClose) {
|
||||||
return new DatabaseSingle<>(dictionary, key, nothingSerializer(dictionary.getAllocator()), onClose);
|
return new DatabaseMapSingle<>(dictionary, key, nothingSerializer(dictionary.getAllocator()), onClose);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class Nothing {
|
public static final class Nothing {
|
||||||
|
@ -4,23 +4,38 @@ import com.google.common.primitives.Ints;
|
|||||||
import it.cavallium.dbengine.database.LLKeyValueDatabaseStructure;
|
import it.cavallium.dbengine.database.LLKeyValueDatabaseStructure;
|
||||||
import it.cavallium.dbengine.database.LLSingleton;
|
import it.cavallium.dbengine.database.LLSingleton;
|
||||||
import it.cavallium.dbengine.database.LLSnapshot;
|
import it.cavallium.dbengine.database.LLSnapshot;
|
||||||
|
import it.cavallium.dbengine.database.serialization.SerializationException;
|
||||||
|
import it.cavallium.dbengine.database.serialization.SerializerFixedBinaryLength;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
public class DatabaseInt implements LLKeyValueDatabaseStructure {
|
public class DatabaseInt implements LLKeyValueDatabaseStructure {
|
||||||
|
|
||||||
private final LLSingleton singleton;
|
private final LLSingleton singleton;
|
||||||
|
private final SerializerFixedBinaryLength<Integer> serializer;
|
||||||
|
|
||||||
public DatabaseInt(LLSingleton singleton) {
|
public DatabaseInt(LLSingleton singleton) {
|
||||||
this.singleton = singleton;
|
this.singleton = singleton;
|
||||||
|
this.serializer = SerializerFixedBinaryLength.intSerializer(singleton.getAllocator());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Mono<Integer> get(@Nullable LLSnapshot snapshot) {
|
public Mono<Integer> get(@Nullable LLSnapshot snapshot) {
|
||||||
return singleton.get(snapshot).map(Ints::fromByteArray);
|
return singleton.get(snapshot).handle((dataSend, sink) -> {
|
||||||
|
try (var data = dataSend.receive()) {
|
||||||
|
sink.next(serializer.deserialize(data));
|
||||||
|
} catch (SerializationException e) {
|
||||||
|
sink.error(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Mono<Void> set(int value) {
|
public Mono<Void> set(int value) {
|
||||||
return singleton.set(Ints.toByteArray(value));
|
return singleton.set(Mono.fromCallable(() -> {
|
||||||
|
try (var buf = singleton.getAllocator().allocate(Integer.BYTES)) {
|
||||||
|
serializer.serialize(value, buf);
|
||||||
|
return buf.send();
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -6,23 +6,33 @@ import it.cavallium.dbengine.database.LLKeyValueDatabaseStructure;
|
|||||||
import it.cavallium.dbengine.database.LLSingleton;
|
import it.cavallium.dbengine.database.LLSingleton;
|
||||||
import it.cavallium.dbengine.database.LLSnapshot;
|
import it.cavallium.dbengine.database.LLSnapshot;
|
||||||
import it.cavallium.dbengine.database.UpdateReturnMode;
|
import it.cavallium.dbengine.database.UpdateReturnMode;
|
||||||
|
import it.cavallium.dbengine.database.serialization.SerializationException;
|
||||||
|
import it.cavallium.dbengine.database.serialization.SerializerFixedBinaryLength;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
public class DatabaseLong implements LLKeyValueDatabaseStructure {
|
public class DatabaseLong implements LLKeyValueDatabaseStructure {
|
||||||
|
|
||||||
private final LLSingleton singleton;
|
private final LLSingleton singleton;
|
||||||
|
private final SerializerFixedBinaryLength<Long> serializer;
|
||||||
|
private final SerializerFixedBinaryLength<Integer> bugSerializer;
|
||||||
|
|
||||||
public DatabaseLong(LLSingleton singleton) {
|
public DatabaseLong(LLSingleton singleton) {
|
||||||
this.singleton = singleton;
|
this.singleton = singleton;
|
||||||
|
this.serializer = SerializerFixedBinaryLength.longSerializer(singleton.getAllocator());
|
||||||
|
this.bugSerializer = SerializerFixedBinaryLength.intSerializer(singleton.getAllocator());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Mono<Long> get(@Nullable LLSnapshot snapshot) {
|
public Mono<Long> get(@Nullable LLSnapshot snapshot) {
|
||||||
return singleton.get(snapshot).map(array -> {
|
return singleton.get(snapshot).handle((dataSend, sink) -> {
|
||||||
if (array.length == 4) {
|
try (var data = dataSend.receive()) {
|
||||||
return (long) Ints.fromByteArray(array);
|
if (data.readableBytes() == 4) {
|
||||||
} else {
|
sink.next((long) (int) bugSerializer.deserialize(data));
|
||||||
return Longs.fromByteArray(array);
|
} else {
|
||||||
|
sink.next(serializer.deserialize(data));
|
||||||
|
}
|
||||||
|
} catch (SerializationException e) {
|
||||||
|
sink.error(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -75,7 +85,12 @@ public class DatabaseLong implements LLKeyValueDatabaseStructure {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Mono<Void> set(long value) {
|
public Mono<Void> set(long value) {
|
||||||
return singleton.set(Longs.toByteArray(value));
|
return singleton.set(Mono.fromCallable(() -> {
|
||||||
|
try (var buf = singleton.getAllocator().allocate(Long.BYTES)) {
|
||||||
|
serializer.serialize(value, buf);
|
||||||
|
return buf.send();
|
||||||
|
}
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -70,6 +70,20 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static <K, V> Flux<Entry<K, V>> getLeavesFrom(DatabaseMapDictionary<K, V> databaseMapDictionary,
|
||||||
|
CompositeSnapshot snapshot,
|
||||||
|
Mono<K> prevMono) {
|
||||||
|
Mono<Optional<K>> prevOptMono = prevMono.map(Optional::of).defaultIfEmpty(Optional.empty());
|
||||||
|
|
||||||
|
return prevOptMono.flatMapMany(prevOpt -> {
|
||||||
|
if (prevOpt.isPresent()) {
|
||||||
|
return databaseMapDictionary.getAllValues(snapshot, prevOpt.get());
|
||||||
|
} else {
|
||||||
|
return databaseMapDictionary.getAllValues(snapshot);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void deserializeValue(T keySuffix, Send<Buffer> valueToReceive, SynchronousSink<U> sink) {
|
private void deserializeValue(T keySuffix, Send<Buffer> valueToReceive, SynchronousSink<U> sink) {
|
||||||
try (var value = valueToReceive.receive()) {
|
try (var value = valueToReceive.receive()) {
|
||||||
try {
|
try {
|
||||||
@ -213,7 +227,7 @@ 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, serializeKeySuffixToKey(keySuffix), valueSerializer, null));
|
new DatabaseMapSingle<>(dictionary, serializeKeySuffixToKey(keySuffix), valueSerializer, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -456,7 +470,7 @@ public class DatabaseMapDictionary<T, U> extends DatabaseMapDictionaryDeep<T, U,
|
|||||||
try (var keyBufCopy = keyBuf.copy()) {
|
try (var keyBufCopy = keyBuf.copy()) {
|
||||||
keySuffix = deserializeSuffix(keyBufCopy);
|
keySuffix = deserializeSuffix(keyBufCopy);
|
||||||
}
|
}
|
||||||
var subStage = new DatabaseSingle<>(dictionary, toKey(keyBuf), valueSerializer, null);
|
var subStage = new DatabaseMapSingle<>(dictionary, toKey(keyBuf), valueSerializer, null);
|
||||||
sink.next(Map.entry(keySuffix, subStage));
|
sink.next(Map.entry(keySuffix, subStage));
|
||||||
} catch (Throwable ex) {
|
} catch (Throwable ex) {
|
||||||
keyBuf.close();
|
keyBuf.close();
|
||||||
@ -467,8 +481,30 @@ public class DatabaseMapDictionary<T, U> extends DatabaseMapDictionaryDeep<T, U,
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Flux<Entry<T, U>> getAllValues(@Nullable CompositeSnapshot snapshot) {
|
public Flux<Entry<T, U>> getAllValues(@Nullable CompositeSnapshot snapshot) {
|
||||||
|
return getAllValues(snapshot, rangeMono);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Flux<Entry<T, U>> getAllValues(@Nullable CompositeSnapshot snapshot, @Nullable T from) {
|
||||||
|
if (from == null) {
|
||||||
|
return getAllValues(snapshot);
|
||||||
|
} else {
|
||||||
|
Mono<Send<LLRange>> boundedRangeMono = rangeMono.flatMap(fullRangeSend -> Mono.fromCallable(() -> {
|
||||||
|
try (var fullRange = fullRangeSend.receive()) {
|
||||||
|
try (var keyWithoutExtBuf = keyPrefix == null ? alloc.allocate(keySuffixLength + keyExtLength)
|
||||||
|
: keyPrefix.copy()) {
|
||||||
|
keyWithoutExtBuf.ensureWritable(keySuffixLength + keyExtLength);
|
||||||
|
serializeSuffix(from, keyWithoutExtBuf);
|
||||||
|
return LLRange.of(keyWithoutExtBuf.send(), fullRange.getMax()).send();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
return getAllValues(snapshot, boundedRangeMono);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Flux<Entry<T, U>> getAllValues(@Nullable CompositeSnapshot snapshot, Mono<Send<LLRange>> sliceRangeMono) {
|
||||||
return dictionary
|
return dictionary
|
||||||
.getRange(resolveSnapshot(snapshot), rangeMono)
|
.getRange(resolveSnapshot(snapshot), sliceRangeMono)
|
||||||
.<Entry<T, U>>handle((serializedEntryToReceive, sink) -> {
|
.<Entry<T, U>>handle((serializedEntryToReceive, sink) -> {
|
||||||
try {
|
try {
|
||||||
Entry<T, U> entry;
|
Entry<T, U> entry;
|
||||||
|
@ -16,11 +16,17 @@ import it.cavallium.dbengine.database.LLRange;
|
|||||||
import it.cavallium.dbengine.database.LLSnapshot;
|
import it.cavallium.dbengine.database.LLSnapshot;
|
||||||
import it.cavallium.dbengine.database.LLUtils;
|
import it.cavallium.dbengine.database.LLUtils;
|
||||||
import it.cavallium.dbengine.database.UpdateMode;
|
import it.cavallium.dbengine.database.UpdateMode;
|
||||||
|
import it.cavallium.dbengine.database.collections.DatabaseEmpty.Nothing;
|
||||||
import it.cavallium.dbengine.database.serialization.SerializationException;
|
import it.cavallium.dbengine.database.serialization.SerializationException;
|
||||||
|
import it.cavallium.dbengine.database.serialization.Serializer;
|
||||||
import it.cavallium.dbengine.database.serialization.SerializerFixedBinaryLength;
|
import it.cavallium.dbengine.database.serialization.SerializerFixedBinaryLength;
|
||||||
import it.unimi.dsi.fastutil.objects.Object2ObjectSortedMap;
|
import it.unimi.dsi.fastutil.objects.Object2ObjectSortedMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.CompletionException;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
import org.apache.commons.lang3.function.TriFunction;
|
import org.apache.commons.lang3.function.TriFunction;
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
@ -82,7 +88,7 @@ public class DatabaseMapDictionaryDeep<T, U, US extends DatabaseStage<U>> extend
|
|||||||
};
|
};
|
||||||
|
|
||||||
protected final LLDictionary dictionary;
|
protected final LLDictionary dictionary;
|
||||||
private final BufferAllocator alloc;
|
protected final BufferAllocator alloc;
|
||||||
private final AtomicLong totalZeroBytesErrors = new AtomicLong();
|
private final AtomicLong totalZeroBytesErrors = new AtomicLong();
|
||||||
protected final SubStageGetter<U, US> subStageGetter;
|
protected final SubStageGetter<U, US> subStageGetter;
|
||||||
protected final SerializerFixedBinaryLength<T> keySuffixSerializer;
|
protected final SerializerFixedBinaryLength<T> keySuffixSerializer;
|
||||||
@ -491,54 +497,110 @@ public class DatabaseMapDictionaryDeep<T, U, US extends DatabaseStage<U>> extend
|
|||||||
this.onClose = null;
|
this.onClose = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <K1, K2, V, R> Flux<R> getAllLeaves2(DatabaseMapDictionaryDeep<K1, Object2ObjectSortedMap<K2, V>, DatabaseMapDictionary<K2, V>> deepMap,
|
public static <K1, K2, V, R> Flux<R> getAllLeaves2(DatabaseMapDictionaryDeep<K1, Object2ObjectSortedMap<K2, V>, ? extends DatabaseStageMap<K2, V, DatabaseStageEntry<V>>> deepMap,
|
||||||
CompositeSnapshot snapshot,
|
CompositeSnapshot snapshot,
|
||||||
TriFunction<K1, K2, V, R> merger) {
|
TriFunction<K1, K2, V, R> merger,
|
||||||
if (deepMap.subStageGetter instanceof SubStageGetterMap<K2, V> subStageGetterMap) {
|
@NotNull Mono<K1> savedProgressKey1) {
|
||||||
var keySuffix1Serializer = deepMap.keySuffixSerializer;
|
var keySuffix1Serializer = deepMap.keySuffixSerializer;
|
||||||
var keySuffix2Serializer = subStageGetterMap.keySerializer;
|
SerializerFixedBinaryLength<?> keySuffix2Serializer;
|
||||||
var valueSerializer = subStageGetterMap.valueSerializer;
|
Serializer<?> valueSerializer;
|
||||||
return deepMap
|
boolean isHashed;
|
||||||
.dictionary
|
boolean isHashedSet;
|
||||||
.getRange(deepMap.resolveSnapshot(snapshot), deepMap.rangeMono)
|
if (deepMap.subStageGetter instanceof SubStageGetterMap subStageGetterMap) {
|
||||||
.handle((entrySend, sink) -> {
|
isHashed = false;
|
||||||
K1 key1 = null;
|
isHashedSet = false;
|
||||||
K2 key2 = null;
|
keySuffix2Serializer = subStageGetterMap.keySerializer;
|
||||||
try (var entry = entrySend.receive()) {
|
valueSerializer = subStageGetterMap.valueSerializer;
|
||||||
var keyBuf = entry.getKeyUnsafe();
|
} else if (deepMap.subStageGetter instanceof SubStageGetterHashMap subStageGetterHashMap) {
|
||||||
var valueBuf = entry.getValueUnsafe();
|
isHashed = true;
|
||||||
try {
|
isHashedSet = false;
|
||||||
assert keyBuf != null;
|
keySuffix2Serializer = subStageGetterHashMap.keyHashSerializer;
|
||||||
keyBuf.skipReadable(deepMap.keyPrefixLength);
|
|
||||||
try (var key1Buf = keyBuf.split(deepMap.keySuffixLength)) {
|
//noinspection unchecked
|
||||||
key1 = keySuffix1Serializer.deserialize(key1Buf);
|
ValueWithHashSerializer<K2, V> valueWithHashSerializer = new ValueWithHashSerializer<>(
|
||||||
}
|
(Serializer<K2>) subStageGetterHashMap.keySerializer,
|
||||||
key2 = keySuffix2Serializer.deserialize(keyBuf);
|
(Serializer<V>) subStageGetterHashMap.valueSerializer
|
||||||
assert valueBuf != null;
|
);
|
||||||
var value = valueSerializer.deserialize(valueBuf);
|
valueSerializer = new ValuesSetSerializer<>(valueWithHashSerializer);
|
||||||
sink.next(merger.apply(key1, key2, value));
|
} else if (deepMap.subStageGetter instanceof SubStageGetterHashSet subStageGetterHashSet) {
|
||||||
} catch (IndexOutOfBoundsException ex) {
|
isHashed = true;
|
||||||
var exMessage = ex.getMessage();
|
isHashedSet = true;
|
||||||
if (exMessage != null && exMessage.contains("read 0 to 0, write 0 to ")) {
|
keySuffix2Serializer = subStageGetterHashSet.keyHashSerializer;
|
||||||
var totalZeroBytesErrors = deepMap.totalZeroBytesErrors.incrementAndGet();
|
|
||||||
if (totalZeroBytesErrors < 512 || totalZeroBytesErrors % 10000 == 0) {
|
//noinspection unchecked
|
||||||
LOG.error("Unexpected zero-bytes value at " + deepMap.dictionary.getDatabaseName()
|
valueSerializer = new ValuesSetSerializer<K2>(subStageGetterHashSet.keySerializer);
|
||||||
+ ":" + deepMap.dictionary.getColumnName()
|
|
||||||
+ ":[" + key1
|
|
||||||
+ ":" + key2
|
|
||||||
+ "](" + LLUtils.toStringSafe(keyBuf) + ") total=" + totalZeroBytesErrors);
|
|
||||||
}
|
|
||||||
sink.complete();
|
|
||||||
} else {
|
|
||||||
sink.error(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (SerializationException ex) {
|
|
||||||
sink.error(ex);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var savedProgressKey1Opt = savedProgressKey1.map(Optional::of).defaultIfEmpty(Optional.empty());
|
||||||
|
|
||||||
|
return deepMap
|
||||||
|
.dictionary
|
||||||
|
.getRange(deepMap.resolveSnapshot(snapshot), Mono.zip(savedProgressKey1Opt, deepMap.rangeMono).handle((tuple, sink) -> {
|
||||||
|
var firstKey = tuple.getT1();
|
||||||
|
try (var fullRange = tuple.getT2().receive()) {
|
||||||
|
if (firstKey.isPresent()) {
|
||||||
|
try (var key1Buf = deepMap.alloc.allocate(keySuffix1Serializer.getSerializedBinaryLength())) {
|
||||||
|
keySuffix1Serializer.serialize(firstKey.get(), key1Buf);
|
||||||
|
sink.next(LLRange.of(key1Buf.send(), fullRange.getMax()).send());
|
||||||
|
} catch (SerializationException e) {
|
||||||
|
sink.error(e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sink.next(fullRange.send());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
.concatMapIterable(entrySend -> {
|
||||||
|
K1 key1 = null;
|
||||||
|
Object key2 = null;
|
||||||
|
try (var entry = entrySend.receive()) {
|
||||||
|
var keyBuf = entry.getKeyUnsafe();
|
||||||
|
var valueBuf = entry.getValueUnsafe();
|
||||||
|
try {
|
||||||
|
assert keyBuf != null;
|
||||||
|
keyBuf.skipReadable(deepMap.keyPrefixLength);
|
||||||
|
try (var key1Buf = keyBuf.split(deepMap.keySuffixLength)) {
|
||||||
|
key1 = keySuffix1Serializer.deserialize(key1Buf);
|
||||||
|
}
|
||||||
|
key2 = keySuffix2Serializer.deserialize(keyBuf);
|
||||||
|
assert valueBuf != null;
|
||||||
|
Object value = valueSerializer.deserialize(valueBuf);
|
||||||
|
if (isHashedSet) {
|
||||||
|
//noinspection unchecked
|
||||||
|
Set<K2> set = (Set<K2>) value;
|
||||||
|
K1 finalKey1 = key1;
|
||||||
|
//noinspection unchecked
|
||||||
|
return set.stream().map(e -> merger.apply(finalKey1, e, (V) Nothing.INSTANCE)).toList();
|
||||||
|
} else if (isHashed) {
|
||||||
|
//noinspection unchecked
|
||||||
|
Set<Entry<K2, V>> set = (Set<Entry<K2, V>>) value;
|
||||||
|
K1 finalKey1 = key1;
|
||||||
|
return set.stream().map(e -> merger.apply(finalKey1, e.getKey(), e.getValue())).toList();
|
||||||
|
} else {
|
||||||
|
//noinspection unchecked
|
||||||
|
return List.of(merger.apply(key1, (K2) key2, (V) value));
|
||||||
|
}
|
||||||
|
} catch (IndexOutOfBoundsException ex) {
|
||||||
|
var exMessage = ex.getMessage();
|
||||||
|
if (exMessage != null && exMessage.contains("read 0 to 0, write 0 to ")) {
|
||||||
|
var totalZeroBytesErrors = deepMap.totalZeroBytesErrors.incrementAndGet();
|
||||||
|
if (totalZeroBytesErrors < 512 || totalZeroBytesErrors % 10000 == 0) {
|
||||||
|
LOG.error("Unexpected zero-bytes value at " + deepMap.dictionary.getDatabaseName()
|
||||||
|
+ ":" + deepMap.dictionary.getColumnName()
|
||||||
|
+ ":[" + key1
|
||||||
|
+ ":" + key2
|
||||||
|
+ "](" + LLUtils.toStringSafe(keyBuf) + ") total=" + totalZeroBytesErrors);
|
||||||
|
}
|
||||||
|
return List.of();
|
||||||
|
} else {
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SerializationException ex) {
|
||||||
|
throw new CompletionException(ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ import it.cavallium.dbengine.client.CompositeSnapshot;
|
|||||||
import it.cavallium.dbengine.database.Delta;
|
import it.cavallium.dbengine.database.Delta;
|
||||||
import it.cavallium.dbengine.database.LLDictionary;
|
import it.cavallium.dbengine.database.LLDictionary;
|
||||||
import it.cavallium.dbengine.database.LLDictionaryResultType;
|
import it.cavallium.dbengine.database.LLDictionaryResultType;
|
||||||
import it.cavallium.dbengine.database.LLEntry;
|
|
||||||
import it.cavallium.dbengine.database.LLRange;
|
import it.cavallium.dbengine.database.LLRange;
|
||||||
import it.cavallium.dbengine.database.LLSnapshot;
|
import it.cavallium.dbengine.database.LLSnapshot;
|
||||||
import it.cavallium.dbengine.database.LLUtils;
|
import it.cavallium.dbengine.database.LLUtils;
|
||||||
@ -26,15 +25,15 @@ import reactor.core.publisher.Flux;
|
|||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import reactor.core.publisher.SynchronousSink;
|
import reactor.core.publisher.SynchronousSink;
|
||||||
|
|
||||||
public class DatabaseSingle<U> extends ResourceSupport<DatabaseStage<U>, DatabaseSingle<U>> implements
|
public class DatabaseMapSingle<U> extends ResourceSupport<DatabaseStage<U>, DatabaseMapSingle<U>> implements
|
||||||
DatabaseStageEntry<U> {
|
DatabaseStageEntry<U> {
|
||||||
|
|
||||||
private static final Logger LOG = LogManager.getLogger(DatabaseSingle.class);
|
private static final Logger LOG = LogManager.getLogger(DatabaseMapSingle.class);
|
||||||
|
|
||||||
private final AtomicLong totalZeroBytesErrors = new AtomicLong();
|
private final AtomicLong totalZeroBytesErrors = new AtomicLong();
|
||||||
private static final Drop<DatabaseSingle<?>> DROP = new Drop<>() {
|
private static final Drop<DatabaseMapSingle<?>> DROP = new Drop<>() {
|
||||||
@Override
|
@Override
|
||||||
public void drop(DatabaseSingle<?> obj) {
|
public void drop(DatabaseMapSingle<?> obj) {
|
||||||
try {
|
try {
|
||||||
obj.key.close();
|
obj.key.close();
|
||||||
} catch (Throwable ex) {
|
} catch (Throwable ex) {
|
||||||
@ -46,12 +45,12 @@ public class DatabaseSingle<U> extends ResourceSupport<DatabaseStage<U>, Databas
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Drop<DatabaseSingle<?>> fork() {
|
public Drop<DatabaseMapSingle<?>> fork() {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void attach(DatabaseSingle<?> obj) {
|
public void attach(DatabaseMapSingle<?> obj) {
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -64,9 +63,9 @@ public class DatabaseSingle<U> extends ResourceSupport<DatabaseStage<U>, Databas
|
|||||||
private Runnable onClose;
|
private Runnable onClose;
|
||||||
|
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
public DatabaseSingle(LLDictionary dictionary, Buffer key, Serializer<U> serializer,
|
public DatabaseMapSingle(LLDictionary dictionary, Buffer key, Serializer<U> serializer,
|
||||||
Runnable onClose) {
|
Runnable onClose) {
|
||||||
super((Drop<DatabaseSingle<U>>) (Drop) DROP);
|
super((Drop<DatabaseMapSingle<U>>) (Drop) DROP);
|
||||||
this.dictionary = dictionary;
|
this.dictionary = dictionary;
|
||||||
this.key = key;
|
this.key = key;
|
||||||
this.keyMono = LLUtils.lazyRetain(this.key);
|
this.keyMono = LLUtils.lazyRetain(this.key);
|
||||||
@ -211,12 +210,12 @@ public class DatabaseSingle<U> extends ResourceSupport<DatabaseStage<U>, Databas
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Owned<DatabaseSingle<U>> prepareSend() {
|
protected Owned<DatabaseMapSingle<U>> prepareSend() {
|
||||||
var keySend = this.key.send();
|
var keySend = this.key.send();
|
||||||
var onClose = this.onClose;
|
var onClose = this.onClose;
|
||||||
return drop -> {
|
return drop -> {
|
||||||
var key = keySend.receive();
|
var key = keySend.receive();
|
||||||
var instance = new DatabaseSingle<>(dictionary, key, serializer, onClose);
|
var instance = new DatabaseMapSingle<>(dictionary, key, serializer, onClose);
|
||||||
drop.attach(instance);
|
drop.attach(instance);
|
||||||
return instance;
|
return instance;
|
||||||
};
|
};
|
@ -0,0 +1,213 @@
|
|||||||
|
package it.cavallium.dbengine.database.collections;
|
||||||
|
|
||||||
|
import io.netty5.buffer.api.Buffer;
|
||||||
|
import io.netty5.buffer.api.Drop;
|
||||||
|
import io.netty5.buffer.api.Owned;
|
||||||
|
import io.netty5.buffer.api.Send;
|
||||||
|
import io.netty5.buffer.api.internal.ResourceSupport;
|
||||||
|
import it.cavallium.dbengine.client.BadBlock;
|
||||||
|
import it.cavallium.dbengine.client.CompositeSnapshot;
|
||||||
|
import it.cavallium.dbengine.database.Delta;
|
||||||
|
import it.cavallium.dbengine.database.LLRange;
|
||||||
|
import it.cavallium.dbengine.database.LLSingleton;
|
||||||
|
import it.cavallium.dbengine.database.LLSnapshot;
|
||||||
|
import it.cavallium.dbengine.database.LLUtils;
|
||||||
|
import it.cavallium.dbengine.database.UpdateReturnMode;
|
||||||
|
import it.cavallium.dbengine.database.serialization.SerializationException;
|
||||||
|
import it.cavallium.dbengine.database.serialization.SerializationFunction;
|
||||||
|
import it.cavallium.dbengine.database.serialization.Serializer;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
import reactor.core.publisher.SynchronousSink;
|
||||||
|
|
||||||
|
public class DatabaseSingleton<U> extends ResourceSupport<DatabaseStage<U>, DatabaseSingleton<U>> implements
|
||||||
|
DatabaseStageEntry<U> {
|
||||||
|
|
||||||
|
private static final Logger LOG = LogManager.getLogger(DatabaseSingleton.class);
|
||||||
|
|
||||||
|
private static final Drop<DatabaseSingleton<?>> DROP = new Drop<>() {
|
||||||
|
@Override
|
||||||
|
public void drop(DatabaseSingleton<?> obj) {
|
||||||
|
if (obj.onClose != null) {
|
||||||
|
obj.onClose.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Drop<DatabaseSingleton<?>> fork() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void attach(DatabaseSingleton<?> obj) {
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final LLSingleton singleton;
|
||||||
|
private final Serializer<U> serializer;
|
||||||
|
|
||||||
|
private Runnable onClose;
|
||||||
|
|
||||||
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
|
public DatabaseSingleton(LLSingleton singleton, Serializer<U> serializer,
|
||||||
|
Runnable onClose) {
|
||||||
|
super((Drop<DatabaseSingleton<U>>) (Drop) DROP);
|
||||||
|
this.singleton = singleton;
|
||||||
|
this.serializer = serializer;
|
||||||
|
this.onClose = onClose;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LLSnapshot resolveSnapshot(@Nullable CompositeSnapshot snapshot) {
|
||||||
|
if (snapshot == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return snapshot.getSnapshot(singleton);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deserializeValue(Send<Buffer> value, SynchronousSink<U> sink) {
|
||||||
|
try {
|
||||||
|
U deserializedValue;
|
||||||
|
try (var valueBuf = value.receive()) {
|
||||||
|
deserializedValue = serializer.deserialize(valueBuf);
|
||||||
|
}
|
||||||
|
sink.next(deserializedValue);
|
||||||
|
} catch (IndexOutOfBoundsException ex) {
|
||||||
|
var exMessage = ex.getMessage();
|
||||||
|
if (exMessage != null && exMessage.contains("read 0 to 0, write 0 to ")) {
|
||||||
|
LOG.error("Unexpected zero-bytes value at "
|
||||||
|
+ singleton.getDatabaseName() + ":" + singleton.getColumnName() + ":" + singleton.getName());
|
||||||
|
sink.complete();
|
||||||
|
} else {
|
||||||
|
sink.error(ex);
|
||||||
|
}
|
||||||
|
} catch (SerializationException ex) {
|
||||||
|
sink.error(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Send<Buffer> serializeValue(U value) throws SerializationException {
|
||||||
|
var valSizeHint = serializer.getSerializedSizeHint();
|
||||||
|
if (valSizeHint == -1) valSizeHint = 128;
|
||||||
|
try (var valBuf = singleton.getAllocator().allocate(valSizeHint)) {
|
||||||
|
serializer.serialize(value, valBuf);
|
||||||
|
return valBuf.send();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<U> get(@Nullable CompositeSnapshot snapshot, boolean existsAlmostCertainly) {
|
||||||
|
return singleton.get(resolveSnapshot(snapshot))
|
||||||
|
.handle(this::deserializeValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<U> setAndGetPrevious(U value) {
|
||||||
|
return Flux
|
||||||
|
.concat(singleton.get(null), singleton.set(Mono.fromCallable(() -> serializeValue(value))).then(Mono.empty()))
|
||||||
|
.singleOrEmpty()
|
||||||
|
.handle(this::deserializeValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<U> update(SerializationFunction<@Nullable U, @Nullable U> updater,
|
||||||
|
UpdateReturnMode updateReturnMode) {
|
||||||
|
return singleton
|
||||||
|
.update((oldValueSer) -> {
|
||||||
|
try (oldValueSer) {
|
||||||
|
U result;
|
||||||
|
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) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return serializeValue(result).receive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, updateReturnMode)
|
||||||
|
.handle(this::deserializeValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Delta<U>> updateAndGetDelta(SerializationFunction<@Nullable U, @Nullable U> updater) {
|
||||||
|
return singleton
|
||||||
|
.updateAndGetDelta((oldValueSer) -> {
|
||||||
|
try (oldValueSer) {
|
||||||
|
U result;
|
||||||
|
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) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return serializeValue(result).receive();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).transform(mono -> LLUtils.mapLLDelta(mono, serialized -> {
|
||||||
|
try (var valueBuf = serialized.receive()) {
|
||||||
|
return serializer.deserialize(valueBuf);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<U> clearAndGetPrevious() {
|
||||||
|
return Flux
|
||||||
|
.concat(singleton.get(null), singleton.set(Mono.empty()).then(Mono.empty()))
|
||||||
|
.singleOrEmpty()
|
||||||
|
.handle(this::deserializeValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Long> leavesCount(@Nullable CompositeSnapshot snapshot, boolean fast) {
|
||||||
|
return singleton.get(null).map(unused -> 1L).defaultIfEmpty(0L);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Boolean> isEmpty(@Nullable CompositeSnapshot snapshot) {
|
||||||
|
return singleton.get(null).map(t -> false).defaultIfEmpty(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Flux<BadBlock> badBlocks() {
|
||||||
|
return Flux.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RuntimeException createResourceClosedException() {
|
||||||
|
throw new IllegalStateException("Closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Owned<DatabaseSingleton<U>> prepareSend() {
|
||||||
|
var onClose = this.onClose;
|
||||||
|
return drop -> {
|
||||||
|
var instance = new DatabaseSingleton<>(singleton, serializer, onClose);
|
||||||
|
drop.attach(instance);
|
||||||
|
return instance;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void makeInaccessible() {
|
||||||
|
this.onClose = null;
|
||||||
|
}
|
||||||
|
}
|
@ -18,10 +18,10 @@ import reactor.core.publisher.Mono;
|
|||||||
public class SubStageGetterHashMap<T, U, TH> implements
|
public class SubStageGetterHashMap<T, U, TH> implements
|
||||||
SubStageGetter<Object2ObjectSortedMap<T, U>, DatabaseMapDictionaryHashed<T, U, TH>> {
|
SubStageGetter<Object2ObjectSortedMap<T, U>, DatabaseMapDictionaryHashed<T, U, TH>> {
|
||||||
|
|
||||||
private final Serializer<T> keySerializer;
|
final Serializer<T> keySerializer;
|
||||||
private final Serializer<U> valueSerializer;
|
final Serializer<U> valueSerializer;
|
||||||
private final Function<T, TH> keyHashFunction;
|
final Function<T, TH> keyHashFunction;
|
||||||
private final SerializerFixedBinaryLength<TH> keyHashSerializer;
|
final SerializerFixedBinaryLength<TH> keyHashSerializer;
|
||||||
|
|
||||||
public SubStageGetterHashMap(Serializer<T> keySerializer,
|
public SubStageGetterHashMap(Serializer<T> keySerializer,
|
||||||
Serializer<U> valueSerializer,
|
Serializer<U> valueSerializer,
|
||||||
|
@ -19,9 +19,9 @@ import reactor.core.publisher.Mono;
|
|||||||
public class SubStageGetterHashSet<T, TH> implements
|
public class SubStageGetterHashSet<T, TH> implements
|
||||||
SubStageGetter<Object2ObjectSortedMap<T, Nothing>, DatabaseSetDictionaryHashed<T, TH>> {
|
SubStageGetter<Object2ObjectSortedMap<T, Nothing>, DatabaseSetDictionaryHashed<T, TH>> {
|
||||||
|
|
||||||
private final Serializer<T> keySerializer;
|
final Serializer<T> keySerializer;
|
||||||
private final Function<T, TH> keyHashFunction;
|
final Function<T, TH> keyHashFunction;
|
||||||
private final SerializerFixedBinaryLength<TH> keyHashSerializer;
|
final SerializerFixedBinaryLength<TH> keyHashSerializer;
|
||||||
|
|
||||||
public SubStageGetterHashSet(Serializer<T> keySerializer,
|
public SubStageGetterHashSet(Serializer<T> keySerializer,
|
||||||
Function<T, TH> keyHashFunction,
|
Function<T, TH> keyHashFunction,
|
||||||
|
@ -20,7 +20,7 @@ public class SubStageGetterSingle<T> implements SubStageGetter<T, DatabaseStageE
|
|||||||
public Mono<DatabaseStageEntry<T>> subStage(LLDictionary dictionary,
|
public Mono<DatabaseStageEntry<T>> subStage(LLDictionary dictionary,
|
||||||
@Nullable CompositeSnapshot snapshot,
|
@Nullable CompositeSnapshot snapshot,
|
||||||
Mono<Send<Buffer>> keyPrefixMono) {
|
Mono<Send<Buffer>> keyPrefixMono) {
|
||||||
return keyPrefixMono.map(keyPrefix -> new DatabaseSingle<>(dictionary, keyPrefix.receive(), serializer, null));
|
return keyPrefixMono.map(keyPrefix -> new DatabaseMapSingle<>(dictionary, keyPrefix.receive(), serializer, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,7 @@ public class LLLocalDictionary implements LLDictionary {
|
|||||||
* It used to be false,
|
* It used to be false,
|
||||||
* now it's true to avoid crashes during iterations on completely corrupted files
|
* now it's true to avoid crashes during iterations on completely corrupted files
|
||||||
*/
|
*/
|
||||||
static final boolean VERIFY_CHECKSUMS_WHEN_NOT_NEEDED = true;
|
static final boolean VERIFY_CHECKSUMS_WHEN_NOT_NEEDED = false;
|
||||||
/**
|
/**
|
||||||
* Default: true. Use false to debug problems with windowing.
|
* Default: true. Use false to debug problems with windowing.
|
||||||
*/
|
*/
|
||||||
@ -874,7 +874,9 @@ public class LLLocalDictionary implements LLDictionary {
|
|||||||
try (var ro = new ReadOptions(getReadOptions(null))) {
|
try (var ro = new ReadOptions(getReadOptions(null))) {
|
||||||
ro.setFillCache(false);
|
ro.setFillCache(false);
|
||||||
if (!range.isSingle()) {
|
if (!range.isSingle()) {
|
||||||
ro.setReadaheadSize(32 * 1024);
|
if (LLUtils.MANUAL_READAHEAD) {
|
||||||
|
ro.setReadaheadSize(32 * 1024);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ro.setVerifyChecksums(true);
|
ro.setVerifyChecksums(true);
|
||||||
try (var rocksIteratorTuple = getRocksIterator(nettyDirect, ro, range, db)) {
|
try (var rocksIteratorTuple = getRocksIterator(nettyDirect, ro, range, db)) {
|
||||||
@ -1294,7 +1296,9 @@ public class LLLocalDictionary implements LLDictionary {
|
|||||||
|
|
||||||
// readOpts.setIgnoreRangeDeletions(true);
|
// readOpts.setIgnoreRangeDeletions(true);
|
||||||
readOpts.setFillCache(false);
|
readOpts.setFillCache(false);
|
||||||
readOpts.setReadaheadSize(32 * 1024); // 32KiB
|
if (LLUtils.MANUAL_READAHEAD) {
|
||||||
|
readOpts.setReadaheadSize(32 * 1024); // 32KiB
|
||||||
|
}
|
||||||
try (CappedWriteBatch writeBatch = new CappedWriteBatch(db,
|
try (CappedWriteBatch writeBatch = new CappedWriteBatch(db,
|
||||||
alloc,
|
alloc,
|
||||||
CAPPED_WRITE_BATCH_CAP,
|
CAPPED_WRITE_BATCH_CAP,
|
||||||
@ -1565,7 +1569,9 @@ public class LLLocalDictionary implements LLDictionary {
|
|||||||
}
|
}
|
||||||
try (var readOpts = new ReadOptions(resolveSnapshot(snapshot))) {
|
try (var readOpts = new ReadOptions(resolveSnapshot(snapshot))) {
|
||||||
readOpts.setFillCache(false);
|
readOpts.setFillCache(false);
|
||||||
readOpts.setReadaheadSize(128 * 1024); // 128KiB
|
if (LLUtils.MANUAL_READAHEAD) {
|
||||||
|
readOpts.setReadaheadSize(128 * 1024); // 128KiB
|
||||||
|
}
|
||||||
readOpts.setVerifyChecksums(VERIFY_CHECKSUMS_WHEN_NOT_NEEDED);
|
readOpts.setVerifyChecksums(VERIFY_CHECKSUMS_WHEN_NOT_NEEDED);
|
||||||
|
|
||||||
if (PARALLEL_EXACT_SIZE) {
|
if (PARALLEL_EXACT_SIZE) {
|
||||||
|
@ -195,7 +195,7 @@ public class LLLocalKeyValueDatabase implements LLKeyValueDatabase {
|
|||||||
tableOptions.setOptimizeFiltersForMemory(true);
|
tableOptions.setOptimizeFiltersForMemory(true);
|
||||||
}
|
}
|
||||||
tableOptions
|
tableOptions
|
||||||
.setChecksumType(ChecksumType.kxxHash64)
|
.setChecksumType(ChecksumType.kCRC32c)
|
||||||
.setBlockCacheCompressed(optionsWithCache.compressedCache())
|
.setBlockCacheCompressed(optionsWithCache.compressedCache())
|
||||||
.setBlockCache(optionsWithCache.standardCache())
|
.setBlockCache(optionsWithCache.standardCache())
|
||||||
.setBlockSize(16 * 1024); // 16KiB
|
.setBlockSize(16 * 1024); // 16KiB
|
||||||
@ -561,13 +561,16 @@ public class LLLocalKeyValueDatabase implements LLKeyValueDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<LLLocalSingleton> getSingleton(byte[] singletonListColumnName, byte[] name, byte[] defaultValue) {
|
public Mono<LLLocalSingleton> getSingleton(byte[] singletonListColumnName,
|
||||||
|
byte[] name,
|
||||||
|
byte @Nullable[] defaultValue) {
|
||||||
return Mono
|
return Mono
|
||||||
.fromCallable(() -> new LLLocalSingleton(
|
.fromCallable(() -> new LLLocalSingleton(
|
||||||
getRocksDBColumn(db, getCfh(singletonListColumnName)),
|
getRocksDBColumn(db, getCfh(singletonListColumnName)),
|
||||||
(snapshot) -> snapshotsHandles.get(snapshot.getSequenceNumber()),
|
(snapshot) -> snapshotsHandles.get(snapshot.getSequenceNumber()),
|
||||||
LLLocalKeyValueDatabase.this.name,
|
LLLocalKeyValueDatabase.this.name,
|
||||||
name,
|
name,
|
||||||
|
ColumnUtils.toString(singletonListColumnName),
|
||||||
dbScheduler,
|
dbScheduler,
|
||||||
defaultValue
|
defaultValue
|
||||||
))
|
))
|
||||||
|
@ -3,22 +3,18 @@ package it.cavallium.dbengine.database.disk;
|
|||||||
import io.netty5.buffer.api.Buffer;
|
import io.netty5.buffer.api.Buffer;
|
||||||
import io.netty5.buffer.api.BufferAllocator;
|
import io.netty5.buffer.api.BufferAllocator;
|
||||||
import io.netty5.buffer.api.Send;
|
import io.netty5.buffer.api.Send;
|
||||||
import it.cavallium.dbengine.database.Delta;
|
import it.cavallium.dbengine.database.LLDelta;
|
||||||
import it.cavallium.dbengine.database.LLSingleton;
|
import it.cavallium.dbengine.database.LLSingleton;
|
||||||
import it.cavallium.dbengine.database.LLSnapshot;
|
import it.cavallium.dbengine.database.LLSnapshot;
|
||||||
import it.cavallium.dbengine.database.UpdateMode;
|
|
||||||
import it.cavallium.dbengine.database.UpdateReturnMode;
|
import it.cavallium.dbengine.database.UpdateReturnMode;
|
||||||
import it.cavallium.dbengine.database.serialization.SerializationFunction;
|
import it.cavallium.dbengine.database.serialization.SerializationFunction;
|
||||||
import it.unimi.dsi.fastutil.bytes.ByteList;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import org.rocksdb.ColumnFamilyHandle;
|
|
||||||
import org.rocksdb.ReadOptions;
|
import org.rocksdb.ReadOptions;
|
||||||
import org.rocksdb.RocksDB;
|
|
||||||
import org.rocksdb.RocksDBException;
|
import org.rocksdb.RocksDBException;
|
||||||
import org.rocksdb.Snapshot;
|
import org.rocksdb.Snapshot;
|
||||||
import org.rocksdb.WriteOptions;
|
import org.rocksdb.WriteOptions;
|
||||||
@ -33,6 +29,7 @@ public class LLLocalSingleton implements LLSingleton {
|
|||||||
private final RocksDBColumn db;
|
private final RocksDBColumn db;
|
||||||
private final Function<LLSnapshot, Snapshot> snapshotResolver;
|
private final Function<LLSnapshot, Snapshot> snapshotResolver;
|
||||||
private final byte[] name;
|
private final byte[] name;
|
||||||
|
private final String columnName;
|
||||||
private final Mono<Send<Buffer>> nameMono;
|
private final Mono<Send<Buffer>> nameMono;
|
||||||
private final String databaseName;
|
private final String databaseName;
|
||||||
private final Scheduler dbScheduler;
|
private final Scheduler dbScheduler;
|
||||||
@ -41,12 +38,14 @@ public class LLLocalSingleton implements LLSingleton {
|
|||||||
Function<LLSnapshot, Snapshot> snapshotResolver,
|
Function<LLSnapshot, Snapshot> snapshotResolver,
|
||||||
String databaseName,
|
String databaseName,
|
||||||
byte[] name,
|
byte[] name,
|
||||||
|
String columnName,
|
||||||
Scheduler dbScheduler,
|
Scheduler dbScheduler,
|
||||||
byte[] defaultValue) throws RocksDBException {
|
byte @Nullable [] defaultValue) throws RocksDBException {
|
||||||
this.db = db;
|
this.db = db;
|
||||||
this.databaseName = databaseName;
|
this.databaseName = databaseName;
|
||||||
this.snapshotResolver = snapshotResolver;
|
this.snapshotResolver = snapshotResolver;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
this.columnName = columnName;
|
||||||
this.nameMono = Mono.fromCallable(() -> {
|
this.nameMono = Mono.fromCallable(() -> {
|
||||||
var alloc = db.getAllocator();
|
var alloc = db.getAllocator();
|
||||||
try (var nameBuf = alloc.allocate(this.name.length)) {
|
try (var nameBuf = alloc.allocate(this.name.length)) {
|
||||||
@ -58,7 +57,7 @@ public class LLLocalSingleton implements LLSingleton {
|
|||||||
if (Schedulers.isInNonBlockingThread()) {
|
if (Schedulers.isInNonBlockingThread()) {
|
||||||
throw new UnsupportedOperationException("Initialized in a nonblocking thread");
|
throw new UnsupportedOperationException("Initialized in a nonblocking thread");
|
||||||
}
|
}
|
||||||
if (db.get(EMPTY_READ_OPTIONS, this.name, true) == null) {
|
if (defaultValue != null && db.get(EMPTY_READ_OPTIONS, this.name, true) == null) {
|
||||||
db.put(EMPTY_WRITE_OPTIONS, this.name, defaultValue);
|
db.put(EMPTY_WRITE_OPTIONS, this.name, defaultValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -81,30 +80,45 @@ public class LLLocalSingleton implements LLSingleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<byte[]> get(@Nullable LLSnapshot snapshot) {
|
public Mono<Send<Buffer>> get(@Nullable LLSnapshot snapshot) {
|
||||||
return Mono
|
return nameMono.publishOn(Schedulers.boundedElastic()).handle((nameSend, sink) -> {
|
||||||
.fromCallable(() -> {
|
try (Buffer name = nameSend.receive()) {
|
||||||
if (Schedulers.isInNonBlockingThread()) {
|
Buffer result = db.get(resolveSnapshot(snapshot), name);
|
||||||
throw new UnsupportedOperationException("Called get in a nonblocking thread");
|
if (result != null) {
|
||||||
}
|
sink.next(result.send());
|
||||||
return db.get(resolveSnapshot(snapshot), name, true);
|
} else {
|
||||||
})
|
sink.complete();
|
||||||
.onErrorMap(cause -> new IOException("Failed to read " + Arrays.toString(name), cause))
|
}
|
||||||
.subscribeOn(dbScheduler);
|
} catch (RocksDBException ex) {
|
||||||
|
sink.error(new IOException("Failed to read " + Arrays.toString(name), ex));
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<Void> set(byte[] value) {
|
public Mono<Void> set(Mono<Send<Buffer>> valueMono) {
|
||||||
return Mono
|
return Mono.zip(nameMono, valueMono).publishOn(Schedulers.boundedElastic()).handle((tuple, sink) -> {
|
||||||
.<Void>fromCallable(() -> {
|
var nameSend = tuple.getT1();
|
||||||
if (Schedulers.isInNonBlockingThread()) {
|
var valueSend = tuple.getT2();
|
||||||
throw new UnsupportedOperationException("Called set in a nonblocking thread");
|
try (Buffer name = nameSend.receive()) {
|
||||||
}
|
try (Buffer value = valueSend.receive()) {
|
||||||
db.put(EMPTY_WRITE_OPTIONS, name, value);
|
db.put(EMPTY_WRITE_OPTIONS, name, value);
|
||||||
return null;
|
sink.next(true);
|
||||||
})
|
}
|
||||||
.onErrorMap(cause -> new IOException("Failed to write " + Arrays.toString(name), cause))
|
} catch (RocksDBException ex) {
|
||||||
.subscribeOn(dbScheduler);
|
sink.error(new IOException("Failed to write " + Arrays.toString(name), ex));
|
||||||
|
}
|
||||||
|
}).switchIfEmpty(unset().thenReturn(true)).then();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Mono<Void> unset() {
|
||||||
|
return nameMono.publishOn(Schedulers.boundedElastic()).handle((nameSend, sink) -> {
|
||||||
|
try (Buffer name = nameSend.receive()) {
|
||||||
|
db.delete(EMPTY_WRITE_OPTIONS, name);
|
||||||
|
} catch (RocksDBException ex) {
|
||||||
|
sink.error(new IOException("Failed to read " + Arrays.toString(name), ex));
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -130,8 +144,31 @@ public class LLLocalSingleton implements LLSingleton {
|
|||||||
keySend -> Mono.fromRunnable(keySend::close));
|
keySend -> Mono.fromRunnable(keySend::close));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Send<LLDelta>> updateAndGetDelta(SerializationFunction<@Nullable Send<Buffer>, @Nullable Buffer> updater) {
|
||||||
|
return Mono.usingWhen(nameMono, keySend -> runOnDb(() -> {
|
||||||
|
if (Schedulers.isInNonBlockingThread()) {
|
||||||
|
throw new UnsupportedOperationException("Called update in a nonblocking thread");
|
||||||
|
}
|
||||||
|
UpdateAtomicResult result
|
||||||
|
= db.updateAtomic(EMPTY_READ_OPTIONS, EMPTY_WRITE_OPTIONS, keySend, updater, UpdateAtomicResultMode.DELTA);
|
||||||
|
return ((UpdateAtomicResultDelta) result).delta();
|
||||||
|
}).onErrorMap(cause -> new IOException("Failed to read or write", cause)),
|
||||||
|
keySend -> Mono.fromRunnable(keySend::close));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDatabaseName() {
|
public String getDatabaseName() {
|
||||||
return databaseName;
|
return databaseName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getColumnName() {
|
||||||
|
return columnName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return new String(name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import java.util.List;
|
|||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ConcurrentSkipListMap;
|
import java.util.concurrent.ConcurrentSkipListMap;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
public class LLMemoryKeyValueDatabase implements LLKeyValueDatabase {
|
public class LLMemoryKeyValueDatabase implements LLKeyValueDatabase {
|
||||||
@ -48,7 +49,9 @@ public class LLMemoryKeyValueDatabase implements LLKeyValueDatabase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<? extends LLSingleton> getSingleton(byte[] singletonListColumnName, byte[] singletonName, byte[] defaultValue) {
|
public Mono<? extends LLSingleton> getSingleton(byte[] singletonListColumnName,
|
||||||
|
byte[] singletonName,
|
||||||
|
byte @Nullable[] defaultValue) {
|
||||||
var columnNameString = new String(singletonListColumnName, StandardCharsets.UTF_8);
|
var columnNameString = new String(singletonListColumnName, StandardCharsets.UTF_8);
|
||||||
var dict = singletons.computeIfAbsent(columnNameString, _unused -> new LLMemoryDictionary(allocator,
|
var dict = singletons.computeIfAbsent(columnNameString, _unused -> new LLMemoryDictionary(allocator,
|
||||||
name,
|
name,
|
||||||
@ -58,9 +61,17 @@ public class LLMemoryKeyValueDatabase implements LLKeyValueDatabase {
|
|||||||
mainDb
|
mainDb
|
||||||
));
|
));
|
||||||
return Mono
|
return Mono
|
||||||
.fromCallable(() -> new LLMemorySingleton(dict, singletonName)).flatMap(singleton -> singleton
|
.fromCallable(() -> new LLMemorySingleton(dict, columnNameString, singletonName)).flatMap(singleton -> singleton
|
||||||
.get(null)
|
.get(null)
|
||||||
.switchIfEmpty(singleton.set(defaultValue).then(Mono.empty()))
|
.transform(mono -> {
|
||||||
|
if (defaultValue != null) {
|
||||||
|
return mono.switchIfEmpty(singleton
|
||||||
|
.set(Mono.fromSupplier(() -> allocator.copyOf(defaultValue).send()))
|
||||||
|
.then(Mono.empty()));
|
||||||
|
} else {
|
||||||
|
return mono;
|
||||||
|
}
|
||||||
|
})
|
||||||
.thenReturn(singleton)
|
.thenReturn(singleton)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -8,30 +8,21 @@ import it.cavallium.dbengine.database.LLDictionaryResultType;
|
|||||||
import it.cavallium.dbengine.database.LLSingleton;
|
import it.cavallium.dbengine.database.LLSingleton;
|
||||||
import it.cavallium.dbengine.database.LLSnapshot;
|
import it.cavallium.dbengine.database.LLSnapshot;
|
||||||
import it.cavallium.dbengine.database.LLUtils;
|
import it.cavallium.dbengine.database.LLUtils;
|
||||||
import it.cavallium.dbengine.database.UpdateMode;
|
|
||||||
import it.cavallium.dbengine.database.UpdateReturnMode;
|
import it.cavallium.dbengine.database.UpdateReturnMode;
|
||||||
import it.cavallium.dbengine.database.disk.UpdateAtomicResult;
|
|
||||||
import it.cavallium.dbengine.database.disk.UpdateAtomicResultCurrent;
|
|
||||||
import it.cavallium.dbengine.database.disk.UpdateAtomicResultMode;
|
|
||||||
import it.cavallium.dbengine.database.disk.UpdateAtomicResultPrevious;
|
|
||||||
import it.cavallium.dbengine.database.serialization.SerializationException;
|
|
||||||
import it.cavallium.dbengine.database.serialization.SerializationFunction;
|
import it.cavallium.dbengine.database.serialization.SerializationFunction;
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.concurrent.Callable;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import reactor.core.scheduler.Schedulers;
|
|
||||||
|
|
||||||
public class LLMemorySingleton implements LLSingleton {
|
public class LLMemorySingleton implements LLSingleton {
|
||||||
|
|
||||||
private final LLMemoryDictionary dict;
|
private final LLMemoryDictionary dict;
|
||||||
|
private final String columnNameString;
|
||||||
private final byte[] singletonName;
|
private final byte[] singletonName;
|
||||||
private final Mono<Send<Buffer>> singletonNameBufMono;
|
private final Mono<Send<Buffer>> singletonNameBufMono;
|
||||||
|
|
||||||
public LLMemorySingleton(LLMemoryDictionary dict, byte[] singletonName) {
|
public LLMemorySingleton(LLMemoryDictionary dict, String columnNameString, byte[] singletonName) {
|
||||||
this.dict = dict;
|
this.dict = dict;
|
||||||
|
this.columnNameString = columnNameString;
|
||||||
this.singletonName = singletonName;
|
this.singletonName = singletonName;
|
||||||
this.singletonNameBufMono = Mono.fromCallable(() -> dict
|
this.singletonNameBufMono = Mono.fromCallable(() -> dict
|
||||||
.getAllocator()
|
.getAllocator()
|
||||||
@ -51,22 +42,15 @@ public class LLMemorySingleton implements LLSingleton {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<byte[]> get(@Nullable LLSnapshot snapshot) {
|
public Mono<Send<Buffer>> get(@Nullable LLSnapshot snapshot) {
|
||||||
return dict
|
return dict.get(snapshot, singletonNameBufMono, false);
|
||||||
.get(snapshot, singletonNameBufMono, false)
|
|
||||||
.map(b -> {
|
|
||||||
try (var buf = b.receive()) {
|
|
||||||
return LLUtils.toArray(buf);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<Void> set(byte[] value) {
|
public Mono<Void> set(Mono<Send<Buffer>> value) {
|
||||||
var bbKey = singletonNameBufMono;
|
var bbKey = singletonNameBufMono;
|
||||||
var bbVal = Mono.fromCallable(() -> dict.getAllocator().allocate(value.length).writeBytes(value).send());
|
|
||||||
return dict
|
return dict
|
||||||
.put(bbKey, bbVal, LLDictionaryResultType.VOID)
|
.put(bbKey, value, LLDictionaryResultType.VOID)
|
||||||
.then();
|
.then();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,4 +59,19 @@ public class LLMemorySingleton implements LLSingleton {
|
|||||||
UpdateReturnMode updateReturnMode) {
|
UpdateReturnMode updateReturnMode) {
|
||||||
return dict.update(singletonNameBufMono, updater, updateReturnMode);
|
return dict.update(singletonNameBufMono, updater, updateReturnMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Send<LLDelta>> updateAndGetDelta(SerializationFunction<@Nullable Send<Buffer>, @Nullable Buffer> updater) {
|
||||||
|
return dict.updateAndGetDelta(singletonNameBufMono, updater);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getColumnName() {
|
||||||
|
return columnNameString;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return new String(singletonName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
package it.cavallium.dbengine.database.remote;
|
||||||
|
|
||||||
|
import it.cavallium.data.generator.DataSerializer;
|
||||||
|
import it.unimi.dsi.fastutil.bytes.ByteArrayList;
|
||||||
|
import it.unimi.dsi.fastutil.bytes.ByteList;
|
||||||
|
import java.io.DataInput;
|
||||||
|
import java.io.DataOutput;
|
||||||
|
import java.io.IOException;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class ByteListSerializer implements DataSerializer<ByteList> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void serialize(DataOutput dataOutput, @NotNull ByteList bytes) throws IOException {
|
||||||
|
dataOutput.writeInt(bytes.size());
|
||||||
|
for (Byte aByte : bytes) {
|
||||||
|
dataOutput.writeByte(aByte);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull ByteList deserialize(DataInput dataInput) throws IOException {
|
||||||
|
var size = dataInput.readInt();
|
||||||
|
var bal = new ByteArrayList(size);
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
bal.add(dataInput.readByte());
|
||||||
|
}
|
||||||
|
return bal;
|
||||||
|
}
|
||||||
|
}
|
@ -2,15 +2,16 @@ package it.cavallium.dbengine.database.remote;
|
|||||||
|
|
||||||
import com.google.common.collect.Multimap;
|
import com.google.common.collect.Multimap;
|
||||||
import io.micrometer.core.instrument.MeterRegistry;
|
import io.micrometer.core.instrument.MeterRegistry;
|
||||||
|
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
|
||||||
|
import io.netty.incubator.codec.quic.QuicSslContextBuilder;
|
||||||
import io.netty5.buffer.api.Buffer;
|
import io.netty5.buffer.api.Buffer;
|
||||||
import io.netty5.buffer.api.BufferAllocator;
|
import io.netty5.buffer.api.BufferAllocator;
|
||||||
import io.netty5.buffer.api.Send;
|
import io.netty5.buffer.api.Send;
|
||||||
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
|
|
||||||
import io.netty.incubator.codec.quic.QuicSslContextBuilder;
|
|
||||||
import it.cavallium.dbengine.client.MemoryStats;
|
import it.cavallium.dbengine.client.MemoryStats;
|
||||||
import it.cavallium.dbengine.client.query.current.data.Query;
|
import it.cavallium.dbengine.client.query.current.data.Query;
|
||||||
import it.cavallium.dbengine.client.query.current.data.QueryParams;
|
import it.cavallium.dbengine.client.query.current.data.QueryParams;
|
||||||
import it.cavallium.dbengine.database.LLDatabaseConnection;
|
import it.cavallium.dbengine.database.LLDatabaseConnection;
|
||||||
|
import it.cavallium.dbengine.database.LLDelta;
|
||||||
import it.cavallium.dbengine.database.LLDictionary;
|
import it.cavallium.dbengine.database.LLDictionary;
|
||||||
import it.cavallium.dbengine.database.LLIndexRequest;
|
import it.cavallium.dbengine.database.LLIndexRequest;
|
||||||
import it.cavallium.dbengine.database.LLKeyValueDatabase;
|
import it.cavallium.dbengine.database.LLKeyValueDatabase;
|
||||||
@ -26,7 +27,6 @@ import it.cavallium.dbengine.database.remote.RPCCodecs.RPCEventCodec;
|
|||||||
import it.cavallium.dbengine.database.serialization.SerializationException;
|
import it.cavallium.dbengine.database.serialization.SerializationException;
|
||||||
import it.cavallium.dbengine.database.serialization.SerializationFunction;
|
import it.cavallium.dbengine.database.serialization.SerializationFunction;
|
||||||
import it.cavallium.dbengine.lucene.LuceneHacks;
|
import it.cavallium.dbengine.lucene.LuceneHacks;
|
||||||
import it.cavallium.dbengine.lucene.LuceneRocksDBManager;
|
|
||||||
import it.cavallium.dbengine.lucene.collector.Buckets;
|
import it.cavallium.dbengine.lucene.collector.Buckets;
|
||||||
import it.cavallium.dbengine.lucene.searcher.BucketParams;
|
import it.cavallium.dbengine.lucene.searcher.BucketParams;
|
||||||
import it.cavallium.dbengine.rpc.current.data.BinaryOptional;
|
import it.cavallium.dbengine.rpc.current.data.BinaryOptional;
|
||||||
@ -52,6 +52,7 @@ import it.cavallium.dbengine.rpc.current.data.SingletonSet;
|
|||||||
import it.cavallium.dbengine.rpc.current.data.SingletonUpdateEnd;
|
import it.cavallium.dbengine.rpc.current.data.SingletonUpdateEnd;
|
||||||
import it.cavallium.dbengine.rpc.current.data.SingletonUpdateInit;
|
import it.cavallium.dbengine.rpc.current.data.SingletonUpdateInit;
|
||||||
import it.cavallium.dbengine.rpc.current.data.SingletonUpdateOldData;
|
import it.cavallium.dbengine.rpc.current.data.SingletonUpdateOldData;
|
||||||
|
import it.cavallium.dbengine.rpc.current.data.nullables.NullableBytes;
|
||||||
import it.cavallium.dbengine.rpc.current.data.nullables.NullableLLSnapshot;
|
import it.cavallium.dbengine.rpc.current.data.nullables.NullableLLSnapshot;
|
||||||
import it.unimi.dsi.fastutil.bytes.ByteList;
|
import it.unimi.dsi.fastutil.bytes.ByteList;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -226,11 +227,11 @@ public class LLQuicConnection implements LLDatabaseConnection {
|
|||||||
@Override
|
@Override
|
||||||
public Mono<? extends LLSingleton> getSingleton(byte[] singletonListColumnName,
|
public Mono<? extends LLSingleton> getSingleton(byte[] singletonListColumnName,
|
||||||
byte[] name,
|
byte[] name,
|
||||||
byte[] defaultValue) {
|
byte @Nullable[] defaultValue) {
|
||||||
return sendRequest(new GetSingleton(id,
|
return sendRequest(new GetSingleton(id,
|
||||||
ByteList.of(singletonListColumnName),
|
ByteList.of(singletonListColumnName),
|
||||||
ByteList.of(name),
|
ByteList.of(name),
|
||||||
ByteList.of(defaultValue)
|
defaultValue == null ? NullableBytes.empty() : NullableBytes.of(ByteList.of(defaultValue))
|
||||||
)).cast(GeneratedEntityId.class).map(GeneratedEntityId::id).map(singletonId -> new LLSingleton() {
|
)).cast(GeneratedEntityId.class).map(GeneratedEntityId::id).map(singletonId -> new LLSingleton() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -239,17 +240,22 @@ public class LLQuicConnection implements LLDatabaseConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<byte[]> get(@Nullable LLSnapshot snapshot) {
|
public Mono<Send<Buffer>> get(@Nullable LLSnapshot snapshot) {
|
||||||
return sendRequest(new SingletonGet(singletonId, NullableLLSnapshot.ofNullable(snapshot)))
|
return sendRequest(new SingletonGet(singletonId, NullableLLSnapshot.ofNullable(snapshot)))
|
||||||
.cast(BinaryOptional.class)
|
.cast(BinaryOptional.class)
|
||||||
.mapNotNull(b -> b.val().getNullable())
|
.mapNotNull(result -> {
|
||||||
.map(binary -> QuicUtils.toArrayNoCopy(binary.val()));
|
if (result.val().isPresent()) {
|
||||||
|
return allocator.copyOf(QuicUtils.toArrayNoCopy(result.val().get().val())).send();
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Mono<Void> set(byte[] value) {
|
public Mono<Void> set(Mono<Send<Buffer>> valueMono) {
|
||||||
return sendRequest(new SingletonSet(singletonId, ByteList.of(value)))
|
return QuicUtils.toBytes(valueMono)
|
||||||
.then();
|
.flatMap(valueSendOpt -> sendRequest(new SingletonSet(singletonId, valueSendOpt)).then());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -285,10 +291,25 @@ public class LLQuicConnection implements LLDatabaseConnection {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Send<LLDelta>> updateAndGetDelta(SerializationFunction<@Nullable Send<Buffer>, @Nullable Buffer> updater) {
|
||||||
|
return Mono.error(new UnsupportedOperationException());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getDatabaseName() {
|
public String getDatabaseName() {
|
||||||
return databaseName;
|
return databaseName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getColumnName() {
|
||||||
|
return new String(singletonListColumnName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return new String(name);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
package it.cavallium.dbengine.database.remote;
|
package it.cavallium.dbengine.database.remote;
|
||||||
|
|
||||||
import io.netty.handler.codec.ByteToMessageCodec;
|
import io.netty.handler.codec.ByteToMessageCodec;
|
||||||
|
import io.netty5.buffer.api.Buffer;
|
||||||
|
import io.netty5.buffer.api.Send;
|
||||||
import it.cavallium.data.generator.nativedata.NullableString;
|
import it.cavallium.data.generator.nativedata.NullableString;
|
||||||
import it.cavallium.dbengine.rpc.current.data.RPCCrash;
|
import it.cavallium.dbengine.rpc.current.data.RPCCrash;
|
||||||
import it.cavallium.dbengine.rpc.current.data.RPCEvent;
|
import it.cavallium.dbengine.rpc.current.data.RPCEvent;
|
||||||
|
import it.cavallium.dbengine.rpc.current.data.nullables.NullableBytes;
|
||||||
import it.unimi.dsi.fastutil.bytes.ByteArrayList;
|
import it.unimi.dsi.fastutil.bytes.ByteArrayList;
|
||||||
import it.unimi.dsi.fastutil.bytes.ByteList;
|
import it.unimi.dsi.fastutil.bytes.ByteList;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
@ -38,6 +42,29 @@ public class QuicUtils {
|
|||||||
return new String(QuicUtils.toArrayNoCopy(b), StandardCharsets.UTF_8);
|
return new String(QuicUtils.toArrayNoCopy(b), StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
|
||||||
|
public static NullableBytes toBytes(Optional<Send<Buffer>> valueSendOpt) {
|
||||||
|
if (valueSendOpt.isPresent()) {
|
||||||
|
try (var value = valueSendOpt.get().receive()) {
|
||||||
|
var bytes = new byte[value.readableBytes()];
|
||||||
|
value.copyInto(value.readerOffset(), bytes, 0, bytes.length);
|
||||||
|
return NullableBytes.ofNullable(ByteList.of(bytes));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return NullableBytes.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Mono<NullableBytes> toBytes(Mono<Send<Buffer>> valueSendOptMono) {
|
||||||
|
return valueSendOptMono.map(valueSendOpt -> {
|
||||||
|
try (var value = valueSendOpt.receive()) {
|
||||||
|
var bytes = new byte[value.readableBytes()];
|
||||||
|
value.copyInto(value.readerOffset(), bytes, 0, bytes.length);
|
||||||
|
return NullableBytes.ofNullable(ByteList.of(bytes));
|
||||||
|
}
|
||||||
|
}).defaultIfEmpty(NullableBytes.empty());
|
||||||
|
}
|
||||||
|
|
||||||
public record QuicStream(NettyInbound in, NettyOutbound out) {}
|
public record QuicStream(NettyInbound in, NettyOutbound out) {}
|
||||||
|
|
||||||
public static Mono<RPCEvent> catchRPCErrors(@NotNull Throwable error) {
|
public static Mono<RPCEvent> catchRPCErrors(@NotNull Throwable error) {
|
||||||
|
@ -13,6 +13,8 @@ import it.cavallium.dbengine.database.LLTerm;
|
|||||||
import it.cavallium.dbengine.database.LLUtils;
|
import it.cavallium.dbengine.database.LLUtils;
|
||||||
import it.cavallium.dbengine.database.collections.DatabaseMapDictionary;
|
import it.cavallium.dbengine.database.collections.DatabaseMapDictionary;
|
||||||
import it.cavallium.dbengine.database.collections.DatabaseMapDictionaryDeep;
|
import it.cavallium.dbengine.database.collections.DatabaseMapDictionaryDeep;
|
||||||
|
import it.cavallium.dbengine.database.collections.DatabaseStageEntry;
|
||||||
|
import it.cavallium.dbengine.database.collections.DatabaseStageMap;
|
||||||
import it.cavallium.dbengine.database.collections.ValueGetter;
|
import it.cavallium.dbengine.database.collections.ValueGetter;
|
||||||
import it.cavallium.dbengine.database.disk.LLIndexSearchers;
|
import it.cavallium.dbengine.database.disk.LLIndexSearchers;
|
||||||
import it.cavallium.dbengine.lucene.analyzer.LegacyWordAnalyzer;
|
import it.cavallium.dbengine.lucene.analyzer.LegacyWordAnalyzer;
|
||||||
@ -230,7 +232,7 @@ public class LuceneUtils {
|
|||||||
|
|
||||||
public static <T, U, V> ValueGetter<Entry<T, U>, V> getAsyncDbValueGetterDeep(
|
public static <T, U, V> ValueGetter<Entry<T, U>, V> getAsyncDbValueGetterDeep(
|
||||||
CompositeSnapshot snapshot,
|
CompositeSnapshot snapshot,
|
||||||
DatabaseMapDictionaryDeep<T, Object2ObjectSortedMap<U, V>, DatabaseMapDictionary<U, V>> dictionaryDeep) {
|
DatabaseMapDictionaryDeep<T, Object2ObjectSortedMap<U, V>, ? extends DatabaseStageMap<U, V, ? extends DatabaseStageEntry<V>>> dictionaryDeep) {
|
||||||
return entry -> LLUtils.usingResource(dictionaryDeep
|
return entry -> LLUtils.usingResource(dictionaryDeep
|
||||||
.at(snapshot, entry.getKey()), sub -> sub.getValue(snapshot, entry.getValue()), true);
|
.at(snapshot, entry.getKey()), sub -> sub.getValue(snapshot, entry.getValue()), true);
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import io.netty5.buffer.api.Buffer;
|
|||||||
import io.netty5.buffer.api.BufferAllocator;
|
import io.netty5.buffer.api.BufferAllocator;
|
||||||
import io.netty5.buffer.api.ReadableComponent;
|
import io.netty5.buffer.api.ReadableComponent;
|
||||||
import io.netty5.buffer.api.WritableComponent;
|
import io.netty5.buffer.api.WritableComponent;
|
||||||
|
import it.cavallium.dbengine.database.LLUtils;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
@ -105,9 +106,11 @@ public class RocksdbFileStore {
|
|||||||
db.put(headers, NEXT_ID_KEY, Longs.toByteArray(100));
|
db.put(headers, NEXT_ID_KEY, Longs.toByteArray(100));
|
||||||
incFlush();
|
incFlush();
|
||||||
}
|
}
|
||||||
this.itReadOpts = new ReadOptions()
|
this.itReadOpts = new ReadOptions();
|
||||||
.setReadaheadSize(blockSize * 4L)
|
if (LLUtils.MANUAL_READAHEAD) {
|
||||||
.setVerifyChecksums(false)
|
itReadOpts.setReadaheadSize(blockSize * 4L);
|
||||||
|
}
|
||||||
|
itReadOpts.setVerifyChecksums(false)
|
||||||
.setIgnoreRangeDeletions(true);
|
.setIgnoreRangeDeletions(true);
|
||||||
} catch (RocksDBException e) {
|
} catch (RocksDBException e) {
|
||||||
throw new IOException("Failed to open RocksDB meta file store", e);
|
throw new IOException("Failed to open RocksDB meta file store", e);
|
||||||
|
@ -5,10 +5,14 @@ import static it.cavallium.dbengine.DbTestUtils.ensureNoLeaks;
|
|||||||
import static it.cavallium.dbengine.DbTestUtils.newAllocator;
|
import static it.cavallium.dbengine.DbTestUtils.newAllocator;
|
||||||
import static it.cavallium.dbengine.DbTestUtils.tempDb;
|
import static it.cavallium.dbengine.DbTestUtils.tempDb;
|
||||||
|
|
||||||
|
import it.cavallium.data.generator.nativedata.StringSerializer;
|
||||||
import it.cavallium.dbengine.DbTestUtils.TestAllocator;
|
import it.cavallium.dbengine.DbTestUtils.TestAllocator;
|
||||||
import it.cavallium.dbengine.database.LLKeyValueDatabase;
|
import it.cavallium.dbengine.database.LLKeyValueDatabase;
|
||||||
|
import it.cavallium.dbengine.database.LLSingleton;
|
||||||
import it.cavallium.dbengine.database.collections.DatabaseInt;
|
import it.cavallium.dbengine.database.collections.DatabaseInt;
|
||||||
import it.cavallium.dbengine.database.collections.DatabaseLong;
|
import it.cavallium.dbengine.database.collections.DatabaseLong;
|
||||||
|
import it.cavallium.dbengine.database.collections.DatabaseSingleton;
|
||||||
|
import it.cavallium.dbengine.database.serialization.Serializer;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
@ -85,6 +89,15 @@ public abstract class TestSingletons {
|
|||||||
.verifyComplete();
|
.verifyComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCreateSingleton() {
|
||||||
|
StepVerifier
|
||||||
|
.create(tempDb(getTempDbGenerator(), allocator, db -> tempSingleton(db, "testsingleton")
|
||||||
|
.flatMap(dbSingleton -> dbSingleton.get(null))
|
||||||
|
))
|
||||||
|
.verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@ValueSource(ints = {Integer.MIN_VALUE, -192, -2, -1, 0, 1, 2, 1292, Integer.MAX_VALUE})
|
@ValueSource(ints = {Integer.MIN_VALUE, -192, -2, -1, 0, 1, 2, 1292, Integer.MAX_VALUE})
|
||||||
public void testDefaultValueInteger(int i) {
|
public void testDefaultValueInteger(int i) {
|
||||||
@ -137,6 +150,21 @@ public abstract class TestSingletons {
|
|||||||
.verifyComplete();
|
.verifyComplete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@MethodSource("provideLongNumberWithRepeats")
|
||||||
|
public void testSetSingleton(Long i, Integer repeats) {
|
||||||
|
StepVerifier
|
||||||
|
.create(tempDb(getTempDbGenerator(), allocator, db -> tempSingleton(db, "test")
|
||||||
|
.flatMap(dbSingleton -> Mono
|
||||||
|
.defer(() -> dbSingleton.set(Long.toString(System.currentTimeMillis())))
|
||||||
|
.repeat(repeats)
|
||||||
|
.then(dbSingleton.set(Long.toString(i)))
|
||||||
|
.then(dbSingleton.get(null)))
|
||||||
|
))
|
||||||
|
.expectNext(Long.toString(i))
|
||||||
|
.verifyComplete();
|
||||||
|
}
|
||||||
|
|
||||||
public static Mono<DatabaseInt> tempInt(LLKeyValueDatabase database, String name, int defaultValue) {
|
public static Mono<DatabaseInt> tempInt(LLKeyValueDatabase database, String name, int defaultValue) {
|
||||||
return database
|
return database
|
||||||
.getInteger("ints", name, defaultValue);
|
.getInteger("ints", name, defaultValue);
|
||||||
@ -146,4 +174,10 @@ public abstract class TestSingletons {
|
|||||||
return database
|
return database
|
||||||
.getLong("longs", name, defaultValue);
|
.getLong("longs", name, defaultValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Mono<DatabaseSingleton<String>> tempSingleton(LLKeyValueDatabase database, String name) {
|
||||||
|
return database
|
||||||
|
.getSingleton("longs", name)
|
||||||
|
.map(singleton -> new DatabaseSingleton<>(singleton, Serializer.UTF8_SERIALIZER, null));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user