From 9c37573da57c7f1ee49b520004525180428d6a72 Mon Sep 17 00:00:00 2001 From: f1v3-dev Date: Wed, 1 Apr 2026 18:13:25 +0900 Subject: [PATCH] FEATURE: Add CompletableFuture BTree position APIs --- .../collection/BTreeFindPosition.java | 18 +- .../collection/BTreeFindPositionWithGet.java | 20 +- .../spy/memcached/v2/AsyncArcusCommands.java | 200 +++++++++ .../memcached/v2/AsyncArcusCommandsIF.java | 51 +++ .../java/net/spy/memcached/v2/vo/BKey.java | 7 + .../memcached/v2/vo/BTreePositionElement.java | 54 +++ .../v2/BTreeAsyncArcusCommandsTest.java | 403 +++++++++++++++++- 7 files changed, 735 insertions(+), 18 deletions(-) create mode 100644 src/main/java/net/spy/memcached/v2/vo/BTreePositionElement.java diff --git a/src/main/java/net/spy/memcached/collection/BTreeFindPosition.java b/src/main/java/net/spy/memcached/collection/BTreeFindPosition.java index 8d8e5678c..045357776 100644 --- a/src/main/java/net/spy/memcached/collection/BTreeFindPosition.java +++ b/src/main/java/net/spy/memcached/collection/BTreeFindPosition.java @@ -36,14 +36,24 @@ public class BTreeFindPosition { private final BTreeOrder order; private String str; - public BTreeFindPosition(long longBKey, BTreeOrder order) { - this.bkey = String.valueOf(longBKey); + public BTreeFindPosition(String bkey, BTreeOrder order) { + if (bkey == null || bkey.isEmpty()) { + throw new IllegalArgumentException("BKey must not be null or empty."); + } + if (order == null) { + throw new IllegalArgumentException("BTreeOrder must not be null."); + } + + this.bkey = bkey; this.order = order; } + public BTreeFindPosition(long longBKey, BTreeOrder order) { + this(String.valueOf(longBKey), order); + } + public BTreeFindPosition(byte[] byteArrayBKey, BTreeOrder order) { - this.bkey = BTreeUtil.toHex(byteArrayBKey); - this.order = order; + this(BTreeUtil.toHex(byteArrayBKey), order); } public String stringify() { diff --git a/src/main/java/net/spy/memcached/collection/BTreeFindPositionWithGet.java b/src/main/java/net/spy/memcached/collection/BTreeFindPositionWithGet.java index 0af624590..ca7ba5baa 100644 --- a/src/main/java/net/spy/memcached/collection/BTreeFindPositionWithGet.java +++ b/src/main/java/net/spy/memcached/collection/BTreeFindPositionWithGet.java @@ -49,32 +49,26 @@ public class BTreeFindPositionWithGet extends CollectionGet { private final int count; private BKeyObject bkey; - public BTreeFindPositionWithGet(long longBKey, BTreeOrder order, int count) { + public BTreeFindPositionWithGet(BKeyObject bkeyObject, BTreeOrder order, int count) { if (order == null) { throw new IllegalArgumentException("BTreeOrder must not be null."); } if (count < 0 || count > 100) { throw new IllegalArgumentException("Count must be a value between 0 and 100."); } - this.bkeyObject = new BKeyObject(longBKey); + this.bkeyObject = bkeyObject; this.order = order; this.count = count; this.eHeadCount = 2; this.eFlagIndex = 1; } + public BTreeFindPositionWithGet(long longBKey, BTreeOrder order, int count) { + this(new BKeyObject(longBKey), order, count); + } + public BTreeFindPositionWithGet(byte[] byteArrayBKey, BTreeOrder order, int count) { - if (order == null) { - throw new IllegalArgumentException("BTreeOrder must not be null."); - } - if (count < 0 || count > 100) { - throw new IllegalArgumentException("Count must be a value between 0 and 100."); - } - this.bkeyObject = new BKeyObject(byteArrayBKey); - this.order = order; - this.count = count; - this.eHeadCount = 2; - this.eFlagIndex = 1; + this(new BKeyObject(byteArrayBKey), order, count); } public String stringify() { diff --git a/src/main/java/net/spy/memcached/v2/AsyncArcusCommands.java b/src/main/java/net/spy/memcached/v2/AsyncArcusCommands.java index 29add2ce5..ef397fb44 100644 --- a/src/main/java/net/spy/memcached/v2/AsyncArcusCommands.java +++ b/src/main/java/net/spy/memcached/v2/AsyncArcusCommands.java @@ -36,14 +36,18 @@ import net.spy.memcached.collection.BKeyObject; import net.spy.memcached.collection.BTreeCount; import net.spy.memcached.collection.BTreeCreate; +import net.spy.memcached.collection.BTreeFindPosition; +import net.spy.memcached.collection.BTreeFindPositionWithGet; import net.spy.memcached.collection.BTreeDelete; import net.spy.memcached.collection.BTreeGet; import net.spy.memcached.collection.BTreeGetBulk; import net.spy.memcached.collection.BTreeGetBulkWithByteTypeBkey; import net.spy.memcached.collection.BTreeGetBulkWithLongTypeBkey; +import net.spy.memcached.collection.BTreeGetByPosition; import net.spy.memcached.collection.BTreeInsert; import net.spy.memcached.collection.BTreeInsertAndGet; import net.spy.memcached.collection.BTreeMutate; +import net.spy.memcached.collection.BTreeOrder; import net.spy.memcached.collection.BTreeSMGet; import net.spy.memcached.collection.BTreeSMGetWithByteTypeBkey; import net.spy.memcached.collection.BTreeSMGetWithLongTypeBkey; @@ -60,7 +64,10 @@ import net.spy.memcached.collection.ElementValueType; import net.spy.memcached.internal.result.GetsResultImpl; import net.spy.memcached.ops.APIType; +import net.spy.memcached.ops.BTreeFindPositionOperation; +import net.spy.memcached.ops.BTreeFindPositionWithGetOperation; import net.spy.memcached.ops.BTreeGetBulkOperation; +import net.spy.memcached.ops.BTreeGetByPositionOperation; import net.spy.memcached.ops.BTreeInsertAndGetOperation; import net.spy.memcached.ops.BTreeSortMergeGetOperation; import net.spy.memcached.ops.CollectionCreateOperation; @@ -79,6 +86,7 @@ import net.spy.memcached.transcoders.TranscoderUtils; import net.spy.memcached.v2.vo.BKey; import net.spy.memcached.v2.vo.BTreeElement; +import net.spy.memcached.v2.vo.BTreePositionElement; import net.spy.memcached.v2.vo.BTreeElements; import net.spy.memcached.v2.vo.BTreeUpdateElement; import net.spy.memcached.v2.vo.BopDeleteArgs; @@ -1252,6 +1260,198 @@ private BTreeGetBulk createBTreeGetBulk(MemcachedNode node, List keys } } + public ArcusFuture bopGetPosition(String key, BKey bKey, BTreeOrder order) { + AbstractArcusResult result = new AbstractArcusResult<>(new AtomicReference<>()); + ArcusFutureImpl future = new ArcusFutureImpl<>(result); + BTreeFindPosition findPosition = new BTreeFindPosition(bKey.toString(), order); + ArcusClient client = arcusClientSupplier.get(); + + BTreeFindPositionOperation.Callback cb = new BTreeFindPositionOperation.Callback() { + @Override + public void gotData(int position) { + result.set(position); + } + + @Override + public void receivedStatus(OperationStatus status) { + switch (status.getStatusCode()) { + case SUCCESS: + break; + case ERR_NOT_FOUND: + case ERR_NOT_FOUND_ELEMENT: + result.set(null); + break; + case CANCELLED: + future.internalCancel(); + break; + default: + /* TYPE_MISMATCH / BKEY_MISMATCH / UNREADABLE / NOT_SUPPORTED or unknown statement */ + result.addError(key, status); + } + } + + @Override + public void complete() { + future.complete(); + } + }; + Operation op = client.getOpFact().bopFindPosition(key, findPosition, cb); + future.setOp(op); + client.addOp(key, op); + + return future; + } + + public ArcusFuture> bopGetByPosition(String key, int pos, BTreeOrder order) { + AbstractArcusResult> result + = new AbstractArcusResult<>(new AtomicReference<>()); + ArcusFutureImpl> future = new ArcusFutureImpl<>(result); + BTreeGetByPosition getByPosition = new BTreeGetByPosition(order, pos); + ArcusClient client = arcusClientSupplier.get(); + + BTreeGetByPositionOperation.Callback cb = new BTreeGetByPositionOperation.Callback() { + @Override + public void gotData(int pos, int flags, BKeyObject bKey, byte[] eFlag, byte[] data) { + result.set(buildBTreeElement(flags, bKey, eFlag, data)); + } + + @Override + public void receivedStatus(OperationStatus status) { + switch (status.getStatusCode()) { + case SUCCESS: + break; + case ERR_NOT_FOUND: + case ERR_NOT_FOUND_ELEMENT: + result.set(null); + break; + case CANCELLED: + future.internalCancel(); + break; + default: + /* TYPE_MISMATCH / UNREADABLE / NOT_SUPPORTED or unknown statement */ + result.addError(key, status); + } + } + + @Override + public void complete() { + future.complete(); + } + }; + Operation op = client.getOpFact().bopGetByPosition(key, getByPosition, cb); + future.setOp(op); + client.addOp(key, op); + + return future; + } + + public ArcusFuture>> bopGetByPosition(String key, + int from, int to, + BTreeOrder order) { + if (from > to) { + throw new IllegalArgumentException("from should be less than or equal to to."); + } + + AbstractArcusResult>> result + = new AbstractArcusResult<>(new AtomicReference<>(new ArrayList<>())); + ArcusFutureImpl>> future = new ArcusFutureImpl<>(result); + BTreeGetByPosition getByPosition = new BTreeGetByPosition(order, from, to); + ArcusClient client = arcusClientSupplier.get(); + + BTreeGetByPositionOperation.Callback cb = new BTreeGetByPositionOperation.Callback() { + @Override + public void gotData(int pos, int flags, BKeyObject bKey, byte[] eFlag, byte[] data) { + result.get().add(buildBTreeElement(flags, bKey, eFlag, data)); + } + + @Override + public void receivedStatus(OperationStatus status) { + switch (status.getStatusCode()) { + case SUCCESS: + case ERR_NOT_FOUND_ELEMENT: + break; + case ERR_NOT_FOUND: + result.set(null); + break; + case CANCELLED: + future.internalCancel(); + break; + default: + /* TYPE_MISMATCH / UNREADABLE / NOT_SUPPORTED or unknown statement */ + result.addError(key, status); + } + } + + @Override + public void complete() { + future.complete(); + } + }; + Operation op = client.getOpFact().bopGetByPosition(key, getByPosition, cb); + future.setOp(op); + client.addOp(key, op); + + return future; + } + + public ArcusFuture>> bopPositionWithGet(String key, + BKey bKey, + int count, + BTreeOrder order) { + AbstractArcusResult>> result = + new AbstractArcusResult<>(new AtomicReference<>(new ArrayList<>())); + ArcusFutureImpl>> future = new ArcusFutureImpl<>(result); + BTreeFindPositionWithGet findPositionWithGet = + new BTreeFindPositionWithGet(bKey.toBKeyObject(), order, count); + ArcusClient client = arcusClientSupplier.get(); + + BTreeFindPositionWithGetOperation.Callback cb = new BTreeFindPositionWithGetOperation + .Callback() { + + @Override + public void gotData(int pos, int flags, BKeyObject bKey, byte[] eFlag, byte[] data) { + T decodedData = tcForCollection.decode( + new CachedData(flags, data, tcForCollection.getMaxSize())); + result.get().add(new BTreePositionElement<>(BKey.of(bKey), decodedData, eFlag, pos)); + } + + @Override + public void receivedStatus(OperationStatus status) { + switch (status.getStatusCode()) { + case SUCCESS: + case ERR_NOT_FOUND_ELEMENT: + break; + case ERR_NOT_FOUND: + result.set(null); + break; + case CANCELLED: + future.internalCancel(); + break; + default: + /* TYPE_MISMATCH / BKEY_MISMATCH / UNREADABLE / NOT_SUPPORTED or unknown statement */ + result.addError(key, status); + } + } + + @Override + public void complete() { + future.complete(); + } + }; + Operation op = client.getOpFact().bopFindPositionWithGet(key, findPositionWithGet, cb); + future.setOp(op); + client.addOp(key, op); + + return future; + } + + private BTreeElement buildBTreeElement(int flags, BKeyObject bKey, + byte[] eFlag, byte[] data) { + T decodedData = tcForCollection.decode( + new CachedData(flags, data, tcForCollection.getMaxSize())); + return new BTreeElement<>(BKey.of(bKey), decodedData, eFlag); + } + public ArcusFuture> bopSortMergeGet(List keys, BKey from, BKey to, boolean unique, BopGetArgs args) { verifyBKeyRange(from, to); diff --git a/src/main/java/net/spy/memcached/v2/AsyncArcusCommandsIF.java b/src/main/java/net/spy/memcached/v2/AsyncArcusCommandsIF.java index 2326a3c3c..3616c624f 100644 --- a/src/main/java/net/spy/memcached/v2/AsyncArcusCommandsIF.java +++ b/src/main/java/net/spy/memcached/v2/AsyncArcusCommandsIF.java @@ -21,10 +21,12 @@ import java.util.Map; import net.spy.memcached.CASValue; +import net.spy.memcached.collection.BTreeOrder; import net.spy.memcached.collection.CollectionAttributes; import net.spy.memcached.collection.ElementFlagFilter; import net.spy.memcached.collection.ElementValueType; import net.spy.memcached.v2.vo.BKey; +import net.spy.memcached.v2.vo.BTreePositionElement; import net.spy.memcached.v2.vo.BTreeElement; import net.spy.memcached.v2.vo.BTreeElements; import net.spy.memcached.v2.vo.BTreeUpdateElement; @@ -369,6 +371,55 @@ ArcusFuture>> bopMultiGet(List keys, BKey from, BKey to, BopGetArgs args); + /** + * Get the position of an element with the given bKey in a btree item. + * + * @param key key of the btree item + * @param bKey BKey of the element to find + * @param order the order of the btree to determine position + * @return the 0-based position of the element, + * or {@code null} if the key or element is not found. + */ + ArcusFuture bopGetPosition(String key, BKey bKey, BTreeOrder order); + + /** + * Get an element at the given position in a btree item. + * + * @param key key of the btree item + * @param pos 0-based position of the element to get + * @param order the order of the btree to determine position + * @return the {@code BTreeElement} at the given position, + * or {@code null} if the key or element is not found. + */ + ArcusFuture> bopGetByPosition(String key, int pos, BTreeOrder order); + + /** + * Get elements in a position range from a btree item. + * + * @param key key of the btree item + * @param from start position (inclusive) + * @param to end position (inclusive) + * @param order the order of the btree to determine position + * @return list of {@code BTreeElement} in the given position range, in traversal order, + * empty list if no elements exist in the range, {@code null} if the key is not found. + */ + ArcusFuture>> bopGetByPosition(String key, + int from, int to, BTreeOrder order); + + /** + * Get an element by bKey and its neighboring elements with position information. + * + * @param key key of the btree item + * @param bKey BKey of the element to find + * @param count the number of neighboring elements to retrieve on each side + * (0 ≤ count ≤ 100) + * @param order the order of the btree to determine position + * @return list of {@code BTreePositionElement} in traversal order, + * empty list if the element is not found, {@code null} if the key is not found. + */ + ArcusFuture>> bopPositionWithGet(String key, BKey bKey, + int count, BTreeOrder order); + /** * Get sort-merged elements from multiple btree items. * diff --git a/src/main/java/net/spy/memcached/v2/vo/BKey.java b/src/main/java/net/spy/memcached/v2/vo/BKey.java index 7b841d790..1e37bae11 100644 --- a/src/main/java/net/spy/memcached/v2/vo/BKey.java +++ b/src/main/java/net/spy/memcached/v2/vo/BKey.java @@ -52,6 +52,13 @@ public static BKey of(Object bKey) { } } + public BKeyObject toBKeyObject() { + if (this.type == BKeyType.LONG) { + return new BKeyObject((Long) this.data); + } + return new BKeyObject((byte[]) this.data); + } + public static BKey of(BKeyObject bkeyObject) { if (bkeyObject == null) { throw new IllegalArgumentException("BKeyObject cannot be null"); diff --git a/src/main/java/net/spy/memcached/v2/vo/BTreePositionElement.java b/src/main/java/net/spy/memcached/v2/vo/BTreePositionElement.java new file mode 100644 index 000000000..284e0e2d6 --- /dev/null +++ b/src/main/java/net/spy/memcached/v2/vo/BTreePositionElement.java @@ -0,0 +1,54 @@ +package net.spy.memcached.v2.vo; + +import java.util.Objects; + +public final class BTreePositionElement implements Comparable> { + private final BTreeElement element; + private final int position; + + public BTreePositionElement(BKey bkey, V value, byte[] eFlag, int position) { + this.element = new BTreeElement<>(bkey, value, eFlag); + this.position = position; + } + + public BKey getBkey() { + return element.getBkey(); + } + + public V getValue() { + return element.getValue(); + } + + public byte[] getEFlag() { + return element.getEFlag(); + } + + public int getPosition() { + return position; + } + + @Override + public int compareTo(BTreePositionElement o) { + return this.element.compareTo(o.element); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + + if (o == null || getClass() != o.getClass()) { + return false; + } + + BTreePositionElement that = (BTreePositionElement) o; + return Objects.equals(element, that.element) && + this.position == that.position; + } + + @Override + public int hashCode() { + return Objects.hash(element, position); + } +} diff --git a/src/test/java/net/spy/memcached/v2/BTreeAsyncArcusCommandsTest.java b/src/test/java/net/spy/memcached/v2/BTreeAsyncArcusCommandsTest.java index 9df6071e0..b6581f324 100644 --- a/src/test/java/net/spy/memcached/v2/BTreeAsyncArcusCommandsTest.java +++ b/src/test/java/net/spy/memcached/v2/BTreeAsyncArcusCommandsTest.java @@ -7,6 +7,7 @@ import java.util.concurrent.TimeoutException; import java.util.stream.Collectors; +import net.spy.memcached.collection.BTreeOrder; import net.spy.memcached.collection.CollectionAttributes; import net.spy.memcached.collection.CollectionOverflowAction; import net.spy.memcached.collection.ElementFlagFilter; @@ -1285,6 +1286,406 @@ void bopDecrBKeyMismatch() throws ExecutionException, InterruptedException, Time .get(300L, TimeUnit.MILLISECONDS); } + @Test + void bopGetPositionSuccess() throws ExecutionException, InterruptedException, + TimeoutException { + // given + String key = keys.get(0); + BKey bKey = ELEMENTS.get(1).getBkey(); + + async.bopCreate(key, ElementValueType.STRING, new CollectionAttributes()) + .thenCompose(result -> { + assertTrue(result); + return async.bopInsert(key, ELEMENTS.get(0)); + }) + .thenCompose(result -> { + assertTrue(result); + return async.bopInsert(key, ELEMENTS.get(1)); + }) + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.bopGetPosition(key, bKey, BTreeOrder.ASC) + // then + .thenAccept(result -> { + assertNotNull(result); + assertEquals(1, result); + }) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void bopGetPositionNotFound() throws ExecutionException, InterruptedException, TimeoutException { + // given + String key = keys.get(0); + BKey bKey = BKey.of(1L); + + // when + async.bopGetPosition(key, bKey, BTreeOrder.ASC) + // then + .thenAccept(Assertions::assertNull) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void bopGetPositionNotFoundElement() throws ExecutionException, InterruptedException, + TimeoutException { + // given + String key = keys.get(0); + BKey bKey = BKey.of(1L); + + async.bopCreate(key, ElementValueType.STRING, new CollectionAttributes()) + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.bopGetPosition(key, bKey, BTreeOrder.ASC) + // then + .thenAccept(Assertions::assertNull) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void bopGetPositionTypeMismatch() throws ExecutionException, InterruptedException, + TimeoutException { + // given + String key = keys.get(0); + BKey bKey = BKey.of(1L); + + async.set(key, 60, "invalid-type-value") + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.bopGetPosition(key, bKey, BTreeOrder.ASC) + .handle((result, ex) -> { + assertInstanceOf(OperationException.class, ex); + assertTrue(ex.getMessage().contains("TYPE_MISMATCH")); + return result; + }) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void bopGetByPositionSuccess() throws ExecutionException, InterruptedException, TimeoutException { + // given + String key = keys.get(0); + BKey bKey = BKey.of(1L); + BTreeElement element = new BTreeElement<>(bKey, "value", null); + + async.bopCreate(key, ElementValueType.STRING, new CollectionAttributes()) + .thenCompose(result -> { + assertTrue(result); + return async.bopInsert(key, element); + }) + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.bopGetByPosition(key, 0, BTreeOrder.ASC) + // then + .thenAccept(result -> { + assertNotNull(result); + assertEquals(result, element); + }) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void bopGetByPositionNotFound() throws ExecutionException, InterruptedException, + TimeoutException { + // given + String key = keys.get(0); + + // when + async.bopGetByPosition(key, 0, BTreeOrder.ASC) + // then + .thenAccept(Assertions::assertNull) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void bopGetByPositionNotFoundElement() throws ExecutionException, InterruptedException, + TimeoutException { + // given + String key = keys.get(0); + + async.bopCreate(key, ElementValueType.STRING, new CollectionAttributes()) + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.bopGetByPosition(key, 0, BTreeOrder.ASC) + // then + .thenAccept(Assertions::assertNull) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void bopGetByPositionTypeMismatch() throws ExecutionException, InterruptedException, + TimeoutException { + // given + String key = keys.get(0); + + async.set(key, 60, "invalid-type-value") + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.bopGetByPosition(key, 0, BTreeOrder.ASC) + .handle((result, ex) -> { + assertInstanceOf(OperationException.class, ex); + assertTrue(ex.getMessage().contains("TYPE_MISMATCH")); + return result; + }) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void bopGetByPositionRangeSuccess() throws ExecutionException, InterruptedException, + TimeoutException { + // given + String key = keys.get(0); + + async.bopCreate(key, ElementValueType.STRING, new CollectionAttributes()) + .thenCompose(result -> { + assertTrue(result); + return async.bopInsert(key, ELEMENTS.get(0)); + }) + .thenCompose(result -> { + assertTrue(result); + return async.bopInsert(key, ELEMENTS.get(1)); + }) + .thenCompose(result -> { + assertTrue(result); + return async.bopInsert(key, ELEMENTS.get(2)); + }) + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.bopGetByPosition(key, 0, 2, BTreeOrder.ASC) + // then + .thenAccept(result -> { + assertNotNull(result); + assertEquals(3, result.size()); + assertEquals(ELEMENTS.get(0), result.get(0)); + assertEquals(ELEMENTS.get(1), result.get(1)); + assertEquals(ELEMENTS.get(2), result.get(2)); + }) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void bopGetByPositionRangeNotFound() throws ExecutionException, InterruptedException, + TimeoutException { + // given + String key = keys.get(0); + + // when + async.bopGetByPosition(key, 0, 2, BTreeOrder.ASC) + // then + .thenAccept(Assertions::assertNull) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void bopGetByPositionRangeNotFoundElement() throws ExecutionException, InterruptedException, + TimeoutException { + // given + String key = keys.get(0); + + async.bopCreate(key, ElementValueType.STRING, new CollectionAttributes()) + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.bopGetByPosition(key, 0, 2, BTreeOrder.ASC) + // then + .thenAccept(result -> { + assertNotNull(result); + assertTrue(result.isEmpty()); + }) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void bopGetByPositionRangeTypeMismatch() throws ExecutionException, InterruptedException, + TimeoutException { + // given + String key = keys.get(0); + + async.set(key, 60, "invalid-type-value") + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.bopGetByPosition(key, 0, 2, BTreeOrder.ASC) + .handle((result, ex) -> { + assertInstanceOf(OperationException.class, ex); + assertTrue(ex.getMessage().contains("TYPE_MISMATCH")); + return result; + }) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void bopPositionWithGetSuccess() throws ExecutionException, InterruptedException, + TimeoutException { + // given + String key = keys.get(0); + BKey bKey = ELEMENTS.get(1).getBkey(); + + async.bopCreate(key, ElementValueType.STRING, new CollectionAttributes()) + .thenCompose(result -> { + assertTrue(result); + return async.bopInsert(key, ELEMENTS.get(0)); + }) + .thenCompose(result -> { + assertTrue(result); + return async.bopInsert(key, ELEMENTS.get(1)); + }) + .thenCompose(result -> { + assertTrue(result); + return async.bopInsert(key, ELEMENTS.get(2)); + }) + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.bopPositionWithGet(key, bKey, 1, BTreeOrder.ASC) + // then + .thenAccept(result -> { + assertNotNull(result); + assertEquals(3, result.size()); + assertEquals(ELEMENTS.get(0).getBkey(), result.get(0).getBkey()); + assertEquals(ELEMENTS.get(0).getValue(), result.get(0).getValue()); + assertEquals(ELEMENTS.get(0).getEFlag(), result.get(0).getEFlag()); + assertEquals(0, result.get(0).getPosition()); + assertEquals(ELEMENTS.get(1).getBkey(), result.get(1).getBkey()); + assertEquals(ELEMENTS.get(1).getValue(), result.get(1).getValue()); + assertEquals(ELEMENTS.get(1).getEFlag(), result.get(1).getEFlag()); + assertEquals(1, result.get(1).getPosition()); + assertEquals(ELEMENTS.get(2).getBkey(), result.get(2).getBkey()); + assertEquals(ELEMENTS.get(2).getValue(), result.get(2).getValue()); + assertEquals(ELEMENTS.get(2).getEFlag(), result.get(2).getEFlag()); + assertEquals(2, result.get(2).getPosition()); + }) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void bopPositionWithGetNotFound() throws ExecutionException, InterruptedException, + TimeoutException { + // given + String key = keys.get(0); + BKey bKey = BKey.of(1L); + + // when + async.bopPositionWithGet(key, bKey, 1, BTreeOrder.ASC) + // then + .thenAccept(Assertions::assertNull) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void bopPositionWithGetNotFoundElement() throws ExecutionException, InterruptedException, + TimeoutException { + // given + String key = keys.get(0); + BKey bKey = BKey.of(999L); + + async.bopCreate(key, ElementValueType.STRING, new CollectionAttributes()) + .thenCompose(result -> { + assertTrue(result); + return async.bopInsert(key, ELEMENTS.get(0)); + }) + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.bopPositionWithGet(key, bKey, 1, BTreeOrder.ASC) + // then + .thenAccept(result -> { + assertEquals(0, result.size()); + }) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void bopPositionWithGetTypeMismatch() throws ExecutionException, InterruptedException, + TimeoutException { + // given + String key = keys.get(0); + BKey bKey = BKey.of(1L); + + async.set(key, 60, "invalid-type-value") + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.bopPositionWithGet(key, bKey, 1, BTreeOrder.ASC) + .handle((result, ex) -> { + assertInstanceOf(OperationException.class, ex); + assertTrue(ex.getMessage().contains("TYPE_MISMATCH")); + return result; + }) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void bopPositionWithGetBKeyMismatch() throws ExecutionException, InterruptedException, + TimeoutException { + // given + String key = keys.get(0); + BKey bKey = BKey.of(new byte[]{0x01}); + + async.bopInsert(key, ELEMENTS.get(0), new CollectionAttributes()) + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.bopPositionWithGet(key, bKey, 1, BTreeOrder.ASC) + // then + .handle((result, ex) -> { + assertInstanceOf(OperationException.class, ex); + assertTrue(ex.getMessage().contains("BKEY_MISMATCH")); + return result; + }) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + @Test void bopDeleteSuccess() throws ExecutionException, InterruptedException, TimeoutException { // given @@ -1525,7 +1926,7 @@ void bopDeleteByRangeEFlag() throws ExecutionException, InterruptedException, Ti .thenAccept(result -> { assertNotNull(result); assertEquals(1, result.getElements().size()); - assertEquals(elementWithoutFlag.getBkey(), result.getElements().get(0).getBkey()); + assertEquals(elementWithoutFlag, result.getElements().get(0)); }) .toCompletableFuture() .get(300L, TimeUnit.MILLISECONDS);