This is an automated email from the ASF dual-hosted git repository.

vatamane pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/couchdb.git


The following commit(s) were added to refs/heads/main by this push:
     new 61c4c4d16 Cleanup fabric r/w parameter handling
61c4c4d16 is described below

commit 61c4c4d162366ffa979cdf39baf67487fdfd9643
Author: Nick Vatamaniuc <[email protected]>
AuthorDate: Mon Oct 27 12:14:36 2025 -0400

    Cleanup fabric r/w parameter handling
    
    For some odd reasons we parse the request `r` and `w` parameters as lists in
    chttpd. Then, in fabric, we jump through hoops switching them to integers 
(we
    switch get_value defaults to lists, then parse the result back to an 
integer).
    
    Add a helper function in fabric_util go DRY-up the quorum option fetching 
and
    make it accept both list and integers. For now chttpd still sends the 
values as
    lists but in the future we can gradually switch to using integers.
---
 src/fabric/src/fabric_doc_open.erl      |   4 +-
 src/fabric/src/fabric_doc_open_revs.erl |   3 +-
 src/fabric/src/fabric_doc_purge.erl     |  10 +--
 src/fabric/src/fabric_doc_update.erl    |   3 +-
 src/fabric/src/fabric_open_revs.erl     |   3 +-
 src/fabric/src/fabric_util.erl          | 122 +++++++++++++++++++++++---------
 6 files changed, 95 insertions(+), 50 deletions(-)

diff --git a/src/fabric/src/fabric_doc_open.erl 
b/src/fabric/src/fabric_doc_open.erl
index 37cb699d6..138a3f2bb 100644
--- a/src/fabric/src/fabric_doc_open.erl
+++ b/src/fabric/src/fabric_doc_open.erl
@@ -41,11 +41,11 @@ go(DbName, Id, Options) ->
     ),
     SuppressDeletedDoc = not lists:member(deleted, Options),
     N = mem3:n(DbName),
-    R = couch_util:get_value(r, Options, integer_to_list(mem3:quorum(DbName))),
+    R = fabric_util:r_from_opts(DbName, Options),
     Acc0 = #acc{
         dbname = DbName,
         workers = Workers,
-        r = min(N, list_to_integer(R)),
+        r = min(N, R),
         state = r_not_met,
         replies = []
     },
diff --git a/src/fabric/src/fabric_doc_open_revs.erl 
b/src/fabric/src/fabric_doc_open_revs.erl
index ae3f72420..93ec7e71e 100644
--- a/src/fabric/src/fabric_doc_open_revs.erl
+++ b/src/fabric/src/fabric_doc_open_revs.erl
@@ -37,12 +37,11 @@ go(DbName, Id, Revs, Options) ->
         open_revs,
         [Id, Revs, Options]
     ),
-    R = couch_util:get_value(r, Options, integer_to_list(mem3:quorum(DbName))),
     State = #state{
         dbname = DbName,
         worker_count = length(Workers),
         workers = Workers,
-        r = list_to_integer(R),
+        r = fabric_util:r_from_opts(DbName, Options),
         revs = Revs,
         latest = lists:member(latest, Options),
         replies = []
diff --git a/src/fabric/src/fabric_doc_purge.erl 
b/src/fabric/src/fabric_doc_purge.erl
index bc96430eb..a4fc9e76f 100644
--- a/src/fabric/src/fabric_doc_purge.erl
+++ b/src/fabric/src/fabric_doc_purge.erl
@@ -62,7 +62,7 @@ go(DbName, IdsRevs, Options) ->
         worker_uuids = WorkerUUIDs,
         resps = Responses,
         uuid_counts = UUIDCounts,
-        w = w(DbName, Options)
+        w = fabric_util:w_from_opts(DbName, Options)
     },
     Callback = fun handle_message/3,
     Acc2 =
