summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul J. Davis <paul.joseph.davis@gmail.com>2019-04-10 15:33:36 -0500
committerPaul J. Davis <paul.joseph.davis@gmail.com>2019-04-11 13:28:04 -0500
commit7538b13281fdaa7d722f48c02b891b02f48f63b6 (patch)
treef44963625cc0baa275168606f9154d603903defb
parent38c0f69410a31507309aef5a86a249efbca6ba7a (diff)
downloadcouchdb-7538b13281fdaa7d722f48c02b891b02f48f63b6.tar.gz
WIP TMP WIP TMP WIP
-rw-r--r--src/fabric/src/fabric2_db.erl201
-rw-r--r--src/fabric/src/fabric2_fdb.erl52
-rw-r--r--src/fabric/src/fabric2_util.erl38
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),
{[