乐趣区

关于redis:聊聊claudb的string-command

本文次要钻研一下 claudb 的 string command

GetCommand

claudb-1.7.1/src/main/java/com/github/tonivade/claudb/command/string/GetCommand.java

@ReadOnly
@Command("get")
@ParamLength(1)
@ParamType(DataType.STRING)
public class GetCommand implements DBCommand {

  @Override
  public RedisToken execute(Database db, Request request) {return convert(db.get(safeKey(request.getParam(0))));
  }
}
  • GetCommand 实现了 DBCommand 接口,其 execute 办法执行 db.get(safeKey(request.getParam(0)))

MultiGetCommand

claudb-1.7.1/src/main/java/com/github/tonivade/claudb/command/string/MultiGetCommand.java

@ReadOnly
@Command("mget")
@ParamLength(1)
public class MultiGetCommand implements DBCommand {

  @Override
  public RedisToken execute(Database db, Request request) {ImmutableArray<DatabaseValue> result = request.getParams()
        .map(DatabaseKey::safeKey)
        .filter(key -> db.isType(key, DataType.STRING))
        .map(db::get);
    return convert(result);
  }
}
  • MultiGetCommand 实现了 DBCommand 接口,其 execute 办法遍历 request.getParams() 取出 type 为 DataType.STRING,而后挨个执行 db::get

SetCommand

claudb-1.7.1/src/main/java/com/github/tonivade/claudb/command/string/SetCommand.java

@Command("set")
@ParamLength(2)
public class SetCommand implements DBCommand {

  @Override
  public RedisToken execute(Database db, Request request) {return com.github.tonivade.purefun.type.Try.of(() -> parse(request))
        .map(params -> onSuccess(db, request, params))
        .recover(this::onFailure)
        .get();}

  private RedisToken onSuccess(Database db, Request request, Parameters parameters) {DatabaseKey key = safeKey(request.getParam(0));
    DatabaseValue value = parseValue(request, parameters);
    return value.equals(saveValue(db, parameters, key, value)) ? responseOk() : nullString();
  }

  private DatabaseValue parseValue(Request request, Parameters parameters) {DatabaseValue value = string(request.getParam(1));
    if (parameters.ttl != null) {value = value.expiredAt(Instant.now().plus(parameters.ttl));
    }
    return value;
  }

  private RedisToken onFailure(Throwable e) {return Pattern1.<Throwable, RedisToken>build()
        .when(instanceOf(SyntaxException.class))
          .returns(error("syntax error"))
        .when(instanceOf(NumberFormatException.class))
          .returns(error("value is not an integer or out of range"))
        .otherwise()
          .returns(error("error:" + e.getMessage()))
        .apply(e);
  }

  private DatabaseValue saveValue(Database db, Parameters params, DatabaseKey key, DatabaseValue value) {
    DatabaseValue savedValue = null;
    if (params.ifExists) {savedValue = putValueIfExists(db, key, value);
    } else if (params.ifNotExists) {savedValue = putValueIfNotExists(db, key, value);
    } else {savedValue = putValue(db, key, value);
    }
    return savedValue;
  }

  private DatabaseValue putValue(Database db, DatabaseKey key, DatabaseValue value) {db.put(key, value);
    return value;
  }

  private DatabaseValue putValueIfExists(Database db, DatabaseKey key, DatabaseValue value) {DatabaseValue oldValue = db.get(key);
    if (oldValue != null) {return putValue(db, key, value);
    }
    return oldValue;
  }

  private DatabaseValue putValueIfNotExists(Database db, DatabaseKey key, DatabaseValue value) {return db.merge(key, value, (oldValue, newValue) -> oldValue);
  }

  private Parameters parse(Request request) {Parameters parameters = new Parameters();
    if (request.getLength() > 2) {for (int i = 2; i < request.getLength(); i++) {SafeString option = request.getParam(i);
        if (match("EX", option)) {if (parameters.ttl != null) {throw new SyntaxException();
          }
          parameters.ttl = parseTtl(request, ++i)
              .map(Duration::ofSeconds)
              .getOrElseThrow(SyntaxException::new);
        } else if (match("PX", option)) {if (parameters.ttl != null) {throw new SyntaxException();
          }
          parameters.ttl = parseTtl(request, ++i)
              .map(Duration::ofMillis)
              .getOrElseThrow(SyntaxException::new);
        } else if (match("NX", option)) {if (parameters.ifExists) {throw new SyntaxException();
          }
          parameters.ifNotExists = true;
        } else if (match("XX", option)) {if (parameters.ifNotExists) {throw new SyntaxException();
          }
          parameters.ifExists = true;
        } else {throw new SyntaxException();
        }
      }
    }
    return parameters;
  }