@@ -128,14 +128,6 @@ group_reqs_by_shard(DbName, Reqs) ->
         end,
     lists:foldl(ReqFoldFun, #{}, Reqs).
 
-w(DbName, Options) ->
-    try
-        list_to_integer(couch_util:get_value(w, Options))
-    catch
-        _:_ ->
-            mem3:quorum(DbName)
-    end.
-
 % Failed WorkerUUIDs = #{#shard{} => [UUIDs, ...]}
 % Resps = #{UUID => [{ok, ...} | {error, ...}]
 append_errors(Type, #{} = WorkerUUIDs, #{} = Resps) ->
diff --git a/src/fabric/src/fabric_doc_update.erl 
b/src/fabric/src/fabric_doc_update.erl
index 1f5755de0..a977180bc 100644
--- a/src/fabric/src/fabric_doc_update.erl
+++ b/src/fabric/src/fabric_doc_update.erl
@@ -42,11 +42,10 @@ go(DbName, AllDocs0, Opts) ->
     ),
     {Workers, _} = lists:unzip(GroupedDocs),
     RexiMon = fabric_util:create_monitors(Workers),
-    W = couch_util:get_value(w, Options, integer_to_list(mem3:quorum(DbName))),
     Acc0 = #acc{
         waiting_count = length(Workers),
         doc_count = length(AllDocs),
-        w = list_to_integer(W),
+        w = fabric_util:w_from_opts(DbName, Options),
         grouped_docs = GroupedDocs,
         reply = dict:new()
     },
