1 Commits

Author SHA1 Message Date
5cd0e5f045 iterator flattening 2025-04-19 13:51:28 +02:00
11 changed files with 406 additions and 318 deletions

View File

@@ -1,6 +1,12 @@
package com.usatiuk.objects.iterators;
import java.util.stream.Stream;
@FunctionalInterface
public interface IterProdFn<K extends Comparable<K>, V> {
CloseableKvIterator<K, V> get(IteratorStart start, K key);
default Stream<CloseableKvIterator<K, MaybeTombstone<V>>> getFlat(IteratorStart start, K key) {
return Stream.of(new MappingKvIterator<>(get(start, key), Data::new));
}
}

View File

@@ -1,16 +1,15 @@
package com.usatiuk.objects.iterators;
import io.quarkus.logging.Log;
import org.apache.commons.lang3.mutable.MutableInt;
import org.apache.commons.lang3.mutable.MutableObject;
import org.apache.commons.lang3.tuple.Pair;
import java.util.List;
import java.util.NavigableMap;
import java.util.NoSuchElementException;
import java.util.TreeMap;
import java.util.*;
public class MergingKvIterator<K extends Comparable<K>, V> extends ReversibleKvIterator<K, V> {
private record IteratorEntry<K extends Comparable<K>, V>(int priority, CloseableKvIterator<K, V> iterator) {
public class MergingKvIterator<K extends Comparable<K>, V> extends ReversibleKvIterator<K, MaybeTombstone<V>> {
private record IteratorEntry<K extends Comparable<K>, V>(int priority,
CloseableKvIterator<K, MaybeTombstone<V>> iterator) {
public IteratorEntry<K, V> reversed() {
return new IteratorEntry<>(priority, iterator.reversed());
}
@@ -26,11 +25,13 @@ public class MergingKvIterator<K extends Comparable<K>, V> extends ReversibleKvI
// Why streams are so slow?
{
IteratorEntry<K, V>[] iteratorEntries = new IteratorEntry[iterators.size()];
for (int i = 0; i < iterators.size(); i++) {
iteratorEntries[i] = new IteratorEntry<>(i, iterators.get(i).get(startType, startKey));
}
_iterators = List.of(iteratorEntries);
var iteratorsTmp = iterators.stream().flatMap(i -> i.getFlat(startType, startKey));
MutableInt i = new MutableInt(0);
ArrayList<IteratorEntry<K, V>> tmp = new ArrayList<>(16);
iteratorsTmp.forEach(i2 -> {
tmp.add(new IteratorEntry<>(i.getAndIncrement(), i2));
});
_iterators = List.copyOf(tmp);
}
if (startType == IteratorStart.LT || startType == IteratorStart.LE) {
@@ -185,7 +186,7 @@ public class MergingKvIterator<K extends Comparable<K>, V> extends ReversibleKvI
}
@Override
protected Pair<K, V> nextImpl() {
protected Pair<K, MaybeTombstone<V>> nextImpl() {
var cur = _goingForward ? _sortedIterators.pollFirstEntry() : _sortedIterators.pollLastEntry();
if (cur == null) {
throw new NoSuchElementException();

View File

@@ -9,7 +9,7 @@ public class TombstoneMergingKvIterator<K extends Comparable<K>, V> implements C
private final CloseableKvIterator<K, V> _backing;
private final String _name;
public TombstoneMergingKvIterator(String name, IteratorStart startType, K startKey, List<IterProdFn<K, MaybeTombstone<V>>> iterators) {
public TombstoneMergingKvIterator(String name, IteratorStart startType, K startKey, List<IterProdFn<K, V>> iterators) {
_name = name;
_backing = new PredicateKvIterator<>(
new MergingKvIterator<>(name + "-merging", startType, startKey, iterators),
@@ -24,7 +24,7 @@ public class TombstoneMergingKvIterator<K extends Comparable<K>, V> implements C
}
@SafeVarargs
public TombstoneMergingKvIterator(String name, IteratorStart startType, K startKey, IterProdFn<K, MaybeTombstone<V>>... iterators) {
public TombstoneMergingKvIterator(String name, IteratorStart startType, K startKey, IterProdFn<K, V>... iterators) {
this(name, startType, startKey, List.of(iterators));
}

View File

@@ -1,15 +1,19 @@
package com.usatiuk.objects.snapshot;
import com.usatiuk.objects.JObjectKey;
import com.usatiuk.objects.iterators.CloseableKvIterator;
import com.usatiuk.objects.iterators.IteratorStart;
import com.usatiuk.dhfs.utils.AutoCloseableNoThrow;
import com.usatiuk.objects.iterators.CloseableKvIterator;
import com.usatiuk.objects.iterators.IterProdFn;
import com.usatiuk.objects.iterators.IteratorStart;
import javax.annotation.Nonnull;
import java.util.Optional;
public interface Snapshot<K extends Comparable<K>, V> extends AutoCloseableNoThrow {
CloseableKvIterator<K, V> getIterator(IteratorStart start, K key);
IterProdFn<K, V> getIterator();
default CloseableKvIterator<K, V> getIterator(IteratorStart start, K key) {
return getIterator().get(start, key);
}
@Nonnull
Optional<V> readObject(K name);

View File

@@ -21,6 +21,7 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;
@ApplicationScoped
public class CachingObjectPersistentStore {
@@ -186,17 +187,43 @@ public class CachingObjectPersistentStore {
}
@Override
public CloseableKvIterator<JObjectKey, JDataVersionedWrapper> getIterator(IteratorStart start, JObjectKey key) {
return new TombstoneMergingKvIterator<>("cache", start, key,
(mS, mK)
-> new MappingKvIterator<>(
new NavigableMapKvIterator<>(_curCache.map(), mS, mK),
e -> {
public IterProdFn<JObjectKey, JDataVersionedWrapper> getIterator() {
IterProdFn<JObjectKey, JDataVersionedWrapper> cacheItProdFn = new IterProdFn<JObjectKey, JDataVersionedWrapper>() {
@Override
public CloseableKvIterator<JObjectKey, JDataVersionedWrapper> get(IteratorStart start, JObjectKey key) {
throw new UnsupportedOperationException("Not implemented");
}
@Override
public Stream<CloseableKvIterator<JObjectKey, MaybeTombstone<JDataVersionedWrapper>>> getFlat(IteratorStart start, JObjectKey key) {
return Stream.of(
new MappingKvIterator<>(
new NavigableMapKvIterator<>(_curCache.map(), start, key),
e -> {
// Log.tracev("Taken from cache: {0}", e);
return e.object();
}
),
(mS, mK) -> new MappingKvIterator<>(new CachingKvIterator(_backing.getIterator(start, key)), Data::new));
return e.object();
}
)
);
}
};
IterProdFn<JObjectKey, JDataVersionedWrapper> backingItProdFn = (mS, mK) -> new CachingKvIterator(_backing.getIterator(mS, mK));
return new IterProdFn<JObjectKey, JDataVersionedWrapper>() {
@Override
public CloseableKvIterator<JObjectKey, JDataVersionedWrapper> get(IteratorStart start, JObjectKey key) {
return new TombstoneMergingKvIterator<>("cache", start, key, cacheItProdFn, backingItProdFn);
}
@Override
public Stream<CloseableKvIterator<JObjectKey, MaybeTombstone<JDataVersionedWrapper>>> getFlat(IteratorStart start, JObjectKey key) {
return Stream.concat(
cacheItProdFn.getFlat(start, key),
backingItProdFn.getFlat(start, key)
);
}
};
}
@Nonnull

View File

@@ -6,7 +6,7 @@ import com.usatiuk.dhfs.utils.RefcountedCloseable;
import com.usatiuk.objects.JObjectKey;
import com.usatiuk.objects.JObjectKeyMax;
import com.usatiuk.objects.JObjectKeyMin;
import com.usatiuk.objects.iterators.CloseableKvIterator;
import com.usatiuk.objects.iterators.IterProdFn;
import com.usatiuk.objects.iterators.IteratorStart;
import com.usatiuk.objects.iterators.KeyPredicateKvIterator;
import com.usatiuk.objects.iterators.ReversibleKvIterator;
@@ -121,9 +121,9 @@ public class LmdbObjectPersistentStore implements ObjectPersistentStore {
private boolean _closed = false;
@Override
public CloseableKvIterator<JObjectKey, ByteString> getIterator(IteratorStart start, JObjectKey key) {
public IterProdFn<JObjectKey, ByteString> getIterator() {
assert !_closed;
return new KeyPredicateKvIterator<>(new LmdbKvIterator(_txn.ref(), start, key), start, key, (k) -> !k.value().equals(DB_VER_OBJ_NAME_STR));
return (start, key) -> new KeyPredicateKvIterator<>(new LmdbKvIterator(_txn.ref(), start, key), start, key, (k) -> !k.value().equals(DB_VER_OBJ_NAME_STR));
}
@Nonnull

View File

@@ -2,9 +2,7 @@ package com.usatiuk.objects.stores;
import com.google.protobuf.ByteString;
import com.usatiuk.objects.JObjectKey;
import com.usatiuk.objects.JObjectKeyImpl;
import com.usatiuk.objects.iterators.CloseableKvIterator;
import com.usatiuk.objects.iterators.IteratorStart;
import com.usatiuk.objects.iterators.IterProdFn;
import com.usatiuk.objects.iterators.NavigableMapKvIterator;
import com.usatiuk.objects.snapshot.Snapshot;
import io.quarkus.arc.properties.IfBuildProperty;
@@ -38,8 +36,8 @@ public class MemoryObjectPersistentStore implements ObjectPersistentStore {
private final long _lastCommitId = MemoryObjectPersistentStore.this._lastCommitId;
@Override
public CloseableKvIterator<JObjectKey, ByteString> getIterator(IteratorStart start, JObjectKey key) {
return new NavigableMapKvIterator<>(_objects, start, key);
public IterProdFn<JObjectKey, ByteString> getIterator() {
return (start, key) -> new NavigableMapKvIterator<>(_objects, start, key);
}
@Nonnull

View File

@@ -4,8 +4,7 @@ import com.google.protobuf.ByteString;
import com.usatiuk.objects.JDataVersionedWrapper;
import com.usatiuk.objects.JObjectKey;
import com.usatiuk.objects.ObjectSerializer;
import com.usatiuk.objects.iterators.CloseableKvIterator;
import com.usatiuk.objects.iterators.IteratorStart;
import com.usatiuk.objects.iterators.IterProdFn;
import com.usatiuk.objects.iterators.MappingKvIterator;
import com.usatiuk.objects.snapshot.Snapshot;
import jakarta.enterprise.context.ApplicationScoped;
@@ -33,8 +32,8 @@ public class SerializingObjectPersistentStore {
private final Snapshot<JObjectKey, ByteString> _backing = delegateStore.getSnapshot();
@Override
public CloseableKvIterator<JObjectKey, JDataVersionedWrapper> getIterator(IteratorStart start, JObjectKey key) {
return new MappingKvIterator<>(_backing.getIterator(start, key), d -> serializer.deserialize(d));
public IterProdFn<JObjectKey, JDataVersionedWrapper> getIterator() {
return (start, key) -> new MappingKvIterator<>(_backing.getIterator(start, key), d -> serializer.deserialize(d));
}
@Nonnull

View File

@@ -27,6 +27,7 @@ import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.stream.Stream;
@ApplicationScoped
public class WritebackObjectPersistentStore {
@@ -349,16 +350,37 @@ public class WritebackObjectPersistentStore {
private final long txId = finalPw.lastCommittedId();
@Override
public CloseableKvIterator<JObjectKey, JDataVersionedWrapper> getIterator(IteratorStart start, JObjectKey key) {
return new TombstoneMergingKvIterator<>("writeback-ps", start, key,
(tS, tK) -> new MappingKvIterator<>(
new NavigableMapKvIterator<>(_pendingWrites, tS, tK),
public IterProdFn<JObjectKey, JDataVersionedWrapper> getIterator() {
IterProdFn<JObjectKey, JDataVersionedWrapper> cacheItProdFn = new IterProdFn<JObjectKey, JDataVersionedWrapper>() {
@Override
public CloseableKvIterator<JObjectKey, JDataVersionedWrapper> get(IteratorStart start, JObjectKey key) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public Stream<CloseableKvIterator<JObjectKey, MaybeTombstone<JDataVersionedWrapper>>> getFlat(IteratorStart start, JObjectKey key) {
return Stream.of(new MappingKvIterator<>(
new NavigableMapKvIterator<>(_pendingWrites, start, key),
e -> switch (e) {
case PendingWrite pw -> new Data<>(pw.data());
case PendingDelete d -> new Tombstone<>();
default -> throw new IllegalStateException("Unexpected value: " + e);
}),
(tS, tK) -> new MappingKvIterator<>(_cache.getIterator(tS, tK), Data::new));
}));
}
};
return new IterProdFn<JObjectKey, JDataVersionedWrapper>() {
@Override
public CloseableKvIterator<JObjectKey, JDataVersionedWrapper> get(IteratorStart start, JObjectKey key) {
return new TombstoneMergingKvIterator<>("writeback-ps", start, key,
cacheItProdFn, _cache.getIterator());
}
@Override
public Stream<CloseableKvIterator<JObjectKey, MaybeTombstone<JDataVersionedWrapper>>> getFlat(IteratorStart start, JObjectKey key) {
return Stream.concat(cacheItProdFn.getFlat(start, key), _cache.getIterator().getFlat(start, key));
}
};
}
@Nonnull

View File

@@ -7,13 +7,13 @@ import com.usatiuk.objects.iterators.*;
import com.usatiuk.objects.snapshot.Snapshot;
import com.usatiuk.objects.snapshot.SnapshotManager;
import io.quarkus.logging.Log;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
import org.apache.commons.lang3.tuple.Pair;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import java.util.*;
import java.util.stream.Stream;
@Singleton
public class TransactionFactoryImpl implements TransactionFactory {
@@ -161,17 +161,48 @@ public class TransactionFactoryImpl implements TransactionFactory {
@Override
public CloseableKvIterator<JObjectKey, JData> getIterator(IteratorStart start, JObjectKey key) {
Log.tracev("Getting tx iterator with start={0}, key={1}", start, key);
return new ReadTrackingIterator(new TombstoneMergingKvIterator<>("tx", start, key,
(tS, tK) -> new MappingKvIterator<>(new NavigableMapKvIterator<>(_writes, tS, tK),
t -> switch (t) {
case TxRecord.TxObjectRecordWrite<?> write ->
new Data<>(new ReadTrackingInternalCrapTx(write.data()));
case TxRecord.TxObjectRecordDeleted deleted -> new Tombstone<>();
case null, default -> null;
}),
(tS, tK) -> new MappingKvIterator<>(_snapshot.getIterator(tS, tK),
d -> new Data<ReadTrackingInternalCrap>(new ReadTrackingInternalCrapSource(d)))));
new IterProdFn<JObjectKey, ReadTrackingInternalCrap>() {
@Override
public CloseableKvIterator<JObjectKey, ReadTrackingInternalCrap> get(IteratorStart start, JObjectKey key) {
throw new UnsupportedOperationException("Not implemented");
}
@Override
public Stream<CloseableKvIterator<JObjectKey, MaybeTombstone<ReadTrackingInternalCrap>>> getFlat(IteratorStart start, JObjectKey key) {
return Stream.of(new MappingKvIterator<>(new NavigableMapKvIterator<>(_writes, start, key),
t -> switch (t) {
case TxRecord.TxObjectRecordWrite<?> write ->
new Data<>(new ReadTrackingInternalCrapTx(write.data()));
case TxRecord.TxObjectRecordDeleted deleted -> new Tombstone<>();
case null, default -> null;
}));
}
},
new IterProdFn<JObjectKey, ReadTrackingInternalCrap>() {
@Override
public CloseableKvIterator<JObjectKey, ReadTrackingInternalCrap> get(IteratorStart start, JObjectKey key) {
throw new UnsupportedOperationException("Not implemented");
}
@Override
public Stream<CloseableKvIterator<JObjectKey, MaybeTombstone<ReadTrackingInternalCrap>>> getFlat(IteratorStart start, JObjectKey key) {
return _snapshot.getIterator().getFlat(start, key).<CloseableKvIterator<JObjectKey, MaybeTombstone<ReadTrackingInternalCrap>>>map(
i -> new MappingKvIterator<JObjectKey, MaybeTombstone<JDataVersionedWrapper>, MaybeTombstone<ReadTrackingInternalCrap>>(i,
d ->
switch (d) {
case Data<JDataVersionedWrapper> data ->
new Data<ReadTrackingInternalCrap>(new ReadTrackingInternalCrapSource(data.value()));
case Tombstone<JDataVersionedWrapper> tombstone ->
new Tombstone<ReadTrackingInternalCrap>();
default ->
throw new IllegalStateException("Unexpected value: " + d);
})
);
}
}));
}
@Override

View File

@@ -1,262 +1,262 @@
package com.usatiuk.objects.iterators;
import net.jqwik.api.*;
import net.jqwik.api.state.Action;
import net.jqwik.api.state.ActionChain;
import org.apache.commons.lang3.tuple.Pair;
import org.junit.jupiter.api.Assertions;
import java.util.*;
public class MergingKvIteratorPbtTest {
static class MergingIteratorModel implements CloseableKvIterator<Integer, Integer> {
private final CloseableKvIterator<Integer, Integer> mergedIterator;
private final CloseableKvIterator<Integer, Integer> mergingIterator;
private MergingIteratorModel(List<List<Map.Entry<Integer, Integer>>> pairs, IteratorStart startType, Integer startKey) {
TreeMap<Integer, Integer> perfectMerged = new TreeMap<>();
for (List<Map.Entry<Integer, Integer>> list : pairs) {
for (Map.Entry<Integer, Integer> pair : list) {
perfectMerged.putIfAbsent(pair.getKey(), pair.getValue());
}
}
mergedIterator = new NavigableMapKvIterator<>(perfectMerged, startType, startKey);
mergingIterator = new MergingKvIterator<>("test", startType, startKey, pairs.stream().<IterProdFn<Integer, Integer>>map(
list -> (IteratorStart start, Integer key) -> new NavigableMapKvIterator<>(new TreeMap<Integer, Integer>(Map.ofEntries(list.toArray(Map.Entry[]::new))), start, key)
).toList());
}
@Override
public Integer peekNextKey() {
var mergedKey = mergedIterator.peekNextKey();
var mergingKey = mergingIterator.peekNextKey();
Assertions.assertEquals(mergedKey, mergingKey);
return mergedKey;
}
@Override
public void skip() {
mergedIterator.skip();
mergingIterator.skip();
}
@Override
public Integer peekPrevKey() {
var mergedKey = mergedIterator.peekPrevKey();
var mergingKey = mergingIterator.peekPrevKey();
Assertions.assertEquals(mergedKey, mergingKey);
return mergedKey;
}
@Override
public Pair<Integer, Integer> prev() {
var mergedKey = mergedIterator.prev();
var mergingKey = mergingIterator.prev();
Assertions.assertEquals(mergedKey, mergingKey);
return mergedKey;
}
@Override
public boolean hasPrev() {
var mergedKey = mergedIterator.hasPrev();
var mergingKey = mergingIterator.hasPrev();
Assertions.assertEquals(mergedKey, mergingKey);
return mergedKey;
}
@Override
public void skipPrev() {
mergedIterator.skipPrev();
mergingIterator.skipPrev();
}
@Override
public void close() {
mergedIterator.close();
mergingIterator.close();
}
@Override
public boolean hasNext() {
var mergedKey = mergedIterator.hasNext();
var mergingKey = mergingIterator.hasNext();
Assertions.assertEquals(mergedKey, mergingKey);
return mergedKey;
}
@Override
public Pair<Integer, Integer> next() {
var mergedKey = mergedIterator.next();
var mergingKey = mergingIterator.next();
Assertions.assertEquals(mergedKey, mergingKey);
return mergedKey;
}
}
static class PeekNextKeyAction extends Action.JustMutate<MergingIteratorModel> {
@Override
public void mutate(MergingIteratorModel state) {
state.peekNextKey();
}
@Override
public boolean precondition(MergingIteratorModel state) {
return state.hasNext();
}
@Override
public String description() {
return "Peek next key";
}
}
static class SkipAction extends Action.JustMutate<MergingIteratorModel> {
@Override
public void mutate(MergingIteratorModel state) {
state.skip();
}
@Override
public boolean precondition(MergingIteratorModel state) {
return state.hasNext();
}
@Override
public String description() {
return "Skip next key";
}
}
static class PeekPrevKeyAction extends Action.JustMutate<MergingIteratorModel> {
@Override
public void mutate(MergingIteratorModel state) {
state.peekPrevKey();
}
@Override
public boolean precondition(MergingIteratorModel state) {
return state.hasPrev();
}
@Override
public String description() {
return "Peek prev key";
}
}
static class SkipPrevAction extends Action.JustMutate<MergingIteratorModel> {
@Override
public void mutate(MergingIteratorModel state) {
state.skipPrev();
}
@Override
public boolean precondition(MergingIteratorModel state) {
return state.hasPrev();
}
@Override
public String description() {
return "Skip prev key";
}
}
static class PrevAction extends Action.JustMutate<MergingIteratorModel> {
@Override
public void mutate(MergingIteratorModel state) {
state.prev();
}
@Override
public boolean precondition(MergingIteratorModel state) {
return state.hasPrev();
}
@Override
public String description() {
return "Prev key";
}
}
static class NextAction extends Action.JustMutate<MergingIteratorModel> {
@Override
public void mutate(MergingIteratorModel state) {
state.next();
}
@Override
public boolean precondition(MergingIteratorModel state) {
return state.hasNext();
}
@Override
public String description() {
return "Next key";
}
}
static class HasNextAction extends Action.JustMutate<MergingIteratorModel> {
@Override
public void mutate(MergingIteratorModel state) {
state.hasNext();
}
@Override
public boolean precondition(MergingIteratorModel state) {
return true;
}
@Override
public String description() {
return "Has next key";
}
}
static class HasPrevAction extends Action.JustMutate<MergingIteratorModel> {
@Override
public void mutate(MergingIteratorModel state) {
state.hasPrev();
}
@Override
public boolean precondition(MergingIteratorModel state) {
return true;
}
@Override
public String description() {
return "Has prev key";
}
}
@Property
public void checkMergingIterator(@ForAll("actions") ActionChain<MergingIteratorModel> actions) {
actions.run();
}
@Provide
Arbitrary<ActionChain<MergingIteratorModel>> actions(@ForAll("lists") List<List<Map.Entry<Integer, Integer>>> list,
@ForAll IteratorStart iteratorStart, @ForAll("startKey") Integer startKey) {
return ActionChain.startWith(() -> new MergingIteratorModel(list, iteratorStart, startKey))
.withAction(new NextAction())
.withAction(new PeekNextKeyAction())
.withAction(new SkipAction())
.withAction(new PeekPrevKeyAction())
.withAction(new SkipPrevAction())
.withAction(new PrevAction())
.withAction(new HasNextAction())
.withAction(new HasPrevAction());
}
@Provide
Arbitrary<List<List<Map.Entry<Integer, Integer>>>> lists() {
return Arbitraries.entries(Arbitraries.integers().between(-50, 50), Arbitraries.integers().between(-50, 50))
.list().uniqueElements(Map.Entry::getKey).ofMinSize(0).ofMaxSize(20)
.list().ofMinSize(1).ofMaxSize(5);
}
@Provide
Arbitrary<Integer> startKey() {
return Arbitraries.integers().between(-51, 51);
}
}
//package com.usatiuk.objects.iterators;
//
//import net.jqwik.api.*;
//import net.jqwik.api.state.Action;
//import net.jqwik.api.state.ActionChain;
//import org.apache.commons.lang3.tuple.Pair;
//import org.junit.jupiter.api.Assertions;
//
//import java.util.*;
//
//public class MergingKvIteratorPbtTest {
// static class MergingIteratorModel implements CloseableKvIterator<Integer, Integer> {
// private final CloseableKvIterator<Integer, Integer> mergedIterator;
// private final CloseableKvIterator<Integer, Integer> mergingIterator;
//
// private MergingIteratorModel(List<List<Map.Entry<Integer, Integer>>> pairs, IteratorStart startType, Integer startKey) {
// TreeMap<Integer, Integer> perfectMerged = new TreeMap<>();
// for (List<Map.Entry<Integer, Integer>> list : pairs) {
// for (Map.Entry<Integer, Integer> pair : list) {
// perfectMerged.putIfAbsent(pair.getKey(), pair.getValue());
// }
// }
// mergedIterator = new NavigableMapKvIterator<>(perfectMerged, startType, startKey);
// mergingIterator = new MergingKvIterator<>("test", startType, startKey, pairs.stream().<IterProdFn<Integer, Integer>>map(
// list -> (IteratorStart start, Integer key) -> new NavigableMapKvIterator<>(new TreeMap<Integer, Integer>(Map.ofEntries(list.toArray(Map.Entry[]::new))), start, key)
// ).toList());
// }
//
// @Override
// public Integer peekNextKey() {
// var mergedKey = mergedIterator.peekNextKey();
// var mergingKey = mergingIterator.peekNextKey();
// Assertions.assertEquals(mergedKey, mergingKey);
// return mergedKey;
// }
//
// @Override
// public void skip() {
// mergedIterator.skip();
// mergingIterator.skip();
// }
//
// @Override
// public Integer peekPrevKey() {
// var mergedKey = mergedIterator.peekPrevKey();
// var mergingKey = mergingIterator.peekPrevKey();
// Assertions.assertEquals(mergedKey, mergingKey);
// return mergedKey;
// }
//
// @Override
// public Pair<Integer, Integer> prev() {
// var mergedKey = mergedIterator.prev();
// var mergingKey = mergingIterator.prev();
// Assertions.assertEquals(mergedKey, mergingKey);
// return mergedKey;
// }
//
// @Override
// public boolean hasPrev() {
// var mergedKey = mergedIterator.hasPrev();
// var mergingKey = mergingIterator.hasPrev();
// Assertions.assertEquals(mergedKey, mergingKey);
// return mergedKey;
// }
//
// @Override
// public void skipPrev() {
// mergedIterator.skipPrev();
// mergingIterator.skipPrev();
// }
//
// @Override
// public void close() {
// mergedIterator.close();
// mergingIterator.close();
// }
//
// @Override
// public boolean hasNext() {
// var mergedKey = mergedIterator.hasNext();
// var mergingKey = mergingIterator.hasNext();
// Assertions.assertEquals(mergedKey, mergingKey);
// return mergedKey;
// }
//
// @Override
// public Pair<Integer, Integer> next() {
// var mergedKey = mergedIterator.next();
// var mergingKey = mergingIterator.next();
// Assertions.assertEquals(mergedKey, mergingKey);
// return mergedKey;
// }
// }
//
// static class PeekNextKeyAction extends Action.JustMutate<MergingIteratorModel> {
// @Override
// public void mutate(MergingIteratorModel state) {
// state.peekNextKey();
// }
//
// @Override
// public boolean precondition(MergingIteratorModel state) {
// return state.hasNext();
// }
//
// @Override
// public String description() {
// return "Peek next key";
// }
// }
//
// static class SkipAction extends Action.JustMutate<MergingIteratorModel> {
// @Override
// public void mutate(MergingIteratorModel state) {
// state.skip();
// }
//
// @Override
// public boolean precondition(MergingIteratorModel state) {
// return state.hasNext();
// }
//
// @Override
// public String description() {
// return "Skip next key";
// }
// }
//
// static class PeekPrevKeyAction extends Action.JustMutate<MergingIteratorModel> {
// @Override
// public void mutate(MergingIteratorModel state) {
// state.peekPrevKey();
// }
//
// @Override
// public boolean precondition(MergingIteratorModel state) {
// return state.hasPrev();
// }
//
// @Override
// public String description() {
// return "Peek prev key";
// }
// }
//
// static class SkipPrevAction extends Action.JustMutate<MergingIteratorModel> {
// @Override
// public void mutate(MergingIteratorModel state) {
// state.skipPrev();
// }
//
// @Override
// public boolean precondition(MergingIteratorModel state) {
// return state.hasPrev();
// }
//
// @Override
// public String description() {
// return "Skip prev key";
// }
// }
//
// static class PrevAction extends Action.JustMutate<MergingIteratorModel> {
// @Override
// public void mutate(MergingIteratorModel state) {
// state.prev();
// }
//
// @Override
// public boolean precondition(MergingIteratorModel state) {
// return state.hasPrev();
// }
//
// @Override
// public String description() {
// return "Prev key";
// }
// }
//
// static class NextAction extends Action.JustMutate<MergingIteratorModel> {
// @Override
// public void mutate(MergingIteratorModel state) {
// state.next();
// }
//
// @Override
// public boolean precondition(MergingIteratorModel state) {
// return state.hasNext();
// }
//
// @Override
// public String description() {
// return "Next key";
// }
// }
//
// static class HasNextAction extends Action.JustMutate<MergingIteratorModel> {
// @Override
// public void mutate(MergingIteratorModel state) {
// state.hasNext();
// }
//
// @Override
// public boolean precondition(MergingIteratorModel state) {
// return true;
// }
//
// @Override
// public String description() {
// return "Has next key";
// }
// }
//
// static class HasPrevAction extends Action.JustMutate<MergingIteratorModel> {
// @Override
// public void mutate(MergingIteratorModel state) {
// state.hasPrev();
// }
//
// @Override
// public boolean precondition(MergingIteratorModel state) {
// return true;
// }
//
// @Override
// public String description() {
// return "Has prev key";
// }
// }
//
// @Property
// public void checkMergingIterator(@ForAll("actions") ActionChain<MergingIteratorModel> actions) {
// actions.run();
// }
//
// @Provide
// Arbitrary<ActionChain<MergingIteratorModel>> actions(@ForAll("lists") List<List<Map.Entry<Integer, Integer>>> list,
// @ForAll IteratorStart iteratorStart, @ForAll("startKey") Integer startKey) {
// return ActionChain.startWith(() -> new MergingIteratorModel(list, iteratorStart, startKey))
// .withAction(new NextAction())
// .withAction(new PeekNextKeyAction())
// .withAction(new SkipAction())
// .withAction(new PeekPrevKeyAction())
// .withAction(new SkipPrevAction())
// .withAction(new PrevAction())
// .withAction(new HasNextAction())
// .withAction(new HasPrevAction());
// }
//
// @Provide
// Arbitrary<List<List<Map.Entry<Integer, Integer>>>> lists() {
// return Arbitraries.entries(Arbitraries.integers().between(-50, 50), Arbitraries.integers().between(-50, 50))
// .list().uniqueElements(Map.Entry::getKey).ofMinSize(0).ofMaxSize(20)
// .list().ofMinSize(1).ofMaxSize(5);
// }
//
// @Provide
// Arbitrary<Integer> startKey() {
// return Arbitraries.integers().between(-51, 51);
// }
//}