  private boolean match(String string, SafeString option) {return string.equalsIgnoreCase(option.toString());
  }

  private Option<Integer> parseTtl(Request request, int i) {Option<SafeString> ttlOption = request.getOptionalParam(i);
    return ttlOption.map(SafeString::toString).map(Integer::parseInt);
  }

  private static class Parameters {
    private boolean ifExists;
    private boolean ifNotExists;
    private TemporalAmount ttl;
  }

  private static class SyntaxException extends RuntimeException {private static final long serialVersionUID = 6960370945568192189L;}
}
  • SetCommand 实现了 DBCommand 接口,其 execute 办法先通过 parse 办法解析成 parameters,这里反对 EX、PX、NX、XX 参数,若语法错误抛出 SyntaxException,而后 onFailure 会针对不同异样进行解决;onSuccess 办法则通过 parseValue(request, parameters) 结构 DatabaseValue,而后执行 saveValue 办法,该办法会针对 ifExists 执行 putValueIfExists,针对 ifNotExists 执行 putValueIfNotExists,其余的执行 putValue

MultiSetCommand

claudb-1.7.1/src/main/java/com/github/tonivade/claudb/command/string/MultiSetCommand.java

@Command("mset")
@ParamLength(2)
public class MultiSetCommand implements DBCommand {

  @Override
  public RedisToken execute(Database db, Request request) {
    SafeString key = null;
    for (SafeString value : request.getParams()) {if (key != null) {db.put(safeKey(key), string(value));
        key = null;
      } else {key = value;}
    }
    return status("OK");
  }
}
  • MultiSetCommand 实现了 DBCommand 接口,其 execute 办法遍历 request.getParams(),挨个执行 db.put(safeKey(key), string(value))

GetSetCommand

claudb-1.7.1/src/main/java/com/github/tonivade/claudb/command/string/GetSetCommand.java

@Command("getset")
@ParamLength(2)
@ParamType(DataType.STRING)
public class GetSetCommand implements DBCommand {

  @Override
  public RedisToken execute(Database db, Request request) {return convert(db.put(safeKey(request.getParam(0)), string(request.getParam(1))));
  }
}
  • GetSetCommand 实现了 DBCommand 接口,其 execute 办法执行 db.put(safeKey(request.getParam(0)), string(request.getParam(1))),同时将返回值转为 RedisToken 返回

IncrementCommand

claudb-1.7.1/src/main/java/com/github/tonivade/claudb/command/string/IncrementCommand.java

@Command("incr")
@ParamLength(1)
@ParamType(DataType.STRING)
public class IncrementCommand implements DBCommand {

  @Override
  public RedisToken execute(Database db, Request request) {
    try {DatabaseValue value = db.merge(safeKey(request.getParam(0)), string("1"),
          (oldValue, newValue) -> {int current = Integer.parseInt(oldValue.getString().toString());
            return string(String.valueOf(current + 1));
          });
      return integer(Integer.parseInt(value.getString().toString()));
    } catch (NumberFormatException e) {return error("ERR value is not an integer or out of range");
    }
  }

}
  • IncrementCommand 实现了 DBCommand 接口,其 execute 办法执行 db.merge,先通过 oldValue 获取 current,再执行 String.valueOf(current + 1)

IncrementByCommand

claudb-1.7.1/src/main/java/com/github/tonivade/claudb/command/string/IncrementByCommand.java

@Command("incrby")
@ParamLength(2)
@ParamType(DataType.STRING)
public class IncrementByCommand implements DBCommand {

  @Override
  public RedisToken execute(Database db, Request request) {
    try {DatabaseValue value = db.merge(safeKey(request.getParam(0)), string(request.getParam(1)),
          (oldValue, newValue) -> {int increment = Integer.parseInt(newValue.getString().toString());
            int current = Integer.parseInt(oldValue.getString().toString());
            return string(String.valueOf(current + increment));
          });
      return integer(Integer.parseInt(value.getString().toString()));
    } catch (NumberFormatException e) {return error("ERR value is not an integer or out of range");
    }
  }
}
  • IncrementByCommand 实现了 DBCommand 接口,其 execute 办法执行 db.merge,先通过 newValue 获取 increment,再 oldValue 获取 current,再执行 String.valueOf(current + increment)

DecrementCommand

claudb-1.7.1/src/main/java/com/github/tonivade/claudb/command/string/DecrementCommand.java

