Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Add HanoiDB backend support

  • Loading branch information...
commit 38bea8305b2abd3a67b43d6478289dfb3bb99c5f 1 parent c531e3f
Gregory Burd authored May 21, 2012
1  .gitignore
@@ -6,3 +6,4 @@ ebin
6 6
 deps
7 7
 erl_crash.dump
8 8
 logs/
  9
+*~
12  Makefile
@@ -13,7 +13,7 @@ all: erl
13 13
 
14 14
 erl:
15 15
 	rebar get-deps && rebar compile
16  
-	
  16
+
17 17
 clean:
18 18
 	rm -rf bin
19 19
 	rebar clean
@@ -27,12 +27,12 @@ analyze: erl
27 27
 
28 28
 xref: erl
29 29
 	rebar skip_deps=true xref
30  
-	
31  
-run: erl
  30
+
  31
+run:
32 32
 	${ERL} -s edis
33 33
 
34 34
 test: erl
35  
-	${ERL} -config test.config -noshell -sname edis_test_server -s edis -run elog debug & 
  35
+	${ERL} -config test.config -noshell -sname edis_test_server -s edis -run elog debug &
36 36
 	mkdir -p ./test/ebin
37 37
 	erlc -o ./test/ebin +debug_info ./test/*_SUITE.erl
38 38
 	rebar skip_deps=true ct ; \
@@ -42,7 +42,7 @@ shell: erl
42 42
 	${ERL}
43 43
 
44 44
 doc: erl
45  
-	cd deps/erldocs 
  45
+	cd deps/erldocs
46 46
 	make
47 47
 	cd ../..
48 48
 	./deps/erldocs/erldocs doc
@@ -57,4 +57,4 @@ service: install
57 57
 	mkdir -p /etc/edis/db/
58 58
 	if [ ! -f /etc/edis/edis.config ] ; then cp priv/edis.config /etc/edis/ ; fi
59 59
 	cp priv/edis.init.d /etc/init.d/edis
60  
-	
  60
+
12  README.md
Source Rendered
@@ -22,9 +22,15 @@ Just run `$ make run` and open connections with your favourite redis client.
22 22
 ### Unsupported Commands
23 23
 _SYNC_, _SLOWLOG_, _SLAVEOF_, _DEBUG *_
24 24
 
25  
-
26  
-
27  
-
28 25
 ### License
29 26
 edis is licensed by Electronic Inaka, LLC under the Apache 2.0 license; see the LICENSE file in this repository.
30 27
 
  28
+### TODO
  29
+
  30
+* Backends
  31
+  * HanoiDB
  32
+    * Make use of the efficient range searchs with start/end for searching ranges
  33
+    * Make use of time-based key expiry
  34
+    * Finish the TODO items
  35
+* Logging
  36
+  * Swap out elog for lager
7  rebar.config
... ...
@@ -1,7 +1,8 @@
1 1
 {deps, [{elog, "\.*", {git, "git://github.com/inaka/elog.git", "master"}},
2 2
         {erldis, "\.*", {git, "git://github.com/inaka/erldis.git", "master"}},
3  
-        {eleveldb, "\.*", {git, "git@github.com:inaka/eleveldb.git", "master"}},
4  
-		{erldocs, "\.*", {git, "git://github.com/daleharvey/erldocs.git", "master"}}]}.
  3
+        {eleveldb, "\.*", {git, "git@github.com:basho/eleveldb.git", "master"}},
  4
+        {hanoidb, "\.*", {git, "git://github.com/basho/hanoidb.git", "master"}},
  5
+        {erldocs, "\.*", {git, "git://github.com/daleharvey/erldocs.git", "master"}}]}.
5 6
 {require_otp_vsn, "R1[45]"}.
6 7
 {erl_first_files, ["src/edis_backend.erl", "test/edis_bench.erl"]}.
7 8
 {erl_opts, [{src_dirs, ["src", "src/backends", "tests", "test/benchmarks"]},
@@ -21,4 +22,4 @@
21 22
             warn_missing_spec,
22 23
             warn_untyped_record, debug_info]}.
23 24
 {xref_checks, [undefined_function_calls]}.
24  
-{ct_extra_params,"-no_auto_compile -dir ./test/ebin -logdir logs/ct"}.
  25
+{ct_extra_params,"-no_auto_compile -dir ./test/ebin -logdir logs/ct"}.
91  src/backends/edis_hanoidb_backend.erl
... ...
@@ -0,0 +1,91 @@
  1
+%%%-------------------------------------------------------------------
  2
+%%% @author Fernando Benavides <fernando.benavides@inakanetworks.com>
  3
+%%% @author Chad DePue <chad@inakanetworks.com>
  4
+%%% @copyright (C) 2011 InakaLabs SRL
  5
+%%% @doc leveldb based backend
  6
+%%% @end
  7
+%%%-------------------------------------------------------------------
  8
+-module(edis_hanoidb_backend).
  9
+-author('Greg Burd <greg@burd.me>').
  10
+
  11
+-behaviour(edis_backend).
  12
+
  13
+-include("edis.hrl").
  14
+-include_lib("hanoidb/include/hanoidb.hrl").
  15
+
  16
+-record(ref, {db    :: hanoidb:db_ref(),
  17
+              file  :: string()}).
  18
+-opaque ref() :: #ref{}.
  19
+-export_type([ref/0]).
  20
+
  21
+-export([init/3, write/2, put/3, delete/2, fold/3, is_empty/1, destroy/1, status/1, get/2]).
  22
+
  23
+%% ====================================================================
  24
+%% Behaviour functions
  25
+%% ====================================================================
  26
+-spec init(string(), non_neg_integer(), hanoidb:open_options()) -> {ok, ref()} | {error, term()}.
  27
+init(Dir, Index, Options) ->
  28
+  File = Dir ++ "/edis-" ++ integer_to_list(Index),
  29
+  case hanoidb:open(File, Options) of
  30
+    {ok, Db} -> {ok, #ref{db = Db, file = File}};
  31
+    Error -> Error
  32
+  end.
  33
+
  34
+-spec write(ref(), edis_backend:write_actions()) -> ok | {error, term()}.
  35
+write(#ref{db = Db}, Actions) ->
  36
+  ParseAction = fun({put, Key, Item}) ->
  37
+                        {put, sext:encode(Key), erlang:term_to_binary(Item)};
  38
+                   (Action) -> Action
  39
+                end,
  40
+  hanoidb:transact(Db, lists:map(ParseAction, Actions)).
  41
+
  42
+-spec put(ref(), binary(), #edis_item{}) -> ok | {error, term()}.
  43
+put(#ref{db = Db}, Key, Item) ->
  44
+  hanoidb:put(Db, sext:encode(Key), erlang:term_to_binary(Item)).
  45
+
  46
+-spec delete(ref(), binary()) -> ok | {error, term()}.
  47
+delete(#ref{db = Db}, Key) ->
  48
+  hanoidb:delete(Db, sext:encode(Key)).
  49
+
  50
+-spec fold(ref(), edis_backend:fold_fun(), term()) -> term().
  51
+fold(#ref{db = Db}, Fun, InitValue) ->
  52
+  hanoidb:range(
  53
+    Db,
  54
+    fun({_Key, Bin}, Acc) ->
  55
+      Fun(erlang:binary_to_term(Bin), Acc)
  56
+    end,
  57
+    InitValue).
  58
+
  59
+-define(MAX_OBJECT_KEY, <<16,0,0,0,4>>). %% TODO: really?
  60
+
  61
+-spec is_empty(ref()) -> boolean().
  62
+is_empty(#ref{db = Db}) ->
  63
+    FoldFun = fun(_K, _V, _Acc) -> throw(ok) end,
  64
+    try
  65
+        Range = #key_range{ from_key = sext:decode(<<>>),
  66
+                  from_inclusive = true,
  67
+                  to_key         = ?MAX_OBJECT_KEY,
  68
+                  to_inclusive   = false
  69
+                },
  70
+        [] =:= hanoi:fold_range(Db, FoldFun, [], Range)
  71
+    catch
  72
+        _:ok ->
  73
+            false
  74
+    end.
  75
+
  76
+-spec destroy(ref()) -> ok | {error, term()}.
  77
+destroy(#ref{file = _File}) ->
  78
+    ok. %% TODO: not yet implemented
  79
+
  80
+-spec status(ref()) -> {ok, binary()} | error.
  81
+status(#ref{db = _Db}) ->
  82
+    []. %% TODO: not yet implemented
  83
+
  84
+-spec get(ref(), binary()) -> #edis_item{} | not_found | {error, term()}.
  85
+get(#ref{db = Db}, Key) ->
  86
+    case hanoidb:get(Db, sext:encode(Key)) of
  87
+        {ok, Bin} ->
  88
+            erlang:binary_to_term(Bin);
  89
+        NotFoundOrError ->
  90
+            NotFoundOrError
  91
+    end.
4  src/edis.app.src
... ...
@@ -1,7 +1,7 @@
1 1
 {application, edis,
2 2
  [
3  
-  {description, "Redis KV Store - The Erlang Way :)"},
4  
-  {vsn, "0.1"},
  3
+  {description, "An Erlang implementation of the Redis KV Store"},
  4
+  {vsn, "0.2.0"},
5 5
   {registered, []},
6 6
   {applications, [
7 7
                   kernel,
2  src/edis.erl
@@ -64,4 +64,4 @@ start(_StartType, _StartArgs) ->
64 64
 
65 65
 %% @private
66 66
 -spec stop(any()) -> ok.
67  
-stop(_State) -> ok.
  67
+stop(_State) -> ok.
4  src/edis_config.erl
@@ -59,7 +59,7 @@ get(requirepass) ->
59 59
 get(dir) ->
60 60
   get(dir, "./db/");
61 61
 get(backend) ->
62  
-  get(backend, {edis_eleveldb_backend, [{create_if_missing, true}]});
  62
+  get(backend, {edis_hanoidb_backend, []});
63 63
 get(Pattern) ->
64 64
   [{K, V} ||
65 65
    {K, V} <- application:get_all_env(edis),
@@ -73,4 +73,4 @@ get(Field, Default) ->
73 73
     _ ->
74 74
       ?DEBUG("~p := ~p~n", [Field, Default]),
75 75
       Default
76  
-  end.
  76
+  end.

2 notes on commit 38bea83

Brujo Benavides

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

Brujo Benavides

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.

Brujo Benavides

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

Brujo Benavides

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

Brujo Benavides

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.

Brujo Benavides

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!

Kresten Krab Thorup

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

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

Gregory Burd
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.

Gregory Burd

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.

Gregory Burd

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

Gregory Burd
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.
Something went wrong with that request. Please try again.