Reformat of CDB
CDB was failing tests (was it always this way?). There has been a little bit of a patch-up of the test, but there are still some potentially outstanding issues with scanning over a file when attempting to read beyond the end of the file. Tabbing reformatting and general tidy. Concierge documentation development ongoing.
This commit is contained in:
parent
c1f6a042d9
commit
28f612426a
2 changed files with 814 additions and 633 deletions
|
@ -46,7 +46,17 @@
|
||||||
|
|
||||||
-module(leveled_cdb).
|
-module(leveled_cdb).
|
||||||
|
|
||||||
-export([from_dict/2,
|
-behaviour(gen_server).
|
||||||
|
|
||||||
|
-export([init/1,
|
||||||
|
handle_call/3,
|
||||||
|
handle_cast/2,
|
||||||
|
handle_info/2,
|
||||||
|
terminate/2,
|
||||||
|
code_change/3,
|
||||||
|
cdb_open_writer/1,
|
||||||
|
cdb_open_reader/1,
|
||||||
|
from_dict/2,
|
||||||
create/2,
|
create/2,
|
||||||
dump/1,
|
dump/1,
|
||||||
get/2,
|
get/2,
|
||||||
|
@ -66,7 +76,88 @@
|
||||||
-define(MAX_FILE_SIZE, 3221225472).
|
-define(MAX_FILE_SIZE, 3221225472).
|
||||||
-define(BASE_POSITION, 2048).
|
-define(BASE_POSITION, 2048).
|
||||||
|
|
||||||
%%
|
-record(state, {hashtree,
|
||||||
|
last_position :: integer(),
|
||||||
|
smallest_sqn :: integer(),
|
||||||
|
highest_sqn :: integer(),
|
||||||
|
filename :: string(),
|
||||||
|
handle :: file:fd(),
|
||||||
|
writer :: boolean}).
|
||||||
|
|
||||||
|
|
||||||
|
%%%============================================================================
|
||||||
|
%%% API
|
||||||
|
%%%============================================================================
|
||||||
|
|
||||||
|
cdb_open_writer(Filename) ->
|
||||||
|
{ok, Pid} = gen_server:start(?MODULE, [], []),
|
||||||
|
case gen_server:call(Pid, {cdb_open_writer, Filename}, infinity) of
|
||||||
|
ok ->
|
||||||
|
{ok, Pid};
|
||||||
|
Error ->
|
||||||
|
Error
|
||||||
|
end.
|
||||||
|
|
||||||
|
cdb_open_reader(Filename) ->
|
||||||
|
{ok, Pid} = gen_server:start(?MODULE, [], []),
|
||||||
|
case gen_server:call(Pid, {cdb_open_reader, Filename}, infinity) of
|
||||||
|
ok ->
|
||||||
|
{ok, Pid};
|
||||||
|
Error ->
|
||||||
|
Error
|
||||||
|
end.
|
||||||
|
|
||||||
|
%cdb_get(Pid, Key) ->
|
||||||
|
% gen_server:call(Pid, {cdb_get, Key}, infinity).
|
||||||
|
%
|
||||||
|
%cdb_put(Pid, Key, Value) ->
|
||||||
|
% gen_server:call(Pid, {cdb_put, Key, Value}, infinity).
|
||||||
|
%
|
||||||
|
%cdb_close(Pid) ->
|
||||||
|
% gen_server:call(Pid, cdb_close, infinity).
|
||||||
|
|
||||||
|
|
||||||
|
%%%============================================================================
|
||||||
|
%%% gen_server callbacks
|
||||||
|
%%%============================================================================
|
||||||
|
|
||||||
|
init([]) ->
|
||||||
|
{ok, #state{}}.
|
||||||
|
|
||||||
|
handle_call({cdb_open_writer, Filename}, _From, State) ->
|
||||||
|
io:format("Opening file for writing with filename ~s~n", [Filename]),
|
||||||
|
{LastPosition, HashTree} = open_active_file(Filename),
|
||||||
|
{ok, Handle} = file:open(Filename, [binary, raw, read,
|
||||||
|
write, delayed_write]),
|
||||||
|
{reply, ok, State#state{handle=Handle,
|
||||||
|
last_position=LastPosition,
|
||||||
|
filename=Filename,
|
||||||
|
hashtree=HashTree,
|
||||||
|
writer=true}};
|
||||||
|
handle_call({cdb_open_reader, Filename}, _From, State) ->
|
||||||
|
io:format("Opening file for reading with filename ~s~n", [Filename]),
|
||||||
|
{ok, Handle} = file:open(Filename, [binary, raw, read]),
|
||||||
|
{reply, ok, State#state{handle=Handle,
|
||||||
|
filename=Filename,
|
||||||
|
writer=false}}.
|
||||||
|
|
||||||
|
handle_cast(_Msg, State) ->
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
handle_info(_Info, State) ->
|
||||||
|
{noreply, State}.
|
||||||
|
|
||||||
|
terminate(_Reason, _State) ->
|
||||||
|
ok.
|
||||||
|
|
||||||
|
code_change(_OldVsn, State, _Extra) ->
|
||||||
|
{ok, State}.
|
||||||
|
|
||||||
|
%%%============================================================================
|
||||||
|
%%% Internal functions
|
||||||
|
%%%============================================================================
|
||||||
|
|
||||||
|
|
||||||
%% from_dict(FileName,ListOfKeyValueTuples)
|
%% from_dict(FileName,ListOfKeyValueTuples)
|
||||||
%% Given a filename and a dictionary, create a cdb
|
%% Given a filename and a dictionary, create a cdb
|
||||||
%% using the key value pairs from the dict.
|
%% using the key value pairs from the dict.
|
||||||
|
@ -102,7 +193,6 @@ dump(FileName, CRCCheck) ->
|
||||||
end,
|
end,
|
||||||
NumberOfPairs = lists:foldl(Fn, 0, lists:seq(0,255)) bsr 1,
|
NumberOfPairs = lists:foldl(Fn, 0, lists:seq(0,255)) bsr 1,
|
||||||
io:format("Count of keys in db is ~w~n", [NumberOfPairs]),
|
io:format("Count of keys in db is ~w~n", [NumberOfPairs]),
|
||||||
|
|
||||||
{ok, _} = file:position(Handle, {bof, 2048}),
|
{ok, _} = file:position(Handle, {bof, 2048}),
|
||||||
Fn1 = fun(_I,Acc) ->
|
Fn1 = fun(_I,Acc) ->
|
||||||
{KL,VL} = read_next_2_integers(Handle),
|
{KL,VL} = read_next_2_integers(Handle),
|
||||||
|
@ -114,7 +204,8 @@ dump(FileName, CRCCheck) ->
|
||||||
Return = {crc_wonky, get(Handle, Key)};
|
Return = {crc_wonky, get(Handle, Key)};
|
||||||
{_, Value} ->
|
{_, Value} ->
|
||||||
{ok, CurrLoc} = file:position(Handle, cur),
|
{ok, CurrLoc} = file:position(Handle, cur),
|
||||||
Return = case get(Handle, Key) of
|
Return =
|
||||||
|
case get(Handle, Key) of
|
||||||
{Key,Value} -> {Key ,Value};
|
{Key,Value} -> {Key ,Value};
|
||||||
X -> {wonky, X}
|
X -> {wonky, X}
|
||||||
end
|
end
|
||||||
|
@ -180,7 +271,6 @@ get(FileNameOrHandle, Key) ->
|
||||||
get(FileName, Key, CRCCheck) when is_list(FileName), is_list(Key) ->
|
get(FileName, Key, CRCCheck) when is_list(FileName), is_list(Key) ->
|
||||||
{ok,Handle} = file:open(FileName,[binary, raw, read]),
|
{ok,Handle} = file:open(FileName,[binary, raw, read]),
|
||||||
get(Handle,Key, CRCCheck);
|
get(Handle,Key, CRCCheck);
|
||||||
|
|
||||||
get(Handle, Key, CRCCheck) when is_tuple(Handle), is_list(Key) ->
|
get(Handle, Key, CRCCheck) when is_tuple(Handle), is_list(Key) ->
|
||||||
Hash = hash(Key),
|
Hash = hash(Key),
|
||||||
Index = hash_to_index(Hash),
|
Index = hash_to_index(Hash),
|
||||||
|
@ -260,21 +350,35 @@ fold(Handle, FoldFun, Acc0, {Position, FirstHashPosition}, KeyOnly) ->
|
||||||
case read_next_2_integers(Handle) of
|
case read_next_2_integers(Handle) of
|
||||||
{KeyLength, ValueLength} ->
|
{KeyLength, ValueLength} ->
|
||||||
NextKey = read_next_term(Handle, KeyLength),
|
NextKey = read_next_term(Handle, KeyLength),
|
||||||
NextPosition = Position + KeyLength + ValueLength + ?DWORD_SIZE,
|
NextPosition = Position
|
||||||
|
+ KeyLength + ValueLength +
|
||||||
|
?DWORD_SIZE,
|
||||||
case KeyOnly of
|
case KeyOnly of
|
||||||
true ->
|
true ->
|
||||||
fold(Handle, FoldFun, FoldFun(NextKey, Acc0),
|
fold(Handle,
|
||||||
{NextPosition, FirstHashPosition}, KeyOnly);
|
FoldFun,
|
||||||
|
FoldFun(NextKey, Acc0),
|
||||||
|
{NextPosition, FirstHashPosition},
|
||||||
|
KeyOnly);
|
||||||
false ->
|
false ->
|
||||||
case read_next_term(Handle, ValueLength, crc, ?CRC_CHECK) of
|
case read_next_term(Handle,
|
||||||
|
ValueLength,
|
||||||
|
crc,
|
||||||
|
?CRC_CHECK) of
|
||||||
{false, _} ->
|
{false, _} ->
|
||||||
io:format("Skipping value for Key ~w as CRC check failed~n",
|
io:format("Skipping value for Key ~w as CRC
|
||||||
[NextKey]),
|
check failed~n", [NextKey]),
|
||||||
fold(Handle, FoldFun, Acc0,
|
fold(Handle,
|
||||||
{NextPosition, FirstHashPosition}, KeyOnly);
|
FoldFun,
|
||||||
|
Acc0,
|
||||||
|
{NextPosition, FirstHashPosition},
|
||||||
|
KeyOnly);
|
||||||
{_, Value} ->
|
{_, Value} ->
|
||||||
fold(Handle, FoldFun, FoldFun(NextKey, Value, Acc0),
|
fold(Handle,
|
||||||
{NextPosition, FirstHashPosition}, KeyOnly)
|
FoldFun,
|
||||||
|
FoldFun(NextKey, Value, Acc0),
|
||||||
|
{NextPosition, FirstHashPosition},
|
||||||
|
KeyOnly)
|
||||||
end
|
end
|
||||||
end;
|
end;
|
||||||
eof ->
|
eof ->
|
||||||
|
@ -369,11 +473,14 @@ scan_over_file(Handle, Position, HashTree) ->
|
||||||
{Key, ValueAsBin, KeyLength, ValueLength} ->
|
{Key, ValueAsBin, KeyLength, ValueLength} ->
|
||||||
case crccheck_value(ValueAsBin) of
|
case crccheck_value(ValueAsBin) of
|
||||||
true ->
|
true ->
|
||||||
NewPosition = Position + KeyLength + ValueLength + ?DWORD_SIZE,
|
NewPosition = Position + KeyLength + ValueLength
|
||||||
scan_over_file(Handle, NewPosition,
|
+ ?DWORD_SIZE,
|
||||||
|
scan_over_file(Handle,
|
||||||
|
NewPosition,
|
||||||
put_hashtree(Key, Position, HashTree));
|
put_hashtree(Key, Position, HashTree));
|
||||||
false ->
|
false ->
|
||||||
io:format("CRC check returned false on key of ~w ~n", [Key]),
|
io:format("CRC check returned false on key of ~w ~n",
|
||||||
|
[Key]),
|
||||||
{Position, HashTree}
|
{Position, HashTree}
|
||||||
end;
|
end;
|
||||||
eof ->
|
eof ->
|
||||||
|
@ -391,12 +498,16 @@ saferead_keyvalue(Handle) ->
|
||||||
eof ->
|
eof ->
|
||||||
false;
|
false;
|
||||||
{KeyL, ValueL} ->
|
{KeyL, ValueL} ->
|
||||||
case read_next_term(Handle, KeyL) of
|
io:format("KeyL ~w ValueL ~w~n", [KeyL, ValueL]),
|
||||||
|
case safe_read_next_term(Handle, KeyL) of
|
||||||
{error, einval} ->
|
{error, einval} ->
|
||||||
false;
|
false;
|
||||||
eof ->
|
eof ->
|
||||||
false;
|
false;
|
||||||
|
false ->
|
||||||
|
false;
|
||||||
Key ->
|
Key ->
|
||||||
|
io:format("Found Key of ~s~n", [Key]),
|
||||||
case file:read(Handle, ValueL) of
|
case file:read(Handle, ValueL) of
|
||||||
{error, einval} ->
|
{error, einval} ->
|
||||||
false;
|
false;
|
||||||
|
@ -408,6 +519,16 @@ saferead_keyvalue(Handle) ->
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
safe_read_next_term(Handle, Length) ->
|
||||||
|
try read_next_term(Handle, Length) of
|
||||||
|
Term ->
|
||||||
|
Term
|
||||||
|
catch
|
||||||
|
error:badarg ->
|
||||||
|
false
|
||||||
|
end.
|
||||||
|
|
||||||
%% The first four bytes of the value are the crc check
|
%% The first four bytes of the value are the crc check
|
||||||
crccheck_value(Value) when byte_size(Value) >4 ->
|
crccheck_value(Value) when byte_size(Value) >4 ->
|
||||||
<< Hash:32/integer, Tail/bitstring>> = Value,
|
<< Hash:32/integer, Tail/bitstring>> = Value,
|
||||||
|
@ -480,8 +601,7 @@ read_next_2_integers(Handle) ->
|
||||||
case file:read(Handle,?DWORD_SIZE) of
|
case file:read(Handle,?DWORD_SIZE) of
|
||||||
{ok, <<Int1:32,Int2:32>>} ->
|
{ok, <<Int1:32,Int2:32>>} ->
|
||||||
{endian_flip(Int1), endian_flip(Int2)};
|
{endian_flip(Int1), endian_flip(Int2)};
|
||||||
ReadError
|
ReadError ->
|
||||||
->
|
|
||||||
ReadError
|
ReadError
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
@ -557,8 +677,8 @@ write_hash_tables([Index|Rest], Handle, HashTree, StartPos, IndexList) ->
|
||||||
{L1, [<<0:32, 0:32>>|L2]} = lists:split(Slot1, AccSlotList),
|
{L1, [<<0:32, 0:32>>|L2]} = lists:split(Slot1, AccSlotList),
|
||||||
lists:append(L1, [Binary|L2])
|
lists:append(L1, [Binary|L2])
|
||||||
end,
|
end,
|
||||||
NewSlotList = lists:foldl(Fn, SlotList, BinList),
|
|
||||||
|
|
||||||
|
NewSlotList = lists:foldl(Fn, SlotList, BinList),
|
||||||
{ok, CurrPos} = file:position(Handle, cur),
|
{ok, CurrPos} = file:position(Handle, cur),
|
||||||
file:write(Handle, NewSlotList),
|
file:write(Handle, NewSlotList),
|
||||||
write_hash_tables(Rest, Handle, HashTree, StartPos,
|
write_hash_tables(Rest, Handle, HashTree, StartPos,
|
||||||
|
@ -679,7 +799,7 @@ key_value_to_record({Key, Value}) ->
|
||||||
-ifdef(TEST).
|
-ifdef(TEST).
|
||||||
|
|
||||||
write_key_value_pairs_1_test() ->
|
write_key_value_pairs_1_test() ->
|
||||||
{ok,Handle} = file:open("test.cdb",write),
|
{ok,Handle} = file:open("../test/test.cdb",write),
|
||||||
{_, HashTree} = write_key_value_pairs(Handle,[{"key1","value1"},{"key2","value2"}]),
|
{_, HashTree} = write_key_value_pairs(Handle,[{"key1","value1"},{"key2","value2"}]),
|
||||||
Hash1 = hash("key1"),
|
Hash1 = hash("key1"),
|
||||||
Index1 = hash_to_index(Hash1),
|
Index1 = hash_to_index(Hash1),
|
||||||
|
@ -691,18 +811,18 @@ write_key_value_pairs_1_test() ->
|
||||||
io:format("HashTree is ~w~n", [HashTree]),
|
io:format("HashTree is ~w~n", [HashTree]),
|
||||||
io:format("Expected HashTree is ~w~n", [R2]),
|
io:format("Expected HashTree is ~w~n", [R2]),
|
||||||
?assertMatch(R2, HashTree),
|
?assertMatch(R2, HashTree),
|
||||||
ok = file:delete("test.cdb").
|
ok = file:delete("../test/test.cdb").
|
||||||
|
|
||||||
|
|
||||||
write_hash_tables_1_test() ->
|
write_hash_tables_1_test() ->
|
||||||
{ok, Handle} = file:open("test.cdb",write),
|
{ok, Handle} = file:open("../test/testx.cdb",write),
|
||||||
R0 = array:new(256, {default, gb_trees:empty()}),
|
R0 = array:new(256, {default, gb_trees:empty()}),
|
||||||
R1 = array:set(64, gb_trees:insert(6383014720, [18], array:get(64, R0)), R0),
|
R1 = array:set(64, gb_trees:insert(6383014720, [18], array:get(64, R0)), R0),
|
||||||
R2 = array:set(67, gb_trees:insert(6383014723, [0], array:get(67, R1)), R1),
|
R2 = array:set(67, gb_trees:insert(6383014723, [0], array:get(67, R1)), R1),
|
||||||
Result = write_hash_tables(Handle, R2),
|
Result = write_hash_tables(Handle, R2),
|
||||||
io:format("write hash tables result of ~w ~n", [Result]),
|
io:format("write hash tables result of ~w ~n", [Result]),
|
||||||
?assertMatch(Result,[{67,16,2},{64,0,2}]),
|
?assertMatch(Result,[{67,16,2},{64,0,2}]),
|
||||||
ok = file:delete("test.cdb").
|
ok = file:delete("../test/testx.cdb").
|
||||||
|
|
||||||
find_open_slot_1_test() ->
|
find_open_slot_1_test() ->
|
||||||
List = [<<1:32,1:32>>,<<0:32,0:32>>,<<1:32,1:32>>,<<1:32,1:32>>],
|
List = [<<1:32,1:32>>,<<0:32,0:32>>,<<1:32,1:32>>,<<1:32,1:32>>],
|
||||||
|
@ -731,10 +851,10 @@ find_open_slot_5_test() ->
|
||||||
|
|
||||||
full_1_test() ->
|
full_1_test() ->
|
||||||
List1 = lists:sort([{"key1","value1"},{"key2","value2"}]),
|
List1 = lists:sort([{"key1","value1"},{"key2","value2"}]),
|
||||||
create("simple.cdb",lists:sort([{"key1","value1"},{"key2","value2"}])),
|
create("../test/simple.cdb",lists:sort([{"key1","value1"},{"key2","value2"}])),
|
||||||
List2 = lists:sort(dump("simple.cdb")),
|
List2 = lists:sort(dump("../test/simple.cdb")),
|
||||||
?assertMatch(List1,List2),
|
?assertMatch(List1,List2),
|
||||||
ok = file:delete("simple.cdb").
|
ok = file:delete("../test/simple.cdb").
|
||||||
|
|
||||||
full_2_test() ->
|
full_2_test() ->
|
||||||
List1 = lists:sort([{lists:flatten(io_lib:format("~s~p",[Prefix,Plug])),
|
List1 = lists:sort([{lists:flatten(io_lib:format("~s~p",[Prefix,Plug])),
|
||||||
|
@ -742,34 +862,34 @@ full_2_test() ->
|
||||||
|| Plug <- lists:seq(1,2000),
|
|| Plug <- lists:seq(1,2000),
|
||||||
Prefix <- ["dsd","so39ds","oe9%#*(","020dkslsldclsldowlslf%$#",
|
Prefix <- ["dsd","so39ds","oe9%#*(","020dkslsldclsldowlslf%$#",
|
||||||
"tiep4||","qweq"]]),
|
"tiep4||","qweq"]]),
|
||||||
create("full.cdb",List1),
|
create("../test/full.cdb",List1),
|
||||||
List2 = lists:sort(dump("full.cdb")),
|
List2 = lists:sort(dump("../test/full.cdb")),
|
||||||
?assertMatch(List1,List2),
|
?assertMatch(List1,List2),
|
||||||
ok = file:delete("full.cdb").
|
ok = file:delete("../test/full.cdb").
|
||||||
|
|
||||||
from_dict_test() ->
|
from_dict_test() ->
|
||||||
D = dict:new(),
|
D = dict:new(),
|
||||||
D1 = dict:store("a","b",D),
|
D1 = dict:store("a","b",D),
|
||||||
D2 = dict:store("c","d",D1),
|
D2 = dict:store("c","d",D1),
|
||||||
ok = from_dict("from_dict_test.cdb",D2),
|
ok = from_dict("../test/from_dict_test.cdb",D2),
|
||||||
io:format("Store created ~n", []),
|
io:format("Store created ~n", []),
|
||||||
KVP = lists:sort(dump("from_dict_test.cdb")),
|
KVP = lists:sort(dump("../test/from_dict_test.cdb")),
|
||||||
D3 = lists:sort(dict:to_list(D2)),
|
D3 = lists:sort(dict:to_list(D2)),
|
||||||
io:format("KVP is ~w~n", [KVP]),
|
io:format("KVP is ~w~n", [KVP]),
|
||||||
io:format("D3 is ~w~n", [D3]),
|
io:format("D3 is ~w~n", [D3]),
|
||||||
?assertMatch(KVP, D3),
|
?assertMatch(KVP, D3),
|
||||||
ok = file:delete("from_dict_test.cdb").
|
ok = file:delete("../test/from_dict_test.cdb").
|
||||||
|
|
||||||
to_dict_test() ->
|
to_dict_test() ->
|
||||||
D = dict:new(),
|
D = dict:new(),
|
||||||
D1 = dict:store("a","b",D),
|
D1 = dict:store("a","b",D),
|
||||||
D2 = dict:store("c","d",D1),
|
D2 = dict:store("c","d",D1),
|
||||||
ok = from_dict("from_dict_test.cdb",D2),
|
ok = from_dict("../test/from_dict_test1.cdb",D2),
|
||||||
Dict = to_dict("from_dict_test.cdb"),
|
Dict = to_dict("../test/from_dict_test1.cdb"),
|
||||||
D3 = lists:sort(dict:to_list(D2)),
|
D3 = lists:sort(dict:to_list(D2)),
|
||||||
D4 = lists:sort(dict:to_list(Dict)),
|
D4 = lists:sort(dict:to_list(Dict)),
|
||||||
?assertMatch(D4,D3),
|
?assertMatch(D4,D3),
|
||||||
ok = file:delete("from_dict_test.cdb").
|
ok = file:delete("../test/from_dict_test1.cdb").
|
||||||
|
|
||||||
crccheck_emptyvalue_test() ->
|
crccheck_emptyvalue_test() ->
|
||||||
?assertMatch(false, crccheck_value(<<>>)).
|
?assertMatch(false, crccheck_value(<<>>)).
|
||||||
|
@ -807,23 +927,23 @@ activewrite_singlewrite_test() ->
|
||||||
Value = "some text as new value",
|
Value = "some text as new value",
|
||||||
InitialD = dict:new(),
|
InitialD = dict:new(),
|
||||||
InitialD1 = dict:store("0001", "Initial value", InitialD),
|
InitialD1 = dict:store("0001", "Initial value", InitialD),
|
||||||
ok = from_dict("test_mem.cdb", InitialD1),
|
ok = from_dict("../test/test_mem.cdb", InitialD1),
|
||||||
io:format("New db file created ~n", []),
|
io:format("New db file created ~n", []),
|
||||||
{LastPosition, KeyDict} = open_active_file("test_mem.cdb"),
|
{LastPosition, KeyDict} = open_active_file("../test/test_mem.cdb"),
|
||||||
io:format("File opened as new active file "
|
io:format("File opened as new active file "
|
||||||
"with LastPosition=~w ~n", [LastPosition]),
|
"with LastPosition=~w ~n", [LastPosition]),
|
||||||
{_, _, UpdKeyDict} = put("test_mem.cdb", Key, Value, {LastPosition, KeyDict}),
|
{_, _, UpdKeyDict} = put("../test/test_mem.cdb", Key, Value, {LastPosition, KeyDict}),
|
||||||
io:format("New key and value added to active file ~n", []),
|
io:format("New key and value added to active file ~n", []),
|
||||||
?assertMatch({Key, Value}, get_mem(Key, "test_mem.cdb", UpdKeyDict)),
|
?assertMatch({Key, Value}, get_mem(Key, "../test/test_mem.cdb", UpdKeyDict)),
|
||||||
ok = file:delete("test_mem.cdb").
|
ok = file:delete("../test/test_mem.cdb").
|
||||||
|
|
||||||
search_hash_table_findinslot_test() ->
|
search_hash_table_findinslot_test() ->
|
||||||
Key1 = "key1", % this is in slot 3 if count is 8
|
Key1 = "key1", % this is in slot 3 if count is 8
|
||||||
D = dict:from_list([{Key1, "value1"}, {"K2", "V2"}, {"K3", "V3"},
|
D = dict:from_list([{Key1, "value1"}, {"K2", "V2"}, {"K3", "V3"},
|
||||||
{"K4", "V4"}, {"K5", "V5"}, {"K6", "V6"}, {"K7", "V7"},
|
{"K4", "V4"}, {"K5", "V5"}, {"K6", "V6"}, {"K7", "V7"},
|
||||||
{"K8", "V8"}]),
|
{"K8", "V8"}]),
|
||||||
ok = from_dict("hashtable1_test.cdb",D),
|
ok = from_dict("../test/hashtable1_test.cdb",D),
|
||||||
{ok, Handle} = file:open("hashtable1_test.cdb", [binary, raw, read, write]),
|
{ok, Handle} = file:open("../test/hashtable1_test.cdb", [binary, raw, read, write]),
|
||||||
Hash = hash(Key1),
|
Hash = hash(Key1),
|
||||||
Index = hash_to_index(Hash),
|
Index = hash_to_index(Hash),
|
||||||
{ok, _} = file:position(Handle, {bof, ?DWORD_SIZE*Index}),
|
{ok, _} = file:position(Handle, {bof, ?DWORD_SIZE*Index}),
|
||||||
|
@ -850,15 +970,15 @@ search_hash_table_findinslot_test() ->
|
||||||
ok = file:pwrite(Handle, FirstHashPosition + (Slot -1) * ?DWORD_SIZE, RBin),
|
ok = file:pwrite(Handle, FirstHashPosition + (Slot -1) * ?DWORD_SIZE, RBin),
|
||||||
ok = file:close(Handle),
|
ok = file:close(Handle),
|
||||||
io:format("Find key following change to hash table~n"),
|
io:format("Find key following change to hash table~n"),
|
||||||
?assertMatch(missing, get("hashtable1_test.cdb", Key1)),
|
?assertMatch(missing, get("../test/hashtable1_test.cdb", Key1)),
|
||||||
ok = file:delete("hashtable1_test.cdb").
|
ok = file:delete("../test/hashtable1_test.cdb").
|
||||||
|
|
||||||
getnextkey_inclemptyvalue_test() ->
|
getnextkey_inclemptyvalue_test() ->
|
||||||
L = [{"K9", "V9"}, {"K2", "V2"}, {"K3", ""},
|
L = [{"K9", "V9"}, {"K2", "V2"}, {"K3", ""},
|
||||||
{"K4", "V4"}, {"K5", "V5"}, {"K6", "V6"}, {"K7", "V7"},
|
{"K4", "V4"}, {"K5", "V5"}, {"K6", "V6"}, {"K7", "V7"},
|
||||||
{"K8", "V8"}, {"K1", "V1"}],
|
{"K8", "V8"}, {"K1", "V1"}],
|
||||||
ok = create("hashtable1_test.cdb", L),
|
ok = create("../test/hashtable2_test.cdb", L),
|
||||||
{FirstKey, Handle, P1} = get_nextkey("hashtable1_test.cdb"),
|
{FirstKey, Handle, P1} = get_nextkey("../test/hashtable2_test.cdb"),
|
||||||
io:format("Next position details of ~w~n", [P1]),
|
io:format("Next position details of ~w~n", [P1]),
|
||||||
?assertMatch("K9", FirstKey),
|
?assertMatch("K9", FirstKey),
|
||||||
{SecondKey, Handle, P2} = get_nextkey(Handle, P1),
|
{SecondKey, Handle, P2} = get_nextkey(Handle, P1),
|
||||||
|
@ -873,14 +993,14 @@ getnextkey_inclemptyvalue_test() ->
|
||||||
{LastKey, Info} = get_nextkey(Handle, P8),
|
{LastKey, Info} = get_nextkey(Handle, P8),
|
||||||
?assertMatch(nomorekeys, Info),
|
?assertMatch(nomorekeys, Info),
|
||||||
?assertMatch("K1", LastKey),
|
?assertMatch("K1", LastKey),
|
||||||
ok = file:delete("hashtable1_test.cdb").
|
ok = file:delete("../test/hashtable2_test.cdb").
|
||||||
|
|
||||||
newactivefile_test() ->
|
newactivefile_test() ->
|
||||||
{LastPosition, _} = open_active_file("activefile_test.cdb"),
|
{LastPosition, _} = open_active_file("../test/activefile_test.cdb"),
|
||||||
?assertMatch(256 * ?DWORD_SIZE, LastPosition),
|
?assertMatch(256 * ?DWORD_SIZE, LastPosition),
|
||||||
Response = get_nextkey("activefile_test.cdb"),
|
Response = get_nextkey("../test/activefile_test.cdb"),
|
||||||
?assertMatch(nomorekeys, Response),
|
?assertMatch(nomorekeys, Response),
|
||||||
ok = file:delete("activefile_test.cdb").
|
ok = file:delete("../test/activefile_test.cdb").
|
||||||
|
|
||||||
emptyvalue_fromdict_test() ->
|
emptyvalue_fromdict_test() ->
|
||||||
D = dict:new(),
|
D = dict:new(),
|
||||||
|
@ -888,14 +1008,14 @@ emptyvalue_fromdict_test() ->
|
||||||
D2 = dict:store("K2", "", D1),
|
D2 = dict:store("K2", "", D1),
|
||||||
D3 = dict:store("K3", "V3", D2),
|
D3 = dict:store("K3", "V3", D2),
|
||||||
D4 = dict:store("K4", "", D3),
|
D4 = dict:store("K4", "", D3),
|
||||||
ok = from_dict("from_dict_test_ev.cdb",D4),
|
ok = from_dict("../test/from_dict_test_ev.cdb",D4),
|
||||||
io:format("Store created ~n", []),
|
io:format("Store created ~n", []),
|
||||||
KVP = lists:sort(dump("from_dict_test_ev.cdb")),
|
KVP = lists:sort(dump("../test/from_dict_test_ev.cdb")),
|
||||||
D_Result = lists:sort(dict:to_list(D4)),
|
D_Result = lists:sort(dict:to_list(D4)),
|
||||||
io:format("KVP is ~w~n", [KVP]),
|
io:format("KVP is ~w~n", [KVP]),
|
||||||
io:format("D_Result is ~w~n", [D_Result]),
|
io:format("D_Result is ~w~n", [D_Result]),
|
||||||
?assertMatch(KVP, D_Result),
|
?assertMatch(KVP, D_Result),
|
||||||
ok = file:delete("from_dict_test_ev.cdb").
|
ok = file:delete("../test/from_dict_test_ev.cdb").
|
||||||
|
|
||||||
fold_test() ->
|
fold_test() ->
|
||||||
K1 = {"Key1", 1},
|
K1 = {"Key1", 1},
|
||||||
|
@ -909,7 +1029,7 @@ fold_test() ->
|
||||||
K5 = {"Key1", 5},
|
K5 = {"Key1", 5},
|
||||||
V5 = 32,
|
V5 = 32,
|
||||||
D = dict:from_list([{K1, V1}, {K2, V2}, {K3, V3}, {K4, V4}, {K5, V5}]),
|
D = dict:from_list([{K1, V1}, {K2, V2}, {K3, V3}, {K4, V4}, {K5, V5}]),
|
||||||
ok = from_dict("fold_test.cdb", D),
|
ok = from_dict("../test/fold_test.cdb", D),
|
||||||
FromSN = 2,
|
FromSN = 2,
|
||||||
FoldFun = fun(K, V, Acc) ->
|
FoldFun = fun(K, V, Acc) ->
|
||||||
{_Key, Seq} = K,
|
{_Key, Seq} = K,
|
||||||
|
@ -919,8 +1039,8 @@ fold_test() ->
|
||||||
Acc
|
Acc
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
?assertMatch(56, fold("fold_test.cdb", FoldFun, 0)),
|
?assertMatch(56, fold("../test/fold_test.cdb", FoldFun, 0)),
|
||||||
ok = file:delete("fold_test.cdb").
|
ok = file:delete("../test/fold_test.cdb").
|
||||||
|
|
||||||
fold_keys_test() ->
|
fold_keys_test() ->
|
||||||
K1 = {"Key1", 1},
|
K1 = {"Key1", 1},
|
||||||
|
@ -934,7 +1054,7 @@ fold_keys_test() ->
|
||||||
K5 = {"Key5", 5},
|
K5 = {"Key5", 5},
|
||||||
V5 = 32,
|
V5 = 32,
|
||||||
D = dict:from_list([{K1, V1}, {K2, V2}, {K3, V3}, {K4, V4}, {K5, V5}]),
|
D = dict:from_list([{K1, V1}, {K2, V2}, {K3, V3}, {K4, V4}, {K5, V5}]),
|
||||||
ok = from_dict("fold_keys_test.cdb", D),
|
ok = from_dict("../test/fold_keys_test.cdb", D),
|
||||||
FromSN = 2,
|
FromSN = 2,
|
||||||
FoldFun = fun(K, Acc) ->
|
FoldFun = fun(K, Acc) ->
|
||||||
{Key, Seq} = K,
|
{Key, Seq} = K,
|
||||||
|
@ -944,9 +1064,9 @@ fold_keys_test() ->
|
||||||
Acc
|
Acc
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
Result = fold_keys("fold_keys_test.cdb", FoldFun, []),
|
Result = fold_keys("../test/fold_keys_test.cdb", FoldFun, []),
|
||||||
?assertMatch(["Key3", "Key4", "Key5"], lists:sort(Result)),
|
?assertMatch(["Key3", "Key4", "Key5"], lists:sort(Result)),
|
||||||
ok = file:delete("fold_keys_test.cdb").
|
ok = file:delete("../test/fold_keys_test.cdb").
|
||||||
|
|
||||||
fold2_test() ->
|
fold2_test() ->
|
||||||
K1 = {"Key1", 1},
|
K1 = {"Key1", 1},
|
||||||
|
@ -963,7 +1083,7 @@ fold2_test() ->
|
||||||
V6 = 64,
|
V6 = 64,
|
||||||
D = dict:from_list([{K1, V1}, {K2, V2}, {K3, V3},
|
D = dict:from_list([{K1, V1}, {K2, V2}, {K3, V3},
|
||||||
{K4, V4}, {K5, V5}, {K6, V6}]),
|
{K4, V4}, {K5, V5}, {K6, V6}]),
|
||||||
ok = from_dict("fold2_test.cdb", D),
|
ok = from_dict("../test/fold2_test.cdb", D),
|
||||||
FoldFun = fun(K, V, Acc) ->
|
FoldFun = fun(K, V, Acc) ->
|
||||||
{Key, Seq} = K,
|
{Key, Seq} = K,
|
||||||
case dict:find(Key, Acc) of
|
case dict:find(Key, Acc) of
|
||||||
|
@ -978,8 +1098,8 @@ fold2_test() ->
|
||||||
RD = dict:new(),
|
RD = dict:new(),
|
||||||
RD1 = dict:store("Key1", {5, 32}, RD),
|
RD1 = dict:store("Key1", {5, 32}, RD),
|
||||||
RD2 = dict:store("Key2", {1, 64}, RD1),
|
RD2 = dict:store("Key2", {1, 64}, RD1),
|
||||||
Result = fold("fold2_test.cdb", FoldFun, dict:new()),
|
Result = fold("../test/fold2_test.cdb", FoldFun, dict:new()),
|
||||||
?assertMatch(RD2, Result),
|
?assertMatch(RD2, Result),
|
||||||
ok = file:delete("fold2_test.cdb").
|
ok = file:delete("../test/fold2_test.cdb").
|
||||||
|
|
||||||
-endif.
|
-endif.
|
||||||
|
|
|
@ -1,3 +1,71 @@
|
||||||
|
%% -------- Overview ---------
|
||||||
|
%%
|
||||||
|
%% The eleveleddb is based on the LSM-tree similar to leveldb, except that:
|
||||||
|
%% - Values are kept seperately to Keys & Metadata
|
||||||
|
%% - Different file formats are used for value store (based on constant
|
||||||
|
%% database), and key store (based on sst)
|
||||||
|
%% - It is not intended to be general purpose, but be specifically suited for
|
||||||
|
%% use as a Riak backend in specific circumstances (relatively large values,
|
||||||
|
%% and frequent use of iterators)
|
||||||
|
%% - The Value store is an extended nursery log in leveldb terms. It is keyed
|
||||||
|
%% on the sequence number of the write
|
||||||
|
%% - The Key Store is a LSM tree, where the key is the actaul object key, and
|
||||||
|
%% the value is the metadata of the object including the sequence number
|
||||||
|
%%
|
||||||
|
%% -------- Concierge & Manifest ---------
|
||||||
|
%%
|
||||||
|
%% The concierge is responsible for opening up the store, and keeps a manifest
|
||||||
|
%% of where items can be found. The manifest keeps a mapping of:
|
||||||
|
%% - Sequence Number ranges and the PID of the Value Store file that contains
|
||||||
|
%% that range
|
||||||
|
%% - Key ranges to PID mappings for each leval of the KeyStore
|
||||||
|
%%
|
||||||
|
%% -------- GET --------
|
||||||
|
%%
|
||||||
|
%% A GET request for Key and Metadata requires a lookup in the KeyStore only.
|
||||||
|
%% - The concierge should consult the manifest for the lowest level to find
|
||||||
|
%% the PID which may contain the Key
|
||||||
|
%% - The concierge should ask the file owner if the Key is present, if not
|
||||||
|
%% present lower levels should be consulted until the objetc is found
|
||||||
|
%%
|
||||||
|
%% If a value is required, when the Key/Metadata has been fetched from the
|
||||||
|
%% KeyStore, the sequence number should be tkane, and matched in the ValueStore
|
||||||
|
%% manifest to find the right value.
|
||||||
|
%%
|
||||||
|
%% For recent PUTs the Key/Metadata is added into memory, and there is an
|
||||||
|
%% in-memory hash table for the entries in the most recent ValueStore CDB.
|
||||||
|
%%
|
||||||
|
%% -------- PUT --------
|
||||||
|
%%
|
||||||
|
%% A PUT request must be persisted to the open (and append only) CDB file which
|
||||||
|
%% acts as a transaction log to persist the change. The Key & Metadata needs
|
||||||
|
%% also to be placed in memory.
|
||||||
|
%%
|
||||||
|
%% Once the CDB file is full, the managing process should be requested to
|
||||||
|
%% complete the lookup hash, and a new CDB file be started.
|
||||||
|
%%
|
||||||
|
%% Once the in-memory
|
||||||
|
%%
|
||||||
|
%% -------- Snapshots (Key Only) --------
|
||||||
|
%%
|
||||||
|
%% If there is a iterator/snapshot request, the concierge will simply handoff a
|
||||||
|
%% copy of the manifest, and register the interest of the iterator at the
|
||||||
|
%% manifest sequence number at the time of the request. Iterators should
|
||||||
|
%% de-register themselves from the manager on completion. Iterators should be
|
||||||
|
%% automatically release after a timeout period. A file can be deleted if
|
||||||
|
%% there are no registered iterators from before the point the file was
|
||||||
|
%% removed from the manifest.
|
||||||
|
%%
|
||||||
|
%% -------- Snapshots (Key & Value) --------
|
||||||
|
%%
|
||||||
|
%%
|
||||||
|
%%
|
||||||
|
%% -------- Special Ops --------
|
||||||
|
%%
|
||||||
|
%% e.g. Get all for SegmentID/Partition
|
||||||
|
%%
|
||||||
|
%% -------- KeyStore ---------
|
||||||
|
%%
|
||||||
%% The concierge is responsible for controlling access to the store and
|
%% The concierge is responsible for controlling access to the store and
|
||||||
%% maintaining both an in-memory view and a persisted state of all the sft
|
%% maintaining both an in-memory view and a persisted state of all the sft
|
||||||
%% files in use across the store.
|
%% files in use across the store.
|
||||||
|
@ -34,14 +102,7 @@
|
||||||
%% will call the manifets manager on a timeout to confirm that they are no
|
%% will call the manifets manager on a timeout to confirm that they are no
|
||||||
%% longer in use (by any iterators).
|
%% longer in use (by any iterators).
|
||||||
%%
|
%%
|
||||||
%% If there is a iterator/snapshot request, the concierge will simply handoff a
|
|
||||||
%% copy of the manifest, and register the interest of the iterator at the
|
|
||||||
%% manifest sequence number at the time of the request. Iterators should
|
|
||||||
%% de-register themselves from the manager on completion. Iterators should be
|
|
||||||
%% automatically release after a timeout period. A file can be deleted if
|
|
||||||
%% there are no registered iterators from before the point the file was
|
|
||||||
%% removed from the manifest.
|
|
||||||
%%
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue