Permalink
Browse files

Add HanoiDB backend support

  • Loading branch information...
1 parent c531e3f commit 38bea8305b2abd3a67b43d6478289dfb3bb99c5f Gregory Burd committed May 21, 2012
Showing with 116 additions and 17 deletions.
  1. +1 −0 .gitignore
  2. +6 −6 Makefile
  3. +9 −3 README.md
  4. +4 −3 rebar.config
  5. +91 −0 src/backends/edis_hanoidb_backend.erl
  6. +2 −2 src/edis.app.src
  7. +1 −1 src/edis.erl
  8. +2 −2 src/edis_config.erl
View
@@ -6,3 +6,4 @@ ebin
deps
erl_crash.dump
logs/
+*~
View
@@ -13,7 +13,7 @@ all: erl
erl:
rebar get-deps && rebar compile
-
+
clean:
rm -rf bin
rebar clean
@@ -27,12 +27,12 @@ analyze: erl
xref: erl
rebar skip_deps=true xref
-
-run: erl
@elbrujohalcon
elbrujohalcon May 21, 2012

We like to have the project compiled before we run :)

@gburd
gburd May 22, 2012 owner

I found it slow to wait on compile every time when I'd just re-compiled it myself a second earlier. But this isn't a necessary change, and it shouldn't have been in the commit.