diff --git a/src/fabric/src/fabric_open_revs.erl 
b/src/fabric/src/fabric_open_revs.erl
index b0a54645a..b4f95df8a 100644
--- a/src/fabric/src/fabric_open_revs.erl
+++ b/src/fabric/src/fabric_open_revs.erl
@@ -83,8 +83,7 @@ handle_message(Reason, Worker, #st{} = St) ->
     handle_error(Reason, St#st{workers = Workers1, reqs = Reqs1}).
 
 init_state(DbName, IdsRevsOpts, Options) ->
-    DefaultR = integer_to_list(mem3:quorum(DbName)),
-    R = list_to_integer(couch_util:get_value(r, Options, DefaultR)),
+    R = fabric_util:r_from_opts(DbName, Options),
     {ArgRefs, Reqs0} = build_req_map(IdsRevsOpts),
     ShardMap = build_worker_map(DbName, Reqs0),
     {Workers, Reqs} = spawn_workers(Reqs0, ShardMap, Options),
diff --git a/src/fabric/src/fabric_util.erl b/src/fabric/src/fabric_util.erl
index b10f20a63..1da214f7f 100644
--- a/src/fabric/src/fabric_util.erl
+++ b/src/fabric/src/fabric_util.erl
@@ -37,13 +37,13 @@
 -export([get_uuid_prefix_len/0]).
 -export([isolate/1, isolate/2]).
 -export([get_design_doc_records/1]).
+-export([w_from_opts/2, r_from_opts/2]).
 
 -compile({inline, [{doc_id_and_rev, 1}]}).
 
 -include_lib("mem3/include/mem3.hrl").
 -include_lib("couch/include/couch_db.hrl").
 -include_lib("couch_mrview/include/couch_mrview.hrl").
--include_lib("eunit/include/eunit.hrl").
 
 remove_down_workers(Workers, BadNode) ->
     remove_down_workers(Workers, BadNode, []).
@@ -261,38 +261,6 @@ create_monitors(Shards) ->
     MonRefs = lists:usort([rexi_utils:server_pid(N) || #shard{node = N} <- 
Shards]),
     rexi_monitor:start(MonRefs).
 
-%% verify only id and rev are used in key.
-update_counter_test() ->
-    Reply =
-        {ok, #doc{
-            id = <<"id">>,
-            revs = <<"rev">>,
-            body = <<"body">>,
-            atts = <<"atts">>
-        }},
-    ?assertEqual(
-        [{{<<"id">>, <<"rev">>}, {Reply, 1}}],
-        update_counter(Reply, 1, [])
-    ).
-
-remove_ancestors_test() ->
-    Foo1 = {ok, #doc{revs = {1, [<<"foo">>]}}},
-    Foo2 = {ok, #doc{revs = {2, [<<"foo2">>, <<"foo">>]}}},
-    Bar1 = {ok, #doc{revs = {1, [<<"bar">>]}}},
-    Bar2 = {not_found, {1, <<"bar">>}},
-    ?assertEqual(
-        [kv(Bar1, 1), kv(Foo1, 1)],
-        remove_ancestors([kv(Bar1, 1), kv(Foo1, 1)], [])
-    ),
-    ?assertEqual(
-        [kv(Bar1, 1), kv(Foo2, 2)],
-        remove_ancestors([kv(Bar1, 1), kv(Foo1, 1), kv(Foo2, 1)], [])
-    ),
-    ?assertEqual(
-        [kv(Bar1, 2)],
-        remove_ancestors([kv(Bar2, 1), kv(Bar1, 1)], [])
-    ).
-
 is_replicator_db(DbName) ->
     path_ends_with(DbName, <<"_replicator">>).
 
@@ -415,6 +383,30 @@ get_design_doc_records(DbName) ->
                 Else
         end
     end).
+
+w_from_opts(Db, Options) ->
+    quorum_from_opts(Db, couch_util:get_value(w, Options)).
+
+r_from_opts(Db, Options) ->
+    quorum_from_opts(Db, couch_util:get_value(r, Options)).
+
+quorum_from_opts(Db, Val) ->
+    try
+        if
+            is_integer(Val) ->
+                Val;
+            is_list(Val) ->
+                % Compatibility clause. Keep as long as chttpd parses r and w
+                % request parameters as lists (strings).
+                list_to_integer(Val);
+            true ->
+                mem3:quorum(Db)
+        end
+    catch
+        _:_ ->
+            mem3:quorum(Db)
+    end.
+
 % If we issue multiple fabric calls from the same process we have to isolate
 % them so in case of error they don't pollute the processes dictionary or the
 % mailbox
@@ -443,6 +435,41 @@ do_isolate(Fun) ->
             {'$isolerr', Tag, Reason, Stack}
     end.
 
+-ifdef(TEST).
+-include_lib("couch/include/couch_eunit.hrl").
+
+%% verify only id and rev are used in key.
+update_counter_test() ->
+    Reply =
+        {ok, #doc{
+            id = <<"id">>,
+            revs = <<"rev">>,
+            body = <<"body">>,
+            atts = <<"atts">>
+        }},
+    ?assertEqual(
+        [{{<<"id">>, <<"rev">>}, {Reply, 1}}],
+        update_counter(Reply, 1, [])
+    ).
+
+remove_ancestors_test() ->
+    Foo1 = {ok, #doc{revs = {1, [<<"foo">>]}}},
+    Foo2 = {ok, #doc{revs = {2, [<<"foo2">>, <<"foo">>]}}},
+    Bar1 = {ok, #doc{revs = {1, [<<"bar">>]}}},
+    Bar2 = {not_found, {1, <<"bar">>}},
+    ?assertEqual(
+        [kv(Bar1, 1), kv(Foo1, 1)],
+        remove_ancestors([kv(Bar1, 1), kv(Foo1, 1)], [])
+    ),
+    ?assertEqual(
+        [kv(Bar1, 1), kv(Foo2, 2)],
+        remove_ancestors([kv(Bar1, 1), kv(Foo1, 1), kv(Foo2, 1)], [])
+    ),
+    ?assertEqual(
+        [kv(Bar1, 2)],
+        remove_ancestors([kv(Bar2, 1), kv(Bar1, 1)], [])
+    ).
+
 get_db_timeout_test() ->
     % Q=1, N=1
     ?assertEqual(20000, get_db_timeout(1, 2, 100, 60000)),
@@ -486,3 +513,32 @@ get_db_timeout_test() ->
     % request_timeout was set to infinity, with enough shards it still gets to
     % 100 min timeout at the start from the exponential logic
     ?assertEqual(100, get_db_timeout(64, 2, 100, infinity)).
+
+rw_opts_test_() ->
+    {
+        foreach,
+        fun() -> meck:new(mem3, [passthrough]) end,
+        fun(_) -> meck:unload() end,
+        [
+            ?TDEF_FE(t_w_opts_get),
+            ?TDEF_FE(t_r_opts_get)
+        ]
+    }.
+
+t_w_opts_get(_) ->
+    meck:expect(mem3, quorum, 1, 3),
+    ?assertEqual(5, w_from_opts(any_db, [{w, 5}])),
+    ?assertEqual(5, w_from_opts(any_db, [{w, "5"}])),
+    ?assertEqual(3, w_from_opts(any_db, [{w, some_other_type}])),
+    ?assertEqual(3, w_from_opts(any_db, [{w, "five"}])),
+    ?assertEqual(3, w_from_opts(any_db, [])).
+
+t_r_opts_get(_) ->
+    meck:expect(mem3, quorum, 1, 3),
+    ?assertEqual(5, r_from_opts(any_db, [{other_opt, 42}, {r, 5}])),
+    ?assertEqual(5, r_from_opts(any_db, [{r, "5"}, {something_else, "xyz"}])),
+    ?assertEqual(3, r_from_opts(any_db, [{r, some_other_type}])),
+    ?assertEqual(3, r_from_opts(any_db, [{r, "five"}])),
+    ?assertEqual(3, r_from_opts(any_db, [])).
+
+-endif.

Reply via email to