Skip to content

Commit

Permalink
Added unwatch with route and improved tests
Browse files Browse the repository at this point in the history
  • Loading branch information
GumpacG committed Jun 13, 2024
1 parent b3475bf commit 57f03b6
Show file tree
Hide file tree
Showing 6 changed files with 235 additions and 2 deletions.
17 changes: 16 additions & 1 deletion java/client/src/main/java/glide/api/RedisClusterClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@
import static redis_request.RedisRequestOuterClass.RequestType.Lolwut;
import static redis_request.RedisRequestOuterClass.RequestType.Ping;
import static redis_request.RedisRequestOuterClass.RequestType.Time;
import static redis_request.RedisRequestOuterClass.RequestType.UnWatch;

import glide.api.commands.ConnectionManagementClusterCommands;
import glide.api.commands.GenericClusterCommands;
import glide.api.commands.ScriptingAndFunctionsClusterCommands;
import glide.api.commands.ServerManagementClusterCommands;
import glide.api.commands.TransactionsBaseClusterCommands;
import glide.api.models.ClusterTransaction;
import glide.api.models.ClusterValue;
import glide.api.models.commands.FlushMode;
Expand All @@ -59,7 +61,8 @@ public class RedisClusterClient extends BaseClient
implements ConnectionManagementClusterCommands,
GenericClusterCommands,
ServerManagementClusterCommands,
ScriptingAndFunctionsClusterCommands {
ScriptingAndFunctionsClusterCommands,
TransactionsBaseClusterCommands {

protected RedisClusterClient(ConnectionManager connectionManager, CommandManager commandManager) {
super(connectionManager, commandManager);
Expand Down Expand Up @@ -575,4 +578,16 @@ public CompletableFuture<ClusterValue<Object>> fcall(
? ClusterValue.ofSingleValue(handleObjectOrNullResponse(response))
: ClusterValue.ofMultiValue(handleMapResponse(response)));
}

@Override
public CompletableFuture<ClusterValue<String>> unwatch(@NonNull Route route) {
return commandManager.submitNewCommand(
UnWatch,
new String[0],
route,
response ->
route instanceof SingleNodeRoute
? ClusterValue.of(handleStringResponse(response))
: ClusterValue.of(handleMapResponse(response)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/** Copyright GLIDE-for-Redis Project Contributors - SPDX Identifier: Apache-2.0 */
package glide.api.commands;

import glide.api.models.ClusterValue;
import glide.api.models.configuration.RequestRoutingConfiguration.Route;
import java.util.concurrent.CompletableFuture;

/**
* Supports commands for the "Transactions Commands" group for cluster clients.
*
* @see <a href="https://redis.io/commands/?group=transactions">Transactions Commands</a>
*/
public interface TransactionsBaseClusterCommands {
/**
* Flushes all the previously watched keys for a transaction. Executing a transaction will
* automatically flush all previously watched keys.
*
* @see <a href="https://redis.io/docs/latest/commands/unwatch/">redis.io</a> for details.
* @param route Specifies the routing configuration for the command. The client will route the
* command to the nodes defined by <code>route</code>.
* @return The string <code>OK</code>.
* @example
* <pre>{@code
* client.watch(new String[] {"sampleKey"});
* client.unwatch(ALL_PRIMARIES); // Flushes "sampleKey" from watched keys for all primary nodes.
* }</pre>
*/
CompletableFuture<ClusterValue<String>> unwatch(Route route);
}
22 changes: 22 additions & 0 deletions java/client/src/test/java/glide/api/RedisClusterClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import static redis_request.RedisRequestOuterClass.RequestType.Lolwut;
import static redis_request.RedisRequestOuterClass.RequestType.Ping;
import static redis_request.RedisRequestOuterClass.RequestType.Time;
import static redis_request.RedisRequestOuterClass.RequestType.UnWatch;

import glide.api.models.ClusterTransaction;
import glide.api.models.ClusterValue;
Expand Down Expand Up @@ -1450,6 +1451,27 @@ public void functionDelete_with_route_returns_success() {
assertEquals(OK, payload);
}

@SneakyThrows
@Test
public void unwatch_with_route_returns_success() {
// setup
CompletableFuture<ClusterValue<String>> testResponse = new CompletableFuture<>();
testResponse.complete(ClusterValue.ofSingleValue(OK));

// match on protobuf request
when(commandManager.<ClusterValue<String>>submitNewCommand(
eq(UnWatch), eq(new String[0]), eq(RANDOM), any()))
.thenReturn(testResponse);

// exercise
CompletableFuture<ClusterValue<String>> response = service.unwatch(RANDOM);
String payload = response.get().getSingleValue();

// verify
assertEquals(testResponse, response);
assertEquals(OK, payload);
}

@SneakyThrows
@Test
public void fcall_without_keys_and_without_args_returns_success() {
Expand Down
1 change: 0 additions & 1 deletion java/integTest/src/test/java/glide/SharedCommandTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import glide.api.RedisClient;
import glide.api.RedisClusterClient;
import glide.api.models.Script;
import glide.api.models.Transaction;
import glide.api.models.commands.ConditionalChange;
import glide.api.models.commands.ExpireOptions;
import glide.api.models.commands.ListDirection;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
import static glide.TestConfiguration.REDIS_VERSION;
import static glide.TestUtilities.assertDeepEquals;
import static glide.api.BaseClient.OK;
import static glide.api.models.configuration.RequestRoutingConfiguration.SimpleMultiNodeRoute.ALL_PRIMARIES;
import static glide.api.models.configuration.RequestRoutingConfiguration.SimpleSingleNodeRoute.RANDOM;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

Expand All @@ -20,10 +23,12 @@
import glide.api.models.configuration.RequestRoutingConfiguration.SingleNodeRoute;
import glide.api.models.configuration.RequestRoutingConfiguration.SlotIdRoute;
import glide.api.models.configuration.RequestRoutingConfiguration.SlotType;
import glide.api.models.exceptions.RequestException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import lombok.SneakyThrows;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
Expand Down Expand Up @@ -176,4 +181,84 @@ public void zrank_zrevrank_withscores() {
assertArrayEquals(new Object[] {0L, 1.0}, (Object[]) result[1]);
assertArrayEquals(new Object[] {2L, 1.0}, (Object[]) result[2]);
}

@Test
@SneakyThrows
public void watch() {
String key1 = "{key}-1" + UUID.randomUUID();
String key2 = "{key}-2" + UUID.randomUUID();
String key3 = "{key}-3" + UUID.randomUUID();
String key4 = "{key}-4" + UUID.randomUUID();
String foobarString = "foobar";
String helloString = "hello";
String[] keys = new String[] {key1, key2, key3};
ClusterTransaction setFoobarTransaction = new ClusterTransaction();
ClusterTransaction setHelloTransaction = new ClusterTransaction();
String[] expectedExecResponse = new String[] {OK, OK, OK};

// Returns null when a watched key is modified before it is executed in a transaction command.
// Transaction commands are not performed.
assertEquals(OK, clusterClient.watch(keys).get());
assertEquals(OK, clusterClient.set(key2, helloString).get());
setFoobarTransaction.set(key1, foobarString).set(key2, foobarString).set(key3, foobarString);
assertEquals(null, clusterClient.exec(setFoobarTransaction).get());
assertEquals(null, clusterClient.get(key1).get());
assertEquals(helloString, clusterClient.get(key2).get());
assertEquals(null, clusterClient.get(key3).get());

// Transaction executes command successfully with a read command on the watch key before
// transaction is executed.
assertEquals(OK, clusterClient.watch(keys).get());
assertEquals(helloString, clusterClient.get(key2).get());
assertArrayEquals(expectedExecResponse, clusterClient.exec(setFoobarTransaction).get());
assertEquals(foobarString, clusterClient.get(key1).get());
assertEquals(foobarString, clusterClient.get(key2).get());
assertEquals(foobarString, clusterClient.get(key3).get());

// Transaction executes command successfully with unmodified watched keys
assertEquals(OK, clusterClient.watch(keys).get());
assertArrayEquals(expectedExecResponse, clusterClient.exec(setFoobarTransaction).get());
assertEquals(foobarString, clusterClient.get(key1).get());
assertEquals(foobarString, clusterClient.get(key2).get());
assertEquals(foobarString, clusterClient.get(key3).get());

// Transaction executes command successfully with a modified watched key but is not in the
// transaction.
assertEquals(OK, clusterClient.watch(new String[] {key4}).get());
setHelloTransaction.set(key1, helloString).set(key2, helloString).set(key3, helloString);
assertArrayEquals(expectedExecResponse, clusterClient.exec(setHelloTransaction).get());
assertEquals(helloString, clusterClient.get(key1).get());
assertEquals(helloString, clusterClient.get(key2).get());
assertEquals(helloString, clusterClient.get(key3).get());

// WATCH can not have an empty String array parameter
ExecutionException executionException =
assertThrows(ExecutionException.class, () -> clusterClient.watch(new String[] {}).get());
assertInstanceOf(RequestException.class, executionException.getCause());
}

@Test
@SneakyThrows
public void unwatch() {
String key1 = "{key}-1" + UUID.randomUUID();
String key2 = "{key}-2" + UUID.randomUUID();
String foobarString = "foobar";
String helloString = "hello";
String[] keys = new String[] {key1, key2};
ClusterTransaction setFoobarTransaction = new ClusterTransaction();
String[] expectedExecResponse = new String[] {OK, OK};

// UNWATCH returns OK when there no watched keys
assertEquals(OK, clusterClient.unwatch().get());

// Transaction executes successfully after modifying a watched key then calling UNWATCH
assertEquals(OK, clusterClient.watch(keys).get());
assertEquals(OK, clusterClient.set(key2, helloString).get());
Map<String, String> multiPayload = clusterClient.unwatch(ALL_PRIMARIES).get().getMultiValue();
multiPayload.forEach((key, value) -> assertEquals(OK, value));
setFoobarTransaction.set(key1, foobarString).set(key2, foobarString);
assertArrayEquals(expectedExecResponse, clusterClient.exec(setFoobarTransaction).get());
assertEquals(foobarString, clusterClient.get(key1).get());
assertEquals(foobarString, clusterClient.get(key2).get());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
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.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assumptions.assumeTrue;

Expand All @@ -18,10 +20,12 @@
import glide.api.models.commands.InfoOptions;
import glide.api.models.configuration.NodeAddress;
import glide.api.models.configuration.RedisClientConfiguration;
import glide.api.models.exceptions.RequestException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import lombok.SneakyThrows;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
Expand Down Expand Up @@ -264,4 +268,83 @@ public void copy() {
Object[] result = client.exec(transaction).get();
assertArrayEquals(expectedResult, result);
}

@Test
@SneakyThrows
public void watch() {
String key1 = "{key}-1" + UUID.randomUUID();
String key2 = "{key}-2" + UUID.randomUUID();
String key3 = "{key}-3" + UUID.randomUUID();
String key4 = "{key}-4" + UUID.randomUUID();
String foobarString = "foobar";
String helloString = "hello";
String[] keys = new String[] {key1, key2, key3};
Transaction setFoobarTransaction = new Transaction();
Transaction setHelloTransaction = new Transaction();
String[] expectedExecResponse = new String[] {OK, OK, OK};

// Returns null when a watched key is modified before it is executed in a transaction command.
// Transaction commands are not performed.
assertEquals(OK, client.watch(keys).get());
assertEquals(OK, client.set(key2, helloString).get());
setFoobarTransaction.set(key1, foobarString).set(key2, foobarString).set(key3, foobarString);
assertEquals(null, client.exec(setFoobarTransaction).get());
assertEquals(null, client.get(key1).get());
assertEquals(helloString, client.get(key2).get());
assertEquals(null, client.get(key3).get());

// Transaction executes command successfully with a read command on the watch key before
// transaction is executed.
assertEquals(OK, client.watch(keys).get());
assertEquals(helloString, client.get(key2).get());
assertArrayEquals(expectedExecResponse, client.exec(setFoobarTransaction).get());
assertEquals(foobarString, client.get(key1).get());
assertEquals(foobarString, client.get(key2).get());
assertEquals(foobarString, client.get(key3).get());

// Transaction executes command successfully with unmodified watched keys
assertEquals(OK, client.watch(keys).get());
assertArrayEquals(expectedExecResponse, client.exec(setFoobarTransaction).get());
assertEquals(foobarString, client.get(key1).get());
assertEquals(foobarString, client.get(key2).get());
assertEquals(foobarString, client.get(key3).get());

// Transaction executes command successfully with a modified watched key but is not in the
// transaction.
assertEquals(OK, client.watch(new String[] {key4}).get());
setHelloTransaction.set(key1, helloString).set(key2, helloString).set(key3, helloString);
assertArrayEquals(expectedExecResponse, client.exec(setHelloTransaction).get());
assertEquals(helloString, client.get(key1).get());
assertEquals(helloString, client.get(key2).get());
assertEquals(helloString, client.get(key3).get());

// WATCH can not have an empty String array parameter
ExecutionException executionException =
assertThrows(ExecutionException.class, () -> client.watch(new String[] {}).get());
assertInstanceOf(RequestException.class, executionException.getCause());
}

@Test
@SneakyThrows
public void unwatch() {
String key1 = "{key}-1" + UUID.randomUUID();
String key2 = "{key}-2" + UUID.randomUUID();
String foobarString = "foobar";
String helloString = "hello";
String[] keys = new String[] {key1, key2};
Transaction setFoobarTransaction = new Transaction();
String[] expectedExecResponse = new String[] {OK, OK};

// UNWATCH returns OK when there no watched keys
assertEquals(OK, client.unwatch().get());

// Transaction executes successfully after modifying a watched key then calling UNWATCH
assertEquals(OK, client.watch(keys).get());
assertEquals(OK, client.set(key2, helloString).get());
assertEquals(OK, client.unwatch().get());
setFoobarTransaction.set(key1, foobarString).set(key2, foobarString);
assertArrayEquals(expectedExecResponse, client.exec(setFoobarTransaction).get());
assertEquals(foobarString, client.get(key1).get());
assertEquals(foobarString, client.get(key2).get());
}
}

0 comments on commit 57f03b6

Please sign in to comment.