+
+run:
${ERL} -s edis
test: erl
- ${ERL} -config test.config -noshell -sname edis_test_server -s edis -run elog debug &
+ ${ERL} -config test.config -noshell -sname edis_test_server -s edis -run elog debug &
mkdir -p ./test/ebin
erlc -o ./test/ebin +debug_info ./test/*_SUITE.erl
rebar skip_deps=true ct ; \
@@ -42,7 +42,7 @@ shell: erl
${ERL}
doc: erl
- cd deps/erldocs
+ cd deps/erldocs
make
cd ../..
./deps/erldocs/erldocs doc
@@ -57,4 +57,4 @@ service: install
mkdir -p /etc/edis/db/
if [ ! -f /etc/edis/edis.config ] ; then cp priv/edis.config /etc/edis/ ; fi
cp priv/edis.init.d /etc/init.d/edis
-
+
View
@@ -22,9 +22,15 @@ Just run `$ make run` and open connections with your favourite redis client.
### Unsupported Commands
_SYNC_, _SLOWLOG_, _SLAVEOF_, _DEBUG *_
-
-
-
### License
edis is licensed by Electronic Inaka, LLC under the Apache 2.0 license; see the LICENSE file in this repository.
+### TODO
+
+* Backends
+ * HanoiDB
+ * Make use of the efficient range searchs with start/end for searching ranges
+ * Make use of time-based key expiry
+ * Finish the TODO items
+* Logging
+ * Swap out elog for lager
View
@@ -1,7 +1,8 @@
{deps, [{elog, "\.*", {git, "git://github.com/inaka/elog.git", "master"}},
{erldis, "\.*", {git, "git://github.com/inaka/erldis.git", "master"}},
- {eleveldb, "\.*", {git, "git@github.com:inaka/eleveldb.git", "master"}},
- {erldocs, "\.*", {git, "git://github.com/daleharvey/erldocs.git", "master"}}]}.
+ {eleveldb, "\.*", {git, "git@github.com:basho/eleveldb.git", "master"}},
+ {hanoidb, "\.*", {git, "git://github.com/basho/hanoidb.git", "master"}},
+ {erldocs, "\.*", {git, "git://github.com/daleharvey/erldocs.git", "master"}}]}.
{require_otp_vsn, "R1[45]"}.
{erl_first_files, ["src/edis_backend.erl", "test/edis_bench.erl"]}.
{erl_opts, [{src_dirs, ["src", "src/backends", "tests", "test/benchmarks"]},
@@ -21,4 +22,4 @@
warn_missing_spec,
warn_untyped_record, debug_info]}.
{xref_checks, [undefined_function_calls]}.
-{ct_extra_params,"-no_auto_compile -dir ./test/ebin -logdir logs/ct"}.
+{ct_extra_params,"-no_auto_compile -dir ./test/ebin -logdir logs/ct"}.
@@ -0,0 +1,91 @@
+%%%-------------------------------------------------------------------
+%%% @author Fernando Benavides <fernando.benavides@inakanetworks.com>
+%%% @author Chad DePue <chad@inakanetworks.com>
+%%% @copyright (C) 2011 InakaLabs SRL
+%%% @doc leveldb based backend
+%%% @end
+%%%-------------------------------------------------------------------
+-module(edis_hanoidb_backend).
+-author('Greg Burd <greg@burd.me>').
+
+-behaviour(edis_backend).
+
+-include("edis.hrl").
+-include_lib("hanoidb/include/hanoidb.hrl").
+
+-record(ref, {db :: hanoidb:db_ref(),
+ file :: string()}).
+-opaque ref() :: #ref{}.
+-export_type([ref/0]).
+
+-export([init/3, write/2, put/3, delete/2, fold/3, is_empty/1, destroy/1, status/1, get/2]).
+
+%% ====================================================================
+%% Behaviour functions
+%% ====================================================================
+-spec init(string(), non_neg_integer(), hanoidb:open_options()) -> {ok, ref()} | {error, term()}.
+init(Dir, Index, Options) ->
+ File = Dir ++ "/edis-" ++ integer_to_list(Index),
+ case hanoidb:open(File, Options) of
+ {ok, Db} -> {ok, #ref{db = Db, file = File}};
+ Error -> Error
+ end.
+
+-spec write(ref(), edis_backend:write_actions()) -> ok | {error, term()}.
+write(#ref{db = Db}, Actions) ->
+ ParseAction = fun({put, Key, Item}) ->
+ {put, sext:encode(Key), erlang:term_to_binary(Item)};
+ (Action) -> Action
@elbrujohalcon
elbrujohalcon May 21, 2012

Shouldn't you parse {delete, Key::binary()} tuples too? I mean, include ({delete, Key}) -> {delete, sext:encode(Key)}; or something like that, maybe

@elbrujohalcon
elbrujohalcon May 21, 2012

And what about clear? Does it work as it is? (I didn't check, just wondering :))

@krestenkrab
krestenkrab May 21, 2012

If keys can be non-binaries, then yes ... we need the delete case too.

What does clear do? HanoiDB doesn't implement it.

@gburd
gburd May 22, 2012 owner

I used the eleveldb backend as a template, it didn't have delete or clear in the write function and I didn't look further than that. If those are needed then sure, we should add them.

+ end,
+ hanoidb:transact(Db, lists:map(ParseAction, Actions)).
+
+-spec put(ref(), binary(), #edis_item{}) -> ok | {error, term()}.
+put(#ref{db = Db}, Key, Item) ->
+ hanoidb:put(Db, sext:encode(Key), erlang:term_to_binary(Item)).
+
+-spec delete(ref(), binary()) -> ok | {error, term()}.
+delete(#ref{db = Db}, Key) ->
+ hanoidb:delete(Db, sext:encode(Key)).
+
+-spec fold(ref(), edis_backend:fold_fun(), term()) -> term().
+fold(#ref{db = Db}, Fun, InitValue) ->
+ hanoidb:range(
+ Db,
+ fun({_Key, Bin}, Acc) ->
+ Fun(erlang:binary_to_term(Bin), Acc)
+ end,
+ InitValue).
+
+-define(MAX_OBJECT_KEY, <<16,0,0,0,4>>). %% TODO: really?
+
+-spec is_empty(ref()) -> boolean().
+is_empty(#ref{db = Db}) ->
+ FoldFun = fun(_K, _V, _Acc) -> throw(ok) end,
+ try
+ Range = #key_range{ from_key = sext:decode(<<>>),
+ from_inclusive = true,
+ to_key = ?MAX_OBJECT_KEY,
+ to_inclusive = false
+ },
+ [] =:= hanoi:fold_range(Db, FoldFun, [], Range)
+ catch
+ _:ok ->
+ false
+ end.
+
+-spec destroy(ref()) -> ok | {error, term()}.
+destroy(#ref{file = _File}) ->
+ ok. %% TODO: not yet implemented
+
+-spec status(ref()) -> {ok, binary()} | error.
+status(#ref{db = _Db}) ->
+ []. %% TODO: not yet implemented
@elbrujohalcon
elbrujohalcon May 21, 2012

Just picking here, but… you should return something like {ok, binary() or error or better throw an exception like throw(not_implemented).
Just because [] doesn't belong to the type on the spec.
Again, just picking, it's not really important.

@gburd
gburd May 22, 2012 owner

Good idea.

+
+-spec get(ref(), binary()) -> #edis_item{} | not_found | {error, term()}.
+get(#ref{db = Db}, Key) ->
+ case hanoidb:get(Db, sext:encode(Key)) of
+ {ok, Bin} ->
+ erlang:binary_to_term(Bin);
+ NotFoundOrError ->
+ NotFoundOrError
+ end.
View
@@ -1,7 +1,7 @@
{application, edis,
[
- {description, "Redis KV Store - The Erlang Way :)"},
- {vsn, "0.1"},
+ {description, "An Erlang implementation of the Redis KV Store"},
+ {vsn, "0.2.0"},
{registered, []},
{applications, [
kernel,
View
@@ -64,4 +64,4 @@ start(_StartType, _StartArgs) ->
%% @private
-spec stop(any()) -> ok.
-stop(_State) -> ok.
+stop(_State) -> ok.
View
@@ -59,7 +59,7 @@ get(requirepass) ->
get(dir) ->
get(dir, "./db/");
get(backend) ->
- get(backend, {edis_eleveldb_backend, [{create_if_missing, true}]});
@elbrujohalcon
elbrujohalcon May 21, 2012

I think, at least for now, we should keep eleveldb as our default backend. That's not saying we won't support HanoiDB, but it doesn't need to be the default option.

@gburd
gburd May 22, 2012 owner

Oops, honestly I should have not included this in the commit.

+ get(backend, {edis_hanoidb_backend, []});
get(Pattern) ->
[{K, V} ||
{K, V} <- application:get_all_env(edis),
@@ -73,4 +73,4 @@ get(Field, Default) ->
_ ->
?DEBUG("~p := ~p~n", [Field, Default]),
Default
- end.
+ end.

2 comments on commit 38bea83

@elbrujohalcon

This is great stuff, @gburd !!
I'm new to HanoiDB, but I'll check it out for sure.
I did a quick code review and I've added a couple of comments to your commit, but those are just details.
We (i.e. somebody at Inaka) will probably test your code in following days and then pull from your repo.
Tank you very much for your contribution!

@gburd
Owner
gburd commented on 38bea83 May 22, 2012

I'll clean up what you've spotted. Thanks for the review. Let me (anb Kresten) know how HanoiDB works out. I think if you add in the TODO items I listed in the README (key ranges, expiry, ...) you'll see that HanoiDB brings more to the table than the other options out there.

Please sign in to comment.