diff options
author | Paul J. Davis <paul.joseph.davis@gmail.com> | 2019-04-10 15:33:36 -0500 |
---|---|---|
committer | Paul J. Davis <paul.joseph.davis@gmail.com> | 2019-04-11 13:28:04 -0500 |
commit | 7538b13281fdaa7d722f48c02b891b02f48f63b6 (patch) | |
tree | f44963625cc0baa275168606f9154d603903defb | |
parent | 38c0f69410a31507309aef5a86a249efbca6ba7a (diff) | |
download | couchdb-7538b13281fdaa7d722f48c02b891b02f48f63b6.tar.gz |
WIP TMP WIP TMP WIP
-rw-r--r-- | src/fabric/src/fabric2_db.erl | 201 | ||||
-rw-r--r-- | src/fabric/src/fabric2_fdb.erl | 52 | ||||
-rw-r--r-- | src/fabric/src/fabric2_util.erl | 38 |
3 files changed, 169 insertions, 122 deletions
diff --git a/src/fabric/src/fabric2_db.erl b/src/fabric/src/fabric2_db.erl index d62e5dc60..0383a8ec5 100644 --- a/src/fabric/src/fabric2_db.erl +++ b/src/fabric/src/fabric2_db.erl @@ -619,78 +619,130 @@ update_doc_int(#{} = Db, #doc{} = Doc, Options) -> update_doc_interactive(Db, Doc0, _Options) -> - DocRevId = doc_to_revid(Doc0), + % Get the current winning revision. This is needed + % regardless of which branch we're updating. The extra + % revision we're grabbing is an optimization to + % save us a round trip if we end up deleting + % the winning revision branch. + NumRevs = if Doc0#doc.deleted -> 2; true -> 1 end, + RevInfos = fabric2_fdb:get_winning_revs(Db, Doc0#doc.id, NumRevs), + {Winner, SecondPlace} = case RevInfos of + [] -> {not_found, not_found}; + [WRI] -> {WRI, not_found}; + [WRI, SPRI] -> {WRI, SPRI} + end, + WinnerRevId = case Winner of + not_found -> + {0, <<>>}; + _ -> + case maps:get(deleted, Winner) of + true -> {0, <<>>}; + false -> maps:get(rev_id, Winner) + end + end, + + % Check that a revision was specified if required + Doc0RevId = doc_to_revid(Doc0), + if Doc0RevId /= {0, <<>>} orelse WinnerRevId == {0, <<>>} -> ok; true -> + ?RETURN({error, conflict}); + end, - {Winner, MaybeNewWinner} = case Doc0#doc.deleted of + % Check that we're not trying to create a deleted doc + if Doc0RevId /= {0, <<>>} orelse not Doc0#doc.deleted -> ok; true -> + ?RETURN({error, conflict}) + end, + + % Get the target revision to update + Target = case DocId0RevId == WinnerRevId of true -> - case fabric2_fdb:get_winning_revs(Db, Doc0, 2) of - [] -> - {not_found, not_found}; - [Winner0] -> - {Winner0, not_found}; - [Winner0, MaybeNewWinner0] -> - {Winner0, MaybeNewWinner0} - end; + Winner; false -> - case fabric2_fdb:get_winning_revs(Db, Doc0, 1) of - [] -> - {not_found, not_found}; - [Winner0] -> - {Winner0, not_found} + case fabric2_fdb:get_non_deleted_rev(Db, Doc0#doc.id, Doc0RevId) of + #{deleted := false} = Target0 -> + Target0; + not_found -> + % Either a missing revision or a deleted + % revision. Either way a conflict. Note + % that we get not_found for a deleted revision + % because we only check for the non-deleted + % key in fdb + ?RETURN({error, conflict}) end end, - {Doc1, ExtendedRevInfo} = case Winner of - not_found when DocRevId == {0, <<>>} -> - {Doc0, not_found}; - #{winner := true, deleted := true} when DocRevId == {0, <<>>} -> - {WPos, WRev} = maps:get(rev_id, Winner), - {Doc0#doc{revs = {WPos, [WRev]}}, Winner}; - #{winner := true, deleted := false} when DocRevId == {0, <<>>} -> + % When recreating a deleted document we want to extend + % the winning revision branch rather than create a + % new branch. If we did not do this we could be + % recreating into a state that previously existed. + Doc1 = case Winner of + #{deleted := true} when not Doc0#doc.deleted -> + {WinnerRevPos, WinnerRev} = maps:get(rev_id, Winner), + Doc0#doc{revs = {WinnerRevPos, [WinnerRev]}}; + #{deleted := true} when Doc0#doc.deleted -> + % We disable extending deleted revisions with + % new deletions during interactive updates ?RETURN({error, conflict}); - #{winner := true, deleted := false, rev_id := DocRevId} -> - {Doc0, Winner}; - #{winner := true, rev_id := WRevId} when WRevId /= DocRevId -> - case fabric2_fdb:get_non_deleted_rev(Db, Doc0) of - not_found -> - ?RETURN({error, conflict}); - #{deleted := false, rev_id := DocRevId} = PrevRevInfo-> - {Doc0, PrevRevInfo}; - #{deleted := true} -> - ?RETURN({error, conflict}) - end + _ -> + Doc0 end, + % Validate the doc update and create the + % new revinfo map + Doc2 = prep_and_validate(Db, Doc1, Target), #doc{ deleted = NewDeleted, revs = {NewRevPos, [NewRev | NewRevPath]} - } = Doc2 = prep_and_validate_interactive(Db, Doc1, ExtendedRevInfo), + } = Doc3 = new_revid(Doc2), NewRevInfo = #{ - winner => undefined, - deleted => NewDeleted, - rev_id => {NewRevPos, NewRev}, - rev_path => NewRevPath, - sequence => undefined, - branch_count => undefined + winner := undefined, + deleted := NewDeleted, + rev_id := {NewRevPos, NewRev}, + rev_path := NewRevPath, + sequence := undefined, + branch_count := undefined }, - AllRevInfos = [NewRevInfo, Winner, MaybeNewWinner], - {NewWinner, ToUpdate} = interactive_winner(AllRevInfos, ExtendedRevInfo), + % Gather the list of possible winnig revisions + Possible = case Target == Winner of + true when not Doc3#doc.deleted -> + [NewRevInfo]; + true when Doc3#doc.deleted -> + case SecondPlace of + #{} -> [NewRevInfo, SecondPlace]; + not_found -> [NewRevInfo] + end; + false -> + [NewRevInfo, Winner] + end, + + % Sort the rev infos such that the winner is first + {NewWinner, NonWinner} = case fabric2_util:sort_revinfos(Possible) of + [W] -> {W, not_found}; + [W, NW] -> {W, NW} + end, + + ToUpdate = if NonWinner == not_found -> []; true -> [NonWinner] end, + ToRemove = if Target == not_found -> []; true -> [Target] end, - ok = fabric2_fdb:write_doc_interactive( + ok = fabric2_fdb:write_doc( Db, - Doc2, + Doc3, NewWinner, Winner, ToUpdate, - [ExtendedRevInfo] + ToRemove ), {ok, {NewRevPos, NewRev}}. -prep_and_validate_interactive(Db, Doc, ExtendedRevInfo) -> +update_doc_replicated(_Db, _Doc, _Options) -> + erlang:error(not_implemented). + + + +prep_and_validate(Db, Doc, PrevRevInfo) -> HasStubs = couch_doc:has_stubs(Doc), HasVDUs = [] /= maps:get(validate_doc_update_funs, Db), IsDDoc = case Doc#doc.id of @@ -700,64 +752,21 @@ prep_and_validate_interactive(Db, Doc, ExtendedRevInfo) -> PrevDoc = case HasStubs orelse (HasVDUs and not IsDDoc) of true -> - case fabric2_fdb:get_doc_body(Db, Doc, ExtendedRevInfo) of + case fabric2_fdb:get_doc_body(Db, Doc#doc.id, PrevRevInfo) of #doc{} = Doc -> Doc; - {not_found, _} -> #doc{} + {not_found, _} -> nil end; false -> nil end, MergedDoc = if not HasStubs -> Doc; true -> + % This will throw an error if we have any + % attachment stubs missing data couch_doc:merge_stubs(Doc, PrevDoc) end, - validate_doc_update(Db, MergedDoc, PrevDoc), - new_revid(MergedDoc). - - -interactive_winner(RevInfos0, ExtendedRevInfo) -> - % Fetch the current winner so we can copy - % the current branch count - BranchCount = case [W || #{winner := true} = W <- RevInfos0] of - [] -> - % Creating a doc, the branch count is - % now 1 by definition - 1; - [#{winner := true, branch_count := Count}] -> - % Interactive edits can never create new - % branches so we just copy the current Count - Count - end, - - % Remove the previous winner if it was updated - RevInfos1 = RevInfos0 -- [ExtendedRevInfo], - - % Remove not_found if we didn't need or have a - % MaybeNewWinner - RevInfos2 = RevInfos1 -- [not_found], - - SortFun = fun(A, B) -> - #{ - deleted := DeletedA, - rev_id := RevIdA - } = A, - #{ - deleted := DeletedB, - rev_id := RevIdB - } = B, - {not DeletedA, RevIdA} > {not DeletedB, RevIdB} - end, - [Winner0 | Rest0] = lists:sort(SortFun, RevInfos2), - Winner = Winner0#{ - winner := true, - branch_count := BranchCount - }, - {Winner, [R#{winner := false} || R <- Rest0]}. - - -update_doc_replicated(_Db, _Doc, _Options) -> - erlang:error(not_implemented). + MergedDoc. validate_doc_update(Db, #doc{id = <<"_design/", _/binary>>} = Doc, _) -> @@ -843,3 +852,7 @@ doc_to_revid(#doc{revs = Revs}) -> {0, []} -> {0, <<>>}; {RevPos, [Rev | _]} -> {RevPos, Rev} end. + + +doc_to_revpath(#doc{revs = {_, [_ | RevPath]}}) -> + RevPath. diff --git a/src/fabric/src/fabric2_fdb.erl b/src/fabric/src/fabric2_fdb.erl index 94d1ef459..13ea05b03 100644 --- a/src/fabric/src/fabric2_fdb.erl +++ b/src/fabric/src/fabric2_fdb.erl @@ -36,7 +36,7 @@ get_doc_body/3, - write_doc_interactive/6, + write_doc/6, fold_docs/4, fold_changes/5, @@ -355,9 +355,6 @@ incr_stat(#{} = Db, StatKey, Increment) when is_integer(Increment) -> erlfdb:add(Tx, Key, Increment). -get_winning_revs(Db, #doc{} = Doc, NumRevs) -> - get_winning_revs(Db, Doc#doc.id, NumRevs); - get_winning_revs(#{} = Db, DocId, NumRevs) -> ?REQUIRE_CURRENT(Db), #{ @@ -367,7 +364,6 @@ get_winning_revs(#{} = Db, DocId, NumRevs) -> Prefix = erlfdb_tuple:pack({?DB_REVS, DocId}, DbPrefix), Options = [{reverse, true}, {limit, NumRevs}], - Future = erlfdb:get_range_startswith(Tx, Prefix, Options), lists:map(fun({K, V}) -> Key = erlfdb_tuple:unpack(K, DbPrefix), Val = erlfdb_tuple:unpack(V), @@ -375,24 +371,22 @@ get_winning_revs(#{} = Db, DocId, NumRevs) -> end, erlfdb:wait(Future)). -get_non_deleted_rev(#{} = Db, #doc{} = Doc) -> +get_non_deleted_rev(#{} = Db, DocId, RevId) -> ?REQUIRE_CURRENT(Db), #{ tx := Tx, db_prefix := DbPrefix } = Db, - #doc{ - revs = {RevPos, [Rev | _]} - } = Doc, + {RevPos, Rev} = RevId, - BaseKey = {?DB_REVS, Doc#doc.id, true, RevPos, Rev}, + BaseKey = {?DB_REVS, DocId, true, RevPos, Rev}, Key = erlfdb_tuple:pack(BaseKey, DbPrefix), case erlfdb:wait(erlfdb:get(Tx, Key)) of not_found -> not_found; Val -> - fdb_to_revinfo(Key, erlfdb_tuple:unpack(Val)) + fdb_to_revinfo(BaseKey, erlfdb_tuple:unpack(Val)) end. @@ -413,7 +407,7 @@ get_doc_body(#{} = Db, DocId, RevInfo) -> fdb_to_doc(Db, DocId, RevPos, [Rev | RevPath], Val). -write_doc_interactive(Db, Doc, NewWinner, OldWinner, UpdateInfos, RemInfos) -> +write_doc(Db, Doc, NewWinner0, OldWinner, ToUpdate, ToRemove) -> ?REQUIRE_CURRENT(Db), #{ tx := Tx, @@ -425,26 +419,30 @@ write_doc_interactive(Db, Doc, NewWinner, OldWinner, UpdateInfos, RemInfos) -> deleted = Deleted } = Doc, - NewRevId = maps:get(rev_id, NewWinner), + % Revision tree - % Update the revision tree + NewWinner = NewWinner0#{ + winner := true, + branch_count := maps:get(branch_count, OldWinner) + }, + NewRevId = maps:get(rev_id, NewWinner), - {WKey, WVal} = revinfo_to_fdb(DbPrefix, DocId, NewWinner), + {WKey, WVal} = revinfo_to_fdb(DbPrefix, DocId, Winner), ok = erlfdb:set_versionstamped_value(Tx, WKey, WVal), - lists:foreach(fun(RI) -> + lists:foreach(fun(RI0) -> + RI = RI0#{winner := false}, {K, V} = revinfo_to_fdb(DbPrefix, DocId, RI), ok = erlfdb:set(Tx, K, V) - end, UpdateInfos), + end, ToUpdate), - lists:foreach(fun(RI) -> - if RI == not_found -> ok; true -> - {K, _} = revinfo_to_fdb(DbPrefix, DocId, RI), - ok = erlfdb:clear(Tx, K) - end - end, RemInfos), + lists:foreach(fun(RI0) -> + RI = RI0#{winner := false}, + {K, _} = revinfo_to_fdb(DbPrefix, DocId, RI), + ok = erlfdb:clear(Tx, K) + end, ToRemove), - % Update all_docs index + % _all_docs UpdateStatus = case {OldWinner, NewWinner} of {not_found, #{deleted := false}} -> @@ -469,7 +467,7 @@ write_doc_interactive(Db, Doc, NewWinner, OldWinner, UpdateInfos, RemInfos) -> ok end, - % Update the changes index + % _changes if OldWinner == not_found -> ok; true -> OldSeq = maps:get(sequence, OldWinner), @@ -481,12 +479,10 @@ write_doc_interactive(Db, Doc, NewWinner, OldWinner, UpdateInfos, RemInfos) -> NewSeqVal = erlfdb_tuple:pack({DocId, Deleted, NewRevId}), erlfdb:set_versionstamped_key(Tx, NewSeqKey, NewSeqVal), - % Store document metadata and body + % And all the rest... ok = write_doc_body(Db, Doc), - % Update doc counts - case UpdateStatus of created -> incr_stat(Db, <<"doc_count">>, 1); diff --git a/src/fabric/src/fabric2_util.erl b/src/fabric/src/fabric2_util.erl index 230a5841b..021a0f7e7 100644 --- a/src/fabric/src/fabric2_util.erl +++ b/src/fabric/src/fabric2_util.erl @@ -17,6 +17,9 @@ transactional/1, get_db_handle/0, + revinfo_to_path/1, + find_winning_revinfo/1, + user_ctx_to_json/1, get_value/2, @@ -51,6 +54,41 @@ get_db_handle() -> end. +revinfo_to_path(RevInfo) -> + #{ + rev_id := {RevPos, Rev}, + rev_path := RevPath + } = RevInfo, + Revs = lists:reverse(RevPath, [Rev]), + Path = revinfo_to_path(RevInfo, Revs), + {RevPos - length(Revs) + 1, Path}. + + +revinfo_to_path(RevInfo, [Rev]) -> + {Rev, RevInfo, []}; + +revinfo_to_path(RevInfo, [Rev | Rest]) -> + {Rev, ?REV_MISSING, [revinfo_to_path(RevInfo, Rest)]}. + + +sort_revinfos(RevInfos) -> + CmpFun = fun(A, B) -> + case rev_sort_key(A) > rev_sort_key(B) of + true -> A; + false -> B + end + end, + lists:sort(CmpFun, RevInfos). + + +rev_sort_key(#{} = RevInfo) -> + #{ + deleted := Deleted, + rev_id := {RevPos, Rev} + } = RevInfo, + {not Deleted, RevPos, Rev}. + + user_ctx_to_json(Db) -> UserCtx = fabric2_db:get_user_ctx(Db), {[ |