feat(command): add dispatch and setrange to keyspace
This commit is contained in:
1 parent
d3a1c170ac
commit
76e425bf21
1 file changed
+156
@@ -156,3 +156,159 @@ defmodule Excache.Command.Keyspace do
|
||||
def dispatch("RENAME", [source, dest], state),
|
||||
do: reply(statusReply(Store.rename(source, dest)), state)
|
||||
|
||||
def dispatch("RENAMENX", [source, dest], state),
|
||||
do: reply(integerReply(Store.renamenx(source, dest)), state)
|
||||
|
||||
def dispatch("COPY", [source, dest | rest], state) do
|
||||
case parseCopyOptions(rest, false) do
|
||||
{:ok, _replace} when source == dest ->
|
||||
reply({:error, "ERR source and destination objects are the same"}, state)
|
||||
|
||||
{:ok, replace} ->
|
||||
reply(integerReply(Store.copy(source, dest, replace)), state)
|
||||
|
||||
{:reply, term} ->
|
||||
reply(term, state)
|
||||
end
|
||||
end
|
||||
|
||||
def dispatch("TOUCH", [_ | _] = keys, state), do: reply({:integer, Store.touch(keys)}, state)
|
||||
def dispatch("UNLINK", [_ | _] = keys, state), do: reply({:integer, Store.del(keys)}, state)
|
||||
|
||||
def dispatch("EXPIREAT", [key, secondsRaw | rest], state) do
|
||||
case Integer.parse(secondsRaw) do
|
||||
{seconds, ""} ->
|
||||
case parseExpireFlags(rest) do
|
||||
{:ok, flags} -> reply({:integer, expireAt(key, seconds * 1000, flags)}, state)
|
||||
{:reply, term} -> reply(term, state)
|
||||
end
|
||||
|
||||
_ ->
|
||||
reply(Reply.not_integer(), state)
|
||||
end
|
||||
end
|
||||
|
||||
def dispatch("EXPIRETIME", [key], state),
|
||||
do: reply({:integer, Store.expiretime(key, :seconds)}, state)
|
||||
|
||||
def dispatch("PEXPIRETIME", [key], state),
|
||||
do: reply({:integer, Store.expiretime(key, :milliseconds)}, state)
|
||||
|
||||
def dispatch(_name, _args, _state), do: :unhandled
|
||||
|
||||
defp setrange(key, _offset, ""), do: integerReply(Store.strlen(key))
|
||||
defp setrange(key, offset, value), do: integerReply(Store.setrange(key, offset, value))
|
||||
|
||||
defp lpos(key, element, %{count: nil} = options, state) do
|
||||
case Store.lpos(key, element, options.rank, 1, options.maxlen) do
|
||||
{:ok, []} -> reply({:bulk, nil}, state)
|
||||
{:ok, [index]} -> reply({:integer, index}, state)
|
||||
{:error, reason} -> reply(errorTerm(reason), state)
|
||||
end
|
||||
end
|
||||
|
||||
defp lpos(key, element, options, state) do
|
||||
case Store.lpos(key, element, options.rank, options.count, options.maxlen) do
|
||||
{:ok, indexes} -> reply({:array, Enum.map(indexes, &{:integer, &1})}, state)
|
||||
{:error, reason} -> reply(errorTerm(reason), state)
|
||||
end
|
||||
end
|
||||
|
||||
defp parseLposOptions([], options), do: {:ok, options}
|
||||
|
||||
defp parseLposOptions([token | rest], options) do
|
||||
case {String.upcase(token), rest} do
|
||||
{"RANK", [raw | tail]} ->
|
||||
case Integer.parse(raw) do
|
||||
{0, ""} -> {:reply, {:error, rankZeroMessage()}}
|
||||
{rank, ""} -> parseLposOptions(tail, %{options | rank: rank})
|
||||
_ -> {:reply, Reply.not_integer()}
|
||||
end
|
||||
|
||||
{"COUNT", [raw | tail]} ->
|
||||
case Integer.parse(raw) do
|
||||
{count, ""} when count >= 0 -> parseLposOptions(tail, %{options | count: count})
|
||||
{_negative, ""} -> {:reply, {:error, "ERR COUNT can't be negative"}}
|
||||
_ -> {:reply, Reply.not_integer()}
|
||||
end
|
||||
|
||||
{"MAXLEN", [raw | tail]} ->
|
||||
case Integer.parse(raw) do
|
||||
{maxlen, ""} when maxlen >= 0 -> parseLposOptions(tail, %{options | maxlen: maxlen})
|
||||
{_negative, ""} -> {:reply, {:error, "ERR MAXLEN can't be negative"}}
|
||||
_ -> {:reply, Reply.not_integer()}
|
||||
end
|
||||
|
||||
_ ->
|
||||
{:reply, Reply.syntax()}
|
||||
end
|
||||
end
|
||||
|
||||
defp rankZeroMessage do
|
||||
"ERR RANK can't be zero. Use 1 to start searching from the first matching element in the " <>
|
||||
"head of the list or -1 searching backward from the tail of the list"
|
||||
end
|
||||
|
||||
defp object("ENCODING", [key], state) do
|
||||
withEntry(key, state, fn entry -> {:bulk, encodingName(entry)} end)
|
||||
end
|
||||
|
||||
defp object("REFCOUNT", [key], state) do
|
||||
withEntry(key, state, fn entry -> {:integer, refcount(entry)} end)
|
||||
end
|
||||
|
||||
defp object("IDLETIME", [key], state) do
|
||||
withEntry(key, state, fn _entry -> {:integer, idleSeconds(key)} end)
|
||||
end
|
||||
|
||||
defp object("HELP", [], state) do
|
||||
lines = [
|
||||
"OBJECT <subcommand> <key>. Subcommands are:",
|
||||
"ENCODING <key>",
|
||||
" Return the kind of internal representation used to store the value of <key>.",
|
||||
"IDLETIME <key>",
|
||||
" Return the seconds since <key> was last accessed, or 0 when the current",
|
||||
" maxmemory-policy keeps no access metadata for it.",
|
||||
"REFCOUNT <key>",
|
||||
" Return the number of references of the value associated with <key>.",
|
||||
"HELP",
|
||||
" Print this help."
|
||||
]
|
||||
|
||||
reply({:array, Enum.map(lines, &{:simple, &1})}, state)
|
||||
end
|
||||
|
||||
defp object(subcommand, _rest, state) do
|
||||
message =
|
||||
"ERR Unknown subcommand or wrong number of arguments for '#{subcommand}'. " <>
|
||||
"Try OBJECT HELP."
|
||||
|
||||
reply({:error, message}, state)
|
||||
end
|
||||
|
||||
defp idleSeconds(key) do
|
||||
case Tracker.idleMs(Tracker, key, System.system_time(:millisecond)) do
|
||||
nil -> 0
|
||||
idleMs -> div(idleMs, 1000)
|
||||
end
|
||||
end
|
||||
|
||||
defp withEntry(key, state, fun) do
|
||||
case Store.encoding(key) do
|
||||
nil -> reply({:error, "ERR no such key"}, state)
|
||||
{:ok, entry} -> reply(fun.(entry), state)
|
||||
end
|
||||
end
|
||||
|
||||
defp encodingName({:string, value, _ea}) do
|
||||
cond do
|
||||
integerString?(value) -> "int"
|
||||
byte_size(value) <= 44 -> "embstr"
|
||||
true -> "raw"
|
||||
end
|
||||
end
|
||||
|
||||
defp encodingName({:list, value, _ea}) do
|
||||
if compact?(value, length(value)), do: "listpack", else: "quicklist"
|
||||
end
|
||||
|
||||
Reference in new issue
Block a user