Skip to content

Commit

Permalink
Resolve command interface commands to known CommandTypes where possible
Browse files Browse the repository at this point in the history
#804

Lettuce now attempts to resolve command names derived from the command interface method name or the command annotation to a known command type which is listed in CommandType. Previously, all command types were instances of StringCommandType. This rendered options set on CommandType items unusable as the object instance was not the same. In a consequence, all commands resolved to a write intent as only CommandType instances (some of them) are configured as read-only commands.

Commands are resolved case-sensitive and unknown commands are resolved to StringCommandType. Using a lowercase command name in the query annotation can be used to force a read-only command to be considered a write one and to be executed on a master node when using a master/slave setup.
  • Loading branch information
mp911de committed Jun 21, 2018
1 parent cf0e56d commit dd0260a
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 3 deletions.
22 changes: 20 additions & 2 deletions src/main/java/io/lettuce/core/dynamic/segment/CommandSegments.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.List;

import io.lettuce.core.internal.LettuceAssert;
import io.lettuce.core.protocol.CommandType;
import io.lettuce.core.protocol.ProtocolKeyword;

/**
Expand All @@ -30,7 +31,7 @@
*/
public class CommandSegments implements Iterable<CommandSegment> {

private final StringCommandType commandType;
private final ProtocolKeyword commandType;
private final List<CommandSegment> segments;

/**
Expand All @@ -44,7 +45,24 @@ public CommandSegments(List<CommandSegment> segments) {

this.segments = segments.size() > 1 ? Collections.unmodifiableList(segments.subList(1, segments.size())) : Collections
.emptyList();
this.commandType = new StringCommandType(segments.get(0).asString());
this.commandType = potentiallyResolveCommand(segments.get(0).asString());
}

/**
* Attempt to resolve the {@code commandType} against {@link CommandType}. This allows reuse of settings associated with the
* actual command type such as read-write routing. Subclasses may override this method.
*
* @param commandType must not be {@literal null}.
* @return the resolved {@link ProtocolKeyword}.
* @since 5.0.5
*/
protected ProtocolKeyword potentiallyResolveCommand(String commandType) {

try {
return CommandType.valueOf(commandType);
} catch (IllegalArgumentException e) {
return new StringCommandType(commandType);
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import io.lettuce.core.dynamic.segment.AnnotationCommandSegmentFactory;
import io.lettuce.core.dynamic.segment.CommandSegmentFactory;
import io.lettuce.core.dynamic.support.ReflectionUtils;
import io.lettuce.core.protocol.CommandType;
import io.lettuce.core.protocol.RedisCommand;

/**
Expand All @@ -51,6 +52,7 @@ public void setKeyValue() {
new StringCodec(), "key", "value");

assertThat(toString(command)).isEqualTo("SET key<key> key<value>");
assertThat(command.getType()).isSameAs(CommandType.SET);
}

@Test
Expand All @@ -69,6 +71,17 @@ public void setKeyValueWithHintedValue() {
new StringCodec(), "key", "value");

assertThat(toString(command)).isEqualTo("SET key<key> value<value>");
assertThat(command.getType()).isSameAs(CommandType.SET);
}

@Test
public void lowercaseCommandResolvesToStringCommand() {

RedisCommand<?, ?, ?> command = createCommand(methodOf(Commands.class, "set3", String.class, String.class),
new StringCodec(), "key", "value");

assertThat(toString(command)).isEqualTo("set key<key> value<value>");
assertThat(command.getType()).isNotInstanceOf(CommandType.class);
}

@Test
Expand Down Expand Up @@ -127,6 +140,15 @@ public void syncWithTimeout() {
null);
}

@Test
public void resolvesUnknownCommandToStringBackedCommandType() {

RedisCommand<?, ?, ?> command = createCommand(methodOf(Commands.class, "unknownCommand"), new StringCodec());

assertThat(toString(command)).isEqualTo("XYZ");
assertThat(command.getType()).isNotInstanceOf(CommandType.class);
}

private CommandMethod methodOf(Class<?> commandInterface, String methodName, Class... args) {
return DeclaredCommandMethod.create(ReflectionUtils.findMethod(commandInterface, methodName, args));
}
Expand All @@ -149,7 +171,12 @@ private String toString(RedisCommand<?, ?, ?> command) {
StringBuilder builder = new StringBuilder();

builder.append(command.getType().name());
builder.append(' ').append(command.getArgs().toCommandString());

String commandString = command.getArgs().toCommandString();

if (!commandString.isEmpty()) {
builder.append(' ').append(commandString);
}

return builder.toString();
}
Expand All @@ -161,6 +188,9 @@ interface Commands {
@Command("SET")
boolean set2(String key, @Value String value);

@Command("set")
boolean set3(String key, @Value String value);

boolean set(String key, String value, SetArgs setArgs);

boolean clientSetname(String connectionName);
Expand All @@ -170,6 +200,9 @@ interface Commands {

@Command("MGET ?1 ?0")
String varargsWithParamIndexes(ScanArgs scanArgs, String... keys);

@Command("XYZ")
boolean unknownCommand();
}

static interface MethodsWithTimeout {
Expand Down

0 comments on commit dd0260a

Please sign in to comment.