@Command("decr")
@ParamLength(1)
@ParamType(DataType.STRING)
public class DecrementCommand implements DBCommand {

  @Override
  public RedisToken execute(Database db, Request request) {
    try {DatabaseValue value = db.merge(safeKey(request.getParam(0)), string("-1"),
          (oldValue, newValue) -> {int current = Integer.parseInt(oldValue.getString().toString());
            return string(String.valueOf(current - 1));
          });
      return integer(Integer.parseInt(value.getString().toString()));
    } catch (NumberFormatException e) {return error("ERR value is not an integer or out of range");
    }
  }
}
  • DecrementCommand 实现了 DBCommand 接口,其 execute 办法执行 db.merge,先通过 oldValue 获取 current,再执行 String.valueOf(current – 1)

DecrementByCommand

claudb-1.7.1/src/main/java/com/github/tonivade/claudb/command/string/DecrementByCommand.java

@Command("decrby")
@ParamLength(2)
@ParamType(DataType.STRING)
public class DecrementByCommand implements DBCommand {

  @Override
  public RedisToken execute(Database db, Request request) {
    try {DatabaseValue value = db.merge(safeKey(request.getParam(0)), string("-" + request.getParam(1)),
          (oldValue, newValue) -> {int decrement = Integer.parseInt(newValue.getString().toString());
            int current = Integer.parseInt(oldValue.getString().toString());
            return string(String.valueOf(current + decrement));
          });
      return integer(Integer.parseInt(value.getString().toString()));
    } catch (NumberFormatException e) {return error("ERR value is not an integer or out of range");
    }
  }
}
  • DecrementByCommand 实现了 DBCommand 接口,其 execute 办法执行 db.merge,先通过 newValue 获取 decrement,再 oldValue 获取 current,再执行 String.valueOf(current + decrement),留神这里将 request.getParam(1) 转为正数了

StringLengthCommand

claudb-1.7.1/src/main/java/com/github/tonivade/claudb/command/string/StringLengthCommand.java

@ReadOnly
@Command("strlen")
@ParamLength(1)
@ParamType(DataType.STRING)
public class StringLengthCommand implements DBCommand {

  @Override
  public RedisToken execute(Database db, Request request) {DatabaseValue value = db.getOrDefault(safeKey(request.getParam(0)), DatabaseValue.EMPTY_STRING);
    SafeString string = value.getString();
    return integer(string.length());
  }
}
  • StringLengthCommand 实现了 DBCommand 接口,其 execute 办法先通过 db.getOrDefault 获取 DatabaseValue,在获取 string.length()

SetExpiredCommand

claudb-1.7.1/src/main/java/com/github/tonivade/claudb/command/string/SetExpiredCommand.java

@Command("setex")
@ParamLength(3)
public class SetExpiredCommand implements DBCommand {

  @Override
  public RedisToken execute(Database db, Request request) {
    try {db.put(safeKey(request.getParam(0)), string(request.getParam(2))
               .expiredAt(parseTtl(request.getParam(1))));
      return responseOk();} catch (NumberFormatException e) {return error("ERR value is not an integer or out of range");
    }
  }

  private int parseTtl(SafeString safeString) {return Integer.parseInt(safeString.toString());
  }
}
  • SetExpiredCommand 实现了 DBCommand 接口,其 execute 办法执行 db.put,这里对 value 执行了 expiredAt(parseTtl(request.getParam(1)))

BitCountCommand

claudb-1.7.1/src/main/java/com/github/tonivade/claudb/command/bitset/BitCountCommand.java

@Command("bitcount")
@ParamLength(1)
@ParamType(DataType.STRING)
public class BitCountCommand implements DBCommand {

  @Override
  public RedisToken execute(Database db, Request request) {DatabaseValue value = db.getOrDefault(safeKey(request.getParam(0)), bitset());
    BitSet bitSet = BitSet.valueOf(value.getString().getBuffer());
    return integer(bitSet.cardinality());
  }
}
  • BitCountCommand 实现了 DBCommand 接口,其 execute 办法先通过 db.getOrDefault 获取 DatabaseValue,而后通过 BitSet.valueOf(value.getString().getBuffer()) 转为 bitSet,最初返回 bitSet.cardinality()

SetBitCommand

claudb-1.7.1/src/main/java/com/github/tonivade/claudb/command/bitset/SetBitCommand.java

@Command("setbit")
@ParamLength(3)
@ParamType(DataType.STRING)
public class SetBitCommand implements DBCommand {

