Skip to content

Commit

Permalink
Change key_tree merge to take path as 2nd arg, add type specs
Browse files Browse the repository at this point in the history
git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@1043452 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information
kocolosk committed Dec 8, 2010
1 parent dcde7c3 commit f9e1dc5
Show file tree
Hide file tree
Showing 4 changed files with 56 additions and 69 deletions.
2 changes: 1 addition & 1 deletion src/couchdb/couch_db.erl
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,7 @@ prep_and_validate_replicated_updates(Db, [Bucket|RestBuckets], [OldInfo|RestOldI
{ok, #full_doc_info{rev_tree=OldTree}} ->
NewRevTree = lists:foldl(
fun(NewDoc, AccTree) ->
{NewTree, _} = couch_key_tree:merge(AccTree, [couch_db:doc_to_tree(NewDoc)]),
{NewTree, _} = couch_key_tree:merge(AccTree, couch_db:doc_to_tree(NewDoc)),
NewTree
end,
OldTree, Bucket),
Expand Down
6 changes: 3 additions & 3 deletions src/couchdb/couch_db_updater.erl
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ merge_rev_trees(Limit, MergeConflicts, [NewDocs|RestDocsList],
NewRevTree0 = lists:foldl(
fun({Client, #doc{revs={Pos,[_Rev|PrevRevs]}}=NewDoc}, AccTree) ->
if not MergeConflicts ->
case couch_key_tree:merge(AccTree, [couch_db:doc_to_tree(NewDoc)]) of
case couch_key_tree:merge(AccTree, couch_db:doc_to_tree(NewDoc)) of
{_NewTree, conflicts} when (not OldDeleted) ->
send_result(Client, Id, {Pos-1,PrevRevs}, conflict),
AccTree;
Expand Down Expand Up @@ -524,7 +524,7 @@ merge_rev_trees(Limit, MergeConflicts, [NewDocs|RestDocsList],
NewDoc#doc{revs={OldPos, [OldRev]}}),
NewDoc2 = NewDoc#doc{revs={OldPos + 1, [NewRevId, OldRev]}},
{NewTree2, _} = couch_key_tree:merge(AccTree,
[couch_db:doc_to_tree(NewDoc2)]),
couch_db:doc_to_tree(NewDoc2)),
% we changed the rev id, this tells the caller we did
send_result(Client, Id, {Pos-1,PrevRevs},
{ok, {OldPos + 1, NewRevId}}),
Expand All @@ -538,7 +538,7 @@ merge_rev_trees(Limit, MergeConflicts, [NewDocs|RestDocsList],
end;
true ->
{NewTree, _} = couch_key_tree:merge(AccTree,
[couch_db:doc_to_tree(NewDoc)]),
couch_db:doc_to_tree(NewDoc)),
NewTree
end
end,
Expand Down
36 changes: 18 additions & 18 deletions src/couchdb/couch_key_tree.erl
Original file line number Diff line number Diff line change
Expand Up @@ -16,31 +16,27 @@
-export([map/2, get_all_leafs/1, count_leafs/1, remove_leafs/2,
get_all_leafs_full/1,stem/2,map_leafs/2]).

% a key tree looks like this:
% Tree -> [] or [{Key, Value, ChildTree} | SiblingTree]
% ChildTree -> Tree
% SiblingTree -> [] or [{SiblingKey, Value, Tree} | Tree]
% And each Key < SiblingKey

% Tree::term() is really a tree(), but we don't want to require R13B04 yet
-type branch() :: {Key::term(), Value::term(), Tree::term()}.
-type path() :: {Start::pos_integer(), branch()}.
-type tree() :: [branch()]. % sorted by key

% partial trees arranged by how much they are cut off.

merge(A, B) ->
{Merged, HasConflicts} =
lists:foldl(
fun(InsertTree, {AccTrees, AccConflicts}) ->
{ok, Merged, Conflicts} = merge_one(AccTrees, InsertTree, [], false),
{Merged, Conflicts or AccConflicts}
end,
{A, false}, B),
if HasConflicts or
((length(Merged) =/= length(A)) and (length(Merged) =/= length(B))) ->
-spec merge([path()], path()) -> {[path()], conflicts | no_conflicts}.
merge(Paths, Path) ->
{ok, Merged, HasConflicts} = merge_one(Paths, Path, [], false),
if HasConflicts ->
Conflicts = conflicts;
(length(Merged) =/= length(Paths)) and (length(Merged) =/= 1) ->
Conflicts = conflicts;
true ->
Conflicts = no_conflicts
end,
{lists:sort(Merged), Conflicts}.

-spec merge_one(Original::[path()], Inserted::path(), [path()], boolean()) ->
{ok, Merged::[path()], NewConflicts::boolean()}.
merge_one([], Insert, OutAcc, ConflictsAcc) ->
{ok, [Insert | OutAcc], ConflictsAcc};
merge_one([{Start, Tree}|Rest], {StartInsert, TreeInsert}, Acc, HasConflicts) ->
Expand All @@ -53,6 +49,8 @@ merge_one([{Start, Tree}|Rest], {StartInsert, TreeInsert}, Acc, HasConflicts) ->
merge_one(Rest, {StartInsert, TreeInsert}, AccOut, HasConflicts)
end.

-spec merge_at(tree(), Place::integer(), tree()) ->
{ok, Merged::tree(), HasConflicts::boolean()} | no.
merge_at(_Ours, _Place, []) ->
no;
merge_at([], _Place, _Insert) ->
Expand Down Expand Up @@ -93,6 +91,8 @@ merge_at([Tree | Sibs], 0, InsertTree) ->
end.

% key tree functions

-spec merge_simple(tree(), tree()) -> {Merged::tree(), NewConflicts::boolean()}.
merge_simple([], B) ->
{B, false};
merge_simple(A, []) ->
Expand Down Expand Up @@ -156,7 +156,7 @@ remove_leafs(Trees, Keys) ->
fun({PathPos, Path},TreeAcc) ->
[SingleTree] = lists:foldl(
fun({K,V},NewTreeAcc) -> [{K,V,NewTreeAcc}] end, [], Path),
{NewTrees, _} = merge(TreeAcc, [{PathPos + 1 - length(Path), SingleTree}]),
{NewTrees, _} = merge(TreeAcc, {PathPos + 1 - length(Path), SingleTree}),
NewTrees
end, [], FilteredPaths),
{NewTree, RemovedKeys}.
Expand Down Expand Up @@ -318,7 +318,7 @@ stem(Trees, Limit) ->
fun({PathPos, Path},TreeAcc) ->
[SingleTree] = lists:foldl(
fun({K,V},NewTreeAcc) -> [{K,V,NewTreeAcc}] end, [], Path),
{NewTrees, _} = merge(TreeAcc, [{PathPos + 1 - length(Path), SingleTree}]),
{NewTrees, _} = merge(TreeAcc, {PathPos + 1 - length(Path), SingleTree}),
NewTrees
end, [], Paths2).

Expand Down
81 changes: 34 additions & 47 deletions test/etap/060-kt-merging.t
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

main(_) ->
test_util:init_code_path(),
etap:plan(14),
etap:plan(12),
case (catch test()) of
ok ->
etap:end_tests();
Expand All @@ -26,101 +26,88 @@ main(_) ->
ok.

test() ->
EmptyTree = [],
One = [{0, {"1","foo",[]}}],
One = {0, {"1","foo",[]}},
TwoSibs = [{0, {"1","foo",[]}},
{0, {"2","foo",[]}}],
OneChild = [{0, {"1","foo",[{"1a", "bar", []}]}}],
TwoChild = [{0, {"1","foo", [{"1a", "bar", [{"1aa", "bar", []}]}]}}],
TwoChildSibs = [{0, {"1","foo", [{"1a", "bar", []},
{"1b", "bar", []}]}}],
TwoChildSibs2 = [{0, {"1","foo", [{"1a", "bar", []},
{"1b", "bar", [{"1bb", "boo", []}]}]}}],
Stemmed1b = [{1, {"1a", "bar", []}}],
Stemmed1a = [{1, {"1a", "bar", [{"1aa", "bar", []}]}}],
Stemmed1aa = [{2, {"1aa", "bar", []}}],
Stemmed1bb = [{2, {"1bb", "boo", []}}],
OneChild = {0, {"1","foo",[{"1a", "bar", []}]}},
TwoChild = {0, {"1","foo", [{"1a", "bar", [{"1aa", "bar", []}]}]}},
TwoChildSibs = {0, {"1","foo", [{"1a", "bar", []},
{"1b", "bar", []}]}},
TwoChildSibs2 = {0, {"1","foo", [{"1a", "bar", []},
{"1b", "bar", [{"1bb", "boo", []}]}]}},
Stemmed1b = {1, {"1a", "bar", []}},
Stemmed1a = {1, {"1a", "bar", [{"1aa", "bar", []}]}},
Stemmed1aa = {2, {"1aa", "bar", []}},
Stemmed1bb = {2, {"1bb", "boo", []}},

etap:is(
{EmptyTree, no_conflicts},
couch_key_tree:merge(EmptyTree, EmptyTree),
"Merging two empty trees yields an empty tree."
),

etap:is(
{One, no_conflicts},
couch_key_tree:merge(EmptyTree, One),
{[One], no_conflicts},
couch_key_tree:merge([], One),
"The empty tree is the identity for merge."
),

etap:is(
{One, no_conflicts},
couch_key_tree:merge(One, EmptyTree),
"Merging is commutative."
),

etap:is(
{TwoSibs, no_conflicts},
couch_key_tree:merge(One, TwoSibs),
couch_key_tree:merge(TwoSibs, One),
"Merging a prefix of a tree with the tree yields the tree."
),

etap:is(
{One, no_conflicts},
couch_key_tree:merge(One, One),
{[One], no_conflicts},
couch_key_tree:merge([One], One),
"Merging is reflexive."
),

etap:is(
{TwoChild, no_conflicts},
couch_key_tree:merge(TwoChild, TwoChild),
{[TwoChild], no_conflicts},
couch_key_tree:merge([TwoChild], TwoChild),
"Merging two children is still reflexive."
),

etap:is(
{TwoChildSibs, no_conflicts},
couch_key_tree:merge(TwoChildSibs, TwoChildSibs),
{[TwoChildSibs], no_conflicts},
couch_key_tree:merge([TwoChildSibs], TwoChildSibs),
"Merging a tree to itself is itself."),

etap:is(
{TwoChildSibs, no_conflicts},
couch_key_tree:merge(TwoChildSibs, Stemmed1b),
{[TwoChildSibs], no_conflicts},
couch_key_tree:merge([TwoChildSibs], Stemmed1b),
"Merging a tree with a stem."
),

etap:is(
{TwoChildSibs2, no_conflicts},
couch_key_tree:merge(TwoChildSibs2, Stemmed1bb),
{[TwoChildSibs2], no_conflicts},
couch_key_tree:merge([TwoChildSibs2], Stemmed1bb),
"Merging a stem at a deeper level."
),

etap:is(
{TwoChild, no_conflicts},
couch_key_tree:merge(TwoChild, Stemmed1aa),
{[TwoChild], no_conflicts},
couch_key_tree:merge([TwoChild], Stemmed1aa),
"Merging a single tree with a deeper stem."
),

etap:is(
{TwoChild, no_conflicts},
couch_key_tree:merge(TwoChild, Stemmed1a),
{[TwoChild], no_conflicts},
couch_key_tree:merge([TwoChild], Stemmed1a),
"Merging a larger stem."
),

etap:is(
{Stemmed1a, no_conflicts},
couch_key_tree:merge(Stemmed1a, Stemmed1aa),
{[Stemmed1a], no_conflicts},
couch_key_tree:merge([Stemmed1a], Stemmed1aa),
"More merging."
),

Expect1 = OneChild ++ Stemmed1aa,
Expect1 = [OneChild, Stemmed1aa],
etap:is(
{Expect1, conflicts},
couch_key_tree:merge(OneChild, Stemmed1aa),
couch_key_tree:merge([OneChild], Stemmed1aa),
"Merging should create conflicts."
),

etap:is(
{TwoChild, no_conflicts},
{[TwoChild], no_conflicts},
couch_key_tree:merge(Expect1, TwoChild),
"Merge should have no conflicts."
),
Expand Down

0 comments on commit f9e1dc5

Please sign in to comment.