├── doc
├── erlang.png
├── rkvs_util.md
├── stylesheet.css
├── rkvs_storage_backend.md
├── rkvs_bitcask.md
├── rkvs_hanoidb.md
├── rkvs_ets.md
├── rkvs_rocksdb.md
├── rkvs_leveldb.md
├── overview.edoc
├── README.md
└── rkvs.md
├── .gitignore
├── NOTICE
├── src
├── rkvs.app.src
├── rkvs_util.erl
├── rkvs_storage_backend.erl
├── rkvs.erl
├── rkvs_hanoidb.erl
├── rkvs_ets.erl
├── rkvs_rocksdb.erl
├── rkvs_leveldb.erl
└── rkvs_bitcask.erl
├── NEWS.md
├── include
└── rkvs.hrl
├── rebar.config
├── test
├── rkvs_tests.hrl
├── rkvs_bitcask_tests.erl
└── rkvs_tests.erl
├── Makefile
├── rebar_dev.config
├── README.md
└── LICENSE
/doc/erlang.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refuge/rkvs/HEAD/doc/erlang.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ebin
2 | *.swp
3 | *.dump
4 | edoc-info
5 | .DS_Store
6 | deps/
7 | .eunit
8 | .rebar
9 | test/temp
10 | log
11 | .eunit
12 | .rebar
13 | test/sst*
14 | test/LOG*
15 | test/MANIFEST*
16 | test/*.log
17 | test/LOCK
18 | test/CURRENT
19 |
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | rkvs
2 | ----
3 | Copyright 2014-2015 - Benoit Chesneau & rkvs Authors
4 |
5 | The rkvs Source Code is subject to the terms of the Mozilla Public
6 | License, v. 2.0. If a copy of the MPL was not distributed with this
7 | file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 |
--------------------------------------------------------------------------------
/src/rkvs.app.src:
--------------------------------------------------------------------------------
1 | {application, rkvs,
2 | [
3 | {description, "simple K/Vs interface"},
4 | {vsn, "0.2.1"},
5 | {registered, []},
6 | {applications, [
7 | kernel,
8 | stdlib
9 | ]},
10 | {include_apps, [eleveldb]},
11 | {env, []}
12 | ]}.
13 |
--------------------------------------------------------------------------------
/NEWS.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 0.2.0 - 2015/05/03
4 |
5 | - add rocksdb backend
6 | - add hanoidb backend
7 | - add bitcask backend
8 | - improve folding: add new gt, gte, lt, lte options
9 | - support different key and value encoding
10 | - add fill_cache option to leveldb backend
11 |
12 | ## 0.1.0 - 2014/11/14
13 |
14 | - initial release
15 |
--------------------------------------------------------------------------------
/include/rkvs.hrl:
--------------------------------------------------------------------------------
1 | %-*- mode: erlang -*-
2 | %%
3 | %% This Source Code Form is subject to the terms of the Mozilla Public
4 | %% License, v. 2.0. If a copy of the MPL was not distributed with this
5 | %% file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 |
7 | -record(engine, {name,
8 | mod,
9 | ref,
10 | key_enc,
11 | val_enc,
12 | options}).
13 |
14 | -record(fold_options, {start_key=first,
15 | end_key=nil,
16 | gt=nil,
17 | gte=nil,
18 | lt=nil,
19 | lte=nil,
20 | max=0,
21 | key_enc,
22 | val_enc}).
23 |
24 | -type engine() :: #engine{}.
25 | -export_type([engine/0]).
26 |
--------------------------------------------------------------------------------
/doc/rkvs_util.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module rkvs_util #
4 | * [Function Index](#index)
5 | * [Function Details](#functions)
6 |
7 |
8 |
9 |
10 | ## Function Index ##
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | ## Function Details ##
19 |
20 |
21 |
22 | ### dec/3 ###
23 |
24 | `dec(T, V, E) -> any()`
25 |
26 |
27 |
28 |
29 | ### enc/3 ###
30 |
31 | `enc(T, V, E) -> any()`
32 |
33 |
34 |
35 |
36 | ### fold_options/2 ###
37 |
38 | `fold_options(Rest, Options) -> any()`
39 |
40 |
41 |
--------------------------------------------------------------------------------
/rebar.config:
--------------------------------------------------------------------------------
1 | %% -*- mode: erlang -*-
2 | {cover_enabled, true}.
3 | {edoc_opts, [{preprocess, true}]}.
4 |
5 | {erl_opts, [
6 | warnings_as_errors
7 | ]}.
8 | {xref_checks, [undefined_function_calls]}.
9 |
10 |
11 | {eunit_opts, [verbose]}.
12 |
13 | {require_otp_vsn, "R16|17|18"}.
14 |
15 | {erl_first_files, [ "src/rkvs_storage_backend.erl"]}.
16 |
17 | {clean_files, ["*~","*/*~","*/*.xfm","test/*.beam", "test/temp/*"]}.
18 |
19 | {deps, [
20 | %% {eleveldb, ".*", {git, "https://github.com/basho/eleveldb.git",
21 | %% {tag, "2.1.0"}}},
22 | %%
23 | %% {erocksdb, ".*", {git, "https://github.com/leo-project/erocksdb.git",
24 | %% {tag, 0.4}}},
25 | %%
26 | %% {hanoidb, ".*", {git, "https://github.com/krestenkrab/hanoidb.git",
27 | %% "4e82d5f81ab087f038bfd13354ff48ee9113f459"}},
28 | %%
29 | %% {bitcask, ".*", {git, "https://github.com/basho/bitcask.git",
30 | %% {tag, "2.0.0"}}},
31 | ]}.
32 |
--------------------------------------------------------------------------------
/test/rkvs_tests.hrl:
--------------------------------------------------------------------------------
1 | %%-*- mode: erlang -*-
2 | %%
3 | %% This Source Code Form is subject to the terms of the Mozilla Public
4 | %% License, v. 2.0. If a copy of the MPL was not distributed with this
5 | %% file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 |
7 |
8 | -include_lib("eunit/include/eunit.hrl").
9 |
10 | -define(BUILDDIR, filename:absname(
11 | filename:join([
12 | filename:dirname(code:which(?MODULE)),
13 | ".."]))).
14 |
15 | -define(TEMPDIR,
16 | filename:join([?BUILDDIR, "test", "temp"])).
17 |
18 | -define(tempfile,
19 | fun() ->
20 | {A, B, C} = erlang:now(),
21 | N = node(),
22 | FileName = lists:flatten(io_lib:format("~p-~p.~p.~p", [N, A, B, C])),
23 | filename:join([?TEMPDIR, FileName])
24 | end).
25 |
26 | -define(tempdb,
27 | fun() ->
28 | Nums = tuple_to_list(erlang:now()),
29 | Prefix = "eunit-test-db",
30 | Suffix = lists:concat([integer_to_list(Num) || Num <- Nums]),
31 | Prefix ++ "-" ++ Suffix
32 | end).
33 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | BASE_DIR = $(shell pwd)
2 | SUPPORT_DIR=$(BASE_DIR)/support
3 | ERLC ?= $(shell which erlc)
4 | ESCRIPT ?= $(shell which escript)
5 | ERL ?= $(shell which erl)
6 | APP := enkidb
7 | REBAR?= rebar
8 |
9 | $(if $(ERLC),,$(warning "Warning: No Erlang found in your path, this will probably not work"))
10 |
11 | $(if $(ESCRIPT),,$(warning "Warning: No escript found in your path, this will probably not work"))
12 |
13 | .PHONY: deps doc test
14 |
15 | all: deps compile
16 |
17 | compile:
18 | @$(REBAR) compile
19 |
20 | deps:
21 | @$(REBAR) get-deps
22 |
23 | doc: dev
24 | $(REBAR) -C rebar_dev.config doc skip_deps=true
25 |
26 | test: dev
27 | $(REBAR) -C rebar_dev.config eunit skip_deps=true
28 |
29 | dev: dev-deps
30 | $(REBAR) -C rebar_dev.config compile
31 |
32 | dev-deps:
33 | $(REBAR) -C rebar_dev.config get-deps
34 |
35 | clean:
36 | @$(REBAR) clean
37 | @rm -f t/*.beam t/temp.*
38 | @rm -f doc/*.html doc/*.css doc/edoc-info doc/*.png
39 |
40 | distclean: clean
41 | @$(REBAR) delete-deps
42 | @rm -rf deps
43 |
44 | dialyzer: compile
45 | @dialyzer -Wno_return -c ebin
46 |
--------------------------------------------------------------------------------
/doc/stylesheet.css:
--------------------------------------------------------------------------------
1 | /* standard EDoc style sheet */
2 | body {
3 | font-family: Verdana, Arial, Helvetica, sans-serif;
4 | margin-left: .25in;
5 | margin-right: .2in;
6 | margin-top: 0.2in;
7 | margin-bottom: 0.2in;
8 | color: #000000;
9 | background-color: #ffffff;
10 | }
11 | h1,h2 {
12 | margin-left: -0.2in;
13 | }
14 | div.navbar {
15 | background-color: #add8e6;
16 | padding: 0.2em;
17 | }
18 | h2.indextitle {
19 | padding: 0.4em;
20 | background-color: #add8e6;
21 | }
22 | h3.function,h3.typedecl {
23 | background-color: #add8e6;
24 | padding-left: 1em;
25 | }
26 | div.spec {
27 | margin-left: 2em;
28 | background-color: #eeeeee;
29 | }
30 | a.module,a.package {
31 | text-decoration:none
32 | }
33 | a.module:hover,a.package:hover {
34 | background-color: #eeeeee;
35 | }
36 | ul.definitions {
37 | list-style-type: none;
38 | }
39 | ul.index {
40 | list-style-type: none;
41 | background-color: #eeeeee;
42 | }
43 |
44 | /*
45 | * Minor style tweaks
46 | */
47 | ul {
48 | list-style-type: square;
49 | }
50 | table {
51 | border-collapse: collapse;
52 | }
53 | td {
54 | padding: 3
55 | }
56 |
--------------------------------------------------------------------------------
/doc/rkvs_storage_backend.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module rkvs_storage_backend #
4 | * [Data Types](#types)
5 |
6 | __This module defines the `rkvs_storage_backend` behaviour.__
Required callback functions: `open/2`, `close/1`, `destroy/1`, `contains/2`, `get/2`, `put/3`, `clear/2`, `write_batch/2`, `scan/4`, `clear_range/4`, `fold_keys/4`, `fold/4`, `is_empty/1`.
7 |
8 |
9 |
10 | ## Data Types ##
11 |
12 |
13 |
14 |
15 | ### fold_options() ###
16 |
17 |
18 |
19 |
20 | fold_options() = [{start_key, binary()} | {end_key, binary()} | {gt, binary()} | {gte, binary()} | {lt, binary()} | {lte, binary()} | {max, integer()}]
21 |
22 |
23 |
24 |
25 |
26 |
27 | ### kv() ###
28 |
29 |
30 |
31 |
32 | kv() = {Key::binary(), Value::binary()}
33 |
34 |
35 |
36 |
37 |
38 |
39 | ### kvs() ###
40 |
41 |
42 |
43 |
44 | kvs() = [kv()]
45 |
46 |
47 |
48 |
49 |
50 |
51 | ### write_ops() ###
52 |
53 |
54 |
55 |
56 | write_ops() = [{put, Key::binary(), Value::any()} | {delete, Key::binary()} | clear]
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/rebar_dev.config:
--------------------------------------------------------------------------------
1 | %% -*- mode: erlang -*-
2 | {cover_enabled, true}.
3 |
4 | {erl_opts, [
5 | warnings_as_errors,
6 | debug_info,
7 | {i, "include"},
8 | {i, "deps"},
9 | {src_dirs, ["src"]}
10 | ]}.
11 |
12 | {xref_checks, [undefined_function_calls]}.
13 |
14 |
15 | {eunit_opts, [verbose]}.
16 |
17 | {require_otp_vsn, "R16|17|18"}.
18 |
19 | {erl_first_files, [ "src/rkvs_storage_backend.erl"]}.
20 |
21 | {clean_files, ["*~","*/*~","*/*.xfm","test/*.beam", "test/temp/*"]}.
22 |
23 | {deps, [
24 | {eleveldb, ".*", {git, "https://github.com/basho/eleveldb.git",
25 | {tag, "2.1.0"}}},
26 |
27 | {erocksdb, ".*", {git, "https://github.com/leo-project/erocksdb.git",
28 | {tag, "0.4"}}},
29 |
30 | {hanoidb, ".*", {git, "https://github.com/krestenkrab/hanoidb.git",
31 | "4e82d5f81ab087f038bfd13354ff48ee9113f459"}},
32 |
33 | {bitcask, ".*", {git, "https://github.com/basho/bitcask.git",
34 | {tag, "2.0.0"}}},
35 |
36 | {edown, ".*",
37 | {git, "git://github.com/esl/edown.git", "HEAD"}}
38 |
39 | ]}.
40 |
41 | {edoc_opts, [{application, ["rkvs"]},
42 | {doclet, edown_doclet},
43 | {subpackages, false},
44 | {top_level_readme,
45 | {"./README.md", "http://github.com/refuge/rkvs"}}]}.
46 |
--------------------------------------------------------------------------------
/src/rkvs_util.erl:
--------------------------------------------------------------------------------
1 | %%-*- mode: erlang -*-
2 | %%
3 | %% This Source Code Form is subject to the terms of the Mozilla Public
4 | %% License, v. 2.0. If a copy of the MPL was not distributed with this
5 | %% file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 |
7 | -module(rkvs_util).
8 |
9 | -include("rkvs.hrl").
10 |
11 | -export([fold_options/2]).
12 | -export([enc/3, dec/3]).
13 |
14 | fold_options([], Options) ->
15 | Options;
16 | fold_options([{start_key, Start} | Rest], Options)
17 | when is_binary(Start) or (Start =:= first) ->
18 | fold_options(Rest, Options#fold_options{gte=Start});
19 | fold_options([{end_key, End} | Rest], Options)
20 | when is_binary(End) or (End == nil) ->
21 | fold_options(Rest, Options#fold_options{lte=End});
22 | fold_options([{gt, GT} | Rest], Options)
23 | when is_binary(GT) or (GT =:= first) ->
24 | fold_options(Rest, Options#fold_options{gt=GT});
25 | fold_options([{gte, GT} | Rest], Options)
26 | when is_binary(GT) or (GT =:= first) ->
27 | fold_options(Rest, Options#fold_options{gte=GT});
28 | fold_options([{lt, LT} | Rest], Options)
29 | when is_binary(LT) or (LT == nil) ->
30 | fold_options(Rest, Options#fold_options{lt=LT});
31 | fold_options([{lte, LT} | Rest], Options)
32 | when is_binary(LT) or (LT == nil) ->
33 | fold_options(Rest, Options#fold_options{lte=LT});
34 | fold_options([{max, Max} | Rest], Options) ->
35 | fold_options(Rest, Options#fold_options{max=Max});
36 | fold_options([_ | Rest], Options) ->
37 | fold_options(Rest, Options).
38 |
39 | enc(_, V, raw) -> V;
40 | enc(_, V, term) -> term_to_binary(V);
41 | enc(_, V, {term, Opts}) -> term_to_binary(V, Opts);
42 | enc(_, V, sext) -> sext:encode(V);
43 | enc(T, V, E) -> erlang:error({cannot_encode, [T, V, E]}).
44 |
45 | dec(_, V, raw) -> V;
46 | dec(_, V, term) -> binary_to_term(V);
47 | dec(_, V, {term, _}) -> binary_to_term(V);
48 | dec(_, V, sext) -> sext:decode(V);
49 | dec(T, V, E) -> erlang:error({cannot_decode, [T, V, E]}).
50 |
--------------------------------------------------------------------------------
/src/rkvs_storage_backend.erl:
--------------------------------------------------------------------------------
1 | %%-*- mode: erlang -*-
2 | %%
3 | %% This Source Code Form is subject to the terms of the Mozilla Public
4 | %% License, v. 2.0. If a copy of the MPL was not distributed with this
5 | %% file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 |
7 | -module(rkvs_storage_backend).
8 |
9 | -include("rkvs.hrl").
10 |
11 | -type write_ops() :: [{put, Key :: binary(), Value :: any()} |
12 | {delete, Key :: binary()} |
13 | clear].
14 |
15 | -type kv() :: {Key:: binary(), Value::binary()}.
16 | -type kvs() :: [kv()].
17 |
18 | -type fold_options() :: [{start_key, binary()} |
19 | {end_key, binary()} |
20 | {gt, binary()} |
21 | {gte, binary()} |
22 | {lt, binary()} |
23 | {lte, binary()} |
24 | {max, integer()}].
25 |
26 |
27 | -export_type([write_ops/0,
28 | kv/0,
29 | kvs/0,
30 | fold_options/0]).
31 |
32 | -callback open(Name :: binary(), Options :: list()) ->
33 | {ok, Engine :: engine()}
34 | | {error, Reason :: any()}.
35 |
36 | -callback close(Engine :: engine()) ->
37 | ok
38 | | {error, Reason :: any()}.
39 |
40 | -callback destroy(Engine :: engine()) ->
41 | ok
42 | | {error, Reason :: any()}.
43 |
44 |
45 | -callback contains(Engine :: engine(), Key :: binary()) ->
46 | true
47 | | false.
48 |
49 |
50 | -callback get(Engine :: engine(), Key :: binary()) ->
51 | {ok, Value :: any()}
52 | | {error, Reason :: any()}.
53 |
54 | -callback put(Engine :: engine(), Key :: binary(), Value :: any()) ->
55 | ok
56 | | {error, Reason :: any()}.
57 |
58 |
59 | -callback clear(Engine :: engine(), Key :: binary()) ->
60 | ok
61 | | {error, Reason :: any()}.
62 |
63 |
64 | -callback write_batch(Engine :: engine(), Ops :: write_ops()) ->
65 | ok
66 | | {error, Reason :: any()}.
67 |
68 |
69 | -callback scan(Engine :: engine(), Start :: binary(), End :: binary(),
70 | Max :: integer()) ->
71 | KeyValues :: kvs()
72 | | {error, Reason :: any()}.
73 |
74 | -callback clear_range(Engine :: engine(), Start :: binary(), End :: binary(),
75 | Max :: integer()) ->
76 | ok
77 | | {error, Reason :: any()}.
78 |
79 |
80 | -callback fold_keys(Engine :: engine(), Fun :: function(), AccIn :: any(),
81 | Options :: fold_options()) ->
82 | AccOut :: any()
83 | | {error, Reason :: any()}.
84 |
85 | -callback fold(Engine :: engine(), Fun :: function(), AccIn :: any(),
86 | Options :: fold_options()) ->
87 | AccOut :: any()
88 | | {error, Reason :: any()}.
89 |
90 | -callback is_empty(Engine :: engine()) ->
91 | boolean()
92 | | {error, term()}.
93 |
--------------------------------------------------------------------------------
/doc/rkvs_bitcask.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module rkvs_bitcask #
4 | * [Description](#description)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 |
9 | bitcask backend.
10 | __Behaviours:__ [`rkvs_storage_backend`](rkvs_storage_backend.md).
11 |
12 |
13 | ## Description ##
14 |
15 |
16 |
17 | You can pass any options from
18 | [bitcask](https://github.com/basho/bitcask)
19 | when opening the database using the db_opts settings.
20 |
21 |
22 | Optionnaly you can pass a db_dir option to set the path of the database.
23 |
24 | ## Function Index ##
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | ## Function Details ##
33 |
34 |
35 |
36 | ### clear/2 ###
37 |
38 | `clear(Engine, Key) -> any()`
39 |
40 |
41 |
42 |
43 | ### clear_range/4 ###
44 |
45 | `clear_range(Engine, Start, End, Max) -> any()`
46 |
47 |
48 |
49 |
50 | ### close/1 ###
51 |
52 | `close(Engine) -> any()`
53 |
54 |
55 |
56 |
57 | ### contains/2 ###
58 |
59 | `contains(Engine, Key) -> any()`
60 |
61 |
62 |
63 |
64 | ### destroy/1 ###
65 |
66 | `destroy(Engine) -> any()`
67 |
68 |
69 |
70 |
71 | ### fold/4 ###
72 |
73 | `fold(Engine, Fun, Acc, Opts) -> any()`
74 |
75 |
76 |
77 |
78 | ### fold_keys/4 ###
79 |
80 | `fold_keys(Engine, Fun, Acc, Opts) -> any()`
81 |
82 |
83 |
84 |
85 | ### get/2 ###
86 |
87 | `get(Engine, Key) -> any()`
88 |
89 |
90 |
91 |
92 | ### is_empty/1 ###
93 |
94 | `is_empty(Engine) -> any()`
95 |
96 |
97 |
98 |
99 | ### open/2 ###
100 |
101 | `open(Name, Options) -> any()`
102 |
103 |
104 |
105 |
106 | ### put/3 ###
107 |
108 | `put(Engine, Key, Value) -> any()`
109 |
110 |
111 |
112 |
113 | ### scan/4 ###
114 |
115 | `scan(Engine, Start, End, Max) -> any()`
116 |
117 |
118 |
119 |
120 | ### write_batch/2 ###
121 |
122 | `write_batch(Engine, Ops) -> any()`
123 |
124 |
125 |
--------------------------------------------------------------------------------
/doc/rkvs_hanoidb.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module rkvs_hanoidb #
4 | * [Description](#description)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 |
9 | hanoidb backend.
10 | __Behaviours:__ [`rkvs_storage_backend`](rkvs_storage_backend.md).
11 |
12 |
13 | ## Description ##
14 |
15 |
16 |
17 | You can pass any options from
18 | [hanoidb](https://github.com/bkrestenkra/erocksdb)
19 | when opening the database using the db_opts settings.
20 |
21 |
22 | Optionnaly you can pass a db_dir option to set the path of the database.
23 |
24 | ## Function Index ##
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | ## Function Details ##
33 |
34 |
35 |
36 | ### clear/2 ###
37 |
38 | `clear(Engine, Key) -> any()`
39 |
40 |
41 |
42 |
43 | ### clear_range/4 ###
44 |
45 | `clear_range(Engine, Start, End, Max) -> any()`
46 |
47 |
48 |
49 |
50 | ### close/1 ###
51 |
52 | `close(Engine) -> any()`
53 |
54 |
55 |
56 |
57 | ### contains/2 ###
58 |
59 | `contains(Engine, Key) -> any()`
60 |
61 |
62 |
63 |
64 | ### destroy/1 ###
65 |
66 | `destroy(Engine) -> any()`
67 |
68 |
69 |
70 |
71 | ### fold/4 ###
72 |
73 | `fold(Engine, Fun, Acc0, Opts) -> any()`
74 |
75 |
76 |
77 |
78 | ### fold_keys/4 ###
79 |
80 | `fold_keys(Engine, Fun, Acc0, Opts) -> any()`
81 |
82 |
83 |
84 |
85 | ### get/2 ###
86 |
87 | `get(Engine, Key) -> any()`
88 |
89 |
90 |
91 |
92 | ### is_empty/1 ###
93 |
94 | `is_empty(Engine) -> any()`
95 |
96 |
97 |
98 |
99 | ### open/2 ###
100 |
101 | `open(Name, Options) -> any()`
102 |
103 |
104 |
105 |
106 | ### put/3 ###
107 |
108 | `put(Engine, Key, Value) -> any()`
109 |
110 |
111 |
112 |
113 | ### scan/4 ###
114 |
115 | `scan(Engine, Start, End, Max) -> any()`
116 |
117 |
118 |
119 |
120 | ### write_batch/2 ###
121 |
122 | `write_batch(Engine, Ops0) -> any()`
123 |
124 |
125 |
--------------------------------------------------------------------------------
/doc/rkvs_ets.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module rkvs_ets #
4 | * [Description](#description)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 |
9 | ETS backend
10 | Note: value encoding is ignored since everything is encoded as a term.
11 | __Behaviours:__ [`rkvs_storage_backend`](rkvs_storage_backend.md).
12 |
13 |
14 | ## Function Index ##
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | ## Function Details ##
23 |
24 |
25 |
26 | ### clear/2 ###
27 |
28 | `clear(Engine, Key) -> any()`
29 |
30 |
31 |
32 |
33 | ### clear_range/4 ###
34 |
35 | `clear_range(Engine, Start, End, Max) -> any()`
36 |
37 |
38 |
39 |
40 | ### close/1 ###
41 |
42 | `close(Engine) -> any()`
43 |
44 |
45 |
46 |
47 | ### contains/2 ###
48 |
49 | `contains(Engine, Key) -> any()`
50 |
51 |
52 |
53 |
54 | ### destroy/1 ###
55 |
56 | `destroy(Engine) -> any()`
57 |
58 |
59 |
60 |
61 | ### fold/4 ###
62 |
63 | `fold(Engine, Fun, Acc0, Opts) -> any()`
64 |
65 |
66 |
67 |
68 | ### fold_keys/4 ###
69 |
70 | `fold_keys(Engine, Fun, Acc0, Opts) -> any()`
71 |
72 |
73 |
74 |
75 | ### get/2 ###
76 |
77 | `get(Engine, Key) -> any()`
78 |
79 |
80 |
81 |
82 | ### is_empty/1 ###
83 |
84 |
85 |
86 | is_empty(Engine::engine()) -> boolean() | {error, term()}
87 |
88 |
89 |
90 | Returns true if this backend contains any values; otherwise returns false.
91 |
92 |
93 | ### open/2 ###
94 |
95 | `open(Name, Options) -> any()`
96 |
97 |
98 |
99 |
100 | ### put/3 ###
101 |
102 | `put(Engine, Key, Value) -> any()`
103 |
104 |
105 |
106 |
107 | ### scan/4 ###
108 |
109 | `scan(Engine, Start, End, Max) -> any()`
110 |
111 |
112 |
113 |
114 | ### write_batch/2 ###
115 |
116 | `write_batch(Engine, Ops) -> any()`
117 |
118 |
119 |
--------------------------------------------------------------------------------
/doc/rkvs_rocksdb.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module rkvs_rocksdb #
4 | * [Description](#description)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 |
9 | erocksdb backend.
10 | __Behaviours:__ [`rkvs_storage_backend`](rkvs_storage_backend.md).
11 |
12 |
13 | ## Description ##
14 |
15 |
16 |
17 | You can pass any options from
18 | [erocksdb](https://github.com/leo-project/erocksdb)
19 | when opening the database using the db_opts settings.
20 |
21 |
22 | Optionnaly you can pass a db_dir option to set the path of the database.
23 |
24 | ## Function Index ##
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | ## Function Details ##
33 |
34 |
35 |
36 | ### clear/2 ###
37 |
38 | `clear(Engine, Key) -> any()`
39 |
40 |
41 |
42 |
43 | ### clear_range/4 ###
44 |
45 | `clear_range(Engine, Start, End, Max) -> any()`
46 |
47 |
48 |
49 |
50 | ### close/1 ###
51 |
52 | `close(Engine) -> any()`
53 |
54 |
55 |
56 |
57 | ### contains/2 ###
58 |
59 | `contains(Engine, Key) -> any()`
60 |
61 |
62 |
63 |
64 | ### destroy/1 ###
65 |
66 | `destroy(Engine) -> any()`
67 |
68 |
69 |
70 |
71 | ### fold/4 ###
72 |
73 | `fold(Engine, Fun, Acc0, Opts) -> any()`
74 |
75 |
76 |
77 |
78 | ### fold_keys/4 ###
79 |
80 | `fold_keys(Engine, Fun, Acc0, Opts) -> any()`
81 |
82 |
83 |
84 |
85 | ### get/2 ###
86 |
87 | `get(Engine, Key) -> any()`
88 |
89 |
90 |
91 |
92 | ### is_empty/1 ###
93 |
94 |
95 |
96 | is_empty(Engine::engine()) -> boolean() | {error, term()}
97 |
98 |
99 |
100 | Returns true if this backend contains any values; otherwise returns false.
101 |
102 |
103 | ### open/2 ###
104 |
105 | `open(Name, Options) -> any()`
106 |
107 |
108 |
109 |
110 | ### put/3 ###
111 |
112 | `put(Engine, Key, Value) -> any()`
113 |
114 |
115 |
116 |
117 | ### scan/4 ###
118 |
119 | `scan(Engine, Start, End, Max) -> any()`
120 |
121 |
122 |
123 |
124 | ### write_batch/2 ###
125 |
126 | `write_batch(Engine, Ops0) -> any()`
127 |
128 |
129 |
--------------------------------------------------------------------------------
/doc/rkvs_leveldb.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module rkvs_leveldb #
4 | * [Description](#description)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 |
9 | eleveldb backendi.
10 | __Behaviours:__ [`rkvs_storage_backend`](rkvs_storage_backend.md).
11 |
12 |
13 | ## Description ##
14 |
15 |
16 |
17 | You can pass any options from
18 | [eleveldb](https://github.com/basho/eleveldb/blob/develop/src/eleveldb.erl)
19 | when opening the database using the db_opts settings.
20 |
21 |
22 | Optionnaly you can pass a db_dir option to set the path of the database.
23 |
24 | ## Function Index ##
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | ## Function Details ##
33 |
34 |
35 |
36 | ### clear/2 ###
37 |
38 | `clear(Engine, Key) -> any()`
39 |
40 |
41 |
42 |
43 | ### clear_range/4 ###
44 |
45 | `clear_range(Engine, Start, End, Max) -> any()`
46 |
47 |
48 |
49 |
50 | ### close/1 ###
51 |
52 | `close(Engine) -> any()`
53 |
54 |
55 |
56 |
57 | ### contains/2 ###
58 |
59 | `contains(Engine, Key) -> any()`
60 |
61 |
62 |
63 |
64 | ### destroy/1 ###
65 |
66 | `destroy(Engine) -> any()`
67 |
68 |
69 |
70 |
71 | ### fold/4 ###
72 |
73 | `fold(Engine, Fun, Acc0, Opts) -> any()`
74 |
75 |
76 |
77 |
78 | ### fold_keys/4 ###
79 |
80 | `fold_keys(Engine, Fun, Acc0, Opts) -> any()`
81 |
82 |
83 |
84 |
85 | ### get/2 ###
86 |
87 | `get(Engine, Key) -> any()`
88 |
89 |
90 |
91 |
92 | ### is_empty/1 ###
93 |
94 |
95 |
96 | is_empty(Engine::engine()) -> boolean() | {error, term()}
97 |
98 |
99 |
100 | Returns true if this backend contains any values; otherwise returns false.
101 |
102 |
103 | ### open/2 ###
104 |
105 | `open(Name, Options) -> any()`
106 |
107 |
108 |
109 |
110 | ### put/3 ###
111 |
112 | `put(Engine, Key, Value) -> any()`
113 |
114 |
115 |
116 |
117 | ### scan/4 ###
118 |
119 | `scan(Engine, Start, End, Max) -> any()`
120 |
121 |
122 |
123 |
124 | ### write_batch/2 ###
125 |
126 | `write_batch(Engine, Ops0) -> any()`
127 |
128 |
129 |
--------------------------------------------------------------------------------
/doc/overview.edoc:
--------------------------------------------------------------------------------
1 | %% This Source Code Form is subject to the terms of the Mozilla Public
2 | %% License, v. 2.0. If a copy of the MPL was not distributed with this
3 | %% file, You can obtain one at http://mozilla.org/MPL/2.0/.
4 |
5 | @copyright 2014-2015 Benoît Chesneau.
6 | @version 0.2.1
7 | @title rkvs - Simple Erlang Key/Values interface
8 |
9 | @doc
10 |
11 | rkvs is a simple Key-Value database interface. It offers for now a frontend to
12 | the following K/V storages: ets, leveldb, rocksdb, hanoidb, bitcask.
13 |
14 |
15 | ## Example of usage
16 |
17 | ### Enable a backend
18 |
19 | To enable one of the backend, add one of the following line to your rebar or
20 | make the depency availavle in ERL_LIBS:
21 |
22 | ```
23 | {leveldb, ".*", {git, "https://github.com/basho/eleveldb.git",
24 | {tag, "2.1.0"}}},
25 |
26 | {erocksdb, ".*", {git, "https://github.com/leo-project/erocksdb.git",
27 | {branch, "develop"}}},
28 |
29 | {hanoidb, ".*", {git, "https://github.com/krestenkrab/hanoidb.git",
30 | "4e82d5f81ab087f038bfd13354ff48ee9113f459"}},
31 |
32 | {bitcask, ".*", {git, "https://github.com/basho/bitcask.git",
33 | {tag, "2.0.0"}}},
34 | '''
35 |
36 | ### Create a database:
37 |
38 | ```
39 | Name = "mydb",
40 | {ok, Engine} = rkvs:open(Name, [{backend, rkvs_leveldb}]).
41 | '''
42 |
43 | 5 backends are available:
44 |
45 | - `rkvs_ets': ETS backend
46 | - `rkvs_leveldb': LevelDB backend using [eleveldb](https://github.com/basho/eleveldb) from Basho.
47 | - `rkvs_rocksdb': [rocksdb](http://rocksdb.org) backend using
48 | [erocksdb](https://github.com/leo-project/erocksdb).
49 | - `rkvs_hanoidb': [hanoidb](https://github.com/krestenkrab/hanoidb) backend
50 | - `rkvs_bitcask': [bitcask](https://github.com/basho/bitcask) backend
51 |
52 | ### Store a values
53 |
54 | Storing a value associated to a key using `rkvs:put/3':
55 |
56 | ```
57 | Key = <<"a">>,
58 | Value = 1,
59 | ok = rkvs:put(Engine, Key, Value).
60 | '''
61 |
62 | ### Retrieve a value
63 |
64 | Use the `rkvs:get/2' function to retrieve a value.
65 |
66 | ```
67 | Value = rkvs:get(Engine, Key).
68 | '''
69 |
70 | Value should be 1
71 |
72 | > Note: you can use `rkvs:contains/2'.
73 |
74 | ### Delete a value
75 |
76 | Use `rkvs:clear/2' to delete a value:
77 |
78 | ```
79 | ok = rkvs:clear(Engine, Key).
80 | '''
81 |
82 |
83 | ### Store and write multiples values in one pass:
84 |
85 | Using `rkvs:write_batch/2' you can write and delete multiple values in one
86 | pass:
87 |
88 | ```
89 | ok = rkvs:write_batch(Engine, [{put, <<"a">>, 1},
90 | {put, <<"b">>, 2},
91 | {put, <<"c">>, 3}]),
92 |
93 | ok = rkvs:write_batch(Engine, [{put, <<"d">>, 4},
94 | {delete, <<"b">>},
95 | {put, <<"e">>, 5}]).
96 | '''
97 |
98 |
99 | ### Retrieve multiple values in a range:
100 |
101 | Use `rkvs:scan/4' to retrieve multiples K/Vs as a list:
102 |
103 | ```
104 | Result = rkvs:scan(Engine, first, nil, 0).
105 | '''
106 |
107 | Result should be `[{<<"a">>,1},{<<"c">>,3},{<<"d">>,4},{<<"e">>,5}]'
108 |
109 | > Use `rkvs:fold/5' to pass a function to keys in a range instead of retrieving
110 | > all the values as a list. `rkvs:clear_range/4' can be used to clear all the
111 | > values in a range.
112 |
113 |
114 | ### Close a storage
115 |
116 | Close a storage using `rkvs:close/1':
117 |
118 | ```
119 | rkvs:close(Engine)
120 | '''
121 |
122 | > You can use `rkvs:destroy/1' to close and delete the full storage
123 |
124 |
125 |
126 | ## Ownership and License
127 |
128 | The contributors are listed in AUTHORS. This project uses the MPL v2
129 | license, see LICENSE.
130 |
131 | rkvs uses the [C4.1 (Collective Code Construction
132 | Contract)](http://rfc.zeromq.org/spec:22) process for contributions.
133 |
134 | ## Development
135 |
136 | Under C4.1 process, you are more than welcome to help us by:
137 |
138 | * join the discussion over anything from design to code style try out
139 | * and [submit issue reports](https://github.com/refuge/rkvs/issues/new)
140 | * or feature requests pick a task in
141 | * [issues](https://github.com/refuge/rkvs/issues) and get it done fork
142 | * the repository and have your own fixes send us pull requests and even
143 | * star this project ^_^
144 |
145 | To run the test suite:
146 |
147 | ```
148 | make test
149 | '''
150 |
151 | @end
152 |
--------------------------------------------------------------------------------
/test/rkvs_bitcask_tests.erl:
--------------------------------------------------------------------------------
1 | %%-*- mode: erlang -*-
2 | %%
3 | %% This Source Code Form is subject to the terms of the Mozilla Public
4 | %% License, v. 2.0. If a copy of the MPL was not distributed with this
5 | %% file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 | %%
7 | -module(rkvs_bitcask_tests).
8 |
9 | -include_lib("include/rkvs.hrl").
10 | -include("rkvs_tests.hrl").
11 |
12 |
13 | basic_test() ->
14 | {ok, Engine} = rkvs:open(?tempdb(), [{backend, rkvs_bitcask}]),
15 | ?assertMatch(ok, rkvs:put(Engine, <<"a">>, 1)),
16 | ?assertMatch(1, rkvs:get(Engine, <<"a">>)),
17 | rkvs:destroy(Engine).
18 |
19 | contains_test() ->
20 | {ok, Engine} = rkvs:open(?tempdb(), [{backend, rkvs_bitcask}]),
21 | ?assert(rkvs:contains(Engine, <<"a">>) =:= false),
22 | ?assertMatch({error, not_found}, rkvs:get(Engine, <<"a">>)),
23 | ok = rkvs:put(Engine, <<"a">>, 1),
24 | ?assert(rkvs:contains(Engine, <<"a">>)),
25 | rkvs:destroy(Engine).
26 |
27 | delete_test() ->
28 | {ok, Engine} = rkvs:open(?tempdb(), [{backend, rkvs_bitcask}]),
29 | ok = rkvs:put(Engine, <<"a">>, 1),
30 | ?assertMatch(ok, rkvs:clear(Engine, <<"a">>)),
31 | ?assertMatch(false, rkvs:contains(Engine, <<"a">>)),
32 | ?assertMatch(ok, rkvs:clear(Engine, <<"a">>)),
33 | rkvs:destroy(Engine).
34 |
35 | update_test() ->
36 | {ok, Engine} = rkvs:open(?tempdb(), [{backend, rkvs_bitcask}]),
37 | ok = rkvs:put(Engine, <<"a">>, 1),
38 | ok = rkvs:put(Engine, <<"a">>, 2),
39 | ?assertMatch(2, rkvs:get(Engine, <<"a">>)),
40 | rkvs:destroy(Engine).
41 |
42 | fold_test() ->
43 | {ok, Engine} = rkvs:open(?tempdb(), [{backend, rkvs_bitcask}]),
44 | ok = rkvs:put(Engine, <<"a">>, 1),
45 | ok = rkvs:put(Engine, <<"b">>, 2),
46 | ok = rkvs:put(Engine, <<"c">>, 3),
47 |
48 | %% test range scanning
49 | ?assertMatch([{<<"a">>, 1}, {<<"b">>, 2}, {<<"c">>, 3}],
50 | rkvs:scan(Engine, first, nil, 0)),
51 |
52 | ?assertMatch([{<<"b">>, 2}, {<<"c">>, 3}],
53 | rkvs:scan(Engine, <<"b">>, nil, 0)),
54 |
55 |
56 | %% test max
57 | ok = rkvs:put(Engine, <<"d">>, 4),
58 |
59 | ?assertMatch([{<<"b">>, 2}, {<<"c">>, 3}],
60 | rkvs:scan(Engine, <<"b">>, <<"c">>, 0)),
61 |
62 |
63 | ?assertMatch([{<<"a">>, 1}, {<<"b">>, 2}],
64 | rkvs:scan(Engine, first, nil, 2)),
65 |
66 |
67 | ?assertMatch([{<<"b">>, 2}],
68 | rkvs:scan(Engine, <<"b">>, <<"c">>, 1)),
69 |
70 |
71 | %% test gt, lt, ge, lte
72 | AccFun = fun({K, V}, Acc) ->
73 | [{K, V} | Acc]
74 | end,
75 |
76 | ?assertMatch([{<<"b">>, 2}, {<<"c">>, 3}, {<<"d">>, 4}],
77 | lists:reverse(rkvs:fold(Engine, AccFun, [], [{gt, <<"a">>}]))),
78 |
79 |
80 | ?assertMatch([{<<"a">>, 1}, {<<"b">>, 2}, {<<"c">>, 3}],
81 | lists:reverse(rkvs:fold(Engine, AccFun, [], [{lt, <<"d">>}]))),
82 |
83 | ?assertMatch([{<<"b">>, 2}, {<<"c">>, 3}],
84 | lists:reverse(rkvs:fold(Engine, AccFun, [], [{gt, <<"a">>},
85 | {lt, <<"d">>}]))),
86 |
87 | ?assertMatch([{<<"b">>, 2}],
88 | rkvs:fold(Engine, AccFun, [], [{gt, <<"a">>},
89 | {lt, <<"d">>},
90 | {max, 1}])),
91 |
92 | rkvs:destroy(Engine).
93 |
94 | clear_range_test() ->
95 | {ok, Engine} = rkvs:open(?tempdb(), [{backend, rkvs_bitcask}]),
96 | ok = rkvs:put(Engine, <<"a">>, 1),
97 | ok = rkvs:put(Engine, <<"b">>, 2),
98 | ok = rkvs:put(Engine, <<"c">>, 3),
99 | ok = rkvs:clear_range(Engine, first, nil, 0),
100 | ?assertMatch([], rkvs:scan(Engine, first, nil, 0)),
101 | rkvs:destroy(Engine).
102 |
103 | write_batch_test() ->
104 | {ok, Engine} = rkvs:open(?tempdb(), [{backend, rkvs_bitcask}]),
105 | ok = rkvs:write_batch(Engine, [{put, <<"a">>, 1},
106 | {put, <<"b">>, 2},
107 | {put, <<"c">>, 3}]),
108 | ?assertMatch([{<<"a">>, 1}, {<<"b">>, 2}, {<<"c">>, 3}],
109 | rkvs:scan(Engine, first, nil, 0)),
110 | rkvs:destroy(Engine).
111 |
112 | write_delete_batch_test() ->
113 | {ok, Engine} = rkvs:open(?tempdb(), [{backend, rkvs_bitcask}]),
114 | ok = rkvs:write_batch(Engine, [{put, <<"a">>, 1},
115 | {put, <<"b">>, 2},
116 | {put, <<"c">>, 3}]),
117 |
118 | ok = rkvs:write_batch(Engine, [{put, <<"d">>, 4},
119 | {delete, <<"b">>},
120 | {put, <<"e">>, 5}]),
121 |
122 | ?assertMatch([{<<"a">>, 1}, {<<"c">>, 3}, {<<"d">>, 4}, {<<"e">>, 5}],
123 | rkvs:scan(Engine, first, nil, 0)),
124 | rkvs:destroy(Engine).
125 |
--------------------------------------------------------------------------------
/doc/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # rkvs - Simple Erlang Key/Values interface #
4 |
5 | Copyright (c) 2014-2015 Benoît Chesneau.
6 |
7 | __Version:__ 0.2.1
8 |
9 | rkvs is a simple Key-Value database interface. It offers for now a frontend to
10 | the following K/V storages: ets, leveldb, rocksdb, hanoidb, bitcask.
11 |
12 | ## Example of usage
13 |
14 | ### Enable a backend
15 |
16 | To enable one of the backend, add one of the following line to your rebar or
17 | make the depency availavle in ERL_LIBS:
18 |
19 | ```
20 | {leveldb, ".*", {git, "https://github.com/basho/eleveldb.git",
21 | {tag, "2.1.0"}}},
22 |
23 | {erocksdb, ".*", {git, "https://github.com/leo-project/erocksdb.git",
24 | {branch, "develop"}}},
25 |
26 | {hanoidb, ".*", {git, "https://github.com/krestenkrab/hanoidb.git",
27 | "4e82d5f81ab087f038bfd13354ff48ee9113f459"}},
28 |
29 | {bitcask, ".*", {git, "https://github.com/basho/bitcask.git",
30 | {tag, "2.0.0"}}},
31 | ```
32 |
33 | ### Create a database:
34 |
35 | ```
36 | Name = "mydb",
37 | {ok, Engine} = rkvs:open(Name, [{backend, rkvs_leveldb}]).
38 | ```
39 |
40 | 5 backends are available:
41 |
42 | - `rkvs_ets`: ETS backend
43 | - `rkvs_leveldb`: LevelDB backend using [eleveldb](https://github.com/basho/eleveldb) from Basho.
44 | - `rkvs_rocksdb`: [rocksdb](http://rocksdb.org) backend using
45 | [erocksdb](https://github.com/leo-project/erocksdb).
46 | - `rkvs_hanoidb`: [hanoidb](https://github.com/krestenkrab/hanoidb) backend
47 | - `rkvs_bitcask`: [bitcask](https://github.com/basho/bitcask) backend
48 |
49 | ### Store a values
50 |
51 | Storing a value associated to a key using `rkvs:put/3`:
52 |
53 | ```
54 | Key = <<"a">>,
55 | Value = 1,
56 | ok = rkvs:put(Engine, Key, Value).
57 | ```
58 |
59 | ### Retrieve a value
60 |
61 | Use the `rkvs:get/2` function to retrieve a value.
62 |
63 | ```
64 | Value = rkvs:get(Engine, Key).
65 | ```
66 |
67 | Value should be 1
68 |
69 | > Note: you can use `rkvs:contains/2`.
70 |
71 | ### Delete a value
72 |
73 | Use `rkvs:clear/2` to delete a value:
74 |
75 | ```
76 | ok = rkvs:clear(Engine, Key).
77 | ```
78 |
79 | ### Store and write multiples values in one pass:
80 |
81 | Using `rkvs:write_batch/2` you can write and delete multiple values in one
82 | pass:
83 |
84 | ```
85 | ok = rkvs:write_batch(Engine, [{put, <<"a">>, 1},
86 | {put, <<"b">>, 2},
87 | {put, <<"c">>, 3}]),
88 |
89 | ok = rkvs:write_batch(Engine, [{put, <<"d">>, 4},
90 | {delete, <<"b">>},
91 | {put, <<"e">>, 5}]).
92 | ```
93 |
94 | ### Retrieve multiple values in a range:
95 |
96 | Use `rkvs:scan/4` to retrieve multiples K/Vs as a list:
97 |
98 | ```
99 | Result = rkvs:scan(Engine, first, nil, 0).
100 | ```
101 |
102 | Result should be `[{<<"a">>,1},{<<"c">>,3},{<<"d">>,4},{<<"e">>,5}]`
103 |
104 | > Use `rkvs:fold/5` to pass a function to keys in a range instead of retrieving
105 | > all the values as a list. `rkvs:clear_range/4` can be used to clear all the
106 | > values in a range.
107 |
108 | ### Close a storage
109 |
110 | Close a storage using `rkvs:close/1`:
111 |
112 | ```
113 | rkvs:close(Engine)
114 | ```
115 |
116 | > You can use `rkvs:destroy/1` to close and delete the full storage
117 |
118 | ## Ownership and License
119 |
120 | The contributors are listed in AUTHORS. This project uses the MPL v2
121 | license, see LICENSE.
122 |
123 | rkvs uses the [C4.1 (Collective Code Construction
124 | Contract)](http://rfc.zeromq.org/spec:22) process for contributions.
125 |
126 | ## Development
127 |
128 | Under C4.1 process, you are more than welcome to help us by:
129 |
130 | * join the discussion over anything from design to code style try out
131 | * and [submit issue reports](https://github.com/refuge/rkvs/issues/new)
132 | * or feature requests pick a task in
133 | * [issues](https://github.com/refuge/rkvs/issues) and get it done fork
134 | * the repository and have your own fixes send us pull requests and even
135 | * star this project ^_^
136 |
137 | To run the test suite:
138 |
139 | ```
140 | make test
141 | ```
142 |
143 |
144 |
145 | ## Modules ##
146 |
147 |
148 |
157 |
158 |
--------------------------------------------------------------------------------
/src/rkvs.erl:
--------------------------------------------------------------------------------
1 | -module(rkvs).
2 |
3 | -export([open/2,
4 | close/1,
5 | destroy/1,
6 | contains/2,
7 | get/2,
8 | put/3,
9 | clear/2,
10 | write_batch/2,
11 | scan/4,
12 | clear_range/4,
13 | fold/4,
14 | fold_keys/4,
15 | is_empty/1]).
16 |
17 | -include("rkvs.hrl").
18 |
19 | -type key() :: term().
20 | -type keys() :: [key()].
21 | -type value() :: term().
22 | -type kvs() :: [{key(), value()}].
23 | -type ops_kvs() :: [{put, key(), value()}
24 | | {delete, key(), value()}].
25 | -type fold_options() :: [{start_key, key()} |
26 | {end_key, key()} |
27 | {gt, key()} | {gte, key()} |
28 | {lt, key()} | {lte, key()} |
29 | {max, integer()} |
30 | {fill_cache, true | false}].
31 |
32 | -export_type([key/0, keys/0,
33 | value/0,
34 | kvs/0,
35 | ops_kvs/0]).
36 |
37 | %% @doc open a storage, amd pass options to the backend.
38 | %% The following optinos can be used:
39 | %%
40 | %% - 'backend': default is rkvs_ets, folowin backend are provided in rkvs:
41 | %% rkvs_ets, rkvs_leveldb, rkvs_rocksdb, rkvs_hanoidb, rvs_bitcask.
42 | %% - 'db_opts': backend options, refers to the backend doc for them
43 | %% - 'key_encoding': to encode the key. defautl is raw (binary). can be
44 | %% term, {term, Opts} or sext
45 | %% - 'value_encoding': to encode the value. defautl is term. can be
46 | %% term, {term, Opts} or sext
47 | %%
48 | -spec open(Name::binary(), Options::list()) ->
49 | {ok, engine()} | {error, any()}.
50 | open(Name, Options) ->
51 | Mod = proplists:get_value(backend, Options, rkvs_ets),
52 | Mod:open(Name, Options).
53 |
54 |
55 | %% @doc close a storage
56 | -spec close(engine()) -> ok | {error, any()}.
57 | close(#engine{mod=Mod}=Engine) ->
58 | Mod:close(Engine).
59 |
60 | %% @doc close a storage and remove all the data
61 | -spec destroy(engine()) -> ok | {error, any()}.
62 | destroy(#engine{mod=Mod}=Engine) ->
63 | Mod:destroy(Engine).
64 |
65 | %% @doc is the key exists in the storage
66 | -spec contains(engine(), key()) -> true | false.
67 | contains(#engine{mod=Mod}=Engine, Key) ->
68 | Mod:contains(Engine, Key).
69 |
70 | %% @doc get the value associated to the key
71 | -spec get(engine(), key()) -> any() | {error, term()}.
72 | get(#engine{mod=Mod}=Engine, Key) ->
73 | Mod:get(Engine, Key).
74 |
75 | %% @doc store the value associated to the key.
76 | -spec put(engine(), key(), value()) -> ok | {error, term()}.
77 | put(#engine{mod=Mod}=Engine, Key, Value) ->
78 | Mod:put(Engine, Key, Value).
79 |
80 | %% @doc delete the value associated to the key
81 | -spec clear(engine(), key()) -> ok | {error, term()}.
82 | clear(#engine{mod=Mod}=Engine, Key) ->
83 | Mod:clear(Engine, Key).
84 |
85 | %% @doc do multiple operations on the backend.
86 | -spec write_batch(engine(), ops_kvs()) -> ok | {error, term()}.
87 | write_batch(#engine{mod=Mod}=Engine, Ops) ->
88 | Mod:write_batch(Engine, Ops).
89 |
90 | %% @doc retrieve a list of Key/Value in a range
91 | -spec scan(engine(), key(), key(), integer()) -> kvs() | {error, term()}.
92 | scan(#engine{mod=Mod}=Engine, Start, End, Max) ->
93 | Mod:scan(Engine, Start, End, Max).
94 |
95 | %% @doc delete all K/Vs in a range
96 | -spec clear_range(engine(), key(), key(), integer()) -> ok | {error, term()}.
97 | clear_range(#engine{mod=Mod}=Engine, Start, End, Max) ->
98 | Mod:clear_range(Engine, Start, End, Max).
99 |
100 | %% @doc fold all keys with a function
101 | %% same parameters as in the fold function.
102 | -spec fold_keys(engine(), fun(), any(), fold_options()) -> any() | {error, term()}.
103 | fold_keys(#engine{mod=Mod}=Engine, Fun, Acc0, Opts) ->
104 | Mod:fold_keys(Engine, Fun, Acc0, Opts).
105 |
106 | %% @doc fold all K/Vs with a function
107 | %% Additionnaly you can pass the following options:
108 | %%
109 | %% - 'gt', (greater than), 'gte' (greather than or equal): define the lower
110 | %% bound of the range to fold. Only the records where the key is greater (or
111 | %% equal to) will be given to the function.
112 | %% - 'lt' (less than), 'lte' (less than or equal): define the higher bound
113 | %% of the range to fold. Only the records where the key is less than (or equal
114 | %% to) will be given to the function
115 | %% - 'start_key', 'end_key', legacy to 'gte', 'lte'
116 | %% - 'max' (default=0), the maximum of records to fold before returning the
117 | %% resut
118 | %% - 'fill_cache' (default is true): should be the data cached in
119 | %% memory?
120 | %%
121 | -spec fold(engine(), function(), any(), fold_options()) ->
122 | any() | {error, term()}.
123 | fold(#engine{mod=Mod}=Engine, Fun, Acc0, Opts) ->
124 | Mod:fold(Engine, Fun, Acc0, Opts).
125 |
126 | %% @doc Returns true if this backend contains any values; otherwise returns false.
127 | -spec is_empty(engine()) -> boolean() | {error, term()}.
128 | is_empty(#engine{mod=Mod}=Engine) ->
129 | Mod:is_empty(Engine).
130 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # rkvs - Simple Erlang Key/Values interface #
4 |
5 | Copyright (c) 2014-2015 Benoît Chesneau.
6 |
7 | __Version:__ 0.2.1
8 |
9 | rkvs is a simple Key-Value database interface. It offers for now a frontend to
10 | the following K/V storages: ets, leveldb, rocksdb, hanoidb, bitcask.
11 |
12 | ## Example of usage
13 |
14 | ### Enable a backend
15 |
16 | To enable one of the backend, add one of the following line to your rebar or
17 | make the depency availavle in ERL_LIBS:
18 |
19 | ```
20 | {leveldb, ".*", {git, "https://github.com/basho/eleveldb.git",
21 | {tag, "2.1.0"}}},
22 |
23 | {erocksdb, ".*", {git, "https://github.com/leo-project/erocksdb.git",
24 | {branch, "develop"}}},
25 |
26 | {hanoidb, ".*", {git, "https://github.com/krestenkrab/hanoidb.git",
27 | "4e82d5f81ab087f038bfd13354ff48ee9113f459"}},
28 |
29 | {bitcask, ".*", {git, "https://github.com/basho/bitcask.git",
30 | {tag, "2.0.0"}}},
31 | ```
32 |
33 | ### Create a database:
34 |
35 | ```
36 | Name = "mydb",
37 | {ok, Engine} = rkvs:open(Name, [{backend, rkvs_leveldb}]).
38 | ```
39 |
40 | 5 backends are available:
41 |
42 | - `rkvs_ets`: ETS backend
43 | - `rkvs_leveldb`: LevelDB backend using [eleveldb](https://github.com/basho/eleveldb) from Basho.
44 | - `rkvs_rocksdb`: [rocksdb](http://rocksdb.org) backend using
45 | [erocksdb](https://github.com/leo-project/erocksdb).
46 | - `rkvs_hanoidb`: [hanoidb](https://github.com/krestenkrab/hanoidb) backend
47 | - `rkvs_bitcask`: [bitcask](https://github.com/basho/bitcask) backend
48 |
49 | ### Store a values
50 |
51 | Storing a value associated to a key using `rkvs:put/3`:
52 |
53 | ```
54 | Key = <<"a">>,
55 | Value = 1,
56 | ok = rkvs:put(Engine, Key, Value).
57 | ```
58 |
59 | ### Retrieve a value
60 |
61 | Use the `rkvs:get/2` function to retrieve a value.
62 |
63 | ```
64 | Value = rkvs:get(Engine, Key).
65 | ```
66 |
67 | Value should be 1
68 |
69 | > Note: you can use `rkvs:contains/2`.
70 |
71 | ### Delete a value
72 |
73 | Use `rkvs:clear/2` to delete a value:
74 |
75 | ```
76 | ok = rkvs:clear(Engine, Key).
77 | ```
78 |
79 | ### Store and write multiples values in one pass:
80 |
81 | Using `rkvs:write_batch/2` you can write and delete multiple values in one
82 | pass:
83 |
84 | ```
85 | ok = rkvs:write_batch(Engine, [{put, <<"a">>, 1},
86 | {put, <<"b">>, 2},
87 | {put, <<"c">>, 3}]),
88 |
89 | ok = rkvs:write_batch(Engine, [{put, <<"d">>, 4},
90 | {delete, <<"b">>},
91 | {put, <<"e">>, 5}]).
92 | ```
93 |
94 | ### Retrieve multiple values in a range:
95 |
96 | Use `rkvs:scan/4` to retrieve multiples K/Vs as a list:
97 |
98 | ```
99 | Result = rkvs:scan(Engine, first, nil, 0).
100 | ```
101 |
102 | Result should be `[{<<"a">>,1},{<<"c">>,3},{<<"d">>,4},{<<"e">>,5}]`
103 |
104 | > Use `rkvs:fold/5` to pass a function to keys in a range instead of retrieving
105 | > all the values as a list. `rkvs:clear_range/4` can be used to clear all the
106 | > values in a range.
107 |
108 | ### Close a storage
109 |
110 | Close a storage using `rkvs:close/1`:
111 |
112 | ```
113 | rkvs:close(Engine)
114 | ```
115 |
116 | > You can use `rkvs:destroy/1` to close and delete the full storage
117 |
118 | ## Ownership and License
119 |
120 | The contributors are listed in AUTHORS. This project uses the MPL v2
121 | license, see LICENSE.
122 |
123 | rkvs uses the [C4.1 (Collective Code Construction
124 | Contract)](http://rfc.zeromq.org/spec:22) process for contributions.
125 |
126 | ## Development
127 |
128 | Under C4.1 process, you are more than welcome to help us by:
129 |
130 | * join the discussion over anything from design to code style try out
131 | * and [submit issue reports](https://github.com/refuge/rkvs/issues/new)
132 | * or feature requests pick a task in
133 | * [issues](https://github.com/refuge/rkvs/issues) and get it done fork
134 | * the repository and have your own fixes send us pull requests and even
135 | * star this project ^_^
136 |
137 | To run the test suite:
138 |
139 | ```
140 | make test
141 | ```
142 |
143 |
144 |
145 | ## Modules ##
146 |
147 |
148 |
157 |
158 |
--------------------------------------------------------------------------------
/src/rkvs_hanoidb.erl:
--------------------------------------------------------------------------------
1 | %%-*- mode: erlang -*-
2 | %%
3 | %% This Source Code Form is subject to the terms of the Mozilla Public
4 | %% License, v. 2.0. If a copy of the MPL was not distributed with this
5 | %% file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 |
7 | %% @doc hanoidb backend
8 | %%
9 | %% You can pass any options from
10 | %% [hanoidb](https://github.com/bkrestenkra/erocksdb)
11 | %% when opening the database using the db_opts settings.
12 | %%
13 | %% Optionnaly you can pass a db_dir option to set the path of the database.
14 |
15 | -module(rkvs_hanoidb).
16 | -behaviour(rkvs_storage_backend).
17 |
18 | -include("rkvs.hrl").
19 |
20 | -include_lib("hanoidb/include/hanoidb.hrl").
21 |
22 |
23 | -export([open/2,
24 | close/1,
25 | destroy/1,
26 | contains/2,
27 | get/2,
28 | put/3,
29 | clear/2,
30 | write_batch/2,
31 | scan/4,
32 | clear_range/4,
33 | fold/4,
34 | fold_keys/4,
35 | is_empty/1]).
36 |
37 | -import(rkvs_util, [enc/3, dec/3]).
38 |
39 | open(Name, Options) ->
40 | Path = case proplists:get_value(db_dir, Options) of
41 | undefined -> Name;
42 | Dir -> filename:join(Dir, Name)
43 | end,
44 |
45 | DbOpts = proplists:get_value(db_opts, Options, [{sync_strategy, sync}]),
46 | KeyEncoding = proplists:get_value(key_encoding, Options, raw),
47 | ValueEncoding = proplists:get_value(value_encoding, Options, term),
48 | case hanoidb:open(Path, DbOpts) of
49 | {ok, Ref} ->
50 | {ok, #engine{name=Name,
51 | mod=?MODULE,
52 | ref=Ref,
53 | key_enc=KeyEncoding,
54 | val_enc=ValueEncoding,
55 | options=Options}};
56 | Error ->
57 | Error
58 | end.
59 |
60 | close(#engine{ref=Ref}) ->
61 | hanoidb:close(Ref).
62 |
63 | destroy(#engine{ref=Ref}) ->
64 | hanoidb:destroy(Ref).
65 |
66 | contains(Engine, Key) ->
67 | Fun = fun(_K, _Acc) -> true end,
68 | fold_keys(Engine, Fun, false, [{start_key, Key},
69 | {end_key, Key},
70 | {max, 1}]).
71 |
72 |
73 | get(#engine{ref=Ref, key_enc=KE, val_enc=VE}, Key) ->
74 | case hanoidb:get(Ref, enc(key, Key, KE)) of
75 | {ok, Val} -> dec(value, Val, VE);
76 | not_found -> {error, not_found};
77 | Error -> Error
78 | end.
79 |
80 | put(#engine{ref=Ref, key_enc=KE, val_enc=VE}, Key, Value) ->
81 | hanoidb:put(Ref, enc(key, Key, KE), enc(value, Value, VE)).
82 |
83 | clear(#engine{ref=Ref, key_enc=KE}, Key) ->
84 | hanoidb:delete(Ref, enc(key, Key, KE)).
85 |
86 | write_batch(#engine{ref=Ref, key_enc=KE, val_enc=VE}, Ops0) ->
87 | Ops = lists:reverse(lists:foldl(fun
88 | ({put, K, V}, Acc) ->
89 | [{put, enc(key, K, KE), enc(value, V, VE)} | Acc];
90 | ({delete, K}, Acc) ->
91 | [{delete, enc(key, K, KE)} | Acc]
92 | end, [], Ops0)),
93 |
94 | hanoidb:transact(Ref, Ops).
95 |
96 |
97 | scan(Engine, Start, End, Max) ->
98 | AccFun = fun({K, V}, Acc) ->
99 | [{K, V} | Acc]
100 | end,
101 | AccOut = fold(Engine, AccFun, [], [{gte, Start},
102 | {lte, End},
103 | {max, Max}]),
104 | lists:reverse(AccOut).
105 |
106 | clear_range(Engine, Start, End, Max) ->
107 | AccFun = fun(K, Acc) ->
108 | [{delete, K} | Acc]
109 | end,
110 | Ops = fold_keys(Engine, AccFun, [], [{gte, Start},
111 | {lte, End},
112 | {max, Max}]),
113 | write_batch(Engine, Ops).
114 |
115 |
116 | fold_keys(#engine{ref=Ref, key_enc=KE}, Fun, Acc0, Opts) ->
117 | KeyRange = key_range(Opts, KE),
118 | WrapperFun = fun(K, _V, Acc1) ->
119 | Fun(dec(key, K, KE), Acc1)
120 | end,
121 | hanoidb:fold_range(Ref, WrapperFun, Acc0, KeyRange).
122 |
123 |
124 | fold(#engine{ref=Ref, key_enc=KE, val_enc=VE}, Fun, Acc0, Opts) ->
125 | KeyRange = key_range(Opts, KE),
126 | WrapperFun = fun(K, V, Acc1) ->
127 | Fun({dec(key, K, KE), dec(value, V, VE)}, Acc1)
128 | end,
129 | hanoidb:fold_range(Ref, WrapperFun, Acc0, KeyRange).
130 |
131 | is_empty(Engine) ->
132 | case scan(Engine, <<>>, undefined, 1) of
133 | [] -> true;
134 | _ -> false
135 | end.
136 |
137 | key_range(Opts, KE) ->
138 | key_range(Opts, KE, #key_range{}).
139 |
140 | key_range([], _KE, KR) ->
141 | KR;
142 | key_range([{start_key, Start} | Rest], KE, KR) ->
143 | key_range(Rest, KE, KR#key_range{from_key=enc(key, Start, KE),
144 | from_inclusive=true});
145 | key_range([{end_key, End} | Rest], KE, KR) ->
146 | key_range(Rest, KE, KR#key_range{to_key=enc(key, End, KE),
147 | to_inclusive=true});
148 | key_range([{gt, Start} | Rest], KE, KR) ->
149 | key_range(Rest, KE, KR#key_range{from_key=enc(key, Start, KE),
150 | from_inclusive=false});
151 | key_range([{gte, Start} | Rest], KE, KR) ->
152 | key_range(Rest, KE, KR#key_range{from_key=enc(key, Start, KE),
153 | from_inclusive=true});
154 | key_range([{lt, End} | Rest], KE, KR) ->
155 | key_range(Rest, KE, KR#key_range{to_key=enc(key, End, KE),
156 | to_inclusive=false});
157 | key_range([{lte, End} | Rest], KE, KR) ->
158 | key_range(Rest, KE, KR#key_range{to_key=enc(key, End, KE),
159 | to_inclusive=true});
160 | key_range([{max, Max} | Rest], KE, KR) ->
161 | key_range(Rest, KE, KR#key_range{limit=Max});
162 | key_range([_ | Rest], KE, KR) ->
163 | key_range(Rest, KE, KR).
164 |
--------------------------------------------------------------------------------
/src/rkvs_ets.erl:
--------------------------------------------------------------------------------
1 | %%-*- mode: erlang -*-
2 | %%
3 | %% This Source Code Form is subject to the terms of the Mozilla Public
4 | %% License, v. 2.0. If a copy of the MPL was not distributed with this
5 | %% file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 |
7 | %% @doc ETS backend
8 | %% Note: value encoding is ignored since everything is encoded as a term
9 |
10 | -module(rkvs_ets).
11 | -behaviour(rkvs_storage_backend).
12 |
13 | -include("rkvs.hrl").
14 |
15 | -export([open/2,
16 | close/1,
17 | destroy/1,
18 | contains/2,
19 | get/2,
20 | put/3,
21 | clear/2,
22 | write_batch/2,
23 | scan/4,
24 | clear_range/4,
25 | fold/4,
26 | fold_keys/4,
27 | is_empty/1]).
28 |
29 | -import(rkvs_util, [enc/3, dec/3]).
30 |
31 | open(Name, Options) ->
32 | Ets = ets:new(kvdb_ets, [ordered_set,public]),
33 | KeyEncoding = proplists:get_value(key_encoding, Options, raw),
34 |
35 | {ok, #engine{name=Name,
36 | mod=?MODULE,
37 | ref=Ets,
38 | key_enc=KeyEncoding,
39 | options=Options}}.
40 |
41 |
42 | close(_Engine) ->
43 | ok.
44 |
45 | destroy(#engine{ref=Ref}) ->
46 | true = ets:delete(Ref),
47 | ok.
48 |
49 | contains(#engine{ref=Ref}, Key) ->
50 | ets:member(Ref, Key).
51 |
52 | get(#engine{ref=Ref, key_enc=KE}, Key) ->
53 | case ets:lookup(Ref, enc(key, Key, KE)) of
54 | [] -> {error, not_found};
55 | [{Key, Value}] -> Value
56 | end.
57 |
58 | put(#engine{ref=Ref, key_enc=KE}, Key, Value) ->
59 | true = ets:insert(Ref, {enc(key, Key, KE), Value}),
60 | ok.
61 |
62 | clear(#engine{ref=Ref, key_enc=KE}, Key) ->
63 | true = ets:delete(Ref, enc(key, Key, KE)),
64 | ok.
65 |
66 | write_batch(#engine{ref=Ref, key_enc=KE}, Ops) ->
67 | lists:foreach(fun
68 | ({put, K, V}) ->
69 | true = ets:insert(Ref, {enc(key, K, KE), V});
70 | ({delete, K}) ->
71 | true = ets:delete(Ref, enc(key, K, KE))
72 | end, Ops).
73 |
74 | scan(Engine, Start, End, Max) ->
75 | AccFun = fun({K, V}, Acc) ->
76 | [{K, V} | Acc]
77 | end,
78 | AccOut = fold(Engine, AccFun, [], [{gte, Start},
79 | {lte, End},
80 | {max, Max}]),
81 | lists:reverse(AccOut).
82 |
83 | clear_range(Engine, Start, End, Max) ->
84 | AccFun = fun(K, Acc) ->
85 | [{delete, K} | Acc]
86 | end,
87 | Ops = fold_keys(Engine, AccFun, [], [{gte, Start},
88 | {lte, End},
89 | {max, Max}]),
90 | write_batch(Engine, Ops).
91 |
92 | fold_keys(#engine{ref=Ref, key_enc=KE}, Fun, Acc0, Opts) ->
93 | do_fold(Ref, Fun, Acc0, rkvs_util:fold_options(Opts,
94 | #fold_options{key_enc=KE}),
95 | keys_only).
96 |
97 | fold(#engine{ref=Ref, key_enc=KE}, Fun, Acc0, Opts) ->
98 | do_fold(Ref, Fun, Acc0, rkvs_util:fold_options(Opts,
99 | #fold_options{key_enc=KE}),
100 | values).
101 |
102 | do_fold(Ref, Fun, Acc0, #fold_options{gt=GT, gte=GTE, key_enc=KE}=Opts, Type) ->
103 | %% define start key and the precondition
104 | {Start, GType} = case {GT, GTE} of
105 | {nil, nil} -> {first, gte};
106 | {nil, K} when K /= nil -> {enc(key, K, KE), gte};
107 | {K, _} -> {enc(key, K, KE), gt}
108 | end,
109 |
110 | FoldKey = case Start of
111 | first ->
112 | %% if first and condition is greater or equal then start
113 | %% with the first key if any.
114 | ets:first(Ref);
115 | _ when GType =:= gte ->
116 | %% does the key exists ? if true, starts with it, else
117 | %% start with the next one if any
118 | case ets:member(Ref, Start) of
119 | true -> Start;
120 | false -> ets:next(Ref, Start)
121 | end;
122 | _ ->
123 | %% if condition is greater, just start with the next
124 | %% one.
125 | ets:next(Ref, Start)
126 | end,
127 | fold_loop(FoldKey, Ref, Fun, Acc0, 0, Opts, Type).
128 |
129 |
130 | fold_loop('$end_of_table', _Ref, _Fun, Acc, _N, _Opts, _Type) ->
131 | Acc;
132 | fold_loop(Key, Ref, Fun, Acc0, N0, #fold_options{lt=End}=Opts, Type)
133 | when End /= nil orelse Key < End ->
134 | fold_loop1(Key, Ref, Fun, Acc0, N0, Opts, Type);
135 | fold_loop(Key, Ref, Fun, Acc0, N0, #fold_options{lte=End}=Opts, Type)
136 | when End =:= nil orelse Key < End ->
137 | fold_loop1(Key, Ref, Fun, Acc0, N0, Opts, Type);
138 | fold_loop(Key, _Ref, Fun, Acc0, _N0, #fold_options{lt=nil, lte=Key, key_enc=KE},
139 | keys_only) ->
140 | Fun(dec(key, Key, KE), Acc0);
141 | fold_loop(Key, Ref, Fun, Acc0, _N0, #fold_options{lt=nil,
142 | lte=Key,
143 | key_enc=KE}, values) ->
144 | case ets:lookup(Ref, Key) of
145 | [] -> Acc0;
146 | [{Key, Val}] -> Fun({dec(key, Key, KE), Val}, Acc0)
147 | end;
148 | fold_loop(_Key, _Ref, _Fun, Acc, _N, _Opts, _Type) ->
149 | Acc.
150 |
151 | fold_loop1(Key, Ref, Fun, Acc0, N0, #fold_options{max=Max}=Opts, Type) ->
152 | #fold_options{key_enc=KE}=Opts,
153 | {Acc, N} = case Type of
154 | keys_only -> {Fun(dec(key, Key, KE), Acc0), N0 + 1};
155 | values ->
156 | case ets:lookup(Ref, Key) of
157 | [] -> {Acc0, N0 + 1};
158 | [{Key, Val}] -> {Fun({dec(key, Key, KE), Val}, Acc0), N0 + 1}
159 | end
160 | end,
161 | if ((Max =:=0) orelse (N < Max)) ->
162 | fold_loop(ets:next(Ref, Key), Ref, Fun, Acc, N, Opts, Type);
163 | true ->
164 | Acc
165 | end.
166 |
167 |
168 | %% @doc Returns true if this backend contains any values; otherwise returns false.
169 | -spec is_empty(engine()) -> boolean() | {error, term()}.
170 | is_empty(#engine{ref=Ref}) ->
171 | case ets:info(Ref, size) of
172 | undefined -> true;
173 | Size -> Size =:= 0
174 | end.
175 |
--------------------------------------------------------------------------------
/src/rkvs_rocksdb.erl:
--------------------------------------------------------------------------------
1 | %%-*- mode: erlang -*-
2 | %%
3 | %% This Source Code Form is subject to the terms of the Mozilla Public
4 | %% License, v. 2.0. If a copy of the MPL was not distributed with this
5 | %% file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 |
7 | %% @doc erocksdb backend
8 | %%
9 | %% You can pass any options from
10 | %% [erocksdb](https://github.com/leo-project/erocksdb)
11 | %% when opening the database using the db_opts settings.
12 | %%
13 | %% Optionnaly you can pass a db_dir option to set the path of the database.
14 | -module(rkvs_rocksdb).
15 | -behaviour(rkvs_storage_backend).
16 |
17 | -include("rkvs.hrl").
18 |
19 | -export([open/2,
20 | close/1,
21 | destroy/1,
22 | contains/2,
23 | get/2,
24 | put/3,
25 | clear/2,
26 | write_batch/2,
27 | scan/4,
28 | clear_range/4,
29 | fold/4,
30 | fold_keys/4,
31 | is_empty/1]).
32 |
33 | -import(rkvs_util, [enc/3, dec/3]).
34 |
35 | open(Name, Options) ->
36 | Path = case proplists:get_value(db_dir, Options) of
37 | undefined -> Name;
38 | Dir -> filename:join(Dir, Name)
39 | end,
40 |
41 | filelib:ensure_dir(filename:join(Path, "dummy")),
42 | DbOpts = proplists:get_value(db_opts, Options,
43 | [{create_if_missing, true}]),
44 |
45 | KeyEncoding = proplists:get_value(key_encoding, Options, raw),
46 | ValueEncoding = proplists:get_value(value_encoding, Options, term),
47 |
48 | case erocksdb:open(Path, DbOpts, []) of
49 | {ok, Ref} ->
50 | {ok, #engine{name=Name,
51 | mod=?MODULE,
52 | ref=Ref,
53 | key_enc=KeyEncoding,
54 | val_enc=ValueEncoding,
55 | options=Options}};
56 | Error ->
57 | Error
58 | end.
59 |
60 | close(#engine{ref=Ref}) ->
61 | erocksdb:close(Ref).
62 |
63 | destroy(#engine{name=Name, options=Options}) ->
64 | DbOpts = proplists:get_value(db_opts, Options,
65 | [{create_if_missing, true}]),
66 |
67 | erocksdb:destroy(Name, DbOpts).
68 |
69 | contains(Engine, Key) ->
70 | Fun = fun(_K, _Acc) -> true end,
71 | fold_keys(Engine, Fun, false, [{start_key, Key},
72 | {end_key, Key},
73 | {max, 1}]).
74 |
75 | get(#engine{ref=Ref, key_enc=KE, val_enc=VE}, Key) ->
76 | case erocksdb:get(Ref, enc(key, Key, KE), []) of
77 | {ok, Val} -> dec(value, Val, VE);
78 | not_found -> {error, not_found};
79 | Error -> Error
80 | end.
81 |
82 | put(#engine{ref=Ref, key_enc=KE, val_enc=VE}, Key, Value) ->
83 | erocksdb:put(Ref, enc(key, Key, KE), enc(value, Value, VE), [{sync, true}]).
84 |
85 | clear(#engine{ref=Ref, key_enc=KE}, Key) ->
86 | erocksdb:delete(Ref, enc(key, Key, KE), [{sync, true}]).
87 |
88 | write_batch(#engine{ref=Ref, key_enc=KE, val_enc=VE}, Ops0) ->
89 | Ops = lists:reverse(lists:foldl(fun
90 | ({put, K, V}, Acc) ->
91 | [{put, enc(key, K, KE), enc(value, V, VE)} | Acc];
92 | ({delete, K}, Acc) ->
93 | [{delete, enc(key, K, KE)} | Acc]
94 | end, [], Ops0)),
95 | erocksdb:write(Ref, Ops, [{sync, true}]).
96 |
97 | scan(Engine, Start, End, Max) ->
98 | AccFun = fun({K, V}, Acc) ->
99 | [{K, V} | Acc]
100 | end,
101 | AccOut = fold(Engine, AccFun, [], [{gte, Start},
102 | {lte, End},
103 | {max, Max}]),
104 | lists:reverse(AccOut).
105 |
106 | clear_range(Engine, Start, End, Max) ->
107 | AccFun = fun(K, Acc) ->
108 | [{delete, K} | Acc]
109 | end,
110 | Ops = fold_keys(Engine, AccFun, [], [{gte, Start},
111 | {lte, End},
112 | {max, Max}]),
113 | write_batch(Engine, Ops).
114 |
115 | fold_keys(#engine{ref=Ref, key_enc=KE, val_enc=VE}, Fun, Acc0, Opts) ->
116 | FillCache = proplists:get_value(fill_cache, Opts, true),
117 | {ok, Itr} = erocksdb:iterator(Ref, [{fill_cache, FillCache}], keys_only),
118 | FoldOpts0 = #fold_options{key_enc=KE, val_enc=VE},
119 | do_fold(Itr, Fun, Acc0, rkvs_util:fold_options(Opts, FoldOpts0)).
120 |
121 |
122 | fold(#engine{ref=Ref, key_enc=KE, val_enc=VE}, Fun, Acc0, Opts) ->
123 | FillCache = proplists:get_value(fill_cache, Opts, true),
124 | {ok, Itr} = erocksdb:iterator(Ref, [{fill_cache, FillCache}]),
125 | FoldOpts0 = #fold_options{key_enc=KE, val_enc=VE},
126 | do_fold(Itr, Fun, Acc0, rkvs_util:fold_options(Opts, FoldOpts0)).
127 |
128 |
129 | %% private
130 |
131 | do_fold(Itr, Fun, Acc0, #fold_options{gt=GT, gte=GTE, key_enc=KE}=Opts) ->
132 | {Start, Inclusive} = case {GT, GTE} of
133 | {nil, nil} -> {first, true};
134 | {first, _} -> {first, false};
135 | {K, _} when is_binary(K) -> {enc(key, K, KE), false};
136 | {_, K} -> {enc(key, K, KE), true}
137 | end,
138 | try
139 | case erocksdb:iterator_move(Itr, Start) of
140 | {ok, Start} when Inclusive /= true ->
141 | fold_loop(erocksdb:iterator_move(Itr, next), Itr, Fun,
142 | Acc0, 0, Opts);
143 | {ok, Start, _V} when Inclusive /= true ->
144 | fold_loop(erocksdb:iterator_move(Itr, next), Itr, Fun,
145 | Acc0, 0, Opts);
146 | Next ->
147 | fold_loop(Next, Itr, Fun, Acc0, 0, Opts)
148 |
149 | end
150 | after
151 | erocksdb:iterator_close(Itr)
152 | end.
153 |
154 | fold_loop({error, iterator_closed}, _Itr, _Fun, Acc0, _N, _Opts) ->
155 | throw({iterator_closed, Acc0});
156 | fold_loop({error, invalid_iterator}, _Itr, _Fun, Acc0, _N, _Opts) ->
157 | Acc0;
158 | fold_loop({ok, K}=KT, Itr, Fun, Acc0, N0, #fold_options{lt=End}=Opts)
159 | when End /= nil, K < End ->
160 | fold_loop1(KT, Itr, Fun, Acc0, N0, Opts);
161 | fold_loop({ok, K}=KT, Itr, Fun, Acc0, N0, #fold_options{lte=End}=Opts)
162 | when End =:= nil orelse K < End ->
163 | fold_loop1(KT, Itr, Fun, Acc0, N0, Opts);
164 | fold_loop({ok, K}, _Itr, Fun, Acc0, _N, #fold_options{lt=nil, lte=K}=Opts) ->
165 | Fun(dec(key, K, Opts#fold_options.key_enc), Acc0);
166 | fold_loop({ok, _K}, _Itr, _Fun, Acc0, _N, _Opts) ->
167 | Acc0;
168 | fold_loop({ok, K, _V}=KV, Itr, Fun, Acc0, N0, #fold_options{lt=End}=Opts)
169 | when End /= nil orelse K < End ->
170 | fold_loop1(KV, Itr, Fun, Acc0, N0, Opts);
171 | fold_loop({ok, K, _V}=KV, Itr, Fun, Acc0, N0, #fold_options{lte=End}=Opts)
172 | when End =:= nil orelse K < End ->
173 | fold_loop1(KV, Itr, Fun, Acc0, N0, Opts);
174 | fold_loop({ok, K, V}, _Itr, Fun, Acc0, _N, #fold_options{lt=nil, lte=K}=Opts) ->
175 | #fold_options{key_enc=KE, val_enc=VE}=Opts,
176 | Fun({dec(key, K, KE), dec(value, V, VE)}, Acc0);
177 | fold_loop({ok, _K, _V}, _Itr, _Fun, Acc0, _N, _Opts) ->
178 | Acc0.
179 |
180 |
181 | fold_loop1({ok, K}, Itr, Fun, Acc0, N0, #fold_options{max=Max}=Opts) ->
182 | Acc = Fun(dec(key, K, Opts#fold_options.key_enc), Acc0),
183 | N = N0 + 1,
184 | if ((Max =:=0) orelse (N < Max)) ->
185 | fold_loop(erocksdb:iterator_move(Itr, next), Itr, Fun, Acc,
186 | N, Opts);
187 | true ->
188 | Acc
189 | end;
190 | fold_loop1({ok, K, V}, Itr, Fun, Acc0, N0, #fold_options{max=Max}=Opts) ->
191 | #fold_options{key_enc=KE, val_enc=VE}=Opts,
192 | Acc = Fun({enc(key, K, KE), dec(value, V, VE)}, Acc0),
193 | N = N0 + 1,
194 | if
195 | ((Max =:= 0) orelse (N < Max)) ->
196 | fold_loop(erocksdb:iterator_move(Itr, next), Itr, Fun, Acc, N,
197 | Opts);
198 | true ->
199 | Acc
200 | end.
201 |
202 | %% @doc Returns true if this backend contains any values; otherwise returns false.
203 | -spec is_empty(engine()) -> boolean() | {error, term()}.
204 | is_empty(#engine{ref=Ref}) ->
205 | erocksdb:is_empty(Ref).
206 |
--------------------------------------------------------------------------------
/src/rkvs_leveldb.erl:
--------------------------------------------------------------------------------
1 | %%-*- mode: erlang -*-
2 | %%
3 | %% This Source Code Form is subject to the terms of the Mozilla Public
4 | %% License, v. 2.0. If a copy of the MPL was not distributed with this
5 | %% file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 |
7 | %% @doc eleveldb backendi
8 | %%
9 | %% You can pass any options from
10 | %% [eleveldb](https://github.com/basho/eleveldb/blob/develop/src/eleveldb.erl)
11 | %% when opening the database using the db_opts settings.
12 | %%
13 | %% Optionnaly you can pass a db_dir option to set the path of the database.
14 | -module(rkvs_leveldb).
15 | -behaviour(rkvs_storage_backend).
16 |
17 | -include("rkvs.hrl").
18 |
19 | -export([open/2,
20 | close/1,
21 | destroy/1,
22 | contains/2,
23 | get/2,
24 | put/3,
25 | clear/2,
26 | write_batch/2,
27 | scan/4,
28 | clear_range/4,
29 | fold/4,
30 | fold_keys/4,
31 | is_empty/1]).
32 |
33 | -import(rkvs_util, [enc/3, dec/3]).
34 |
35 | open(Name, Options) ->
36 | Path = case proplists:get_value(db_dir, Options) of
37 | undefined -> Name;
38 | Dir -> filename:join(Dir, Name)
39 | end,
40 |
41 | filelib:ensure_dir(filename:join(Path, "dummy")),
42 | DbOpts = proplists:get_value(db_opts, Options,
43 | [{create_if_missing, true}]),
44 |
45 | KeyEncoding = proplists:get_value(key_encoding, Options, raw),
46 | ValueEncoding = proplists:get_value(value_encoding, Options, term),
47 |
48 | case eleveldb:open(Path, DbOpts) of
49 | {ok, Ref} ->
50 | {ok, #engine{name=Name,
51 | mod=?MODULE,
52 | ref=Ref,
53 | key_enc=KeyEncoding,
54 | val_enc=ValueEncoding,
55 | options=Options}};
56 | Error ->
57 | Error
58 | end.
59 |
60 | close(#engine{ref=Ref}) ->
61 | eleveldb:close(Ref).
62 |
63 | destroy(#engine{name=Name, options=Options}) ->
64 | DbOpts = proplists:get_value(db_opts, Options,
65 | [{create_if_missing, true}]),
66 |
67 | eleveldb:destroy(Name, DbOpts).
68 |
69 | contains(Engine, Key) ->
70 | Fun = fun(_K, _Acc) -> true end,
71 | fold_keys(Engine, Fun, false, [{start_key, Key},
72 | {end_key, Key},
73 | {max, 1}]).
74 |
75 | get(#engine{ref=Ref, key_enc=KE, val_enc=VE}, Key) ->
76 | case eleveldb:get(Ref, enc(key, Key, KE), []) of
77 | {ok, Val} -> dec(value, Val, VE);
78 | not_found -> {error, not_found};
79 | Error -> Error
80 | end.
81 |
82 | put(#engine{ref=Ref, key_enc=KE, val_enc=VE}, Key, Value) ->
83 | eleveldb:put(Ref, enc(key, Key, KE), enc(value, Value, VE), [{sync, true}]).
84 |
85 | clear(#engine{ref=Ref, key_enc=KE}, Key) ->
86 | eleveldb:delete(Ref, enc(key, Key, KE), [{sync, true}]).
87 |
88 |
89 | write_batch(#engine{ref=Ref, key_enc=KE, val_enc=VE}, Ops0) ->
90 | Ops = lists:reverse(lists:foldl(fun
91 | ({put, K, V}, Acc) ->
92 | [{put, enc(key, K, KE), enc(value, V, VE)} | Acc];
93 | ({delete, K}, Acc) ->
94 | [{delete, enc(key, K, KE)} | Acc]
95 | end, [], Ops0)),
96 |
97 | eleveldb:write(Ref, Ops, [{sync, true}]).
98 |
99 | scan(Engine, Start, End, Max) ->
100 | AccFun = fun({K, V}, Acc) ->
101 | [{K, V} | Acc]
102 | end,
103 | AccOut = fold(Engine, AccFun, [], [{gte, Start},
104 | {lte, End},
105 | {max, Max}]),
106 | lists:reverse(AccOut).
107 |
108 | clear_range(Engine, Start, End, Max) ->
109 | AccFun = fun(K, Acc) ->
110 | [{delete, K} | Acc]
111 | end,
112 | Ops = fold_keys(Engine, AccFun, [], [{gte, Start},
113 | {lte, End},
114 | {max, Max}]),
115 | write_batch(Engine, Ops).
116 |
117 | fold_keys(#engine{ref=Ref, key_enc=KE, val_enc=VE}, Fun, Acc0, Opts) ->
118 | FillCache = proplists:get_value(fill_cache, Opts, true),
119 | {ok, Itr} = eleveldb:iterator(Ref, [{fill_cache, FillCache}], keys_only),
120 | FoldOpts0 = #fold_options{key_enc=KE, val_enc=VE},
121 | do_fold(Itr, Fun, Acc0, rkvs_util:fold_options(Opts, FoldOpts0)).
122 |
123 |
124 | fold(#engine{ref=Ref, key_enc=KE, val_enc=VE}, Fun, Acc0, Opts) ->
125 | FillCache = proplists:get_value(fill_cache, Opts, true),
126 | {ok, Itr} = eleveldb:iterator(Ref, [{fill_cache, FillCache}]),
127 | FoldOpts0 = #fold_options{key_enc=KE, val_enc=VE},
128 | do_fold(Itr, Fun, Acc0, rkvs_util:fold_options(Opts, FoldOpts0)).
129 |
130 |
131 | %% private
132 | do_fold(Itr, Fun, Acc0, #fold_options{gt=GT, gte=GTE, key_enc=KE}=Opts) ->
133 | {Start, Inclusive} = case {GT, GTE} of
134 | {nil, nil} -> {first, true};
135 | {first, _} -> {first, false};
136 | {K, _} when is_binary(K) -> {enc(key, K, KE), false};
137 | {_, K} -> {enc(key, K, KE), true}
138 | end,
139 |
140 |
141 | try
142 | case eleveldb:iterator_move(Itr, Start) of
143 | {ok, Start} when Inclusive /= true ->
144 | fold_loop(eleveldb:iterator_move(Itr, prefetch), Itr, Fun,
145 | Acc0, 0, Opts);
146 | {ok, Start, _V} when Inclusive /= true ->
147 | fold_loop(eleveldb:iterator_move(Itr, prefetch), Itr, Fun,
148 | Acc0, 0, Opts);
149 | Next ->
150 | fold_loop(Next, Itr, Fun, Acc0, 0, Opts)
151 |
152 | end
153 | after
154 | eleveldb:iterator_close(Itr)
155 | end.
156 |
157 |
158 | fold_loop({error, iterator_closed}, _Itr, _Fun, Acc0, _N, _Opts) ->
159 | throw({iterator_closed, Acc0});
160 | fold_loop({error, invalid_iterator}, _Itr, _Fun, Acc0, _N, _Opts) ->
161 | Acc0;
162 | fold_loop({ok, K}=KT, Itr, Fun, Acc0, N0, #fold_options{lt=End}=Opts)
163 | when End /= nil, K < End ->
164 | fold_loop1(KT, Itr, Fun, Acc0, N0, Opts);
165 | fold_loop({ok, K}=KT, Itr, Fun, Acc0, N0, #fold_options{lte=End}=Opts)
166 | when End =:= nil orelse K < End ->
167 | fold_loop1(KT, Itr, Fun, Acc0, N0, Opts);
168 | fold_loop({ok, K}, _Itr, Fun, Acc0, _N, #fold_options{lt=nil, lte=K}=Opts) ->
169 | Fun(dec(key, K, Opts#fold_options.key_enc), Acc0);
170 | fold_loop({ok, _K}, _Itr, _Fun, Acc0, _N, _Opts) ->
171 | Acc0;
172 | fold_loop({ok, K, _V}=KV, Itr, Fun, Acc0, N0, #fold_options{lt=End}=Opts)
173 | when End /= nil orelse K < End ->
174 | fold_loop1(KV, Itr, Fun, Acc0, N0, Opts);
175 | fold_loop({ok, K, _V}=KV, Itr, Fun, Acc0, N0, #fold_options{lte=End}=Opts)
176 | when End =:= nil orelse K < End ->
177 | fold_loop1(KV, Itr, Fun, Acc0, N0, Opts);
178 | fold_loop({ok, K, V}, _Itr, Fun, Acc0, _N, #fold_options{lt=nil, lte=K}=Opts) ->
179 | #fold_options{key_enc=KE, val_enc=VE}=Opts,
180 | Fun({dec(key, K, KE), dec(value, V, VE)}, Acc0);
181 | fold_loop({ok, _K, _V}, _Itr, _Fun, Acc0, _N, _Opts) ->
182 | Acc0.
183 |
184 |
185 | fold_loop1({ok, K}, Itr, Fun, Acc0, N0, #fold_options{max=Max}=Opts) ->
186 | Acc = Fun(dec(key, K, Opts#fold_options.key_enc), Acc0),
187 | N = N0 + 1,
188 | if ((Max =:=0) orelse (N < Max)) ->
189 | fold_loop(eleveldb:iterator_move(Itr, prefetch), Itr, Fun, Acc,
190 | N, Opts);
191 | true ->
192 | Acc
193 | end;
194 | fold_loop1({ok, K, V}, Itr, Fun, Acc0, N0, #fold_options{max=Max}=Opts) ->
195 | #fold_options{key_enc=KE, val_enc=VE}=Opts,
196 | Acc = Fun({enc(key, K, KE), dec(value, V, VE)}, Acc0),
197 | N = N0 + 1,
198 | if
199 | ((Max =:= 0) orelse (N < Max)) ->
200 | fold_loop(eleveldb:iterator_move(Itr, prefetch), Itr, Fun, Acc, N,
201 | Opts);
202 | true ->
203 | Acc
204 | end.
205 |
206 | %% @doc Returns true if this backend contains any values; otherwise returns false.
207 | -spec is_empty(engine()) -> boolean() | {error, term()}.
208 | is_empty(#engine{ref=Ref}) ->
209 | eleveldb:is_empty(Ref).
210 |
--------------------------------------------------------------------------------
/doc/rkvs.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Module rkvs #
4 | * [Data Types](#types)
5 | * [Function Index](#index)
6 | * [Function Details](#functions)
7 |
8 |
9 |
10 |
11 |
12 | ## Data Types ##
13 |
14 |
15 |
16 |
17 | ### fold_options() ###
18 |
19 |
20 |
21 |
22 | fold_options() = [{start_key, key()} | {end_key, key()} | {gt, key()} | {gte, key()} | {lt, key()} | {lte, key()} | {max, integer()} | {fill_cache, true | false}]
23 |
24 |
25 |
26 |
27 |
28 |
29 | ### key() ###
30 |
31 |
32 |
33 |
34 | key() = term()
35 |
36 |
37 |
38 |
39 |
40 |
41 | ### keys() ###
42 |
43 |
44 |
45 |
46 | keys() = [key()]
47 |
48 |
49 |
50 |
51 |
52 |
53 | ### kvs() ###
54 |
55 |
56 |
57 |
58 | kvs() = [{key(), value()}]
59 |
60 |
61 |
62 |
63 |
64 |
65 | ### ops_kvs() ###
66 |
67 |
68 |
69 |
70 | ops_kvs() = [{put, key(), value()} | {delete, key(), value()}]
71 |
72 |
73 |
74 |
75 |
76 |
77 | ### value() ###
78 |
79 |
80 |
81 |
82 | value() = term()
83 |
84 |
85 |
86 |
87 |
88 | ## Function Index ##
89 |
90 |
91 | | clear/2 | delete the value associated to the key. |
| clear_range/4 | delete all K/Vs in a range. |
| close/1 | close a storage. |
| contains/2 | is the key exists in the storage. |
| destroy/1 | close a storage and remove all the data. |
| fold/4 | fold all K/Vs with a function
92 | Additionnaly you can pass the following options:
93 |
94 | - 'gt', (greater than), 'gte' (greather than or equal): define the lower
95 | bound of the range to fold. Only the records where the key is greater (or
96 | equal to) will be given to the function.
97 | - 'lt' (less than), 'lte' (less than or equal): define the higher bound
98 | of the range to fold. Only the records where the key is less than (or equal
99 | to) will be given to the function
100 | - 'start_key', 'end_key', legacy to 'gte', 'lte'
101 | - 'max' (default=0), the maximum of records to fold before returning the
102 | resut
103 | - 'fill_cache' (default is true): should be the data cached in
104 | memory?
105 | |
| fold_keys/4 | fold all keys with a function
106 | same parameters as in the fold function. |
| get/2 | get the value associated to the key. |
| is_empty/1 | Returns true if this backend contains any values; otherwise returns false. |
| open/2 | open a storage, amd pass options to the backend. |
| put/3 | store the value associated to the key. |
| scan/4 | retrieve a list of Key/Value in a range. |
| write_batch/2 | do multiple operations on the backend. |
107 |
108 |
109 |
110 |
111 | ## Function Details ##
112 |
113 |
114 |
115 | ### clear/2 ###
116 |
117 |
118 |
119 | clear(Engine::engine(), Key::key()) -> ok | {error, term()}
120 |
121 |
122 |
123 | delete the value associated to the key
124 |
125 |
126 | ### clear_range/4 ###
127 |
128 |
129 |
130 | clear_range(Engine::engine(), Start::key(), End::key(), Max::integer()) -> ok | {error, term()}
131 |
132 |
133 |
134 | delete all K/Vs in a range
135 |
136 |
137 | ### close/1 ###
138 |
139 |
140 |
141 | close(Engine::engine()) -> ok | {error, any()}
142 |
143 |
144 |
145 | close a storage
146 |
147 |
148 | ### contains/2 ###
149 |
150 |
151 |
152 | contains(Engine::engine(), Key::key()) -> true | false
153 |
154 |
155 |
156 | is the key exists in the storage
157 |
158 |
159 | ### destroy/1 ###
160 |
161 |
162 |
163 | destroy(Engine::engine()) -> ok | {error, any()}
164 |
165 |
166 |
167 | close a storage and remove all the data
168 |
169 |
170 | ### fold/4 ###
171 |
172 |
173 |
174 | fold(Engine::engine(), Fun::function(), Acc0::any(), Opts::fold_options()) -> any() | {error, term()}
175 |
176 |
177 |
178 | fold all K/Vs with a function
179 | Additionnaly you can pass the following options:
180 |
181 | * 'gt', (greater than), 'gte' (greather than or equal): define the lower
182 | bound of the range to fold. Only the records where the key is greater (or
183 | equal to) will be given to the function.
184 |
185 | * 'lt' (less than), 'lte' (less than or equal): define the higher bound
186 | of the range to fold. Only the records where the key is less than (or equal
187 | to) will be given to the function
188 |
189 | * 'start_key', 'end_key', legacy to 'gte', 'lte'
190 |
191 | * 'max' (default=0), the maximum of records to fold before returning the
192 | resut
193 |
194 | * 'fill_cache' (default is true): should be the data cached in
195 | memory?
196 |
197 |
198 |
199 |
200 | ### fold_keys/4 ###
201 |
202 |
203 |
204 | fold_keys(Engine::engine(), Fun::function(), Acc0::any(), Opts::fold_options()) -> any() | {error, term()}
205 |
206 |
207 |
208 | fold all keys with a function
209 | same parameters as in the fold function.
210 |
211 |
212 | ### get/2 ###
213 |
214 |
215 |
216 | get(Engine::engine(), Key::key()) -> any() | {error, term()}
217 |
218 |
219 |
220 | get the value associated to the key
221 |
222 |
223 | ### is_empty/1 ###
224 |
225 |
226 |
227 | is_empty(Engine::engine()) -> boolean() | {error, term()}
228 |
229 |
230 |
231 | Returns true if this backend contains any values; otherwise returns false.
232 |
233 |
234 | ### open/2 ###
235 |
236 |
237 |
238 | open(Name::binary(), Options::list()) -> {ok, engine()} | {error, any()}
239 |
240 |
241 |
242 | open a storage, amd pass options to the backend.
243 | The following optinos can be used:
244 |
245 | * 'backend': default is rkvs_ets, folowin backend are provided in rkvs:
246 | rkvs_ets, rkvs_leveldb, rkvs_rocksdb, rkvs_hanoidb, rvs_bitcask.
247 |
248 | * 'db_opts': backend options, refers to the backend doc for them
249 |
250 | * 'key_encoding': to encode the key. defautl is raw (binary). can be
251 | term, {term, Opts} or sext
252 |
253 | * 'value_encoding': to encode the value. defautl is term. can be
254 | term, {term, Opts} or sext
255 |
256 |
257 |
258 |
259 | ### put/3 ###
260 |
261 |
262 |
263 | put(Engine::engine(), Key::key(), Value::value()) -> ok | {error, term()}
264 |
265 |
266 |
267 | store the value associated to the key.
268 |
269 |
270 | ### scan/4 ###
271 |
272 |
273 |
274 | scan(Engine::engine(), Start::key(), End::key(), Max::integer()) -> kvs() | {error, term()}
275 |
276 |
277 |
278 | retrieve a list of Key/Value in a range
279 |
280 |
281 | ### write_batch/2 ###
282 |
283 |
284 |
285 | write_batch(Engine::engine(), Ops::ops_kvs()) -> ok | {error, term()}
286 |
287 |
288 |
289 | do multiple operations on the backend.
290 |
--------------------------------------------------------------------------------
/test/rkvs_tests.erl:
--------------------------------------------------------------------------------
1 | %%-*- mode: erlang -*-
2 | %%
3 | %% This Source Code Form is subject to the terms of the Mozilla Public
4 | %% License, v. 2.0. If a copy of the MPL was not distributed with this
5 | %% file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 | %%
7 | -module(rkvs_tests).
8 |
9 | -include_lib("include/rkvs.hrl").
10 | -include("rkvs_tests.hrl").
11 |
12 | -define(setup(F), {setup, fun setup/0, fun teardown/1, F}).
13 | -define(foreach(Fs), {foreach, fun setup/1, fun teardown/1, Fs}).
14 |
15 | -define(dbg(Backend,E),
16 | (fun() ->
17 | try (E) of
18 | __V ->
19 | ?debugFmt(<<"backend = ~p; ~s = ~P">>, [Backend,(??E), __V, 15]),
20 | __V
21 | catch
22 | error:__Err ->
23 | io:fwrite(user,
24 | "FAIL: backend = ~p; test = ~s~n"
25 | "Error = ~p~n"
26 | "Trace = ~p~n", [Backend,(??E), __Err,
27 | erlang:get_stacktrace()]),
28 | error(__Err)
29 | end
30 | end)()).
31 |
32 | backends() ->
33 | [rkvs_ets, rkvs_leveldb, rkvs_rocksdb, rkvs_hanoidb].
34 |
35 | basic_test() ->
36 | lists:foreach(fun(Backend) ->
37 | File = ?tempdb(),
38 | {ok, Engine} = rkvs:open(File, [{backend, Backend}]),
39 | ?assert(is_record(Engine, engine)),
40 |
41 | ?assertMatch(ok, rkvs:close(Engine)),
42 |
43 | %% test db destroy
44 | ok = rkvs:destroy(Engine)
45 | end, backends()),
46 | ok.
47 |
48 |
49 | ops_test_funs() ->
50 | [
51 | fun should_create_kv/2,
52 | fun should_get_kv/2,
53 | fun should_contains_kv/2,
54 | fun should_return_notfound/2,
55 | fun should_delete_kv/2,
56 | fun delete_undefined_return_ok/2,
57 | fun should_replace_kv/2,
58 | fun should_scan_all/2,
59 | fun should_scan_from/2,
60 | fun should_scan_end/2,
61 | fun should_scan_max/2,
62 | fun should_scan_max2/2,
63 | fun should_fold_lt/2,
64 | fun should_fold_gt/2,
65 | fun should_fold_lt_gt/2,
66 | fun should_fold_lt_gt_max/2,
67 | fun should_clear_range/2,
68 | fun should_write_batch/2,
69 | fun should_write_delete_batch/2
70 | ].
71 |
72 | basic_ops_test_() ->
73 | RunTests = lists:foldr(fun(Backend, Acc) ->
74 | [{Backend, Fun} || Fun <- ops_test_funs()] ++ Acc
75 | end, [], backends()),
76 |
77 | {setup,
78 | fun() -> ok end,
79 | fun(_) -> ok end,
80 | [{foreachx,
81 | fun(Backend) ->
82 | {ok, Engine} = rkvs:open(?tempdb(), [{backend, Backend}]),
83 | Engine
84 | end,
85 | fun(_Backend, Engine) ->
86 | ok = rkvs:close(Engine),
87 | ok = rkvs:destroy(Engine)
88 | end,
89 | [{Backend, fun(_, Engine) ->
90 | [?_test(?dbg(Backend, Fun(Backend, Engine)))]
91 | end} || {Backend, Fun} <- RunTests]
92 | }]}.
93 |
94 | should_create_kv(_Backend, Engine) ->
95 | ?_assertMatch(ok, rkvs:put(Engine, <<"a">>, 1)).
96 |
97 | should_get_kv(_Backend, Engine) ->
98 | ok = rkvs:put(Engine, <<"a">>, 1),
99 | ?_assertMatch(1, rkvs:get(Engine, <<"a">>)).
100 |
101 | should_contains_kv(_Backend, Engine) ->
102 | ?assert(rkvs:contains(Engine, <<"a">>) =:= false),
103 | ok = rkvs:put(Engine, <<"a">>, 1),
104 | ?_assert(rkvs:contains(Engine, <<"a">>)).
105 |
106 | should_return_notfound(_Backend, Engine) ->
107 | ?_assertMatch({error, not_found}, rkvs:get(Engine, <<"a">>)).
108 |
109 | should_delete_kv(_Backend, Engine) ->
110 | ok = rkvs:put(Engine, <<"a">>, 1),
111 | ?assertMatch(ok, rkvs:clear(Engine, <<"a">>)),
112 | ?_assertMatch(false, rkvs:contains(Engine, <<"a">>)).
113 |
114 | delete_undefined_return_ok(_Backend, Engine) ->
115 | ?_assertMatch(ok, rkvs:clear(Engine, <<"a">>)).
116 |
117 | should_replace_kv(_Backend, Engine) ->
118 | ok = rkvs:put(Engine, <<"a">>, 1),
119 | ok = rkvs:put(Engine, <<"a">>, 2),
120 | ?_assertMatch(2, rkvs:get(Engine, <<"a">>)).
121 |
122 |
123 | should_scan_all(_Backend, Engine) ->
124 | ok = rkvs:put(Engine, <<"a">>, 1),
125 | ok = rkvs:put(Engine, <<"b">>, 2),
126 | ok = rkvs:put(Engine, <<"c">>, 3),
127 |
128 | ?_assertMatch([{<<"a">>, 1}, {<<"b">>, 2}, {<<"c">>, 3}],
129 | rkvs:scan(Engine, first, nil, 0)).
130 |
131 |
132 | should_scan_from(_Backend, Engine) ->
133 | ok = rkvs:put(Engine, <<"a">>, 1),
134 | ok = rkvs:put(Engine, <<"b">>, 2),
135 | ok = rkvs:put(Engine, <<"c">>, 3),
136 |
137 | ?_assertMatch([{<<"b">>, 2}, {<<"c">>, 3}],
138 | rkvs:scan(Engine, <<"b">>, nil, 0)).
139 |
140 | should_scan_end(_Backend, Engine) ->
141 | ok = rkvs:put(Engine, <<"a">>, 1),
142 | ok = rkvs:put(Engine, <<"b">>, 2),
143 | ok = rkvs:put(Engine, <<"c">>, 3),
144 | ok = rkvs:put(Engine, <<"d">>, 4),
145 |
146 | ?_assertMatch([{<<"b">>, 2}, {<<"c">>, 3}],
147 | rkvs:scan(Engine, <<"b">>, <<"c">>, 0)).
148 |
149 |
150 | should_scan_max(_Backend, Engine) ->
151 | ok = rkvs:put(Engine, <<"a">>, 1),
152 | ok = rkvs:put(Engine, <<"b">>, 2),
153 | ok = rkvs:put(Engine, <<"c">>, 3),
154 | ok = rkvs:put(Engine, <<"d">>, 4),
155 |
156 | ?_assertMatch([{<<"a">>, 1}, {<<"b">>, 2}],
157 | rkvs:scan(Engine, first, nil, 2)).
158 |
159 |
160 | should_scan_max2(_Backend, Engine) ->
161 | ok = rkvs:put(Engine, <<"a">>, 1),
162 | ok = rkvs:put(Engine, <<"b">>, 2),
163 | ok = rkvs:put(Engine, <<"c">>, 3),
164 | ok = rkvs:put(Engine, <<"d">>, 4),
165 |
166 | ?_assertMatch([{<<"b">>, 2}],
167 | rkvs:scan(Engine, <<"b">>, <<"c">>, 1)).
168 |
169 |
170 | should_fold_lt(_Backend, Engine) ->
171 | ok = rkvs:put(Engine, <<"a">>, 1),
172 | ok = rkvs:put(Engine, <<"b">>, 2),
173 | ok = rkvs:put(Engine, <<"c">>, 3),
174 | ok = rkvs:put(Engine, <<"d">>, 4),
175 |
176 | AccFun = fun({K, V}, Acc) ->
177 | [{K, V} | Acc]
178 | end,
179 |
180 | ?_assertMatch([{<<"b">>, 2}, {<<"c">>, 3}, {<<"d">>, 4}],
181 | lists:reverse(rkvs:fold(Engine, AccFun, [],
182 | [{gt, <<"a">>}]))).
183 |
184 |
185 | should_fold_gt(_Backend, Engine) ->
186 | ok = rkvs:put(Engine, <<"a">>, 1),
187 | ok = rkvs:put(Engine, <<"b">>, 2),
188 | ok = rkvs:put(Engine, <<"c">>, 3),
189 | ok = rkvs:put(Engine, <<"d">>, 4),
190 |
191 | AccFun = fun({K, V}, Acc) ->
192 | [{K, V} | Acc]
193 | end,
194 |
195 | ?_assertMatch([{<<"a">>, 1}, {<<"b">>, 2}, {<<"c">>, 3}],
196 | lists:reverse(rkvs:fold(Engine, AccFun, [],
197 | [{lt, <<"d">>}]))).
198 |
199 | should_fold_lt_gt(_Backend, Engine) ->
200 | ok = rkvs:put(Engine, <<"a">>, 1),
201 | ok = rkvs:put(Engine, <<"b">>, 2),
202 | ok = rkvs:put(Engine, <<"c">>, 3),
203 | ok = rkvs:put(Engine, <<"d">>, 4),
204 |
205 | AccFun = fun({K, V}, Acc) ->
206 | [{K, V} | Acc]
207 | end,
208 |
209 | ?_assertMatch([{<<"b">>, 2}, {<<"c">>, 3}],
210 | lists:reverse(rkvs:fold(Engine, AccFun, [], [{gt, <<"a">>},
211 | {lt, <<"d">>}]))).
212 |
213 | should_fold_lt_gt_max(_Backend, Engine) ->
214 | ok = rkvs:put(Engine, <<"a">>, 1),
215 | ok = rkvs:put(Engine, <<"b">>, 2),
216 | ok = rkvs:put(Engine, <<"c">>, 3),
217 | ok = rkvs:put(Engine, <<"d">>, 4),
218 |
219 | AccFun = fun({K, V}, Acc) ->
220 | [{K, V} | Acc]
221 | end,
222 |
223 | ?_assertMatch([{<<"b">>, 2}],
224 | rkvs:fold(Engine, AccFun, [], [{gt, <<"a">>},
225 | {lt, <<"d">>},
226 | {max, 1}])).
227 |
228 | should_clear_range(_Backend, Engine) ->
229 | ok = rkvs:put(Engine, <<"a">>, 1),
230 | ok = rkvs:put(Engine, <<"b">>, 2),
231 | ok = rkvs:put(Engine, <<"c">>, 3),
232 |
233 | ok = rkvs:clear_range(Engine, first, nil, 0),
234 | ?_assertMatch([], rkvs:scan(Engine, first, nil, 0)).
235 |
236 | should_write_batch(_Backend, Engine) ->
237 | ok = rkvs:write_batch(Engine, [{put, <<"a">>, 1},
238 | {put, <<"b">>, 2},
239 | {put, <<"c">>, 3}]),
240 |
241 | ?_assertMatch([{<<"a">>, 1}, {<<"b">>, 2}, {<<"c">>, 3}],
242 | rkvs:scan(Engine, first, nil, 0)).
243 |
244 |
245 | should_write_delete_batch(_Backend, Engine) ->
246 | ok = rkvs:write_batch(Engine, [{put, <<"a">>, 1},
247 | {put, <<"b">>, 2},
248 | {put, <<"c">>, 3}]),
249 |
250 | ok = rkvs:write_batch(Engine, [{put, <<"d">>, 4},
251 | {delete, <<"b">>},
252 | {put, <<"e">>, 5}]),
253 |
254 | ?_assertMatch([{<<"a">>, 1}, {<<"c">>, 3}, {<<"d">>, 4}, {<<"e">>, 5}],
255 | rkvs:scan(Engine, first, nil, 0)).
256 |
--------------------------------------------------------------------------------
/src/rkvs_bitcask.erl:
--------------------------------------------------------------------------------
1 | %%-*- mode: erlang -*-
2 | %%
3 | %% This Source Code Form is subject to the terms of the Mozilla Public
4 | %% License, v. 2.0. If a copy of the MPL was not distributed with this
5 | %% file, You can obtain one at http://mozilla.org/MPL/2.0/.
6 |
7 | %% @doc bitcask backend
8 | %%
9 | %% You can pass any options from
10 | %% [bitcask](https://github.com/basho/bitcask)
11 | %% when opening the database using the db_opts settings.
12 | %%
13 | %% Optionnaly you can pass a db_dir option to set the path of the database.
14 | -module(rkvs_bitcask).
15 | -behaviour(rkvs_storage_backend).
16 |
17 | -include("rkvs.hrl").
18 | -include_lib("bitcask/include/bitcask.hrl").
19 |
20 | -export([open/2,
21 | close/1,
22 | destroy/1,
23 | contains/2,
24 | get/2,
25 | put/3,
26 | clear/2,
27 | write_batch/2,
28 | scan/4,
29 | clear_range/4,
30 | fold/4,
31 | fold_keys/4,
32 | is_empty/1]).
33 |
34 | -import(rkvs_util, [enc/3, dec/3]).
35 |
36 | -record(fold_st, {useracc,
37 | cb,
38 | key_enc,
39 | val_enc,
40 | num = 0,
41 | max,
42 | from = <<>>,
43 | from_inclusive = false,
44 | to = nil,
45 | to_inclusive = false}).
46 |
47 |
48 | open(Name, Options) ->
49 | Path = case proplists:get_value(db_dir, Options) of
50 | undefined -> Name;
51 | Dir -> filename:join(Dir, Name)
52 | end,
53 |
54 | DbOpts = proplists:get_value(db_opts, Options, [read_write]),
55 | KeyEncoding = proplists:get_value(key_encoding, Options, raw),
56 | ValueEncoding = proplists:get_value(value_encoding, Options, term),
57 | case catch bitcask:open(Path, DbOpts) of
58 | {error, _Reason}=Error -> Error;
59 | {'EXIT', Reason} -> {error, Reason};
60 | Ref when is_reference(Ref) ->
61 | {ok, #engine{name=Name,
62 | mod=?MODULE,
63 | ref=Ref,
64 | key_enc=KeyEncoding,
65 | val_enc=ValueEncoding,
66 | options=Options}}
67 | end.
68 |
69 | close(#engine{ref=Ref}) ->
70 | case catch bitcask:close(Ref) of
71 | ok -> ok;
72 | {error,{invalid_ref, Ref}} -> ok; %% already closed
73 | {error, Reason} -> {error, Reason};
74 | {'EXIT', Reason} -> {error, Reason}
75 | end.
76 |
77 | destroy(#engine{name=Name, options=Options}=Engine) ->
78 | ok = close(Engine),
79 | Path = case proplists:get_value(db_dir, Options) of
80 | undefined -> Name;
81 | Dir -> filename:join(Dir, Name)
82 | end,
83 | data_directory_cleanup(Path),
84 | ok.
85 |
86 | contains(Engine, Key) ->
87 | Fun = fun(_K, _Acc) -> true end,
88 | fold_keys(Engine, Fun, false, [{start_key, Key},
89 | {end_key, Key},
90 | {max, 1}]).
91 |
92 |
93 | get(#engine{ref=Ref, key_enc=KE, val_enc=VE}, Key) ->
94 | case catch bitcask:get(Ref, enc(key, Key, KE)) of
95 | {ok, Val} -> dec(value, Val, VE);
96 | not_found -> {error, not_found};
97 | {error, _Reason}=Error -> Error;
98 | {'EXIT', Reason} -> {error, Reason}
99 | end.
100 |
101 |
102 | put(#engine{ref=Ref, key_enc=KE, val_enc=VE}, Key, Value) ->
103 | case catch bitcask:put(Ref, enc(key, Key, KE), enc(value, Value, VE)) of
104 | ok -> ok;
105 | {error, _Reason}=Error -> Error;
106 | {'EXIT', Reason} -> {error, Reason}
107 | end.
108 |
109 | clear(#engine{ref=Ref, key_enc=KE}, Key) ->
110 | case catch bitcask:delete(Ref, enc(key, Key, KE)) of
111 | ok -> ok;
112 | {error, _Reason}=Error -> Error;
113 | {'EXIT', Reason} -> {error, Reason}
114 | end.
115 |
116 | write_batch(Engine, Ops) ->
117 | do_write_batch(Ops, Engine).
118 |
119 |
120 | do_write_batch([], _Engine) ->
121 | ok;
122 | do_write_batch([{put, K, V} | Rest], Engine) ->
123 | ok = rkvs_bitcask:put(Engine, K, V),
124 | do_write_batch(Rest, Engine);
125 | do_write_batch([{delete, K} | Rest], Engine) ->
126 | ok = clear(Engine, K),
127 | do_write_batch(Rest, Engine).
128 |
129 |
130 | scan(Engine, Start, End, Max) ->
131 | AccFun = fun({K, V}, Acc) ->
132 | [{K, V} | Acc]
133 | end,
134 | AccOut = fold(Engine, AccFun, [], [{gte, Start},
135 | {lte, End},
136 | {max, Max}]),
137 | lists:reverse(AccOut).
138 |
139 | clear_range(Engine, Start, End, Max) ->
140 | AccFun = fun(K, Acc) ->
141 | [{delete, K} | Acc]
142 | end,
143 | Ops = fold_keys(Engine, AccFun, [], [{gte, Start},
144 | {lte, End},
145 | {max, Max}]),
146 | write_batch(Engine, Ops).
147 |
148 |
149 | fold_keys(#engine{ref=Ref, key_enc=KE, val_enc=VE}, Fun, Acc, Opts) ->
150 | FoldAcc = fold_acc(Fun, Acc, KE, VE, Opts),
151 | case catch bitcask:fold_keys(Ref, fun fold_fun/2, FoldAcc) of
152 | #fold_st{useracc=UserAcc} -> UserAcc;
153 | {stop, #fold_st{useracc=UserAcc}} -> UserAcc;
154 | {error, Reason} -> {error, Reason};
155 | {'EXIT', Reason} -> {error, Reason};
156 | _ -> {error, badarg}
157 | end.
158 |
159 | fold(#engine{ref=Ref, key_enc=KE, val_enc=VE}, Fun, Acc, Opts) ->
160 | FoldAcc = fold_acc(Fun, Acc, KE, VE, Opts),
161 | case catch bitcask:fold(Ref, fun fold_fun/3, FoldAcc) of
162 | #fold_st{useracc=UserAcc} -> UserAcc;
163 | {stop, #fold_st{useracc=UserAcc}} -> UserAcc;
164 | {error, Reason} -> {error, Reason};
165 | {'EXIT', Reason} -> {error, Reason};
166 | _ -> {error, badarg}
167 | end.
168 |
169 | is_empty(#engine{ref=Ref}) ->
170 | bitcask:is_empty_estimate(Ref).
171 |
172 |
173 | fold_acc(Fun, Acc, KE, VE, Opts) ->
174 | fold_acc(Opts, KE, #fold_st{useracc=Acc, cb=Fun, key_enc=KE, val_enc=VE}).
175 |
176 | fold_acc([], _KE, St) ->
177 | St;
178 | fold_acc([{start_key, Start} | Rest], KE, St) ->
179 | fold_acc(Rest, KE, St#fold_st{from=start_key(Start, KE),
180 | from_inclusive=true});
181 | fold_acc([{end_key, End} | Rest], KE, St) ->
182 | fold_acc(Rest, KE, St#fold_st{to=enc(key, End, KE),
183 | to_inclusive=true});
184 | fold_acc([{gt, Start} | Rest], KE, St) ->
185 | fold_acc(Rest, KE, St#fold_st{from=start_key(Start, KE),
186 | from_inclusive=false});
187 | fold_acc([{gte, Start} | Rest], KE, St) ->
188 | fold_acc(Rest, KE, St#fold_st{from=start_key(Start, KE),
189 | from_inclusive=true});
190 | fold_acc([{lt, End} | Rest], KE, St) ->
191 | fold_acc(Rest, KE, St#fold_st{to=enc(key, End, KE),
192 | to_inclusive=false});
193 | fold_acc([{lte, End} | Rest], KE, St) ->
194 | fold_acc(Rest, KE, St#fold_st{to=enc(key, End, KE),
195 | to_inclusive=true});
196 | fold_acc([{max, Max} | Rest], KE, St) ->
197 | fold_acc(Rest, KE, St#fold_st{max=Max});
198 | fold_acc([_ | Rest], KE, St) ->
199 | fold_acc(Rest, KE, St).
200 |
201 |
202 |
203 | start_key(first, _KE) ->
204 | <<>>;
205 | start_key(Key, KE) ->
206 | enc(key, Key, KE).
207 |
208 |
209 | fold_fun(#bitcask_entry{key=K}, #fold_st{from=Start,
210 | from_inclusive=Inclusive}=Acc) ->
211 | Res = case Inclusive of
212 | false when K > Start -> fold_fun1(K, Acc);
213 | true when K =:= Start orelse K > Start -> fold_fun1(K, Acc);
214 | _ -> {ok, Acc}
215 | end,
216 |
217 | case Res of
218 | {ok, Acc2} -> Acc2;
219 | {stop, Acc2} -> throw({stop, Acc2})
220 | end.
221 |
222 | fold_fun(K, V, #fold_st{from=Start, from_inclusive=Inclusive}=Acc) ->
223 | Res = case Inclusive of
224 | false when K > Start -> fold_fun1(K, V, Acc);
225 | true when K =:= Start orelse K > Start -> fold_fun1(K, V, Acc);
226 | _ -> {ok, Acc}
227 | end,
228 |
229 | case Res of
230 | {ok, Acc2} -> Acc2;
231 | {stop, Acc2} -> throw({stop, Acc2})
232 | end.
233 |
234 | fold_fun1(K, #fold_st{to=nil}=St) ->
235 | do_fold({ok, K}, St);
236 | fold_fun1(K, #fold_st{to=End, to_inclusive=false}=St) when K < End ->
237 | do_fold({ok, K}, St);
238 | fold_fun1(K, #fold_st{to=K, to_inclusive=true}=St) ->
239 | {stop, exec_cb(K, St)};
240 | fold_fun1(K, #fold_st{to=End, to_inclusive=true}=St) when K < End ->
241 | do_fold({ok, K}, St);
242 | fold_fun1(_K, St) ->
243 | {stop, St}.
244 |
245 | fold_fun1(K, V, #fold_st{to=nil}=St) ->
246 | do_fold({ok, K, V}, St);
247 | fold_fun1(K, V, #fold_st{to=End, to_inclusive=false}=St) when K < End ->
248 | do_fold({ok, K, V}, St);
249 | fold_fun1(K, V, #fold_st{to=K, to_inclusive=true}=St) ->
250 | {stop, exec_cb(K, V, St)};
251 | fold_fun1(K, V, #fold_st{to=End, to_inclusive=true}=St) when K < End ->
252 | do_fold({ok, K, V}, St);
253 | fold_fun1(_K, _V, St) ->
254 | {stop, St}.
255 |
256 |
257 | do_fold({ok, K}, #fold_st{max=0}=St) ->
258 | {ok, exec_cb(K, St)};
259 | do_fold({ok, K}, St) ->
260 | NewSt = exec_cb(K, St),
261 | N = NewSt#fold_st.num + 1,
262 | if
263 | N < St#fold_st.max -> {ok, NewSt#fold_st{num=N}};
264 | true -> {stop, NewSt}
265 | end;
266 | do_fold({ok, K, V}, #fold_st{max=0}=St) ->
267 | {ok, exec_cb(K, V, St)};
268 | do_fold({ok, K, V}, St) ->
269 | NewSt = exec_cb(K, V, St),
270 | N = NewSt#fold_st.num + 1,
271 | if
272 | N < St#fold_st.max -> {ok, NewSt#fold_st{num=N}};
273 | true -> {stop, NewSt}
274 | end.
275 |
276 | exec_cb(K, #fold_st{useracc=Acc, cb=Cb, key_enc=KE}=St) ->
277 | St#fold_st{useracc=Cb(dec(key, K, KE), Acc)}.
278 |
279 | exec_cb(K, V, #fold_st{useracc=Acc, cb=Cb, key_enc=KE, val_enc=VE}=St) ->
280 | NewAcc = Cb({dec(key, K, KE), dec(value, V, VE)}, Acc),
281 | St#fold_st{useracc=NewAcc}.
282 |
283 | data_directory_cleanup(DirPath) ->
284 | case file:list_dir(DirPath) of
285 | {ok, Files} ->
286 | [file:delete(filename:join([DirPath, File])) ||
287 | File <- Files],
288 | file:del_dir(DirPath);
289 | _ ->
290 | ignore
291 | end.
292 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Mozilla Public License Version 2.0
2 | ==================================
3 |
4 | 1. Definitions
5 | --------------
6 |
7 | 1.1. "Contributor"
8 | means each individual or legal entity that creates, contributes to
9 | the creation of, or owns Covered Software.
10 |
11 | 1.2. "Contributor Version"
12 | means the combination of the Contributions of others (if any) used
13 | by a Contributor and that particular Contributor's Contribution.
14 |
15 | 1.3. "Contribution"
16 | means Covered Software of a particular Contributor.
17 |
18 | 1.4. "Covered Software"
19 | means Source Code Form to which the initial Contributor has attached
20 | the notice in Exhibit A, the Executable Form of such Source Code
21 | Form, and Modifications of such Source Code Form, in each case
22 | including portions thereof.
23 |
24 | 1.5. "Incompatible With Secondary Licenses"
25 | means
26 |
27 | (a) that the initial Contributor has attached the notice described
28 | in Exhibit B to the Covered Software; or
29 |
30 | (b) that the Covered Software was made available under the terms of
31 | version 1.1 or earlier of the License, but not also under the
32 | terms of a Secondary License.
33 |
34 | 1.6. "Executable Form"
35 | means any form of the work other than Source Code Form.
36 |
37 | 1.7. "Larger Work"
38 | means a work that combines Covered Software with other material, in
39 | a separate file or files, that is not Covered Software.
40 |
41 | 1.8. "License"
42 | means this document.
43 |
44 | 1.9. "Licensable"
45 | means having the right to grant, to the maximum extent possible,
46 | whether at the time of the initial grant or subsequently, any and
47 | all of the rights conveyed by this License.
48 |
49 | 1.10. "Modifications"
50 | means any of the following:
51 |
52 | (a) any file in Source Code Form that results from an addition to,
53 | deletion from, or modification of the contents of Covered
54 | Software; or
55 |
56 | (b) any new file in Source Code Form that contains any Covered
57 | Software.
58 |
59 | 1.11. "Patent Claims" of a Contributor
60 | means any patent claim(s), including without limitation, method,
61 | process, and apparatus claims, in any patent Licensable by such
62 | Contributor that would be infringed, but for the grant of the
63 | License, by the making, using, selling, offering for sale, having
64 | made, import, or transfer of either its Contributions or its
65 | Contributor Version.
66 |
67 | 1.12. "Secondary License"
68 | means either the GNU General Public License, Version 2.0, the GNU
69 | Lesser General Public License, Version 2.1, the GNU Affero General
70 | Public License, Version 3.0, or any later versions of those
71 | licenses.
72 |
73 | 1.13. "Source Code Form"
74 | means the form of the work preferred for making modifications.
75 |
76 | 1.14. "You" (or "Your")
77 | means an individual or a legal entity exercising rights under this
78 | License. For legal entities, "You" includes any entity that
79 | controls, is controlled by, or is under common control with You. For
80 | purposes of this definition, "control" means (a) the power, direct
81 | or indirect, to cause the direction or management of such entity,
82 | whether by contract or otherwise, or (b) ownership of more than
83 | fifty percent (50%) of the outstanding shares or beneficial
84 | ownership of such entity.
85 |
86 | 2. License Grants and Conditions
87 | --------------------------------
88 |
89 | 2.1. Grants
90 |
91 | Each Contributor hereby grants You a world-wide, royalty-free,
92 | non-exclusive license:
93 |
94 | (a) under intellectual property rights (other than patent or trademark)
95 | Licensable by such Contributor to use, reproduce, make available,
96 | modify, display, perform, distribute, and otherwise exploit its
97 | Contributions, either on an unmodified basis, with Modifications, or
98 | as part of a Larger Work; and
99 |
100 | (b) under Patent Claims of such Contributor to make, use, sell, offer
101 | for sale, have made, import, and otherwise transfer either its
102 | Contributions or its Contributor Version.
103 |
104 | 2.2. Effective Date
105 |
106 | The licenses granted in Section 2.1 with respect to any Contribution
107 | become effective for each Contribution on the date the Contributor first
108 | distributes such Contribution.
109 |
110 | 2.3. Limitations on Grant Scope
111 |
112 | The licenses granted in this Section 2 are the only rights granted under
113 | this License. No additional rights or licenses will be implied from the
114 | distribution or licensing of Covered Software under this License.
115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a
116 | Contributor:
117 |
118 | (a) for any code that a Contributor has removed from Covered Software;
119 | or
120 |
121 | (b) for infringements caused by: (i) Your and any other third party's
122 | modifications of Covered Software, or (ii) the combination of its
123 | Contributions with other software (except as part of its Contributor
124 | Version); or
125 |
126 | (c) under Patent Claims infringed by Covered Software in the absence of
127 | its Contributions.
128 |
129 | This License does not grant any rights in the trademarks, service marks,
130 | or logos of any Contributor (except as may be necessary to comply with
131 | the notice requirements in Section 3.4).
132 |
133 | 2.4. Subsequent Licenses
134 |
135 | No Contributor makes additional grants as a result of Your choice to
136 | distribute the Covered Software under a subsequent version of this
137 | License (see Section 10.2) or under the terms of a Secondary License (if
138 | permitted under the terms of Section 3.3).
139 |
140 | 2.5. Representation
141 |
142 | Each Contributor represents that the Contributor believes its
143 | Contributions are its original creation(s) or it has sufficient rights
144 | to grant the rights to its Contributions conveyed by this License.
145 |
146 | 2.6. Fair Use
147 |
148 | This License is not intended to limit any rights You have under
149 | applicable copyright doctrines of fair use, fair dealing, or other
150 | equivalents.
151 |
152 | 2.7. Conditions
153 |
154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
155 | in Section 2.1.
156 |
157 | 3. Responsibilities
158 | -------------------
159 |
160 | 3.1. Distribution of Source Form
161 |
162 | All distribution of Covered Software in Source Code Form, including any
163 | Modifications that You create or to which You contribute, must be under
164 | the terms of this License. You must inform recipients that the Source
165 | Code Form of the Covered Software is governed by the terms of this
166 | License, and how they can obtain a copy of this License. You may not
167 | attempt to alter or restrict the recipients' rights in the Source Code
168 | Form.
169 |
170 | 3.2. Distribution of Executable Form
171 |
172 | If You distribute Covered Software in Executable Form then:
173 |
174 | (a) such Covered Software must also be made available in Source Code
175 | Form, as described in Section 3.1, and You must inform recipients of
176 | the Executable Form how they can obtain a copy of such Source Code
177 | Form by reasonable means in a timely manner, at a charge no more
178 | than the cost of distribution to the recipient; and
179 |
180 | (b) You may distribute such Executable Form under the terms of this
181 | License, or sublicense it under different terms, provided that the
182 | license for the Executable Form does not attempt to limit or alter
183 | the recipients' rights in the Source Code Form under this License.
184 |
185 | 3.3. Distribution of a Larger Work
186 |
187 | You may create and distribute a Larger Work under terms of Your choice,
188 | provided that You also comply with the requirements of this License for
189 | the Covered Software. If the Larger Work is a combination of Covered
190 | Software with a work governed by one or more Secondary Licenses, and the
191 | Covered Software is not Incompatible With Secondary Licenses, this
192 | License permits You to additionally distribute such Covered Software
193 | under the terms of such Secondary License(s), so that the recipient of
194 | the Larger Work may, at their option, further distribute the Covered
195 | Software under the terms of either this License or such Secondary
196 | License(s).
197 |
198 | 3.4. Notices
199 |
200 | You may not remove or alter the substance of any license notices
201 | (including copyright notices, patent notices, disclaimers of warranty,
202 | or limitations of liability) contained within the Source Code Form of
203 | the Covered Software, except that You may alter any license notices to
204 | the extent required to remedy known factual inaccuracies.
205 |
206 | 3.5. Application of Additional Terms
207 |
208 | You may choose to offer, and to charge a fee for, warranty, support,
209 | indemnity or liability obligations to one or more recipients of Covered
210 | Software. However, You may do so only on Your own behalf, and not on
211 | behalf of any Contributor. You must make it absolutely clear that any
212 | such warranty, support, indemnity, or liability obligation is offered by
213 | You alone, and You hereby agree to indemnify every Contributor for any
214 | liability incurred by such Contributor as a result of warranty, support,
215 | indemnity or liability terms You offer. You may include additional
216 | disclaimers of warranty and limitations of liability specific to any
217 | jurisdiction.
218 |
219 | 4. Inability to Comply Due to Statute or Regulation
220 | ---------------------------------------------------
221 |
222 | If it is impossible for You to comply with any of the terms of this
223 | License with respect to some or all of the Covered Software due to
224 | statute, judicial order, or regulation then You must: (a) comply with
225 | the terms of this License to the maximum extent possible; and (b)
226 | describe the limitations and the code they affect. Such description must
227 | be placed in a text file included with all distributions of the Covered
228 | Software under this License. Except to the extent prohibited by statute
229 | or regulation, such description must be sufficiently detailed for a
230 | recipient of ordinary skill to be able to understand it.
231 |
232 | 5. Termination
233 | --------------
234 |
235 | 5.1. The rights granted under this License will terminate automatically
236 | if You fail to comply with any of its terms. However, if You become
237 | compliant, then the rights granted under this License from a particular
238 | Contributor are reinstated (a) provisionally, unless and until such
239 | Contributor explicitly and finally terminates Your grants, and (b) on an
240 | ongoing basis, if such Contributor fails to notify You of the
241 | non-compliance by some reasonable means prior to 60 days after You have
242 | come back into compliance. Moreover, Your grants from a particular
243 | Contributor are reinstated on an ongoing basis if such Contributor
244 | notifies You of the non-compliance by some reasonable means, this is the
245 | first time You have received notice of non-compliance with this License
246 | from such Contributor, and You become compliant prior to 30 days after
247 | Your receipt of the notice.
248 |
249 | 5.2. If You initiate litigation against any entity by asserting a patent
250 | infringement claim (excluding declaratory judgment actions,
251 | counter-claims, and cross-claims) alleging that a Contributor Version
252 | directly or indirectly infringes any patent, then the rights granted to
253 | You by any and all Contributors for the Covered Software under Section
254 | 2.1 of this License shall terminate.
255 |
256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all
257 | end user license agreements (excluding distributors and resellers) which
258 | have been validly granted by You or Your distributors under this License
259 | prior to termination shall survive termination.
260 |
261 | ************************************************************************
262 | * *
263 | * 6. Disclaimer of Warranty *
264 | * ------------------------- *
265 | * *
266 | * Covered Software is provided under this License on an "as is" *
267 | * basis, without warranty of any kind, either expressed, implied, or *
268 | * statutory, including, without limitation, warranties that the *
269 | * Covered Software is free of defects, merchantable, fit for a *
270 | * particular purpose or non-infringing. The entire risk as to the *
271 | * quality and performance of the Covered Software is with You. *
272 | * Should any Covered Software prove defective in any respect, You *
273 | * (not any Contributor) assume the cost of any necessary servicing, *
274 | * repair, or correction. This disclaimer of warranty constitutes an *
275 | * essential part of this License. No use of any Covered Software is *
276 | * authorized under this License except under this disclaimer. *
277 | * *
278 | ************************************************************************
279 |
280 | ************************************************************************
281 | * *
282 | * 7. Limitation of Liability *
283 | * -------------------------- *
284 | * *
285 | * Under no circumstances and under no legal theory, whether tort *
286 | * (including negligence), contract, or otherwise, shall any *
287 | * Contributor, or anyone who distributes Covered Software as *
288 | * permitted above, be liable to You for any direct, indirect, *
289 | * special, incidental, or consequential damages of any character *
290 | * including, without limitation, damages for lost profits, loss of *
291 | * goodwill, work stoppage, computer failure or malfunction, or any *
292 | * and all other commercial damages or losses, even if such party *
293 | * shall have been informed of the possibility of such damages. This *
294 | * limitation of liability shall not apply to liability for death or *
295 | * personal injury resulting from such party's negligence to the *
296 | * extent applicable law prohibits such limitation. Some *
297 | * jurisdictions do not allow the exclusion or limitation of *
298 | * incidental or consequential damages, so this exclusion and *
299 | * limitation may not apply to You. *
300 | * *
301 | ************************************************************************
302 |
303 | 8. Litigation
304 | -------------
305 |
306 | Any litigation relating to this License may be brought only in the
307 | courts of a jurisdiction where the defendant maintains its principal
308 | place of business and such litigation shall be governed by laws of that
309 | jurisdiction, without reference to its conflict-of-law provisions.
310 | Nothing in this Section shall prevent a party's ability to bring
311 | cross-claims or counter-claims.
312 |
313 | 9. Miscellaneous
314 | ----------------
315 |
316 | This License represents the complete agreement concerning the subject
317 | matter hereof. If any provision of this License is held to be
318 | unenforceable, such provision shall be reformed only to the extent
319 | necessary to make it enforceable. Any law or regulation which provides
320 | that the language of a contract shall be construed against the drafter
321 | shall not be used to construe this License against a Contributor.
322 |
323 | 10. Versions of the License
324 | ---------------------------
325 |
326 | 10.1. New Versions
327 |
328 | Mozilla Foundation is the license steward. Except as provided in Section
329 | 10.3, no one other than the license steward has the right to modify or
330 | publish new versions of this License. Each version will be given a
331 | distinguishing version number.
332 |
333 | 10.2. Effect of New Versions
334 |
335 | You may distribute the Covered Software under the terms of the version
336 | of the License under which You originally received the Covered Software,
337 | or under the terms of any subsequent version published by the license
338 | steward.
339 |
340 | 10.3. Modified Versions
341 |
342 | If you create software not governed by this License, and you want to
343 | create a new license for such software, you may create and use a
344 | modified version of this License if you rename the license and remove
345 | any references to the name of the license steward (except to note that
346 | such modified license differs from this License).
347 |
348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary
349 | Licenses
350 |
351 | If You choose to distribute Source Code Form that is Incompatible With
352 | Secondary Licenses under the terms of this version of the License, the
353 | notice described in Exhibit B of this License must be attached.
354 |
355 | Exhibit A - Source Code Form License Notice
356 | -------------------------------------------
357 |
358 | This Source Code Form is subject to the terms of the Mozilla Public
359 | License, v. 2.0. If a copy of the MPL was not distributed with this
360 | file, You can obtain one at http://mozilla.org/MPL/2.0/.
361 |
362 | If it is not possible or desirable to put the notice in a particular
363 | file, then You may include the notice in a location (such as a LICENSE
364 | file in a relevant directory) where a recipient would be likely to look
365 | for such a notice.
366 |
367 | You may add additional accurate notices of copyright ownership.
368 |
369 | Exhibit B - "Incompatible With Secondary Licenses" Notice
370 | ---------------------------------------------------------
371 |
372 | This Source Code Form is "Incompatible With Secondary Licenses", as
373 | defined by the Mozilla Public License, v. 2.0.
374 |
--------------------------------------------------------------------------------