feat(store): add core key-value and list operations
This commit is contained in:
1 parent
c3e69b3fc3
commit
deb559023d
1 file changed
+210
@@ -0,0 +1,210 @@
|
|||||||
|
defmodule Excache.Store.Ops do
|
||||||
|
@moduledoc false
|
||||||
|
|
||||||
|
# Every operation runs inside the owning shard process, so ETS access is
|
||||||
|
# serialized per shard and read-modify-write sequences are atomic.
|
||||||
|
# Entry layout: {key, kind, value, expires_at} where kind is one of
|
||||||
|
# :string | :list | :hash | :set | :zset and expires_at is epoch ms or nil.
|
||||||
|
|
||||||
|
alias Excache.SkipList
|
||||||
|
alias Excache.Store.Ops.Helpers
|
||||||
|
|
||||||
|
import Helpers,
|
||||||
|
only: [
|
||||||
|
fetch: 3,
|
||||||
|
collection: 6,
|
||||||
|
with_kind: 6,
|
||||||
|
pop_list: 3,
|
||||||
|
rank_window: 3,
|
||||||
|
slice: 3,
|
||||||
|
at: 2,
|
||||||
|
expired?: 2,
|
||||||
|
expiry_from_opts: 2,
|
||||||
|
remaining: 2,
|
||||||
|
compile_pattern: 1
|
||||||
|
]
|
||||||
|
|
||||||
|
def apply_op(table, now, {:stream, op}),
|
||||||
|
do: Excache.Store.Ops.Streams.apply_op(table, now, op)
|
||||||
|
|
||||||
|
def apply_op(table, now, {:binary, op}),
|
||||||
|
do: Excache.Store.Ops.Binary.apply_op(table, now, op)
|
||||||
|
|
||||||
|
def apply_op(table, now, {:extra, op}),
|
||||||
|
do: Excache.Store.Ops.Extra.apply_op(table, now, op)
|
||||||
|
|
||||||
|
def apply_op(table, now, {:peek, key}), do: fetch(table, key, now)
|
||||||
|
|
||||||
|
def apply_op(table, now, {:get, key}) do
|
||||||
|
case fetch(table, key, now) do
|
||||||
|
:miss -> nil
|
||||||
|
{:string, value, _ea} -> {:ok, value}
|
||||||
|
_ -> {:error, :wrongtype}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_op(table, now, {:set, key, value, opts}) do
|
||||||
|
existing = fetch(table, key, now)
|
||||||
|
exists = existing != :miss
|
||||||
|
|
||||||
|
cond do
|
||||||
|
Keyword.get(opts, :nx, false) and exists ->
|
||||||
|
nil
|
||||||
|
|
||||||
|
Keyword.get(opts, :xx, false) and not exists ->
|
||||||
|
nil
|
||||||
|
|
||||||
|
true ->
|
||||||
|
:ets.insert(table, {key, :string, value, expiry_from_opts(opts, now)})
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_op(table, now, {:del, key}) do
|
||||||
|
case fetch(table, key, now) do
|
||||||
|
:miss ->
|
||||||
|
0
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
:ets.delete(table, key)
|
||||||
|
1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_op(table, now, {:exists, key}) do
|
||||||
|
case fetch(table, key, now) do
|
||||||
|
:miss -> 0
|
||||||
|
_ -> 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_op(table, now, {:type, key}) do
|
||||||
|
case fetch(table, key, now) do
|
||||||
|
:miss -> "none"
|
||||||
|
{kind, _value, _ea} -> Atom.to_string(kind)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_op(table, now, {:incrby, key, delta}) do
|
||||||
|
case fetch(table, key, now) do
|
||||||
|
:miss ->
|
||||||
|
:ets.insert(table, {key, :string, Integer.to_string(delta), nil})
|
||||||
|
{:ok, delta}
|
||||||
|
|
||||||
|
{:string, value, ea} ->
|
||||||
|
case Integer.parse(value) do
|
||||||
|
{current, ""} ->
|
||||||
|
updated = current + delta
|
||||||
|
:ets.insert(table, {key, :string, Integer.to_string(updated), ea})
|
||||||
|
{:ok, updated}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:error, :not_integer}
|
||||||
|
end
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:error, :wrongtype}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_op(table, now, {:append, key, value}) do
|
||||||
|
case fetch(table, key, now) do
|
||||||
|
:miss ->
|
||||||
|
:ets.insert(table, {key, :string, value, nil})
|
||||||
|
{:ok, byte_size(value)}
|
||||||
|
|
||||||
|
{:string, current, ea} ->
|
||||||
|
updated = current <> value
|
||||||
|
:ets.insert(table, {key, :string, updated, ea})
|
||||||
|
{:ok, byte_size(updated)}
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
{:error, :wrongtype}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_op(table, now, {:getset, key, value}) do
|
||||||
|
previous =
|
||||||
|
case fetch(table, key, now) do
|
||||||
|
:miss -> {:ok, nil}
|
||||||
|
{:string, current, _ea} -> {:ok, current}
|
||||||
|
_ -> {:error, :wrongtype}
|
||||||
|
end
|
||||||
|
|
||||||
|
case previous do
|
||||||
|
{:ok, prior} ->
|
||||||
|
:ets.insert(table, {key, :string, value, nil})
|
||||||
|
{:ok, prior}
|
||||||
|
|
||||||
|
error ->
|
||||||
|
error
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_op(table, now, {:expire, key, ms}) do
|
||||||
|
case fetch(table, key, now) do
|
||||||
|
:miss ->
|
||||||
|
0
|
||||||
|
|
||||||
|
{kind, value, _ea} ->
|
||||||
|
:ets.insert(table, {key, kind, value, now + ms})
|
||||||
|
1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_op(table, now, {:ttl, key, unit}) do
|
||||||
|
case fetch(table, key, now) do
|
||||||
|
:miss -> -2
|
||||||
|
{_kind, _value, nil} -> -1
|
||||||
|
{_kind, _value, ea} -> remaining(ea - now, unit)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_op(table, now, {:persist, key}) do
|
||||||
|
case fetch(table, key, now) do
|
||||||
|
:miss ->
|
||||||
|
0
|
||||||
|
|
||||||
|
{_kind, _value, nil} ->
|
||||||
|
0
|
||||||
|
|
||||||
|
{kind, value, _ea} ->
|
||||||
|
:ets.insert(table, {key, kind, value, nil})
|
||||||
|
1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_op(table, now, {:lpush, key, values}) do
|
||||||
|
collection(table, key, now, :list, [], fn current ->
|
||||||
|
updated = Enum.reverse(values) ++ current
|
||||||
|
{updated, {:ok, length(updated)}}
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_op(table, now, {:rpush, key, values}) do
|
||||||
|
collection(table, key, now, :list, [], fn current ->
|
||||||
|
updated = current ++ values
|
||||||
|
{updated, {:ok, length(updated)}}
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_op(table, now, {:lpop, key, count}) do
|
||||||
|
collection(table, key, now, :list, [], fn current -> pop_list(current, :left, count) end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_op(table, now, {:rpop, key, count}) do
|
||||||
|
collection(table, key, now, :list, [], fn current -> pop_list(current, :right, count) end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_op(table, now, {:lrange, key, start, stop}) do
|
||||||
|
with_kind(table, key, now, :list, [], fn list -> {:ok, slice(list, start, stop)} end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_op(table, now, {:llen, key}) do
|
||||||
|
with_kind(table, key, now, :list, [], fn list -> {:ok, length(list)} end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def apply_op(table, now, {:lindex, key, index}) do
|
||||||
|
with_kind(table, key, now, :list, [], fn list -> at(list, index) end)
|
||||||
|
end
|
||||||
|
|
||||||
Reference in new issue
Block a user