  @Override
  public RedisToken execute(Database db, Request request) {
    try {int offset = Integer.parseInt(request.getParam(1).toString());
      int bit = Integer.parseInt(request.getParam(2).toString());
      Queue<Boolean> queue = new LinkedList<>();
      db.merge(safeKey(request.getParam(0)), bitset(), (oldValue, newValue) -> {BitSet bitSet = BitSet.valueOf(oldValue.getString().getBuffer());
        queue.add(bitSet.get(offset));
        bitSet.set(offset, bit != 0);
        return oldValue;
      });
      return integer(queue.poll());
    } catch (NumberFormatException e) {return error("bit or offset is not an integer");
    }
  }
}
  • SetBitCommand 实现了 DBCommand 接口,其 execute 办法先解析参数获取 offset、bit,之后执行 db.merge,先将 oldValue 转为 bitSet,再执行 bitSet.set(offset, bit != 0)

GetBitCommand

claudb-1.7.1/src/main/java/com/github/tonivade/claudb/command/bitset/GetBitCommand.java

@Command("getbit")
@ParamLength(2)
@ParamType(DataType.STRING)
public class GetBitCommand implements DBCommand {

  @Override
  public RedisToken execute(Database db, Request request) {
    try {int offset = Integer.parseInt(request.getParam(1).toString());
      DatabaseValue value = db.getOrDefault(safeKey(request.getParam(0)), bitset());
      BitSet bitSet = BitSet.valueOf(value.getString().getBuffer());
      return integer(bitSet.get(offset));
    } catch (NumberFormatException e) {return error("bit offset is not an integer");
    }
  }
}
  • GetBitCommand 实现了 DBCommand 接口,其 execute 办法先通过 db.getOrDefault 获取 DatabaseValue,而后转为 bitSet,最初返回 bitSet.get(offset)

SetIfNotExistsCommand

claudb-1.7.1/src/main/java/com/github/tonivade/claudb/command/string/SetIfNotExistsCommand.java

@Command("setnx")
@ParamLength(2)
public class SetIfNotExistsCommand implements DBCommand {
  
  @Override
  public RedisToken execute(Database db, Request request) {DatabaseKey key = safeKey(request.getParam(0));
    DatabaseValue value = string(request.getParam(1));
    return integer(putValueIfNotExists(db, key, value).equals(value));
  }

  private DatabaseValue putValueIfNotExists(Database db, DatabaseKey key, DatabaseValue value) {return db.merge(key, value, (oldValue, newValue) -> oldValue);
  }
}
  • SetIfNotExistsCommand 实现了 DBCommand 接口,其 execute 办法执行 putValueIfNotExists,该办法的 remappingFunction 返回 oldValue

MultiSetIfNotExistsCommand

claudb-1.7.1/src/main/java/com/github/tonivade/claudb/command/string/MultiSetIfNotExistsCommand.java

@Command("msetnx")
@ParamLength(2)
public class MultiSetIfNotExistsCommand implements DBCommand {
  @Override
  public RedisToken execute(Database db, Request request) {Set<Tuple2<SafeString, SafeString>> pairs = toPairs(request);
    if (noneExists(db, pairs)) {pairs.forEach(entry -> db.put(safeKey(entry.get1()), string(entry.get2())));
      return integer(1);
    }
    return integer(0);
  }

  private boolean noneExists(Database db, Set<Tuple2<SafeString, SafeString>> pairs) {return pairs.stream()
        .map(Tuple2::get1)
        .map(DatabaseKey::safeKey)
        .noneMatch(db::containsKey);
  }

  private Set<Tuple2<SafeString, SafeString>> toPairs(Request request) {Set<Tuple2<SafeString, SafeString>> pairs = new HashSet<>();
    SafeString key = null;
    for (SafeString value : request.getParams()) {if (key != null) {pairs.add(entry(key, value));
        key = null;
      } else {key = value;}
    }
    return pairs;
  }
}
  • MultiSetIfNotExistsCommand 实现了 DBCommand 接口,其 execute 办法在 noneExists(db, pairs) 为 true 时遍历 pairs 挨个执行 db.put(safeKey(entry.get1()), string(entry.get2()))

小结

claudb string 相干的 command 有 GetCommand、MultiGetCommand、SetCommand、MultiSetCommand、GetSetCommand、IncrementCommand、IncrementByCommand、DecrementCommand、DecrementByCommand、StringLengthCommand、SetExpiredCommand、BitCountCommand、GetBitCommand、SetIfNotExistsCommand、MultiSetIfNotExistsCommand

doc

  • command/string
退出移动版