geode-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From u..@apache.org
Subject [10/21] geode git commit: GEODE-2449: Moved Redis out of core with minimal Extension work added
Date Fri, 10 Feb 2017 21:34:13 GMT
http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/DoubleWrapper.java
----------------------------------------------------------------------
diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/DoubleWrapper.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/DoubleWrapper.java
new file mode 100644
index 0000000..460a330
--- /dev/null
+++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/DoubleWrapper.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.redis.internal;
+
+import org.apache.geode.DataSerializable;
+import org.apache.geode.DataSerializer;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * This is a wrapper class for doubles, similar to {@link ByteArrayWrapper}
+ * 
+ *
+ */
+public class DoubleWrapper implements DataSerializable, Comparable<Object> {
+
+  private static final long serialVersionUID = 6946858357297398633L;
+
+  public Double score;
+  private String toString;
+
+  public DoubleWrapper() {}
+
+  public DoubleWrapper(Double dubs) {
+    this.score = dubs;
+  }
+
+  @Override
+  public void toData(DataOutput out) throws IOException {
+    DataSerializer.writeDouble(score, out);
+  }
+
+  @Override
+  public void fromData(DataInput in) throws IOException, ClassNotFoundException {
+    this.score = DataSerializer.readDouble(in);
+  }
+
+  @Override
+  public int compareTo(Object arg0) {
+    Double other;
+    if (arg0 instanceof DoubleWrapper) {
+      other = ((DoubleWrapper) arg0).score;
+    } else if (arg0 instanceof Double) {
+      other = (Double) arg0;
+    } else
+      return 0;
+    Double diff = this.score - other;
+    if (diff > 0)
+      return 1;
+    else if (diff < 0)
+      return -1;
+    else
+      return 0;
+  }
+
+  public String toString() {
+    if (this.toString == null)
+      this.toString = Coder.doubleToString(score);
+    return this.toString;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/ExecutionHandlerContext.java
----------------------------------------------------------------------
diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/ExecutionHandlerContext.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/ExecutionHandlerContext.java
new file mode 100644
index 0000000..8202b10
--- /dev/null
+++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/ExecutionHandlerContext.java
@@ -0,0 +1,392 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.redis.internal;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import io.netty.handler.codec.DecoderException;
+import io.netty.util.concurrent.EventExecutor;
+import org.apache.geode.LogWriter;
+import org.apache.geode.cache.Cache;
+import org.apache.geode.cache.CacheClosedException;
+import org.apache.geode.cache.CacheTransactionManager;
+import org.apache.geode.cache.RegionDestroyedException;
+import org.apache.geode.cache.TransactionException;
+import org.apache.geode.cache.TransactionId;
+import org.apache.geode.cache.UnsupportedOperationInTransactionException;
+import org.apache.geode.cache.query.QueryInvocationTargetException;
+import org.apache.geode.cache.query.RegionNotFoundException;
+import org.apache.geode.redis.GeodeRedisServiceImpl;
+import org.apache.geode.redis.internal.executor.transactions.TransactionExecutor;
+
+import java.io.IOException;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * This class extends {@link ChannelInboundHandlerAdapter} from Netty and it is the last part of the
+ * channel pipeline. The {@link ByteToCommandDecoder} forwards a {@link Command} to this class which
+ * executes it and sends the result back to the client. Additionally, all exception handling is done
+ * by this class.
+ * <p>
+ * Besides being part of Netty's pipeline, this class also serves as a context to the execution of a
+ * command. It abstracts transactions, provides access to the {@link RegionProvider} and anything
+ * else an executing {@link Command} may need.
+ * 
+ *
+ */
+public class ExecutionHandlerContext extends ChannelInboundHandlerAdapter {
+
+  private static final int WAIT_REGION_DSTRYD_MILLIS = 100;
+  private static final int MAXIMUM_NUM_RETRIES = (1000 * 60) / WAIT_REGION_DSTRYD_MILLIS; // 60
+                                                                                          // seconds
+                                                                                          // total
+
+  private final Cache cache;
+  private final GeodeRedisServiceImpl server;
+  private final LogWriter logger;
+  private final Channel channel;
+  private final AtomicBoolean needChannelFlush;
+  private final Runnable flusher;
+  private final EventExecutor lastExecutor;
+  private final ByteBufAllocator byteBufAllocator;
+  /**
+   * TransactionId for any transactions started by this client
+   */
+  private TransactionId transactionID;
+
+  /**
+   * Queue of commands for a given transaction
+   */
+  private Queue<Command> transactionQueue;
+  private final RegionProvider regionProvider;
+  private final byte[] authPwd;
+
+  private boolean isAuthenticated;
+
+  /**
+   * Default constructor for execution contexts.
+   * 
+   * @param ch Channel used by this context, should be one to one
+   * @param cache The Geode cache instance of this vm
+   * @param regionProvider The region provider of this context
+   * @param server Instance of the server it is attached to, only used so that any execution can
+   *        initiate a shutdwon
+   * @param pwd Authentication password for each context, can be null
+   */
+  public ExecutionHandlerContext(Channel ch, Cache cache, RegionProvider regionProvider,
+      GeodeRedisServiceImpl server, byte[] pwd) {
+    if (ch == null || cache == null || regionProvider == null || server == null)
+      throw new IllegalArgumentException("Only the authentication password may be null");
+    this.cache = cache;
+    this.server = server;
+    this.logger = cache.getLogger();
+    this.channel = ch;
+    this.needChannelFlush = new AtomicBoolean(false);
+    this.flusher = new Runnable() {
+
+      @Override
+      public void run() {
+        flushChannel();
+      }
+
+    };
+    this.lastExecutor = channel.pipeline().lastContext().executor();
+    this.byteBufAllocator = channel.alloc();
+    this.transactionID = null;
+    this.transactionQueue = null; // Lazy
+    this.regionProvider = regionProvider;
+    this.authPwd = pwd;
+    this.isAuthenticated = pwd != null ? false : true;
+  }
+
+  private void flushChannel() {
+    while (needChannelFlush.getAndSet(false)) {
+      channel.flush();
+    }
+  }
+
+  private void writeToChannel(ByteBuf message) {
+    channel.write(message, channel.voidPromise());
+    if (!needChannelFlush.getAndSet(true)) {
+      this.lastExecutor.execute(flusher);
+    }
+  }
+
+  /**
+   * This will handle the execution of received commands
+   */
+  @Override
+  public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
+    Command command = (Command) msg;
+    executeCommand(ctx, command);
+  }
+
+  /**
+   * Exception handler for the entire pipeline
+   */
+  @Override
+  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
+    if (cause instanceof IOException) {
+      channelInactive(ctx);
+      return;
+    }
+    ByteBuf response = getExceptionResponse(ctx, cause);
+    writeToChannel(response);
+  }
+
+  private ByteBuf getExceptionResponse(ChannelHandlerContext ctx, Throwable cause) {
+    ByteBuf response;
+    if (cause instanceof RedisDataTypeMismatchException)
+      response = Coder.getWrongTypeResponse(this.byteBufAllocator, cause.getMessage());
+    else if (cause instanceof DecoderException
+        && cause.getCause() instanceof RedisCommandParserException)
+      response =
+          Coder.getErrorResponse(this.byteBufAllocator, RedisConstants.PARSING_EXCEPTION_MESSAGE);
+    else if (cause instanceof RegionCreationException) {
+      this.logger.error(cause);
+      response =
+          Coder.getErrorResponse(this.byteBufAllocator, RedisConstants.ERROR_REGION_CREATION);
+    } else if (cause instanceof InterruptedException || cause instanceof CacheClosedException)
+      response =
+          Coder.getErrorResponse(this.byteBufAllocator, RedisConstants.SERVER_ERROR_SHUTDOWN);
+    else if (cause instanceof IllegalStateException) {
+      response = Coder.getErrorResponse(this.byteBufAllocator, cause.getMessage());
+    } else {
+      if (this.logger.errorEnabled())
+        this.logger.error("GeodeRedisServiceImpl-Unexpected error handler for " + ctx.channel(),
+            cause);
+      response = Coder.getErrorResponse(this.byteBufAllocator, RedisConstants.SERVER_ERROR_MESSAGE);
+    }
+    return response;
+  }
+
+  @Override
+  public void channelInactive(ChannelHandlerContext ctx) {
+    if (logger.fineEnabled())
+      logger.fine("GeodeRedisServiceImpl-Connection closing with " + ctx.channel().remoteAddress());
+    ctx.channel().close();
+    ctx.close();
+  }
+
+  private void executeCommand(ChannelHandlerContext ctx, Command command) throws Exception {
+    RedisCommandType type = command.getCommandType();
+    Executor exec = type.getExecutor();
+    if (isAuthenticated) {
+      if (type == RedisCommandType.SHUTDOWN) {
+        this.server.stop();
+        return;
+      }
+      if (hasTransaction() && !(exec instanceof TransactionExecutor))
+        executeWithTransaction(ctx, exec, command);
+      else
+        executeWithoutTransaction(exec, command);
+
+      if (hasTransaction() && command.getCommandType() != RedisCommandType.MULTI) {
+        writeToChannel(
+            Coder.getSimpleStringResponse(this.byteBufAllocator, RedisConstants.COMMAND_QUEUED));
+      } else {
+        ByteBuf response = command.getResponse();
+        writeToChannel(response);
+      }
+    } else if (type == RedisCommandType.QUIT) {
+      exec.executeCommand(command, this);
+      ByteBuf response = command.getResponse();
+      writeToChannel(response);
+      channelInactive(ctx);
+    } else if (type == RedisCommandType.AUTH) {
+      exec.executeCommand(command, this);
+      ByteBuf response = command.getResponse();
+      writeToChannel(response);
+    } else {
+      ByteBuf r = Coder.getNoAuthResponse(this.byteBufAllocator, RedisConstants.ERROR_NOT_AUTH);
+      writeToChannel(r);
+    }
+  }
+
+  /**
+   * Private helper method to execute a command without a transaction, done for special exception
+   * handling neatness
+   * 
+   * @param exec Executor to use
+   * @param command Command to execute
+   * @throws Exception Throws exception if exception is from within execution and not to be handled
+   */
+  private void executeWithoutTransaction(final Executor exec, Command command) throws Exception {
+    Exception cause = null;
+    for (int i = 0; i < MAXIMUM_NUM_RETRIES; i++) {
+      try {
+        exec.executeCommand(command, this);
+        return;
+      } catch (Exception e) {
+        cause = e;
+        if (e instanceof RegionDestroyedException || e instanceof RegionNotFoundException
+            || e.getCause() instanceof QueryInvocationTargetException)
+          Thread.sleep(WAIT_REGION_DSTRYD_MILLIS);
+      }
+    }
+    throw cause;
+  }
+
+  private void executeWithTransaction(ChannelHandlerContext ctx, final Executor exec,
+      Command command) throws Exception {
+    CacheTransactionManager txm = cache.getCacheTransactionManager();
+    TransactionId transactionId = getTransactionID();
+    txm.resume(transactionId);
+    try {
+      exec.executeCommand(command, this);
+    } catch (UnsupportedOperationInTransactionException e) {
+      command.setResponse(Coder.getErrorResponse(this.byteBufAllocator,
+          RedisConstants.ERROR_UNSUPPORTED_OPERATION_IN_TRANSACTION));
+    } catch (TransactionException e) {
+      command.setResponse(Coder.getErrorResponse(this.byteBufAllocator,
+          RedisConstants.ERROR_TRANSACTION_EXCEPTION));
+    } catch (Exception e) {
+      ByteBuf response = getExceptionResponse(ctx, e);
+      command.setResponse(response);
+    }
+    getTransactionQueue().add(command);
+    transactionId = txm.suspend();
+    setTransactionID(transactionId);
+  }
+
+  /**
+   * Get the current transacationId
+   * 
+   * @return The current transactionId, null if one doesn't exist
+   */
+  public TransactionId getTransactionID() {
+    return this.transactionID;
+  }
+
+  /**
+   * Check if client has transaction
+   * 
+   * @return True if client has transaction, false otherwise
+   */
+  public boolean hasTransaction() {
+    return transactionID != null;
+  }
+
+  /**
+   * Setter method for transaction
+   * 
+   * @param id TransactionId of current transaction for client
+   */
+  public void setTransactionID(TransactionId id) {
+    this.transactionID = id;
+  }
+
+  /**
+   * Reset the transaction of client
+   */
+  public void clearTransaction() {
+    this.transactionID = null;
+    if (this.transactionQueue != null) {
+      for (Command c : this.transactionQueue) {
+        ByteBuf r = c.getResponse();
+        if (r != null)
+          r.release();
+      }
+      this.transactionQueue.clear();
+    }
+  }
+
+  /**
+   * Getter for transaction command queue
+   * 
+   * @return Command queue
+   */
+  public Queue<Command> getTransactionQueue() {
+    if (this.transactionQueue == null)
+      this.transactionQueue = new ConcurrentLinkedQueue<Command>();
+    return this.transactionQueue;
+  }
+
+  /**
+   * {@link ByteBuf} allocator for this context. All executors must use this pooled allocator as
+   * opposed to having unpooled buffers for maximum performance
+   * 
+   * @return allocator instance
+   */
+  public ByteBufAllocator getByteBufAllocator() {
+    return this.byteBufAllocator;
+  }
+
+  /**
+   * Gets the provider of Regions
+   * 
+   * @return Provider
+   */
+  public RegionProvider getRegionProvider() {
+    return this.regionProvider;
+  }
+
+  /**
+   * Getter for manager to allow pausing and resuming transactions
+   * 
+   * @return Instance
+   */
+  public CacheTransactionManager getCacheTransactionManager() {
+    return this.cache.getCacheTransactionManager();
+  }
+
+  /**
+   * Getter for logger
+   * 
+   * @return instance
+   */
+  public LogWriter getLogger() {
+    return this.cache.getLogger();
+  }
+
+  /**
+   * Get the channel for this context
+   * 
+   * @return instance
+   *
+   *         public Channel getChannel() { return this.channel; }
+   */
+
+  /**
+   * Get the authentication password, this will be same server wide. It is exposed here as opposed
+   * to {@link GeodeRedisServiceImpl}.
+   * 
+   * @return password
+   */
+  public byte[] getAuthPwd() {
+    return this.authPwd;
+  }
+
+  /**
+   * Checker if user has authenticated themselves
+   * 
+   * @return True if no authentication required or authentication complete, false otherwise
+   */
+  public boolean isAuthenticated() {
+    return this.isAuthenticated;
+  }
+
+  /**
+   * Lets this context know the authentication is complete
+   */
+  public void setAuthenticationVerified() {
+    this.isAuthenticated = true;
+  }
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/Executor.java
----------------------------------------------------------------------
diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/Executor.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/Executor.java
new file mode 100644
index 0000000..7698535
--- /dev/null
+++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/Executor.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.redis.internal;
+
+
+/**
+ * Interface for executors of a {@link Command}
+ * 
+ *
+ */
+public interface Executor {
+
+  /**
+   * This method executes the command and sets the response. Any runtime errors from this execution
+   * should be handled by caller to ensure the client gets a response
+   * 
+   * @param command The command to be executed
+   * @param context The execution context by which this command is to be executed
+   */
+  public void executeCommand(Command command, ExecutionHandlerContext context);
+
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/Extendable.java
----------------------------------------------------------------------
diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/Extendable.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/Extendable.java
new file mode 100644
index 0000000..ed36030
--- /dev/null
+++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/Extendable.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.redis.internal;
+
+/**
+ * This defines a command that can be extended, and there may need some level of abstraction
+ * 
+ *
+ */
+public interface Extendable {
+
+  /**
+   * Getter for error message in case of argument arity mismatch
+   * 
+   * @return Error string
+   */
+  public String getArgsError();
+
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/RedisCommandParserException.java
----------------------------------------------------------------------
diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/RedisCommandParserException.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/RedisCommandParserException.java
new file mode 100644
index 0000000..4ec154a
--- /dev/null
+++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/RedisCommandParserException.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.redis.internal;
+
+/**
+ * Exception thrown by CommandParser (non-existent class) when a command has illegal syntax
+ * 
+ *
+ */
+public class RedisCommandParserException extends Exception {
+
+  private static final long serialVersionUID = 4707944288714910949L;
+
+  public RedisCommandParserException() {
+    super();
+  }
+
+  public RedisCommandParserException(String message) {
+    super(message);
+  }
+
+  public RedisCommandParserException(Throwable cause) {
+    super(cause);
+  }
+
+  public RedisCommandParserException(String message, Throwable cause) {
+    super(message, cause);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/geode/blob/c6dbc6d4/geode-redis/src/main/java/org/apache/geode/redis/internal/RedisCommandType.java
----------------------------------------------------------------------
diff --git a/geode-redis/src/main/java/org/apache/geode/redis/internal/RedisCommandType.java b/geode-redis/src/main/java/org/apache/geode/redis/internal/RedisCommandType.java
new file mode 100644
index 0000000..acacd12
--- /dev/null
+++ b/geode-redis/src/main/java/org/apache/geode/redis/internal/RedisCommandType.java
@@ -0,0 +1,2896 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package org.apache.geode.redis.internal;
+
+import org.apache.geode.redis.internal.executor.AuthExecutor;
+import org.apache.geode.redis.internal.executor.DBSizeExecutor;
+import org.apache.geode.redis.internal.executor.DelExecutor;
+import org.apache.geode.redis.internal.executor.EchoExecutor;
+import org.apache.geode.redis.internal.executor.ExistsExecutor;
+import org.apache.geode.redis.internal.executor.ExpireAtExecutor;
+import org.apache.geode.redis.internal.executor.ExpireExecutor;
+import org.apache.geode.redis.internal.executor.FlushAllExecutor;
+import org.apache.geode.redis.internal.executor.KeysExecutor;
+import org.apache.geode.redis.internal.executor.PExpireAtExecutor;
+import org.apache.geode.redis.internal.executor.PExpireExecutor;
+import org.apache.geode.redis.internal.executor.PTTLExecutor;
+import org.apache.geode.redis.internal.executor.PersistExecutor;
+import org.apache.geode.redis.internal.executor.PingExecutor;
+import org.apache.geode.redis.internal.executor.QuitExecutor;
+import org.apache.geode.redis.internal.executor.ScanExecutor;
+import org.apache.geode.redis.internal.executor.ShutDownExecutor;
+import org.apache.geode.redis.internal.executor.TTLExecutor;
+import org.apache.geode.redis.internal.executor.TimeExecutor;
+import org.apache.geode.redis.internal.executor.TypeExecutor;
+import org.apache.geode.redis.internal.executor.UnkownExecutor;
+import org.apache.geode.redis.internal.executor.hash.HDelExecutor;
+import org.apache.geode.redis.internal.executor.hash.HExistsExecutor;
+import org.apache.geode.redis.internal.executor.hash.HGetAllExecutor;
+import org.apache.geode.redis.internal.executor.hash.HGetExecutor;
+import org.apache.geode.redis.internal.executor.hash.HIncrByExecutor;
+import org.apache.geode.redis.internal.executor.hash.HIncrByFloatExecutor;
+import org.apache.geode.redis.internal.executor.hash.HKeysExecutor;
+import org.apache.geode.redis.internal.executor.hash.HLenExecutor;
+import org.apache.geode.redis.internal.executor.hash.HMGetExecutor;
+import org.apache.geode.redis.internal.executor.hash.HMSetExecutor;
+import org.apache.geode.redis.internal.executor.hash.HScanExecutor;
+import org.apache.geode.redis.internal.executor.hash.HSetExecutor;
+import org.apache.geode.redis.internal.executor.hash.HSetNXExecutor;
+import org.apache.geode.redis.internal.executor.hash.HValsExecutor;
+import org.apache.geode.redis.internal.executor.hll.PFAddExecutor;
+import org.apache.geode.redis.internal.executor.hll.PFCountExecutor;
+import org.apache.geode.redis.internal.executor.hll.PFMergeExecutor;
+import org.apache.geode.redis.internal.executor.list.LIndexExecutor;
+import org.apache.geode.redis.internal.executor.list.LInsertExecutor;
+import org.apache.geode.redis.internal.executor.list.LLenExecutor;
+import org.apache.geode.redis.internal.executor.list.LPopExecutor;
+import org.apache.geode.redis.internal.executor.list.LPushExecutor;
+import org.apache.geode.redis.internal.executor.list.LPushXExecutor;
+import org.apache.geode.redis.internal.executor.list.LRangeExecutor;
+import org.apache.geode.redis.internal.executor.list.LRemExecutor;
+import org.apache.geode.redis.internal.executor.list.LSetExecutor;
+import org.apache.geode.redis.internal.executor.list.LTrimExecutor;
+import org.apache.geode.redis.internal.executor.list.RPopExecutor;
+import org.apache.geode.redis.internal.executor.list.RPushExecutor;
+import org.apache.geode.redis.internal.executor.list.RPushXExecutor;
+import org.apache.geode.redis.internal.executor.set.SAddExecutor;
+import org.apache.geode.redis.internal.executor.set.SCardExecutor;
+import org.apache.geode.redis.internal.executor.set.SDiffExecutor;
+import org.apache.geode.redis.internal.executor.set.SDiffStoreExecutor;
+import org.apache.geode.redis.internal.executor.set.SInterExecutor;
+import org.apache.geode.redis.internal.executor.set.SInterStoreExecutor;
+import org.apache.geode.redis.internal.executor.set.SIsMemberExecutor;
+import org.apache.geode.redis.internal.executor.set.SMembersExecutor;
+import org.apache.geode.redis.internal.executor.set.SMoveExecutor;
+import org.apache.geode.redis.internal.executor.set.SPopExecutor;
+import org.apache.geode.redis.internal.executor.set.SRandMemberExecutor;
+import org.apache.geode.redis.internal.executor.set.SRemExecutor;
+import org.apache.geode.redis.internal.executor.set.SScanExecutor;
+import org.apache.geode.redis.internal.executor.set.SUnionExecutor;
+import org.apache.geode.redis.internal.executor.set.SUnionStoreExecutor;
+import org.apache.geode.redis.internal.executor.sortedset.ZAddExecutor;
+import org.apache.geode.redis.internal.executor.sortedset.ZCardExecutor;
+import org.apache.geode.redis.internal.executor.sortedset.ZCountExecutor;
+import org.apache.geode.redis.internal.executor.sortedset.ZIncrByExecutor;
+import org.apache.geode.redis.internal.executor.sortedset.ZLexCountExecutor;
+import org.apache.geode.redis.internal.executor.sortedset.ZRangeByLexExecutor;
+import org.apache.geode.redis.internal.executor.sortedset.ZRangeByScoreExecutor;
+import org.apache.geode.redis.internal.executor.sortedset.ZRangeExecutor;
+import org.apache.geode.redis.internal.executor.sortedset.ZRankExecutor;
+import org.apache.geode.redis.internal.executor.sortedset.ZRemExecutor;
+import org.apache.geode.redis.internal.executor.sortedset.ZRemRangeByLexExecutor;
+import org.apache.geode.redis.internal.executor.sortedset.ZRemRangeByRankExecutor;
+import org.apache.geode.redis.internal.executor.sortedset.ZRemRangeByScoreExecutor;
+import org.apache.geode.redis.internal.executor.sortedset.ZRevRangeByScoreExecutor;
+import org.apache.geode.redis.internal.executor.sortedset.ZRevRangeExecutor;
+import org.apache.geode.redis.internal.executor.sortedset.ZRevRankExecutor;
+import org.apache.geode.redis.internal.executor.sortedset.ZScanExecutor;
+import org.apache.geode.redis.internal.executor.sortedset.ZScoreExecutor;
+import org.apache.geode.redis.internal.executor.string.AppendExecutor;
+import org.apache.geode.redis.internal.executor.string.BitCountExecutor;
+import org.apache.geode.redis.internal.executor.string.BitOpExecutor;
+import org.apache.geode.redis.internal.executor.string.BitPosExecutor;
+import org.apache.geode.redis.internal.executor.string.DecrByExecutor;
+import org.apache.geode.redis.internal.executor.string.DecrExecutor;
+import org.apache.geode.redis.internal.executor.string.GetBitExecutor;
+import org.apache.geode.redis.internal.executor.string.GetExecutor;
+import org.apache.geode.redis.internal.executor.string.GetRangeExecutor;
+import org.apache.geode.redis.internal.executor.string.GetSetExecutor;
+import org.apache.geode.redis.internal.executor.string.IncrByExecutor;
+import org.apache.geode.redis.internal.executor.string.IncrByFloatExecutor;
+import org.apache.geode.redis.internal.executor.string.IncrExecutor;
+import org.apache.geode.redis.internal.executor.string.MGetExecutor;
+import org.apache.geode.redis.internal.executor.string.MSetExecutor;
+import org.apache.geode.redis.internal.executor.string.MSetNXExecutor;
+import org.apache.geode.redis.internal.executor.string.PSetEXExecutor;
+import org.apache.geode.redis.internal.executor.string.SetBitExecutor;
+import org.apache.geode.redis.internal.executor.string.SetEXExecutor;
+import org.apache.geode.redis.internal.executor.string.SetExecutor;
+import org.apache.geode.redis.internal.executor.string.SetNXExecutor;
+import org.apache.geode.redis.internal.executor.string.SetRangeExecutor;
+import org.apache.geode.redis.internal.executor.string.StrlenExecutor;
+import org.apache.geode.redis.internal.executor.transactions.DiscardExecutor;
+import org.apache.geode.redis.internal.executor.transactions.ExecExecutor;
+import org.apache.geode.redis.internal.executor.transactions.MultiExecutor;
+import org.apache.geode.redis.internal.executor.transactions.UnwatchExecutor;
+import org.apache.geode.redis.internal.executor.transactions.WatchExecutor;
+
+/**
+ * The redis command type used by the server. Each command is directly from the redis protocol and
+ * calling {@link #getExecutor()} on a type returns the executor class for that command.
+ * 
+ *
+ */
+public enum RedisCommandType {
+
+  /***************************************
+   *************** Keys ******************
+   ***************************************/
+
+  /**
+   * AUTH password
+   * <p>
+   * Authenticate to the server
+   */
+  AUTH {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new AuthExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.NONE;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * DEL key [key ...]
+   * <p>
+   * Delete a key
+   */
+  DEL {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new DelExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.NONE;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * EXISTS key
+   * <p>
+   * Determine if a key exists
+   */
+  EXISTS {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new ExistsExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.NONE;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * EXPIRE key seconds
+   * <p>
+   * Set a key's time to live in seconds
+   */
+  EXPIRE {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new ExpireExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.NONE;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * EXPIREAT key timestamp
+   * <p>
+   * Set the expiration for a key as a UNIX timestamp
+   */
+  EXPIREAT {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new ExpireAtExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.NONE;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * FLUSHALL
+   * <p>
+   * Remove all keys from all databases
+   */
+  FLUSHALL {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new FlushAllExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.NONE;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * FLUSHDB
+   * <p>
+   * Remove all keys from the current database
+   * <p>
+   * Same as FLUSHALL for this implementation
+   */
+  FLUSHDB {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new FlushAllExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.NONE;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * KEYS pattern
+   * <p>
+   * Find all keys matching the given pattern
+   */
+  KEYS {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new KeysExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.NONE;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * PERSIST key
+   * <p>
+   * Remove the expiration from a key
+   */
+  PERSIST {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new PersistExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.NONE;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * PEXPIRE key milliseconds
+   * <p>
+   * Set a key's time to live in milliseconds
+   */
+  PEXPIRE {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new PExpireExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.NONE;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * PEXPIREAT key milliseconds-timestamp
+   * <p>
+   * Set the expiration for a key as a UNIX timestamp specified in milliseconds
+   */
+  PEXPIREAT {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new PExpireAtExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.NONE;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * PTTL key
+   * <p>
+   * Get the time to live for a key in milliseconds
+   */
+  PTTL {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new PTTLExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.NONE;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * SCAN cursor [MATCH pattern] [COUNT count]
+   * <p>
+   * Incrementally iterate the keys space
+   */
+  SCAN {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new ScanExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.NONE;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * TTL key
+   * <p>
+   * Get the time to live for a key
+   */
+  TTL {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new TTLExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.NONE;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * TYPE key
+   * <p>
+   * Determine the type stored at key
+   */
+  TYPE {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new TypeExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.NONE;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /***************************************
+   ************** Strings ****************
+   ***************************************/
+
+  /**
+   * APPEND key value
+   * <p>
+   * Append a value to a key
+   */
+  APPEND {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new AppendExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_STRING;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * BITCOUNT key start end [start end ...]
+   * <p>
+   * Count set bits in a string
+   */
+  BITCOUNT {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new BitCountExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_STRING;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * BITOP operation destkey key [key ...]
+   * <p>
+   * Perform bitwise operations between strings
+   */
+  BITOP {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new BitOpExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_STRING;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * BITPOS key bit [start] [end]
+   * <p>
+   * Find first bit set or clear in a string
+   */
+  BITPOS {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new BitPosExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_STRING;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * DECR key
+   * <p>
+   * Decrement the integer value of a key by one
+   */
+  DECR {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new DecrExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_STRING;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * DECRBY key decrement
+   * <p>
+   * Decrement the integer value of a key by the given number
+   */
+  DECRBY {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new DecrByExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_STRING;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * GET key
+   * <p>
+   * Get the value of a key
+   */
+  GET {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new GetExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_STRING;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * GETBIT key offset
+   * <p>
+   * Returns the bit value at offset in the string value stored at key
+   */
+  GETBIT {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new GetBitExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_STRING;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * GETRANGE key start end
+   * <p>
+   * Get a substring of the string stored at a key
+   */
+  GETRANGE {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new GetRangeExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_STRING;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * GETSET key value
+   * <p>
+   * Set the string value of a key and return its old value
+   */
+  GETSET {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new GetSetExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_STRING;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * INCR key
+   * <p>
+   * Increment the integer value of a key by one
+   */
+  INCR {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new IncrExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_STRING;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * INCRBY key increment
+   * <p>
+   * Increment the integer value of a key by the given amount
+   */
+  INCRBY {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new IncrByExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_STRING;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * INCRBYFLOAT key increment
+   * <p>
+   * Increment the float value of a key by the given amount
+   */
+  INCRBYFLOAT {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new IncrByFloatExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_STRING;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * MGET key [key ...]
+   * <p>
+   * Get the values of all the given keys
+   */
+  MGET {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new MGetExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_STRING;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * MSET key value [key value ...]
+   * <p>
+   * Set multiple keys to multiple values
+   */
+  MSET {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new MSetExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_STRING;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * MSETNX key value [key value ...]
+   * <p>
+   * Set multiple keys to multiple values, only if none of the keys exist
+   */
+  MSETNX {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new MSetNXExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_STRING;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * PSETEX key milliseconds value
+   * <p>
+   * Set the value and expiration in milliseconds of a key
+   */
+  PSETEX {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new PSetEXExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_STRING;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * SETEX key seconds value
+   * <p>
+   * Set the value and expiration of a key
+   */
+  SETEX {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new SetEXExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_STRING;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * SET key value [EX seconds] [PX milliseconds] [NX|XX]
+   * <p>
+   * Set the string value of a key
+   */
+  SET {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new SetExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_STRING;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * SETBIT key offset value
+   * <P>
+   * Sets or clears the bit at offset in the string value stored at key
+   */
+  SETBIT {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new SetBitExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_STRING;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * SETNX key value
+   * <p>
+   * Set the value of a key, only if the key does not exist
+   */
+  SETNX {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new SetNXExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_STRING;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * SETRANGE key offset value
+   * <p>
+   * Overwrite part of a string at key starting at the specified offset
+   */
+  SETRANGE {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new SetRangeExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_STRING;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * STRLEN key
+   * <p>
+   * Get the length of the value stored in a key
+   */
+  STRLEN {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new StrlenExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_STRING;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /***************************************
+   **************** Hashes ***************
+   ***************************************/
+  /**
+   * HDEL key field [field ...]
+   * <p>
+   * Delete one or more hash fields
+   */
+  HDEL {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new HDelExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_HASH;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * HEXISTS key field
+   * <p>
+   * Determine if a hash field exists
+   */
+  HEXISTS {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new HExistsExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_HASH;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * HGET key field
+   * <p>
+   * Get the value of a hash field
+   */
+  HGET {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new HGetExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_HASH;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * HGETALL key
+   * <p>
+   * Get all the fields and values in a hash
+   */
+  HGETALL {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new HGetAllExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_HASH;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * HINCRBY key field increment
+   * <p>
+   * Increment the integer value of a hash field by the given number
+   */
+  HINCRBY {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new HIncrByExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_HASH;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * HINCRBYFLOAT key field increment
+   * <p>
+   * Increment the float value of a hash field by the given amount
+   */
+  HINCRBYFLOAT {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new HIncrByFloatExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_HASH;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * HKEYS key
+   * <p>
+   * Get all the fields in a hash
+   */
+  HKEYS {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new HKeysExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_HASH;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * HLEN key
+   * <p>
+   * Get the number of fields in a hash
+   */
+  HLEN {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new HLenExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_HASH;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * HMGET key field [field ...]
+   * <p>
+   * Get the values of all the given hash fields
+   */
+  HMGET {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new HMGetExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_HASH;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * HMSET key field value [field value ...]
+   * <p>
+   * Set multiple hash fields to multiple values
+   */
+  HMSET {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new HMSetExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_HASH;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * HSCAN key cursor [MATCH pattern] [COUNT count]
+   * <p>
+   * Incrementally iterate hash fields and associated values
+   */
+  HSCAN {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new HScanExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_HASH;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * HSET key field value
+   * <p>
+   * Set the string value of a hash field
+   */
+  HSET {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new HSetExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_HASH;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * HSETNX key field value
+   * <p>
+   * Set the value of a hash field, only if the field does not exist
+   */
+  HSETNX {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new HSetNXExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_HASH;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * HVALS key
+   * <p>
+   * Get all the values in a hash
+   */
+  HVALS {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new HValsExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_HASH;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /***************************************
+   *********** HyperLogLogs **************
+   ***************************************/
+
+  /**
+   * PFADD key element [element ...]
+   * <p>
+   * Adds the specified elements to the specified HyperLogLog
+   */
+  PFADD {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new PFAddExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_HLL;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * PFCOUNT key [key ...]
+   * <p>
+   * Return the approximated cardinality of the set(s) observed by the HyperLogLog at key(s)
+   */
+  PFCOUNT {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new PFCountExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_HLL;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * PFMERGE destkey sourcekey [sourcekey ...]
+   * <p>
+   * Merge N different HyperLogLogs into a single one
+   */
+  PFMERGE {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new PFMergeExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_HLL;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /***************************************
+   *************** Lists *****************
+   ***************************************/
+
+  /**
+   * LINDEX key index
+   * <p>
+   * Get an element from a list by its index
+   */
+  LINDEX {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new LIndexExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_LIST;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * LINSERT key BEFORE|AFTER pivot value
+   * <p>
+   * Insert an element before or after another element in a list
+   */
+  LINSERT {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new LInsertExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_LIST;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * LLEN key
+   * <p>
+   * Get the length of a list
+   */
+  LLEN {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new LLenExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_LIST;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * LPOP key
+   * <p>
+   * Remove and get the first element in a list
+   */
+  LPOP {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new LPopExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_LIST;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * LPUSH key value [value ...]
+   * <p>
+   * Prepend one or multiple values to a list
+   */
+  LPUSH {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new LPushExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_LIST;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * LPUSHX key value
+   * <p>
+   * Prepend a value to a list, only if the list exists
+   */
+  LPUSHX {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new LPushXExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_LIST;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * LRANGE key start stop
+   * <p>
+   * Get a range of elements from a list
+   */
+  LRANGE {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new LRangeExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_LIST;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * LREM key count value
+   * <p>
+   * Remove elements from a list
+   */
+  LREM {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new LRemExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_LIST;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * LSET key index value
+   * <p>
+   * Set the value of an element in a list by its index
+   */
+  LSET {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new LSetExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_LIST;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * LTRIM key start stop
+   * <p>
+   * Trim a list to the specified range
+   */
+  LTRIM {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new LTrimExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_LIST;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * RPOP key
+   * <p>
+   * Remove and get the last element in a list
+   */
+  RPOP {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new RPopExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_LIST;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * RPUSH key value [value ...]
+   * <p>
+   * Append one or multiple values to a list
+   */
+  RPUSH {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new RPushExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_LIST;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * RPUSHX key value
+   * <p>
+   * Append a value to a list, only if the list exists
+   */
+  RPUSHX {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new RPushXExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_LIST;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /***************************************
+   **************** Sets *****************
+   ***************************************/
+
+  /**
+   * SADD key member [member ...]
+   * <p>
+   * Add one or more members to a set
+   */
+  SADD {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new SAddExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * SCARD key
+   * <p>
+   * Get the number of members in a set
+   */
+  SCARD {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new SCardExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * SDIFF key [key ...]
+   * <p>
+   * Subtract multiple sets
+   */
+  SDIFF {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new SDiffExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * SDIFFSTORE destination key [key ...]
+   * <p>
+   * Subtract multiple sets and store the resulting set in a key
+   */
+  SDIFFSTORE {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new SDiffStoreExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * SISMEMBER key member
+   * <p>
+   * Determine if a given value is a member of a set
+   */
+  SISMEMBER {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new SIsMemberExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * SINTER key [key ...]
+   * <p>
+   * Intersect multiple sets
+   */
+  SINTER {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new SInterExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * SINTERSTORE destination key [key ...]
+   * <p>
+   * Intersect multiple sets and store the resulting set in a key
+   */
+  SINTERSTORE {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new SInterStoreExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * SMEMBERS key
+   * <p>
+   * Get all the members in a set
+   */
+  SMEMBERS {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new SMembersExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * SMOVE source destination member
+   * <p>
+   * Move a member from one set to another
+   */
+  SMOVE {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new SMoveExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * SPOP key
+   * <p>
+   * Remove and return a random member from a set
+   */
+  SPOP {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new SPopExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * SRANDMEMBER key [count]
+   * <p>
+   * Get one or multiple random members from a set
+   */
+  SRANDMEMBER {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new SRandMemberExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * SUNION key [key ...]
+   * <p>
+   * Add multiple sets
+   */
+  SUNION {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new SUnionExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * SUNIONSTORE destination key [key ...]
+   * <p>
+   * Add multiple sets and store the resulting set in a key
+   */
+  SUNIONSTORE {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new SUnionStoreExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * SSCAN key cursor [MATCH pattern] [COUNT count]
+   * <p>
+   * Incrementally iterate Set elements
+   */
+  SSCAN {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new SScanExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * SREM key member [member ...]
+   * <p>
+   * Remove one or more members from a set
+   */
+  SREM {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new SRemExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /***************************************
+   ************* Sorted Sets *************
+   ***************************************/
+
+  /**
+   * ZADD key score member [score member ...]
+   * <p>
+   * Add one or more members to a sorted set, or update its score if it already exists
+   */
+  ZADD {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new ZAddExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SORTEDSET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * ZCARD key
+   * <p>
+   * Get the number of members in a sorted set
+   */
+  ZCARD {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new ZCardExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SORTEDSET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * ZCOUNT key min max
+   * <p>
+   * Count the members in a sorted set with scores within the given values
+   */
+  ZCOUNT {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new ZCountExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SORTEDSET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * ZINCRBY key increment member
+   * <p>
+   * Increment the score of a member in a sorted set
+   */
+  ZINCRBY {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new ZIncrByExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SORTEDSET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * ZLEXCOUNT key min max
+   * <p>
+   * Count the number of members in a sorted set between a given lexicographical range
+   */
+  ZLEXCOUNT {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new ZLexCountExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SORTEDSET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * ZRANGE key start stop [WITHSCORES]
+   * <p>
+   * Return a range of members in a sorted set, by index
+   */
+  ZRANGE {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new ZRangeExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SORTEDSET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * ZRANGEBYLEX key min max [LIMIT offset count]
+   * <p>
+   * Return a range of members in a sorted set, by lexicographical range
+   */
+  ZRANGEBYLEX {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new ZRangeByLexExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SORTEDSET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count]
+   * <p>
+   * Return a range of members in a sorted set, by score
+   */
+  ZRANGEBYSCORE {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new ZRangeByScoreExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SORTEDSET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * ZREVRANGE key start stop [WITHSCORES]
+   * <p>
+   * Return a range of members in a sorted set, by index, with scores ordered from high to low
+   */
+  ZREVRANGE {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new ZRevRangeExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SORTEDSET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * ZRANK key member
+   * <p>
+   * Determine the index of a member in a sorted set
+   */
+  ZRANK {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new ZRankExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SORTEDSET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * ZREM key member [member ...]
+   * <p>
+   * Remove one or more members from a sorted set
+   */
+  ZREM {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new ZRemExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SORTEDSET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * ZREMRANGEBYLEX key min max
+   * <p>
+   * Remove all members in a sorted set between the given lexicographical range
+   */
+  ZREMRANGEBYLEX {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new ZRemRangeByLexExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SORTEDSET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * ZREMRANGEBYRANK key start stop
+   * <p>
+   * Remove all members in a sorted set within the given indexes
+   */
+  ZREMRANGEBYRANK {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new ZRemRangeByRankExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SORTEDSET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * ZREMRANGEBYSCORE key min max
+   * <p>
+   * Remove all members in a sorted set within the given scores
+   */
+  ZREMRANGEBYSCORE {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new ZRemRangeByScoreExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SORTEDSET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count]
+   * <p>
+   * Return a range of members in a sorted set, by score, with scores ordered from high to low
+   */
+  ZREVRANGEBYSCORE {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new ZRevRangeByScoreExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SORTEDSET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * ZREVRANK key member
+   * <p>
+   * Determine the index of a member in a sorted set, with scores ordered from high to low
+   */
+  ZREVRANK {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new ZRevRankExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SORTEDSET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * ZSCAN key cursor [MATCH pattern] [COUNT count]
+   * <P>
+   * Incrementally iterate sorted sets elements and associated scores
+   */
+  ZSCAN {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new ZScanExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SORTEDSET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * ZSCORE key member
+   * <p>
+   * Get the score associated with the given member in a sorted set
+   */
+  ZSCORE {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new ZScoreExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.REDIS_SORTEDSET;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /***************************************
+   ************ Transactions *************
+   ***************************************/
+
+  /**
+   * DISCARD
+   * <p>
+   * Discard all commands issued after MULTI
+   */
+  DISCARD {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new DiscardExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.NONE;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * EXEC
+   * <p>
+   * Execute all commands issued after MULTI
+   */
+  EXEC {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new ExecExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.NONE;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * MULTI
+   * <p>
+   * Mark the start of a transaction block
+   */
+  MULTI {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new MultiExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.NONE;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * UNWATCH
+   * <p>
+   * Forget about all watched keys
+   */
+  UNWATCH {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new UnwatchExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.NONE;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * WATCH key [key ...]
+   * <p>
+   * Watch the given keys to determine execution of the MULTI/EXEC block
+   */
+  WATCH {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new WatchExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.NONE;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /***************************************
+   *************** Server ****************
+   ***************************************/
+
+  /**
+   * DBSIZE
+   * <p>
+   * Return the number of keys in the selected database
+   */
+  DBSIZE {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new DBSizeExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.NONE;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * ECHO message
+   * <p>
+   * Echo the given string
+   */
+  ECHO {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new EchoExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.NONE;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * TIME
+   * <p>
+   * Return the current server time
+   */
+  TIME {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new TimeExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.NONE;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * PING
+   * <p>
+   * Ping the server
+   */
+  PING {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new PingExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.NONE;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * QUIT
+   * <p>
+   * Close the connection
+   */
+  QUIT {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new QuitExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.NONE;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  /**
+   * SHUTDOWN
+   * <p>
+   * Shut down the server
+   */
+  SHUTDOWN {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new ShutDownExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.NONE;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  },
+
+  // UNKNOWN
+  UNKNOWN {
+    private Executor executor;
+
+    @Override
+    public Executor getExecutor() {
+      if (executor == null) {
+        executor = new UnkownExecutor();
+      }
+      return executor;
+    }
+
+    private final RedisDataType dataType = RedisDataType.NONE;
+
+    @Override
+    public RedisDataType getDataType() {
+      return this.dataType;
+    }
+  };
+
+  /**
+   * Abstract method overridden by each value in enum to get the executor associated with that
+   * command type
+   * 
+   * @return {@link Executor} for command type
+   */
+  public abstract Executor getExecutor();
+
+  public abstract RedisDataType getDataType();
+  /*
+   * private RedisCommandType (RedisDataType dataType) { this.dataType = dataType; }
+   */
+}


Mime
View raw message