Packages
cubdb
1.0.0-rc.5
2.0.2
2.0.1
2.0.0
2.0.0-rc.1
1.1.0
1.0.0
1.0.0-rc.10
1.0.0-rc.9
1.0.0-rc.8
1.0.0-rc.7
1.0.0-rc.6
1.0.0-rc.5
1.0.0-rc.4
1.0.0-rc.3
1.0.0-rc.2
1.0.0-rc.1
0.17.0
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.0
0.14.0
0.13.1
0.13.0
0.12.0
0.11.0
0.10.0
0.9.0
0.8.0
0.7.0
0.6.0
0.5.0
0.4.0
0.3.0
0.2.0
0.1.4
0.1.3
0.1.2
0.1.1
0.1.0
A pure-Elixir embedded key-value database
Current section
Files
Jump to
Current section
Files
lib/cubdb/reader.ex
defmodule CubDB.Reader do
@moduledoc false
# The `CubDB.Reader` module performs all read operations that involve access
# to the store. Each read operation is ran in its own process, as a `Task`, so
# that read operations can run concurrently. Read operations are performed on
# the Btree representing a snapshot of the database at the time the read
# operation was invoked.
#
# At the end of each read operation, a `{:check_out_reader, btree}` message is
# sent to the main `db` process. That allows the main process to keep track of
# which files are still referenced by readers, so that clean-up of old files
# after a compaction can be delayed until no more `Reader` processes reference
# them.
alias CubDB.Btree
@type operation ::
{:get, CubDB.key(), CubDB.value()}
| {:get_multi, [CubDB.key()]}
| {:fetch, CubDB.key()}
| {:has_key?, CubDB.key()}
| {:select, Keyword.t()}
@spec run(Btree.t(), GenServer.from(), operation) :: :ok
def run(btree, caller, operation) do
GenServer.reply(caller, perform(btree, operation))
end
@spec perform(Btree.t(), operation) :: any
def perform(btree, {:get, key, default}) do
case Btree.fetch(btree, key) do
{:ok, value} -> value
:error -> default
end
end
def perform(btree, {:get_multi, keys}) do
Enum.reduce(keys, %{}, fn key, map ->
case Btree.fetch(btree, key) do
{:ok, value} -> Map.put(map, key, value)
:error -> map
end
end)
end
def perform(btree, {:fetch, key}) do
Btree.fetch(btree, key)
end
def perform(btree, {:has_key?, key}) do
case Btree.fetch(btree, key) do
{:ok, _} -> true
:error -> false
end
end
def perform(btree, {:select, options}) do
{:ok, select(btree, options)}
rescue
error -> {:error, error}
end
@spec select(Btree.t(), Keyword.t()) :: any
defp select(btree, options) when is_list(options) do
min_key =
case Keyword.fetch(options, :min_key) do
{:ok, key} ->
{key, Keyword.get(options, :min_key_inclusive, true)}
:error ->
nil
end
max_key =
case Keyword.fetch(options, :max_key) do
{:ok, key} ->
{key, Keyword.get(options, :max_key_inclusive, true)}
:error ->
nil
end
pipe = Keyword.get(options, :pipe, [])
reduce = Keyword.get(options, :reduce)
reverse = Keyword.get(options, :reverse, false)
key_range = Btree.key_range(btree, min_key, max_key, reverse)
stream =
Enum.reduce(pipe, key_range, fn
{:filter, fun}, stream when is_function(fun) -> Stream.filter(stream, fun)
{:map, fun}, stream when is_function(fun) -> Stream.map(stream, fun)
{:take, n}, stream when is_integer(n) -> Stream.take(stream, n)
{:drop, n}, stream when is_integer(n) -> Stream.drop(stream, n)
{:take_while, fun}, stream when is_function(fun) -> Stream.take_while(stream, fun)
{:drop_while, fun}, stream when is_function(fun) -> Stream.drop_while(stream, fun)
op, _ -> raise(ArgumentError, message: "invalid pipe operation #{inspect(op)}")
end)
case reduce do
fun when is_function(fun) -> Enum.reduce(stream, fun)
{acc, fun} when is_function(fun) -> Enum.reduce(stream, acc, fun)
nil -> Enum.to_list(stream)
end
end
end