From 029d2d8439ddf6d0b2269686269ea6980d020e01 Mon Sep 17 00:00:00 2001 From: f1v3-dev Date: Thu, 2 Apr 2026 18:13:22 +0900 Subject: [PATCH] FEATURE: Add CompletableFuture Map APIs --- .../spy/memcached/v2/AsyncArcusCommands.java | 145 ++++ .../memcached/v2/AsyncArcusCommandsIF.java | 137 ++++ .../v2/MapAsyncArcusCommandsTest.java | 699 ++++++++++++++++++ 3 files changed, 981 insertions(+) create mode 100644 src/test/java/net/spy/memcached/v2/MapAsyncArcusCommandsTest.java diff --git a/src/main/java/net/spy/memcached/v2/AsyncArcusCommands.java b/src/main/java/net/spy/memcached/v2/AsyncArcusCommands.java index 3a5fead9a..eeb7c9bda 100644 --- a/src/main/java/net/spy/memcached/v2/AsyncArcusCommands.java +++ b/src/main/java/net/spy/memcached/v2/AsyncArcusCommands.java @@ -20,6 +20,7 @@ import java.util.AbstractMap; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -73,6 +74,12 @@ import net.spy.memcached.collection.SetExist; import net.spy.memcached.collection.SetGet; import net.spy.memcached.collection.SetInsert; +import net.spy.memcached.collection.MapCreate; +import net.spy.memcached.collection.MapDelete; +import net.spy.memcached.collection.MapGet; +import net.spy.memcached.collection.MapInsert; +import net.spy.memcached.collection.MapUpdate; +import net.spy.memcached.collection.MapUpsert; import net.spy.memcached.internal.result.GetsResultImpl; import net.spy.memcached.ops.APIType; import net.spy.memcached.ops.BTreeFindPositionOperation; @@ -1980,4 +1987,142 @@ public ArcusFuture sopDelete(String key, T value, boolean dropIfEmpty) SetDelete delete = new SetDelete<>(value, dropIfEmpty, false, tcForCollection); return collectionDelete(key, delete); } + + public ArcusFuture mopCreate(String key, ElementValueType type, + CollectionAttributes attributes) { + MapCreate create = new MapCreate(TranscoderUtils.examineFlags(type), + attributes.getExpireTime(), attributes.getMaxCount(), + attributes.getReadable(), false); + return collectionCreate(key, create); + } + + public ArcusFuture mopInsert(String key, String mKey, T value) { + return mopInsert(key, mKey, value, null); + } + + public ArcusFuture mopInsert(String key, String mKey, T value, + CollectionAttributes attributes) { + MapInsert insert = new MapInsert<>(value, null, attributes); + return collectionInsert(key, mKey, insert); + } + + public ArcusFuture mopUpsert(String key, String mKey, T value) { + return mopUpsert(key, mKey, value, null); + } + + public ArcusFuture mopUpsert(String key, String mKey, T value, + CollectionAttributes attributes) { + MapUpsert upsert = new MapUpsert<>(value, attributes); + return collectionInsert(key, mKey, upsert); + } + + public ArcusFuture mopUpdate(String key, String mKey, T value) { + MapUpdate update = new MapUpdate<>(value, false); + return collectionUpdate(key, mKey, update); + } + + public ArcusFuture> mopGet(String key, GetArgs args) { + return mopGet(key, new ArrayList<>(), args); + } + + public ArcusFuture mopGet(String key, String mKey, GetArgs args) { + AbstractArcusResult result = new AbstractArcusResult<>(new AtomicReference<>()); + ArcusFutureImpl future = new ArcusFutureImpl<>(result); + List mKeys = Collections.singletonList(mKey); + MapGet get = new MapGet(mKeys, args.isWithDelete(), args.isDropIfEmpty()); + ArcusClient client = arcusClientSupplier.get(); + + CollectionGetOperation.Callback cb = new CollectionGetOperation.Callback() { + @Override + public void gotData(String mKey, int flags, byte[] data, byte[] eFlag) { + CachedData cachedData = new CachedData(flags, data, tcForCollection.getMaxSize()); + result.set(tcForCollection.decode(cachedData)); + } + + @Override + public void receivedStatus(OperationStatus status) { + switch (status.getStatusCode()) { + case SUCCESS: + break; + case ERR_NOT_FOUND_ELEMENT: + 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().collectionGet(key, get, cb); + future.setOp(op); + client.addOp(key, op); + + return future; + } + + public ArcusFuture> mopGet(String key, List mKeys, GetArgs args) { + AbstractArcusResult> result = + new AbstractArcusResult<>(new AtomicReference<>(new HashMap<>())); + ArcusFutureImpl> future = new ArcusFutureImpl<>(result); + MapGet get = new MapGet(mKeys, args.isWithDelete(), args.isDropIfEmpty()); + ArcusClient client = arcusClientSupplier.get(); + + CollectionGetOperation.Callback cb = new CollectionGetOperation.Callback() { + @Override + public void gotData(String mKey, int flags, byte[] data, byte[] eFlag) { + CachedData cachedData = new CachedData(flags, data, tcForCollection.getMaxSize()); + result.get().put(mKey, tcForCollection.decode(cachedData)); + } + + @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().collectionGet(key, get, cb); + future.setOp(op); + client.addOp(key, op); + + return future; + } + + public ArcusFuture mopDelete(String key, boolean dropIfEmpty) { + return mopDelete(key, new ArrayList<>(), dropIfEmpty); + } + + public ArcusFuture mopDelete(String key, String mKey, boolean dropIfEmpty) { + return mopDelete(key, Collections.singletonList(mKey), dropIfEmpty); + } + + public ArcusFuture mopDelete(String key, List mKeys, boolean dropIfEmpty) { + MapDelete delete = new MapDelete(mKeys, dropIfEmpty, false); + return collectionDelete(key, delete); + } } diff --git a/src/main/java/net/spy/memcached/v2/AsyncArcusCommandsIF.java b/src/main/java/net/spy/memcached/v2/AsyncArcusCommandsIF.java index 2241827a5..ea8c78c3a 100644 --- a/src/main/java/net/spy/memcached/v2/AsyncArcusCommandsIF.java +++ b/src/main/java/net/spy/memcached/v2/AsyncArcusCommandsIF.java @@ -671,4 +671,141 @@ ArcusFuture sopCreate(String key, ElementValueType type, * {@code null} if the key is not found. */ ArcusFuture sopDelete(String key, T value, boolean dropIfEmpty); + + /** + * Create an empty map with the given attributes. + * + * @param key key of the map to create + * @param type element value type + * @param attributes initial attributes of the map + * @return {@code true} if created, {@code false} if the key already exists. + */ + ArcusFuture mopCreate(String key, ElementValueType type, + CollectionAttributes attributes); + + /** + * Insert an element with the given MKey into a map. + * + * @param key key of the map + * @param mKey MKey of the element to insert + * @param value the value to insert + * @return {@code true} if the element was inserted, {@code false} if the MKey already exists, + * {@code null} if the key is not found. + */ + ArcusFuture mopInsert(String key, String mKey, T value); + + /** + * Insert an element with the given MKey into a map. + * If the map does not exist, it is created with the given attributes. + * + * @param key key of the map + * @param mKey MKey of the element to insert + * @param value the value to insert + * @param attributes attributes to use when creating the map + * @return {@code true} if the element was inserted, {@code false} if the MKey already exists, + * {@code null} if the key is not found. + * + */ + ArcusFuture mopInsert(String key, String mKey, T value, CollectionAttributes attributes); + + /** + * Upsert an element with the given MKey in a map. + * If an element with the given MKey exists, it is replaced, otherwise a new element is inserted. + * + * @param key key of the map + * @param mKey MKey of the element to upsert + * @param value the value to insert or replace with + * @return {@code true} if the element was inserted or replaced, + * {@code null} if the key is not found. + */ + ArcusFuture mopUpsert(String key, String mKey, T value); + + /** + * Upsert an element with the given MKey in a map. + * If an element with the given MKey exists, it is replaced, otherwise a new element is inserted. + * If the map does not exist, it is created with the given attributes. + * + * @param key key of the map + * @param mKey MKey of the element to upsert + * @param value the value to insert or replace with + * @param attributes attributes to use when creating the map + * @return {@code true} if the element was inserted or replaced, + * {@code null} if the key is not found. + */ + ArcusFuture mopUpsert(String key, String mKey, T value, CollectionAttributes attributes); + + /** + * Update the value of an element with the given MKey in a map. + * + * @param key key of the map + * @param mKey MKey of the element to update + * @param value the new value + * @return {@code true} if the element was updated, {@code null} if the key is not found, + * {@code false} if the MKey is not found. + */ + ArcusFuture mopUpdate(String key, String mKey, T value); + + /** + * Get all elements from a map. + * + * @param key key of the map + * @param args arguments for get operation + * @return map of MKey to value, empty map if no elements exist, + * {@code null} if the key is not found. + */ + ArcusFuture> mopGet(String key, GetArgs args); + + /** + * Get an element with the given MKey from a map. + * + * @param key key of the map + * @param mKey MKey of the element to get + * @param args arguments for get operation + * @return the element value, {@code null} if the key or MKey is not found. + */ + ArcusFuture mopGet(String key, String mKey, GetArgs args); + + /** + * Get elements with the MKeys from a map. + * + * @param key key of the map + * @param mKeys list of MKeys to get + * @param args arguments for get operation + * @return map of MKey to value for found elements, empty map if no MKeys are found, + * {@code null} if the key is not found. + */ + ArcusFuture> mopGet(String key, List mKeys, GetArgs args); + + /** + * Delete all elements from a map. + * + * @param key key of the map + * @param dropIfEmpty whether to drop the map if it becomes empty after delection + * @return {@code true} if the elements were deleted, {@code false} if no elements exist, + * {@code null} if the key is not found. + */ + ArcusFuture mopDelete(String key, boolean dropIfEmpty); + + /** + * Delete an element with the given MKey from a map. + * + * @param key key of the map + * @param mKey MKey of the element to delete + * @param dropIfEmpty whether to drop the map if it becomes empty after delection + * @return {@code true} if the element was deleted, {@code false} if the MKey is not found, + * {@code null} if the key is not found. + */ + ArcusFuture mopDelete(String key, String mKey, boolean dropIfEmpty); + + /** + * Delete elements with the given MKeys from a map. + * + * @param key key of the map + * @param mKeys MKey of the element to delete + * @param dropIfEmpty whether to drop the map if it becomes empty after delection + * @return {@code true} if the element was deleted, {@code false} if the MKey is not found, + * {@code null} if the key is not found. + */ + ArcusFuture mopDelete(String key, List mKeys, boolean dropIfEmpty); + } diff --git a/src/test/java/net/spy/memcached/v2/MapAsyncArcusCommandsTest.java b/src/test/java/net/spy/memcached/v2/MapAsyncArcusCommandsTest.java new file mode 100644 index 000000000..721bb3c33 --- /dev/null +++ b/src/test/java/net/spy/memcached/v2/MapAsyncArcusCommandsTest.java @@ -0,0 +1,699 @@ +package net.spy.memcached.v2; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import net.spy.memcached.collection.CollectionAttributes; +import net.spy.memcached.collection.ElementValueType; +import net.spy.memcached.ops.OperationException; +import net.spy.memcached.v2.vo.GetArgs; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class MapAsyncArcusCommandsTest extends AsyncArcusCommandsTest { + + private static final String MKEY1 = "mkey1"; + private static final String MKEY2 = "mkey2"; + private static final String MKEY3 = "mkey3"; + private static final String VALUE1 = "value1"; + private static final String VALUE2 = "value2"; + private static final String VALUE3 = "value3"; + + @Test + void mopCreate() throws ExecutionException, InterruptedException, TimeoutException { + // given + String key = keys.get(0); + + // when + async.mopCreate(key, ElementValueType.STRING, new CollectionAttributes()) + // then + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopCreateAlreadyExists() throws ExecutionException, InterruptedException, TimeoutException { + // given + String key = keys.get(0); + + async.mopCreate(key, ElementValueType.STRING, new CollectionAttributes()) + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.mopCreate(key, ElementValueType.STRING, new CollectionAttributes()) + // then + .thenAccept(Assertions::assertFalse) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopInsert() throws ExecutionException, InterruptedException, TimeoutException { + // given + String key = keys.get(0); + + async.mopCreate(key, ElementValueType.STRING, new CollectionAttributes()) + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.mopInsert(key, MKEY1, VALUE1) + // then + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopInsertNotFound() throws ExecutionException, InterruptedException, TimeoutException { + // given + // when + async.mopInsert(keys.get(0), MKEY1, VALUE1) + // then + .thenAccept(Assertions::assertNull) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopInsertWithAttributes() throws ExecutionException, InterruptedException, TimeoutException { + // given + String key = keys.get(0); + + // when + async.mopInsert(key, MKEY1, VALUE1, new CollectionAttributes()) + // then + .thenCompose(result -> { + assertTrue(result); + return async.mopGet(key, MKEY1, GetArgs.DEFAULT); + }) + .thenAccept(result -> assertEquals(VALUE1, result)) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopInsertDuplicate() throws ExecutionException, InterruptedException, TimeoutException { + // given + String key = keys.get(0); + + async.mopInsert(key, MKEY1, VALUE1, new CollectionAttributes()) + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.mopInsert(key, MKEY1, VALUE2) + // then + .thenAccept(Assertions::assertFalse) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopInsertTypeMismatch() throws ExecutionException, InterruptedException, TimeoutException { + // given + String key = keys.get(0); + + async.set(key, 0, VALUE) + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.mopInsert(key, MKEY1, VALUE1, new CollectionAttributes()) + // then + .handle((result, ex) -> { + assertInstanceOf(OperationException.class, ex); + assertTrue(ex.getMessage().contains("TYPE_MISMATCH")); + return result; + }) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopUpsertInsert() throws ExecutionException, InterruptedException, TimeoutException { + // given + String key = keys.get(0); + + async.mopCreate(key, ElementValueType.STRING, new CollectionAttributes()) + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.mopUpsert(key, MKEY1, VALUE1) + // then + .thenCompose(result -> { + assertTrue(result); + return async.mopGet(key, MKEY1, GetArgs.DEFAULT); + }) + .thenAccept(result -> assertEquals(VALUE1, result)) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopUpsertReplace() throws ExecutionException, InterruptedException, TimeoutException { + // given + String key = keys.get(0); + + async.mopInsert(key, MKEY1, VALUE1, new CollectionAttributes()) + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.mopUpsert(key, MKEY1, VALUE2) + // then + .thenCompose(result -> { + assertTrue(result); + return async.mopGet(key, MKEY1, GetArgs.DEFAULT); + }) + .thenAccept(result -> assertEquals(VALUE2, result)) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopUpsertNotFound() throws ExecutionException, InterruptedException, TimeoutException { + // when + async.mopUpsert(keys.get(0), MKEY1, VALUE1) + // then + .thenAccept(Assertions::assertNull) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopUpdate() throws ExecutionException, InterruptedException, TimeoutException { + // given + String key = keys.get(0); + + async.mopInsert(key, MKEY1, VALUE1, new CollectionAttributes()) + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.mopUpdate(key, MKEY1, VALUE2) + // then + .thenCompose(result -> { + assertTrue(result); + return async.mopGet(key, MKEY1, GetArgs.DEFAULT); + }) + .thenAccept(result -> assertEquals(VALUE2, result)) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopUpdateNotFoundElement() throws ExecutionException, InterruptedException, + TimeoutException { + // given + String key = keys.get(0); + + async.mopCreate(key, ElementValueType.STRING, new CollectionAttributes()) + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.mopUpdate(key, MKEY1, VALUE1) + // then + .thenAccept(Assertions::assertFalse) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopUpdateNotFound() throws ExecutionException, InterruptedException, TimeoutException { + // given + // when + async.mopUpdate(keys.get(0), MKEY1, VALUE1) + // then + .thenAccept(Assertions::assertNull) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopGetAll() throws ExecutionException, InterruptedException, TimeoutException { + // given + String key = keys.get(0); + + async.mopInsert(key, MKEY1, VALUE1, new CollectionAttributes()) + .thenCompose(result -> async.mopInsert(key, MKEY2, VALUE2)) + .thenCompose(result -> async.mopInsert(key, MKEY3, VALUE3)) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.mopGet(key, GetArgs.DEFAULT) + // then + .thenAccept(result -> { + assertNotNull(result); + assertEquals(3, result.size()); + assertEquals(VALUE1, result.get(MKEY1)); + assertEquals(VALUE2, result.get(MKEY2)); + assertEquals(VALUE3, result.get(MKEY3)); + }) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopGetAllNotFound() throws ExecutionException, InterruptedException, TimeoutException { + // given + // when + async.mopGet(keys.get(0), GetArgs.DEFAULT) + // then + .thenAccept(Assertions::assertNull) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopGetAllNotFoundElement() throws ExecutionException, InterruptedException, + TimeoutException { + // given + String key = keys.get(0); + + async.mopCreate(key, ElementValueType.STRING, new CollectionAttributes()) + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.mopGet(key, GetArgs.DEFAULT) + // then + .thenAccept(result -> { + assertNotNull(result); + assertTrue(result.isEmpty()); + }) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopGetSingle() throws ExecutionException, InterruptedException, TimeoutException { + // given + String key = keys.get(0); + + async.mopInsert(key, MKEY1, VALUE1, new CollectionAttributes()) + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.mopGet(key, MKEY1, GetArgs.DEFAULT) + // then + .thenAccept(result -> assertEquals(VALUE1, result)) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopGetSingleNotFound() throws ExecutionException, InterruptedException, TimeoutException { + // given + // when + async.mopGet(keys.get(0), MKEY1, GetArgs.DEFAULT) + // then + .thenAccept(Assertions::assertNull) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopGetSingleNotFoundElement() throws ExecutionException, InterruptedException, + TimeoutException { + // given + String key = keys.get(0); + + async.mopCreate(key, ElementValueType.STRING, new CollectionAttributes()) + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.mopGet(key, MKEY1, GetArgs.DEFAULT) + // then + .thenAccept(Assertions::assertNull) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopGetMultipleKeys() throws ExecutionException, InterruptedException, TimeoutException { + // given + String key = keys.get(0); + List mKeys = Arrays.asList(MKEY1, MKEY2); + + async.mopInsert(key, MKEY1, VALUE1, new CollectionAttributes()) + .thenCompose(result -> async.mopInsert(key, MKEY2, VALUE2)) + .thenCompose(result -> async.mopInsert(key, MKEY3, VALUE3)) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.mopGet(key, mKeys, GetArgs.DEFAULT) + // then + .thenAccept(result -> { + assertNotNull(result); + assertEquals(2, result.size()); + assertEquals(VALUE1, result.get(MKEY1)); + assertEquals(VALUE2, result.get(MKEY2)); + }) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopGetMultipleKeysNotFoundElement() throws ExecutionException, InterruptedException, + TimeoutException { + // given + String key = keys.get(0); + + async.mopCreate(key, ElementValueType.STRING, new CollectionAttributes()) + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.mopGet(key, Arrays.asList(MKEY1, MKEY2), GetArgs.DEFAULT) + // then + .thenAccept(result -> { + assertNotNull(result); + assertTrue(result.isEmpty()); + }) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopGetMultipleKeysPartialFound() throws ExecutionException, InterruptedException, + TimeoutException { + // given + String key = keys.get(0); + + async.mopInsert(key, MKEY1, VALUE1, new CollectionAttributes()) + .thenCompose(result -> async.mopInsert(key, MKEY2, VALUE2)) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.mopGet(key, Arrays.asList(MKEY1, MKEY2, MKEY3), GetArgs.DEFAULT) + // then + .thenAccept(result -> { + assertNotNull(result); + assertEquals(2, result.size()); + assertEquals(VALUE1, result.get(MKEY1)); + assertEquals(VALUE2, result.get(MKEY2)); + }) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopGetAllWithDelete() throws ExecutionException, InterruptedException, TimeoutException { + // given + String key = keys.get(0); + GetArgs args = new GetArgs.Builder() + .withDelete() + .dropIfEmpty() + .build(); + + async.mopInsert(key, MKEY1, VALUE1, new CollectionAttributes()) + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.mopGet(key, args) + .thenCompose(result -> { + assertNotNull(result); + assertEquals(1, result.size()); + return async.mopGet(key, GetArgs.DEFAULT); + }) + // then + .thenAccept(Assertions::assertNull) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopDelete() throws ExecutionException, InterruptedException, TimeoutException { + // given + String key = keys.get(0); + + async.mopInsert(key, MKEY1, VALUE1, new CollectionAttributes()) + .thenCompose(result -> async.mopInsert(key, MKEY2, VALUE2)) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.mopDelete(key, false) + // then + .thenCompose(result -> { + assertTrue(result); + return async.mopGet(key, GetArgs.DEFAULT); + }) + .thenAccept(result -> { + assertNotNull(result); + assertTrue(result.isEmpty()); + }) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopDeleteDropIfEmpty() throws ExecutionException, InterruptedException, TimeoutException { + // given + String key = keys.get(0); + + async.mopInsert(key, MKEY1, VALUE1, new CollectionAttributes()) + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.mopDelete(key, true) + .thenCompose(result -> { + assertTrue(result); + return async.mopGet(key, GetArgs.DEFAULT); + }) + // then + .thenAccept(Assertions::assertNull) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopDeleteNotFound() throws ExecutionException, InterruptedException, TimeoutException { + // when + async.mopDelete(keys.get(0), false) + // then + .thenAccept(Assertions::assertNull) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopDeleteSingle() throws ExecutionException, InterruptedException, TimeoutException { + // given + String key = keys.get(0); + + async.mopInsert(key, MKEY1, VALUE1, new CollectionAttributes()) + .thenCompose(result -> async.mopInsert(key, MKEY2, VALUE2)) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.mopDelete(key, MKEY1, false) + // then + .thenCompose(result -> { + assertTrue(result); + return async.mopGet(key, GetArgs.DEFAULT); + }) + .thenAccept(result -> { + assertNotNull(result); + assertEquals(1, result.size()); + assertFalse(result.containsKey(MKEY1)); + assertTrue(result.containsKey(MKEY2)); + }) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopDeleteSingleNotFoundElement() throws ExecutionException, InterruptedException, + TimeoutException { + // given + String key = keys.get(0); + + async.mopCreate(key, ElementValueType.STRING, new CollectionAttributes()) + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.mopDelete(key, MKEY1, false) + // then + .thenAccept(Assertions::assertFalse) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopDeleteSingleNotFound() throws ExecutionException, InterruptedException, TimeoutException { + // when + async.mopDelete(keys.get(0), MKEY1, false) + // then + .thenAccept(Assertions::assertNull) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopDeleteSingleDropIfEmpty() throws ExecutionException, InterruptedException, + TimeoutException { + // given + String key = keys.get(0); + + async.mopInsert(key, MKEY1, VALUE1, new CollectionAttributes()) + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.mopDelete(key, MKEY1, true) + .thenCompose(result -> { + assertTrue(result); + return async.mopGet(key, GetArgs.DEFAULT); + }) + // then + .thenAccept(Assertions::assertNull) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopDeleteMultiple() throws ExecutionException, InterruptedException, TimeoutException { + // given + String key = keys.get(0); + + async.mopInsert(key, MKEY1, VALUE1, new CollectionAttributes()) + .thenCompose(result -> { + assertTrue(result); + return async.mopInsert(key, MKEY2, VALUE2); + }) + .thenCompose(result -> { + assertTrue(result); + return async.mopInsert(key, MKEY3, VALUE3); + }) + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.mopDelete(key, Arrays.asList(MKEY1, MKEY2), false) + // then + .thenCompose(result -> { + assertTrue(result); + return async.mopGet(key, GetArgs.DEFAULT); + }) + .thenAccept(result -> { + assertNotNull(result); + assertEquals(1, result.size()); + assertFalse(result.containsKey(MKEY1)); + assertFalse(result.containsKey(MKEY2)); + assertTrue(result.containsKey(MKEY3)); + }) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopDeleteMultipleNotFoundElement() throws ExecutionException, InterruptedException, + TimeoutException { + // given + String key = keys.get(0); + + async.mopCreate(key, ElementValueType.STRING, new CollectionAttributes()) + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when - MKEY2, MKEY3 don't exist + async.mopDelete(key, Arrays.asList(MKEY2, MKEY3), false) + // then + .thenAccept(Assertions::assertFalse) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopDeleteMultipleDropIfEmpty() throws ExecutionException, InterruptedException, + TimeoutException { + // given + String key = keys.get(0); + + async.mopInsert(key, MKEY1, VALUE1, new CollectionAttributes()) + .thenCompose(result -> { + assertTrue(result); + return async.mopInsert(key, MKEY2, VALUE2); + }) + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.mopDelete(key, Arrays.asList(MKEY1, MKEY2), true) + // then + .thenCompose(result -> { + assertTrue(result); + return async.mopGet(key, GetArgs.DEFAULT); + }) + .thenAccept(Assertions::assertNull) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + + @Test + void mopDeleteTypeMismatch() throws ExecutionException, InterruptedException, TimeoutException { + // given + String key = keys.get(0); + + async.set(key, 0, VALUE) + .thenAccept(Assertions::assertTrue) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + + // when + async.mopDelete(key, false) + // then + .handle((result, ex) -> { + assertInstanceOf(OperationException.class, ex); + assertTrue(ex.getMessage().contains("TYPE_MISMATCH")); + return result; + }) + .toCompletableFuture() + .get(300L, TimeUnit.MILLISECONDS); + } + +}