couchdb-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From dav...@apache.org
Subject [couchdb] branch prototype/rfc-001-revision-metadata-model updated: WIP TMP WIP TMP WIP
Date Wed, 10 Apr 2019 20:31:21 GMT
This is an automated email from the ASF dual-hosted git repository.

davisp pushed a commit to branch prototype/rfc-001-revision-metadata-model
in repository https://gitbox.apache.org/repos/asf/couchdb.git


The following commit(s) were added to refs/heads/prototype/rfc-001-revision-metadata-model
by this push:
     new 61f8f16  WIP TMP WIP TMP WIP
61f8f16 is described below

commit 61f8f1624660d4b22b3ec51e1d3265dce5047db3
Author: Paul J. Davis <paul.joseph.davis@gmail.com>
AuthorDate: Wed Apr 10 15:33:36 2019 -0500

    WIP TMP WIP TMP WIP
---
 src/fabric/src/fabric2_db.erl   | 216 +++++++++++++++++++++++-----------------
 src/fabric/src/fabric2_fdb.erl  |  52 +++++-----
 src/fabric/src/fabric2_util.erl |  38 +++++++
 3 files changed, 184 insertions(+), 122 deletions(-)

diff --git a/src/fabric/src/fabric2_db.erl b/src/fabric/src/fabric2_db.erl
index d62e5dc..df640c9 100644
--- a/src/fabric/src/fabric2_db.erl
+++ b/src/fabric/src/fabric2_db.erl
@@ -619,78 +619,145 @@ 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
+    % 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),
+    % Determine the new winner
+    {NewWinner, NonWinner} = case Target == Winner of
+        true when not Doc3#doc.deleted ->
+            % This was a standard issue update where we have
+            % updated a doc into a non-deleted state
+            {NewRevInfo, not_found};
+        true when Doc3#doc.deleted ->
+            % We just deleted the winning branch. Thus, we need
+            % to compare to the "second place" branch to see if
+            % it is the new winner.
+            if SecondPlace == not_found -> {NewRevInfo, not_found}; true ->
+                case fabric2_util:sort_revinfos([NewRevInfo, SecondPlace]) of
+                    [SecondPlace, NewRevInfo] ->
+                        % Change of winner
+                        {SecondPlace, NewRevInfo};
+                    [NewRevInfo, SecondPlace] ->
+                        % No change so no need to modify the
+                        % second place branch
+                        {NewRevInfo, not_found}
+                end
+            end;
+        false ->
+            % Our update may have just moved a conflict
+            % into the winner spot.
+            case fabric2_util:sort_revinfos([NewRevInfo, Winner]) of
+                [NewRevInfo, Winner] ->
+                    {NewRevInfo, Winner};
+                [Winner, NewRevInfo] ->
+                    {Winner, NewRevInfo}
+            end
+    end,
+
+    ToUpdate = case NonWinner of
+        not_found -> [];
+        _ -> [NonWinner]
+    end,
+
+    ToRemove = case Target of
+        not_found -> [],
+        _ -> [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 +767,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 +867,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 94d1ef4..13ea05b 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 230a584..021a0f7 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),
     {[


Mime
View raw message