From 78cb23553ff9fb7b28a6c2b552ad77b5fadb69f8 Mon Sep 17 00:00:00 2001
From: Bryan Hunt
Date: Wed, 27 Jul 2016 16:01:57 +0100
Subject: [PATCH 01/42] try out lukes requests
---
src/riakc_datatype.erl | 3 +-
src/riakc_gset.erl | 141 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 143 insertions(+), 1 deletion(-)
create mode 100644 src/riakc_gset.erl
diff --git a/src/riakc_datatype.erl b/src/riakc_datatype.erl
index 6561d76d..19bc7bbd 100644
--- a/src/riakc_datatype.erl
+++ b/src/riakc_datatype.erl
@@ -34,7 +34,7 @@
-endif.
--define(MODULES, [riakc_set, riakc_counter, riakc_flag, riakc_register, riakc_map]).
+-define(MODULES, [riakc_set, riakc_gset, riakc_counter, riakc_flag, riakc_register, riakc_map]).
-export([module_for_type/1,
module_for_term/1]).
@@ -82,6 +82,7 @@
%% type.
-spec module_for_type(Type::atom()) -> module().
module_for_type(set) -> riakc_set;
+module_for_type(gset) -> riakc_gset;
module_for_type(counter) -> riakc_counter;
module_for_type(flag) -> riakc_flag;
module_for_type(register) -> riakc_register;
diff --git a/src/riakc_gset.erl b/src/riakc_gset.erl
new file mode 100644
index 00000000..8682cf8a
--- /dev/null
+++ b/src/riakc_gset.erl
@@ -0,0 +1,141 @@
+%% -------------------------------------------------------------------
+%%
+%% riakc_gset: Eventually-consistent set type
+%%
+%% Copyright (c) 2013 Basho Technologies, Inc. All Rights Reserved.
+%%
+%% This file is provided to you under the Apache License,
+%% Version 2.0 (the "License"); you may not use this file
+%% except in compliance with the License. You may obtain
+%% a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing,
+%% software distributed under the License is distributed on an
+%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%% KIND, either express or implied. See the License for the
+%% specific language governing permissions and limitations
+%% under the License.
+%%
+%% -------------------------------------------------------------------
+
+%% @doc Encapsulates a gset data-type. Riak's gsets differ from Erlang
+%% gset types in several ways:
+%%
+%% - Only binaries are allowed as elements. Convert other terms to a
+%% binary before adding them.
+%% - Like the other eventually-consistent types, update %% (`add_element/2')
+%% is not applied to local state. Instead, additions are captured for later
+%% application by Riak.
+%% - You may add an element that already exists in the original set
+%% value.
+%% - The query functions `size/1', `is_element/1' and `fold/3' only
+%% operate on the original value of the set, disregarding local
+%% updates.
+%%
+%% @end
+-module(riakc_gset).
+-behaviour(riakc_datatype).
+
+-ifdef(EQC).
+-include_lib("eqc/include/eqc.hrl").
+-compile(export_all).
+-endif.
+
+%% Callbacks
+-export([new/0, new/1, new/2,
+ value/1,
+ to_op/1,
+ is_type/1,
+ type/0]).
+
+%% Operations
+-export([add_element/2]).
+
+%% Query functions
+-export([size/1,
+ is_element/2,
+ fold/3]).
+
+-record(gset, {value = ordsets:new() :: ordsets:ordset(binary()),
+ adds = ordsets:new() :: ordsets:ordset(binary()),
+ context = undefined :: riakc_datatype:context() }).
+
+-export_type([riakc_gset/0, gset_op/0]).
+-opaque riakc_gset() :: #gset{}.
+
+-type simple_gset_op() :: {add_all, [binary()]}.
+-type gset_op() :: simple_gset_op() | {update, [simple_gset_op()]}.
+
+%% @doc Creates a new, empty set container type.
+-spec new() -> riakc_gset().
+new() ->
+ #gset{}.
+
+%% @doc Creates a new sset container with the opaque context.
+-spec new(riakc_datatype:context()) -> riakc_gset().
+new(Context) ->
+ #gset{context=Context}.
+
+%% @doc Creates a new gset container with the given members and opaque
+%% context.
+-spec new([binary()], riakc_datatype:context()) -> riakc_gset().
+new(Value, Context) when is_list(Value) ->
+ #gset{value=ordsets:from_list(Value),
+ context=Context}.
+
+%% @doc Returns the original value of the set as an ordset.
+-spec value(riakc_gset()) -> ordsets:ordset(binary()).
+value(#gset{value=V}) -> V.
+
+%% @doc Extracts an operation from the gset that can be encoded into an
+%% update request.
+-spec to_op(riakc_gset()) -> riakc_datatype:update(gset_op()).
+to_op(#gset{adds=[]}) ->
+ undefined;
+to_op(#gset{adds=[A|AT], context=C}) ->
+ {type(), {add_all, [A|AT]}, C};
+to_op(#gset{adds=A, context=C}) ->
+ {type(), {update, [{add_all, [A]}]}, C}.
+
+%% @doc Determines whether the passed term is a gset container.
+-spec is_type(term()) -> boolean().
+is_type(T) ->
+ is_record(T, gset).
+
+%% @doc Returns the symbolic name of this container.
+-spec type() -> atom().
+type() -> gset.
+
+%% @doc Adds an element to the set.
+-spec add_element(binary(), riakc_gset()) -> riakc_gset().
+add_element(Bin, #gset{adds=A0}=Set) when is_binary(Bin) ->
+ Set#gset{adds=ordsets:add_element(Bin, A0)}.
+
+%% @doc Returns the cardinality (size) of the set. Note: this only
+%% operates on the original value as retrieved from Riak.
+-spec size(riakc_gset()) -> pos_integer().
+size(#gset{value=V}) ->
+ ordsets:size(V).
+
+%% @doc Test whether an element is a member of the set. Note: this
+%% only operates on the original value as retrieved from Riak.
+-spec is_element(binary(), riakc_gset()) -> boolean().
+is_element(Bin, #gset{value=V}) when is_binary(Bin) ->
+ ordsets:is_element(Bin, V).
+
+%% @doc Folds over the members of the set. Note: this only
+%% operates on the original value as retrieved from Riak.
+-spec fold(fun((binary(), term()) -> term()), term(), riakc_gset()) -> term().
+fold(Fun, Acc0, #gset{value=V}) ->
+ ordsets:fold(Fun, Acc0, V).
+
+-ifdef(EQC).
+gen_type() ->
+ ?LET({Elems, Ctx}, {list(binary()), binary()}, new(Elems, Ctx)).
+
+gen_op() ->
+ {elements([add_element]),
+ [binary()]}.
+-endif.
From 37379f1c4427632feba38a53ee451696f108874f Mon Sep 17 00:00:00 2001
From: Russell Brown
Date: Mon, 31 Oct 2016 10:47:00 +0000
Subject: [PATCH 02/42] Bring bet365 gset code up to date with refactor
---
test/riakc_pb_socket_tests.erl | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/test/riakc_pb_socket_tests.erl b/test/riakc_pb_socket_tests.erl
index 88e95d09..32192177 100644
--- a/test/riakc_pb_socket_tests.erl
+++ b/test/riakc_pb_socket_tests.erl
@@ -1378,6 +1378,23 @@ integration_tests() ->
Rsp ->
?debugFmt("hlls bucket is not present, skipping (~p)", [Rsp])
end
+ end)},
+ {"add item to gset, twice",
+ ?_test(begin
+ riakc_test_utils:reset_riak(),
+ {ok, Pid} = riakc_test_utils:start_link(test_ip(), test_port()),
+ ok = riakc_pb_socket:update_type(Pid,
+ {<<"gset_bucket">>, <<"bucket">>}, <<"key">>,
+ riakc_gset:to_op(riakc_gset:add_element(<<"X">>, riakc_gset:new()))),
+ {ok, S0} = riakc_pb_socket:fetch_type(Pid, {<<"gset_bucket">>, <<"bucket">>}, <<"key">>),
+ ?assert(riakc_gset:is_element(<<"X">>, S0)),
+ ?assertEqual(riakc_gset:size(S0), 1),
+ ok = riakc_pb_socket:update_type(Pid,
+ {<<"set_bucket">>, <<"bucket">>}, <<"key">>,
+ riakc_gset:to_op(riakc_gset:add_element(<<"X">>, S0))),
+ {ok, S1} = riakc_pb_socket:fetch_type(Pid, {<<"gset_bucket">>, <<"bucket">>}, <<"key">>),
+ ?assert(riakc_gset:is_element(<<"X">>, S1)),
+ ?assertEqual(riakc_gset:size(S1), 1)
end)}
].
From 2bb67a0bcd9eeefdeb71fcc03b1bf85c83720701 Mon Sep 17 00:00:00 2001
From: Russell Brown
Date: Tue, 1 Nov 2016 14:33:38 +0000
Subject: [PATCH 03/42] Small changes for gset integration tests
Typo in bucket name, copywrite etc etc
---
src/riakc_gset.erl | 6 +++---
test/riakc_pb_socket_tests.erl | 4 ++--
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/riakc_gset.erl b/src/riakc_gset.erl
index 8682cf8a..b8379961 100644
--- a/src/riakc_gset.erl
+++ b/src/riakc_gset.erl
@@ -2,7 +2,7 @@
%%
%% riakc_gset: Eventually-consistent set type
%%
-%% Copyright (c) 2013 Basho Technologies, Inc. All Rights Reserved.
+%% Copyright (c) 2016 Basho Technologies, Inc. All Rights Reserved.
%%
%% This file is provided to you under the Apache License,
%% Version 2.0 (the "License"); you may not use this file
@@ -21,7 +21,7 @@
%% -------------------------------------------------------------------
%% @doc Encapsulates a gset data-type. Riak's gsets differ from Erlang
-%% gset types in several ways:
+%% set types in several ways:
%%
%% - Only binaries are allowed as elements. Convert other terms to a
%% binary before adding them.
@@ -40,7 +40,7 @@
-ifdef(EQC).
-include_lib("eqc/include/eqc.hrl").
--compile(export_all).
+-export([gen_type/0, gen_op/0]).
-endif.
%% Callbacks
diff --git a/test/riakc_pb_socket_tests.erl b/test/riakc_pb_socket_tests.erl
index 32192177..c3654414 100644
--- a/test/riakc_pb_socket_tests.erl
+++ b/test/riakc_pb_socket_tests.erl
@@ -1382,7 +1382,7 @@ integration_tests() ->
{"add item to gset, twice",
?_test(begin
riakc_test_utils:reset_riak(),
- {ok, Pid} = riakc_test_utils:start_link(test_ip(), test_port()),
+ {ok, Pid} = riakc_test_utils:start_link(),
ok = riakc_pb_socket:update_type(Pid,
{<<"gset_bucket">>, <<"bucket">>}, <<"key">>,
riakc_gset:to_op(riakc_gset:add_element(<<"X">>, riakc_gset:new()))),
@@ -1390,7 +1390,7 @@ integration_tests() ->
?assert(riakc_gset:is_element(<<"X">>, S0)),
?assertEqual(riakc_gset:size(S0), 1),
ok = riakc_pb_socket:update_type(Pid,
- {<<"set_bucket">>, <<"bucket">>}, <<"key">>,
+ {<<"gset_bucket">>, <<"bucket">>}, <<"key">>,
riakc_gset:to_op(riakc_gset:add_element(<<"X">>, S0))),
{ok, S1} = riakc_pb_socket:fetch_type(Pid, {<<"gset_bucket">>, <<"bucket">>}, <<"key">>),
?assert(riakc_gset:is_element(<<"X">>, S1)),
From d300bf25859122daaaebac597bfbd5be534623f4 Mon Sep 17 00:00:00 2001
From: Russell Brown
Date: Wed, 11 Oct 2017 12:00:39 +0100
Subject: [PATCH 04/42] Update rebar.config for nhs-riak riak_pb patch
Also add a long timeout to the datatype tests as they failed once or
twice on timeout (I suspect it might have been a network issue acquiring
an EQC license, but having a timeout hurts no one.)
---
rebar.config | 2 +-
src/riakc_datatype.erl | 10 ++++++----
2 files changed, 7 insertions(+), 5 deletions(-)
diff --git a/rebar.config b/rebar.config
index 2577cc37..c1e1a6be 100644
--- a/rebar.config
+++ b/rebar.config
@@ -13,7 +13,7 @@
]}.
{deps, [
- {riak_pb, ".*", {git, "https://github.com/basho/riak_pb", {tag, "2.2.0.0"}}}
+ {riak_pb, ".*", {git, "git://github.com/nhs-riak/riak_pb.git", {branch, "rdb/2.2.0.0-nhs-2.2.5"}}}
]}.
{edoc_opts, [
diff --git a/src/riakc_datatype.erl b/src/riakc_datatype.erl
index dd6c5a10..3f90e3b6 100644
--- a/src/riakc_datatype.erl
+++ b/src/riakc_datatype.erl
@@ -112,12 +112,14 @@ module_for_term(T) ->
prop_module_for_term]).
-define(F(Fmt, Args), lists:flatten(io_lib:format(Fmt, Args))).
datatypes_test_() ->
+ {timeout, 120,
[{" prop_module_type() ",
?_assert(eqc:quickcheck(?QC_OUT(prop_module_type())))}] ++
- [ {?F(" ~s(~s) ", [Prop, Mod]),
- ?_assert(eqc:quickcheck(?QC_OUT(eqc:testing_time(2, ?MODULE:Prop(Mod)))))} ||
- Prop <- ?MODPROPS,
- Mod <- ?MODULES ].
+ [ {?F(" ~s(~s) ", [Prop, Mod]),
+ ?_assert(eqc:quickcheck(?QC_OUT(eqc:testing_time(2, ?MODULE:Prop(Mod)))))} ||
+ Prop <- ?MODPROPS,
+ Mod <- ?MODULES ]
+ }.
run_props() ->
run_props(500).
From 9bda9ba96a91ce4898dd14477198fcff9a1393de Mon Sep 17 00:00:00 2001
From: Russell Brown
Date: Thu, 30 Nov 2017 11:27:22 +0000
Subject: [PATCH 05/42] update deps for basho-dev-2.2.5
---
rebar.config | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rebar.config b/rebar.config
index c1e1a6be..7409cd3e 100644
--- a/rebar.config
+++ b/rebar.config
@@ -13,7 +13,7 @@
]}.
{deps, [
- {riak_pb, ".*", {git, "git://github.com/nhs-riak/riak_pb.git", {branch, "rdb/2.2.0.0-nhs-2.2.5"}}}
+ {riak_pb, ".*", {git, "git://github.com/basho/riak_pb.git", {branch, "develop-2.2.5"}}}
]}.
{edoc_opts, [
From de52b2e94c42f6d03beebe49c42f7d77ff620eb0 Mon Sep 17 00:00:00 2001
From: Russell Brown
Date: Wed, 13 Dec 2017 10:42:14 +0000
Subject: [PATCH 06/42] Move deps back to dev-2.2
As part of setting up for the 2.2.5 release I made a temp branch off the
last released tag, this takes that branch back into the branch the tag
was cut from.
---
rebar.config | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rebar.config b/rebar.config
index 7409cd3e..bb56a7f9 100644
--- a/rebar.config
+++ b/rebar.config
@@ -13,7 +13,7 @@
]}.
{deps, [
- {riak_pb, ".*", {git, "git://github.com/basho/riak_pb.git", {branch, "develop-2.2.5"}}}
+ {riak_pb, ".*", {git, "git://github.com/nhs-riak/riak_pb.git", {branch, "develop-2.2"}}}
]}.
{edoc_opts, [
From fd6b68499d9e104f7c78a9c7b94597be51f53d4f Mon Sep 17 00:00:00 2001
From: Russell Brown
Date: Wed, 17 Jan 2018 14:25:13 +0000
Subject: [PATCH 07/42] Add `node_confirms` option to PUT request
And counter/datatype operations. NOTE: depends on riak-2.2.5
`node_confirms` feature.
---
src/riakc_pb_socket.erl | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/riakc_pb_socket.erl b/src/riakc_pb_socket.erl
index ad0127ee..e0a8a43f 100644
--- a/src/riakc_pb_socket.erl
+++ b/src/riakc_pb_socket.erl
@@ -1582,6 +1582,9 @@ put_options([{n_val, N} | Rest], Req)
put_options([{sloppy_quorum, Bool} | Rest], Req)
when Bool == true; Bool == false ->
put_options(Rest, Req#rpbputreq{sloppy_quorum = Bool});
+put_options([{node_confirms, NodeConfirms} | Rest], Req) ->
+ NCOpt = riak_pb_kv_codec:encode_quorum(NodeConfirms),
+ put_options(Rest, Req#rpbputreq{node_confirms = NCOpt});
put_options([{_, _} | _Rest], _Req) ->
erlang:error(badarg).
@@ -1642,6 +1645,8 @@ counter_incr_options([{dw, DW} | Rest], Req) ->
counter_incr_options(Rest, Req#rpbcounterupdatereq{dw=riak_pb_kv_codec:encode_quorum(DW)});
counter_incr_options([{pw, PW} | Rest], Req) ->
counter_incr_options(Rest, Req#rpbcounterupdatereq{pw=riak_pb_kv_codec:encode_quorum(PW)});
+counter_incr_options([{node_confirms, NodeConfirms} | Rest], Req) ->
+ counter_incr_options(Rest, Req#rpbcounterupdatereq{node_confirms=riak_pb_kv_codec:encode_quorum(NodeConfirms)});
counter_incr_options([returnvalue | Rest], Req) ->
counter_incr_options(Rest, Req#rpbcounterupdatereq{returnvalue=true});
counter_incr_options([_ | _Rest], _Req) ->
From 6c247559c8d5a4d6314ac934292dc315e320421e Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Thu, 5 Sep 2019 12:15:35 +0100
Subject: [PATCH 08/42] Add fetch request/response
The fetch response returns a repl encoded binary object, to match that returned by the HTTP API
---
rebar.config | 2 +-
src/riakc_pb_socket.erl | 33 +++++++++++++++++++++++++++++++++
2 files changed, 34 insertions(+), 1 deletion(-)
diff --git a/rebar.config b/rebar.config
index f8067271..7444132e 100644
--- a/rebar.config
+++ b/rebar.config
@@ -13,7 +13,7 @@
]}.
{deps, [
- {riak_pb, ".*", {git, "https://github.com/basho/riak_pb", {tag, "2.2.1.0"}}}
+ {riak_pb, ".*", {git, "https://github.com/martinsumner/riak_pb", {tag, "mas-i1691-ttaaefullsync"}}}
]}.
{edoc_opts, [
diff --git a/src/riakc_pb_socket.erl b/src/riakc_pb_socket.erl
index e0a8a43f..b3999b7b 100644
--- a/src/riakc_pb_socket.erl
+++ b/src/riakc_pb_socket.erl
@@ -49,6 +49,7 @@
set_client_id/2, set_client_id/3,
get_server_info/1, get_server_info/2,
get/3, get/4, get/5,
+ fetch/2,
put/2, put/3, put/4,
delete/3, delete/4, delete/5,
delete_vclock/4, delete_vclock/5, delete_vclock/6,
@@ -321,6 +322,15 @@ get(Pid, Bucket, Key, Options, Timeout) ->
Req = get_options(Options, #rpbgetreq{type =T, bucket = B, key = Key}),
call_infinity(Pid, {req, Req, Timeout}).
+%% @doc Fetch replicated objects from a queue
+-spec fetch(pid(), binary()) ->
+ {ok, queue_empty}|
+ {ok|crc_wonky, {deleted, term(), binary()}|binary()}.
+fetch(Pid, QueueName) ->
+ Req = #rpbfetchreq{queuename = QueueName},
+ call_infinity(Pid, {req, Req, default_timeout(get_timeout)}).
+
+
%% @doc Put the metadata/value in the object under bucket/key
%% @equiv put(Pid, Obj, [])
%% @see put/4
@@ -1715,6 +1725,23 @@ process_response(#request{msg = #rpbgetreq{type = Type, bucket = Bucket, key = K
B = maybe_make_bucket_type(Type, Bucket),
{reply, {ok, riakc_obj:new_obj(B, Key, Vclock, Contents)}, State};
+%% rpbfetchreq
+process_response(#request{msg = #rpbfetchreq{}},
+ #rpbfetchrsp{queue_empty = true}, State) ->
+ {reply, {ok, queue_empty}, State};
+process_response(#request{msg = #rpbfetchreq{}},
+ #rpbfetchrsp{deleted = true,
+ crc_check = CRC,
+ replencoded_object = ObjBin,
+ deleted_vclock = VclockBin}, State) ->
+ {reply,
+ {crc_check(CRC,ObjBin), {deleted, binary_to_term(VclockBin), ObjBin}},
+ State};
+process_response(#request{msg = #rpbfetchreq{}},
+ #rpbfetchrsp{crc_check = CRC,
+ replencoded_object = ObjBin}, State) ->
+ {reply, {crc_check(CRC,ObjBin), ObjBin}, State};
+
%% rpbputreq
process_response(#request{msg = #rpbputreq{}},
rpbputresp, State) ->
@@ -2384,6 +2411,12 @@ remove_queued_request(Ref, State) ->
NewState#state{queue = queue:from_list(L2)}
end.
+crc_check(CRC, Bin) ->
+ case erlang:crc32(Bin) of
+ CRC -> ok;
+ _ -> crc_wonky
+ end.
+
%% @private
-ifdef(deprecated_19).
mk_reqid() -> erlang:phash2(crypto:strong_rand_bytes(10)). % only has to be unique per-pid
From aafca5d07983977d1446d58789b2c2ad2e24287c Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Thu, 5 Sep 2019 21:57:00 +0100
Subject: [PATCH 09/42] Use resp not rsp
For consistency with other messages
---
src/riakc_pb_socket.erl | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/riakc_pb_socket.erl b/src/riakc_pb_socket.erl
index b3999b7b..4ae217d2 100644
--- a/src/riakc_pb_socket.erl
+++ b/src/riakc_pb_socket.erl
@@ -1727,10 +1727,10 @@ process_response(#request{msg = #rpbgetreq{type = Type, bucket = Bucket, key = K
%% rpbfetchreq
process_response(#request{msg = #rpbfetchreq{}},
- #rpbfetchrsp{queue_empty = true}, State) ->
+ #rpbfetchresp{queue_empty = true}, State) ->
{reply, {ok, queue_empty}, State};
process_response(#request{msg = #rpbfetchreq{}},
- #rpbfetchrsp{deleted = true,
+ #rpbfetchresp{deleted = true,
crc_check = CRC,
replencoded_object = ObjBin,
deleted_vclock = VclockBin}, State) ->
@@ -1738,7 +1738,7 @@ process_response(#request{msg = #rpbfetchreq{}},
{crc_check(CRC,ObjBin), {deleted, binary_to_term(VclockBin), ObjBin}},
State};
process_response(#request{msg = #rpbfetchreq{}},
- #rpbfetchrsp{crc_check = CRC,
+ #rpbfetchresp{crc_check = CRC,
replencoded_object = ObjBin}, State) ->
{reply, {crc_check(CRC,ObjBin), ObjBin}, State};
From 4646153d93a257de235a22c045f612c01e5516aa Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Thu, 5 Sep 2019 23:46:14 +0100
Subject: [PATCH 10/42] Do not decode vector clock
Should be opaque
---
src/riakc_pb_socket.erl | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/riakc_pb_socket.erl b/src/riakc_pb_socket.erl
index 4ae217d2..f15c922a 100644
--- a/src/riakc_pb_socket.erl
+++ b/src/riakc_pb_socket.erl
@@ -1735,7 +1735,7 @@ process_response(#request{msg = #rpbfetchreq{}},
replencoded_object = ObjBin,
deleted_vclock = VclockBin}, State) ->
{reply,
- {crc_check(CRC,ObjBin), {deleted, binary_to_term(VclockBin), ObjBin}},
+ {crc_check(CRC,ObjBin), {deleted, VclockBin, ObjBin}},
State};
process_response(#request{msg = #rpbfetchreq{}},
#rpbfetchresp{crc_check = CRC,
From 46ea6704cda863695b1a69e217e6cf187afe1907 Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Fri, 6 Sep 2019 02:09:55 +0100
Subject: [PATCH 11/42] suppress crash when terminates
Have this as an option - to be used by the nextgenrepl snk worker PB client to reduce log noise.
---
src/riakc_pb_socket.erl | 21 +++++++++++++++++----
1 file changed, 17 insertions(+), 4 deletions(-)
diff --git a/src/riakc_pb_socket.erl b/src/riakc_pb_socket.erl
index f15c922a..9c0531d8 100644
--- a/src/riakc_pb_socket.erl
+++ b/src/riakc_pb_socket.erl
@@ -178,7 +178,8 @@
% certificate authentication
ssl_opts = [], % Arbitrary SSL options, see the erlang SSL
% documentation.
- reconnect_interval=?FIRST_RECONNECT_INTERVAL :: non_neg_integer()}).
+ reconnect_interval=?FIRST_RECONNECT_INTERVAL :: non_neg_integer(),
+ silence_terminate_crash = false :: boolean()}).
-export_type([address/0, portnum/0]).
@@ -1338,7 +1339,12 @@ init([Address, Port, Options]) ->
queue = queue:new()}),
case connect(State) of
{error, Reason} when State#state.auto_reconnect /= true ->
- {stop, {tcp, Reason}};
+ case State#state.silence_terminate_crash of
+ true ->
+ {stop, normal};
+ _ ->
+ {stop, {tcp, Reason}}
+ end;
{error, _Reason} ->
erlang:send_after(State#state.reconnect_interval, self(), reconnect),
{ok, State};
@@ -1513,7 +1519,9 @@ parse_options([{cacertfile, File}|Options], State) ->
parse_options([{keyfile, File}|Options], State) ->
parse_options(Options, State#state{keyfile=File});
parse_options([{ssl_opts, Opts}|Options], State) ->
- parse_options(Options, State#state{ssl_opts=Opts}).
+ parse_options(Options, State#state{ssl_opts=Opts});
+parse_options([{silence_terminate_crash,Bool}|Options], State) ->
+ parse_options(Options, State#state{silence_terminate_crash=Bool}).
maybe_reply({reply, Reply, State}) ->
Request = State#state.active,
@@ -2318,7 +2326,12 @@ disconnect(State) ->
erlang:send_after(State#state.reconnect_interval, self(), reconnect),
{noreply, increase_reconnect_interval(NewState)};
false ->
- {stop, disconnected, NewState}
+ case State#state.silence_terminate_crash of
+ true ->
+ {stop, normal, NewState};
+ _ ->
+ {stop, disconnected, NewState}
+ end
end.
%% Double the reconnect interval up to the maximum
From 48c035ca3db49b28a05fd48b3a0906aa8dae00d5 Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Wed, 6 Nov 2019 11:36:48 +0000
Subject: [PATCH 12/42] Add client option to spec
---
include/riakc.hrl | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/include/riakc.hrl b/include/riakc.hrl
index 74495626..fdb8e2e7 100644
--- a/include/riakc.hrl
+++ b/include/riakc.hrl
@@ -32,7 +32,8 @@
auto_reconnect |
{auto_reconnect, boolean()} |
keepalive |
- {keepalive, boolean()}.
+ {keepalive, boolean()|
+ {silence_terminate_crash, boolean()}}.
%% Options for starting or modifying the connection:
%% `queue_if_disconnected' when present or true will cause requests to
%% be queued while the connection is down. `auto_reconnect' when
From 6fd45a22728f50b6fb97c23d879a8e93bb780265 Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Wed, 6 Nov 2019 12:17:01 +0000
Subject: [PATCH 13/42] Fix type typo
---
include/riakc.hrl | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/include/riakc.hrl b/include/riakc.hrl
index fdb8e2e7..1a18f4f2 100644
--- a/include/riakc.hrl
+++ b/include/riakc.hrl
@@ -32,8 +32,8 @@
auto_reconnect |
{auto_reconnect, boolean()} |
keepalive |
- {keepalive, boolean()|
- {silence_terminate_crash, boolean()}}.
+ {keepalive, boolean()}|
+ {silence_terminate_crash, boolean()}.
%% Options for starting or modifying the connection:
%% `queue_if_disconnected' when present or true will cause requests to
%% be queued while the connection is down. `auto_reconnect' when
From 3da0d233463daa9b0862787d1941efed4ad51861 Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Tue, 12 Nov 2019 10:44:00 +0000
Subject: [PATCH 14/42] Add aae_fold API
Initial draft of adding aae_fold API
---
rebar.config | 2 +-
src/riakc_pb_socket.erl | 393 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 394 insertions(+), 1 deletion(-)
diff --git a/rebar.config b/rebar.config
index f8067271..52bdf605 100644
--- a/rebar.config
+++ b/rebar.config
@@ -13,7 +13,7 @@
]}.
{deps, [
- {riak_pb, ".*", {git, "https://github.com/basho/riak_pb", {tag, "2.2.1.0"}}}
+ {riak_pb, ".*", {git, "https://github.com/basho/riak_pb", {branch, "mas-i1676-aaefoldpb"}}}
]}.
{edoc_opts, [
diff --git a/src/riakc_pb_socket.erl b/src/riakc_pb_socket.erl
index e0a8a43f..0df414d8 100644
--- a/src/riakc_pb_socket.erl
+++ b/src/riakc_pb_socket.erl
@@ -70,6 +70,9 @@
search/3, search/4, search/5, search/6,
get_index/4, get_index/5, get_index/6, get_index/7, %% @deprecated
get_index_eq/4, get_index_range/5, get_index_eq/5, get_index_range/6,
+ aae_merge_root/2, aae_merge_branches/3, aae_fetch_clocks/3,
+ aae_range_tree/7, aae_range_clocks/5, % aae_range_replkeys/5,
+ aae_find_keys/5, aae_object_stats/4,
cs_bucket_fold/3,
default_timeout/1,
tunnel/4,
@@ -129,6 +132,14 @@
{end_incl, boolean()}.
-type cs_opts() :: [cs_opt()].
+-type key_range() :: {riakc_obj:key(), riakc_obj:key()} | all.
+-type segment_filter() :: {list(pos_integer()), tree_size()} | all.
+-type modified_range() :: {ts(), ts()} | all.
+-type ts() :: pos_integer().
+-type hash_method() :: pre_hash | {rehash, non_neg_integer()}.
+
+-type tree_size() :: xxsmall| xsmall| small| medium| large| xlarge.
+
%% Which client operation the default timeout is being requested
%% for. `timeout' is the global default timeout. Any of these defaults
%% can be overridden by setting the application environment variable
@@ -1315,6 +1326,312 @@ replace_coverage(Pid, Bucket, Cover, Other) ->
{req, #rpbcoveragereq{type=T, bucket=B, replace_cover=Cover, unavailable_cover=Other},
Timeout}).
+
+%% @doc Get the merged aae tictactree root for the given `NVal'
+-spec aae_merge_root(pid(), NVal::pos_integer()) ->
+ {ok, {root, binary()}} |
+ {error, any()}.
+aae_merge_root(Pid, NVal) ->
+ Timeout = default_timeout(get_coverage_timeout),
+ call_infinity(Pid,
+ {req,
+ #rpbaaefoldmergerootnvalreq{n_val = NVal},
+ Timeout}).
+
+
+%% @doc get the aae merged branches for the given `NVal', restricted
+%% to the given list of `Branches'
+-spec aae_merge_branches(pid(),
+ NVal::pos_integer(),
+ Branches::list(pos_integer())) ->
+ {ok, {branches, [{BranchId::integer(), Branch::binary()}]}} |
+ {error, any()}.
+aae_merge_branches(Pid, NVal, Branches) ->
+ Timeout = default_timeout(get_coverage_timeout),
+ call_infinity(Pid,
+ {req,
+ #rpbaaefoldmergebranchnvalreq{n_val = NVal,
+ id_filter = Branches},
+ Timeout}).
+
+
+%% @doc get the aae merged branches for the given `NVal', restricted
+%% to the given list of `Branches'
+-spec aae_fetch_clocks(pid(),
+ NVal::pos_integer(),
+ Segments::list(pos_integer())) ->
+ {ok, {keysclocks,
+ [{{riakc_obj:bucket(),
+ riakc_obj:key()},
+ binary()}]}} |
+ {error, any()}.
+aae_fetch_clocks(Pid, NVal, Segments) ->
+ Timeout = default_timeout(get_coverage_timeout),
+ call_infinity(Pid,
+ {req,
+ #rpbaaefoldfetchclocksnvalreq{n_val = NVal,
+ id_filter = Segments},
+ Timeout}).
+
+%% @doc generate a tictac tree by folding over a range of keys
+%% in`Bucket'. The fold can be limited to the keys in `KeyRange' which
+%% is a pair `{Start::binary(), End::binary()}` that defines a range
+%% of keys, or the atom `all'. The `TreeSize' parameter is an atom,
+%% one of `xxsmall', `xsmall', `small', `medium', `large', or `xlarge'
+%% which determines, well, the tictac tree size. `SegmentFilter'
+%% further limits ths returned tree, it can be a pair of `{Segments,
+%% TreeSize}' where `Segments' is a list of integers (segments to
+%% return) and `TreeSize' the tree size that was initially queried to
+%% return the segments in `Segments', or it can be the atom
+%% `all'. `ModifiedRange' can restrict the tree fold to only include
+%% keys whose last modified date is in the range. The Range is a pair
+%% `{Start::pos_integer(), End::pos_integer()}' where both `Start' and
+%% `End' are 32-bit unix timestamps that represents seconds since the
+%% epoch. Finally `HashMethod' is one of `pre_hash' or `{rehash,
+%% IV::non_neg_integer()}'. The former uses the default hashing, the
+%% latter instructs the tictac tree to be built hashing the objects'
+%% vector clocks with a hash initialised with the value of `IV'. This
+%% is for those of you worried about hash collisions. NOTE: what is
+%% returned is mochijson2 style {struct, ETC} terms, as this is what
+%% leveled_tictact:import_tree expects
+-spec aae_range_tree(pid(), riakc_obj:bucket(),
+ key_range(), tree_size(),
+ segment_filter(), modified_range(), hash_method()) ->
+ {ok, {tree, Tree::any()}} | {error, any()}.
+aae_range_tree(Pid, BucketType, KeyRange, TreeSize,
+ SegmentFilter, ModifiedRange, HashMethod) ->
+ Timeout = default_timeout(get_coverage_timeout),
+ {KR, SK, EK} =
+ case KeyRange of
+ all ->
+ {false, undefined, undefined};
+ {SK0, EK0} ->
+ {true, SK0, EK0}
+ end,
+ {SF, SFL, FTS} =
+ case SegmentFilter of
+ all ->
+ {false, [], undefined};
+ {SFL0, FTS0} ->
+ {true, SFL0, FTS0}
+ end,
+ {MR, MRLow, MRHigh} =
+ case ModifiedRange of
+ all ->
+ {false, undefined, undefined};
+ {MRL, MRH} ->
+ {true, MRL, MRH}
+ end,
+ {HM, IV} =
+ case HashMethod of
+ pre_hash ->
+ {false, undefined};
+ {rehash, IV0} ->
+ {true, IV0}
+ end,
+ {T, B} =
+ case BucketType of
+ B0 when is_binary(B0) ->
+ {undefined, B0};
+ {T0, B0} ->
+ {T0, B0}
+ end,
+ call_infinity(Pid,
+ {req,
+ #rpbaaefoldmergetreesrangereq{type = T,
+ bucket = B,
+ key_range = KR,
+ start_key = SK,
+ end_key = EK,
+ tree_size = TreeSize,
+ segment_filter = SF,
+ id_filter = SFL,
+ filter_tree_size = FTS,
+ modified_range = MR,
+ last_mod_start = MRLow,
+ last_mod_end = MRHigh,
+ use_prehash = HM,
+ init_vector = IV},
+ Timeout}).
+
+-spec aae_range_clocks(pid(),
+ riakc_obj:bucket(), key_range(),
+ segment_filter(), modified_range()) ->
+ {ok,
+ {keysclocks,
+ [{{riakc_obj:bucket(),
+ riakc_obj:key()},
+ binary()}]}} |
+ {error, any()}.
+aae_range_clocks(Pid, BucketType, KeyRange, SegmentFilter, ModifiedRange) ->
+ Timeout = default_timeout(get_coverage_timeout),
+ {KR, SK, EK} =
+ case KeyRange of
+ all ->
+ {false, undefined, undefined};
+ {SK0, EK0} ->
+ {true, SK0, EK0}
+ end,
+ {SF, SFL, FTS} =
+ case SegmentFilter of
+ all ->
+ {false, [], undefined};
+ {SFL0, FTS0} ->
+ {true, SFL0, FTS0}
+ end,
+ {MR, MRLow, MRHigh} =
+ case ModifiedRange of
+ all ->
+ {false, undefined, undefined};
+ {MRL, MRH} ->
+ {true, MRL, MRH}
+ end,
+ {T, B} =
+ case BucketType of
+ B0 when is_binary(B0) ->
+ {undefined, B0};
+ {T0, B0} ->
+ {T0, B0}
+ end,
+ call_infinity(Pid,
+ {req,
+ #rpbaaefoldfetchclocksrangereq{type = T,
+ bucket = B,
+ key_range = KR,
+ start_key = SK,
+ end_key = EK,
+ segment_filter = SF,
+ id_filter = SFL,
+ filter_tree_size = FTS,
+ modified_range = MR,
+ last_mod_start = MRLow,
+ last_mod_end = MRHigh},
+ Timeout}).
+
+%% @doc aae_find_keys folds over the tictacaae store to get
+%% operational information. `Rhc' is the client. `Bucket' is the
+%% bucket to fold over. `KeyRange' as before is a two tuple of
+%% `{Start, End}' where both ` Start' and `End' are binaries that
+%% represent the first and last key of a range to fold over. The atom
+%% `all' means all fol over keys in the bucket. `ModifiedRange' is a
+%% pair `{StartDate, EndDate}' or 32-bit integer unix timestamps, or
+%% the atom `all', that limits the fold to only the keys that have a
+%% last-modified date in the range. the `Query' is either
+%% `{sibling_coun, N}` or `{object_size, N}' where `N' is an
+%% integer. for `sibling_count' `N' means return all keys that have
+%% more than `N' siblings. NOTE: 1 sibling means a single value in
+%% this implementation, therefore if you want all keys that have more
+%% than a single value AT THE VNODE then `{sibling_count, 1}' is your
+%% query. NOTE NOTE: It is possible that all N vnodes have a single
+%% value, and that value is different on each vnode (temporarily
+%% only), this query would not detect that state. For `object_size' it
+%% means return all keys whose object size is greater than `N'. The
+%% result is a list of pairs `{Key, Count | Size}'
+-spec aae_find_keys(pid(),
+ riakc_obj:bucket(), key_range(),
+ modified_range(), Query) ->
+ {ok, {keys, list({riakc_obj:key(), pos_integer()})}} |
+ {error, any()} when
+ Query :: {sibling_count, pos_integer()} | {object_size, pos_integer()}.
+aae_find_keys(Pid, BucketType, KeyRange, ModifiedRange, Query) ->
+ Timeout = default_timeout(get_coverage_timeout),
+ {KR, SK, EK} =
+ case KeyRange of
+ all ->
+ {false, undefined, undefined};
+ {SK0, EK0} ->
+ {true, SK0, EK0}
+ end,
+ {MR, MRLow, MRHigh} =
+ case ModifiedRange of
+ all ->
+ {false, undefined, undefined};
+ {MRL, MRH} ->
+ {true, MRL, MRH}
+ end,
+ {T, B} =
+ case BucketType of
+ B0 when is_binary(B0) ->
+ {undefined, B0};
+ {T0, B0} ->
+ {T0, B0}
+ end,
+ call_infinity(Pid,
+ {req,
+ #rpbaaefoldfindkeysreq{type = T,
+ bucket = B,
+ key_range = KR,
+ start_key = SK,
+ end_key = EK,
+ modified_range = MR,
+ last_mod_start = MRLow,
+ last_mod_end = MRHigh,
+ finder = element(1, Query),
+ find_limit = element(2, Query)
+ },
+ Timeout}).
+
+
+%% @doc aae_object_stats folds over the tictacaae store to get
+%% operational information. `Rhc' is the client. `Bucket' is the
+%% bucket to fold over. `KeyRange' as before is a two tuple of
+%% `{Start, End}' where both ` Start' and `End' are binaries that
+%% represent the first and last key of a range to fold over. The atom
+%% `all' means all fol over keys in the bucket. `ModifiedRange' is a
+%% pair `{StartDate, EndDate}' or 32-bit integer unix timestamps, or
+%% the atom `all', that limits the fold to only the keys that have a
+%% last-modified date in the range. the `Query' is either
+%% `{sibling_coun, N}` or `{object_size, N}' where `N' is an
+%% integer. for `sibling_count' `N' means return all keys that have
+%% more than `N' siblings. NOTE: 1 sibling means a single value in
+%% this implementation, therefore if you want all keys that have more
+%% than a single value AT THE VNODE then `{sibling_count, 1}' is your
+%% query. NOTE NOTE: It is possible that all N vnodes have a single
+%% value, and that value is different on each vnode (temporarily
+%% only), this query would not detect that state. For `object_size' it
+%% means return all keys whose object size is greater than `N'. The
+%% result is a list of pairs `{Key, Count | Size}'
+-spec aae_object_stats(pid(),
+ riakc_obj:bucket(), key_range(),
+ modified_range()) ->
+ {ok, {stats, list({Key::atom(), Val::atom() | list()})}} |
+ {error, any()}.
+aae_object_stats(Pid, BucketType, KeyRange, ModifiedRange) ->
+ Timeout = default_timeout(get_coverage_timeout),
+ {KR, SK, EK} =
+ case KeyRange of
+ all ->
+ {false, undefined, undefined};
+ {SK0, EK0} ->
+ {true, SK0, EK0}
+ end,
+ {MR, MRLow, MRHigh} =
+ case ModifiedRange of
+ all ->
+ {false, undefined, undefined};
+ {MRL, MRH} ->
+ {true, MRL, MRH}
+ end,
+ {T, B} =
+ case BucketType of
+ B0 when is_binary(B0) ->
+ {undefined, B0};
+ {T0, B0} ->
+ {T0, B0}
+ end,
+ call_infinity(Pid,
+ {req,
+ #rpbaaefoldobjectstatsreq{type = T,
+ bucket = B,
+ key_range = KR,
+ start_key = SK,
+ end_key = EK,
+ modified_range = MR,
+ last_mod_start = MRLow,
+ last_mod_end = MRHigh},
+ Timeout}).
+
+
%% ====================================================================
%% gen_server callbacks
%% ====================================================================
@@ -1911,6 +2228,53 @@ process_response(#request{msg = #rpbcountergetreq{}},
process_response(#request{msg = #rpbcountergetreq{}},
#rpbcountergetresp{value=Value}, State) ->
{reply, {ok, Value}, State};
+%% Responses to AAE fold requests
+process_response(#request{msg = #rpbaaefoldmergerootnvalreq{}},
+ #rpbaaefoldtreeresp{size = _TreeSize,
+ level_one = Root},
+ State) ->
+ {reply, {ok, {root, Root}}, State};
+process_response(#request{msg = #rpbaaefoldmergebranchnvalreq{}},
+ #rpbaaefoldtreeresp{size = _TreeSize,
+ level_two = Branches},
+ State) ->
+ {reply, {ok, {branches, lists:map(fun unpack_branch/1, Branches)}}, State};
+process_response(#request{msg = #rpbaaefoldfetchclocksnvalreq{}},
+ #rpbaaefoldkeyclockresp{keys_clock = KeysClocks},
+ State) ->
+ {reply,
+ {ok, {keyclocks, lists:map(fun unpack_keyclock_fun/1, KeysClocks)}},
+ State};
+process_response(#request{msg = #rpbaaefoldmergetreesrangereq{tree_size = TS}},
+ #rpbaaefoldtreeresp{size = TS,
+ level_one = Root,
+ level_two = Branches},
+ State) ->
+ TreeToImport =
+ {struct,
+ [{<<"level1">>,
+ base64:encode_to_string(Root)},
+ {<<"level2">>,
+ {struct, lists:map(fun split_branch/1, Branches)}}]},
+ {reply, {ok, {tree, TreeToImport}}, State};
+process_response(#request{msg = #rpbaaefoldfetchclocksrangereq{}},
+ #rpbaaefoldkeyclockresp{keys_clock = KeysClocks},
+ State) ->
+ {reply,
+ {ok, {keyclocks, lists:map(fun unpack_keyclock_fun/1, KeysClocks)}},
+ State};
+process_response(#request{msg = #rpbaaefoldfindkeysreq{}},
+ #rpbaaefoldkeycountresp{keys_count = KeysCount},
+ State) ->
+ {reply,
+ {ok, {keys, lists:map(fun unpack_keycount_fun/1, KeysCount)}},
+ State};
+process_response(#request{msg = #rpbaaefoldobjectstatsreq{}},
+ #rpbaaefoldkeycountresp{keys_count = KeysCount},
+ State) ->
+ {reply,
+ {ok, {stats, lists:map(fun unpack_keycount_fun/1, KeysCount)}},
+ State};
process_response(#request{msg = #dtfetchreq{}}, #dtfetchresp{}=Resp,
State) ->
@@ -2055,6 +2419,35 @@ response_type(_ReturnTerms, _ReturnBody) ->
keys.
+unpack_keyclock_fun(RpbKeysClock) ->
+ case RpbKeysClock#rpbkeysclock.type of
+ undefined ->
+ {RpbKeysClock#rpbkeysclock.bucket,
+ RpbKeysClock#rpbkeysclock.key,
+ RpbKeysClock#rpbkeysclock.value};
+ T ->
+ {{T, RpbKeysClock#rpbkeysclock.bucket},
+ RpbKeysClock#rpbkeysclock.key,
+ RpbKeysClock#rpbkeysclock.value}
+ end.
+
+unpack_keycount_fun(RpbKeysCount) ->
+ case RpbKeysCount#rpbkeyscount.order of
+ undefined ->
+ {RpbKeysCount#rpbkeyscount.tag,
+ RpbKeysCount#rpbkeyscount.count};
+ Order ->
+ {RpbKeysCount#rpbkeyscount.tag,
+ Order,
+ RpbKeysCount#rpbkeyscount.count}
+ end.
+
+unpack_branch(BranchBin) ->
+ {I, CB} = split_branch(BranchBin),
+ {I, zlib:uncompress(base64:decode(CB))}.
+
+split_branch(<>) -> {I, CB}.
+
%% Helper for index responses
-spec process_index_response('keys'|'terms'|'objects', list(), list()) ->
index_stream_result().
From 67b5daa62d54940ce9b98318a5f5fa5fce23f467 Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Tue, 12 Nov 2019 15:10:26 +0000
Subject: [PATCH 15/42] Change stats output to match http client
---
src/riakc_pb_socket.erl | 56 ++++++++++++++++++++++++++++++++++++++---
1 file changed, 53 insertions(+), 3 deletions(-)
diff --git a/src/riakc_pb_socket.erl b/src/riakc_pb_socket.erl
index 0df414d8..c632b977 100644
--- a/src/riakc_pb_socket.erl
+++ b/src/riakc_pb_socket.erl
@@ -2272,9 +2272,8 @@ process_response(#request{msg = #rpbaaefoldfindkeysreq{}},
process_response(#request{msg = #rpbaaefoldobjectstatsreq{}},
#rpbaaefoldkeycountresp{keys_count = KeysCount},
State) ->
- {reply,
- {ok, {stats, lists:map(fun unpack_keycount_fun/1, KeysCount)}},
- State};
+ RawStats = lists:map(fun unpack_keycount_fun/1, KeysCount),
+ {reply, {ok, {stats, stats_output(RawStats)}}, State};
process_response(#request{msg = #dtfetchreq{}}, #dtfetchresp{}=Resp,
State) ->
@@ -2442,6 +2441,34 @@ unpack_keycount_fun(RpbKeysCount) ->
RpbKeysCount#rpbkeyscount.count}
end.
+%% For absolute equivalence with HTTP client output need to
+%% sort both this list of stats and nest lists for any repeated keys, and ehn
+%% sort nay nested lists
+stats_output(Stats) ->
+ Output =
+ lists:sort(lists:foldr(fun stats_fold_fun/2, [], lists:sort(Stats))),
+ lists:map(fun sort_nested/1, Output).
+
+
+sort_nested({K, L}) when is_list(L) ->
+ L0 =
+ lists:map(fun({O, C}) -> {integer_to_binary(O), C} end,
+ lists:reverse(lists:sort(L))),
+ {K, L0};
+sort_nested(AnyOther) ->
+ AnyOther.
+
+stats_fold_fun({K, O, C}, Acc) ->
+ case lists:keyfind(K, 1, Acc) of
+ false ->
+ [{K, [{O, C}]}|Acc];
+ {K, L} ->
+ lists:keyreplace(K, 1, Acc, {K, lists:sort([{O, C}|L])})
+ end;
+stats_fold_fun({K, C}, Acc) ->
+ [{K, C}|Acc].
+
+
unpack_branch(BranchBin) ->
{I, CB} = split_branch(BranchBin),
{I, zlib:uncompress(base64:decode(CB))}.
@@ -2949,4 +2976,27 @@ increase_reconnect_interval_test(State) ->
increase_reconnect_interval_test(NextState)
end.
+stats_output_test() ->
+ %% Raw details returned as in the mapped protocol buffer records
+ RawStats =
+ [{<<"total_count">>,10000},
+ {<<"total_size">>,1218213},
+ {<<"sizes">>,2,9994},{<<"sizes">>,3,6},
+ {<<"siblings">>,1,9900},{<<"siblings">>,2,10},{<<"siblings">>,3,10},
+ {<<"siblings">>,4,10},{<<"siblings">>,5,10},{<<"siblings">>,6,10},
+ {<<"siblings">>,7,10},{<<"siblings">>,8,10},{<<"siblings">>,9,10},
+ {<<"siblings">>,10,10},{<<"siblings">>,11,10}],
+ %% What the HTTP client would return for the equivalent
+ ExpectedStats =
+ lists:sort(
+ [{<<"siblings">>,
+ [{<<"11">>,10},{<<"10">>,10},{<<"9">>,10},{<<"8">>,10},
+ {<<"7">>,10},{<<"6">>,10},{<<"5">>,10},{<<"4">>,10},
+ {<<"3">>,10},{<<"2">>,10},{<<"1">>,9900}]},
+ {<<"sizes">>,[{<<"3">>,6},{<<"2">>,9994}]},
+ {<<"total_size">>,1218213},
+ {<<"total_count">>,10000}]),
+ OutStats = stats_output(RawStats),
+ ?assertMatch(ExpectedStats, OutStats).
+
-endif.
From 547c3069936058c2b5189a7bfb4b31ae32fc24f0 Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Tue, 12 Nov 2019 16:37:06 +0000
Subject: [PATCH 16/42] Update to latest pb definitions
More general keyvalueresp
---
src/riakc_pb_socket.erl | 24 +++++++++++++-----------
1 file changed, 13 insertions(+), 11 deletions(-)
diff --git a/src/riakc_pb_socket.erl b/src/riakc_pb_socket.erl
index c632b977..72c41617 100644
--- a/src/riakc_pb_socket.erl
+++ b/src/riakc_pb_socket.erl
@@ -2240,10 +2240,11 @@ process_response(#request{msg = #rpbaaefoldmergebranchnvalreq{}},
State) ->
{reply, {ok, {branches, lists:map(fun unpack_branch/1, Branches)}}, State};
process_response(#request{msg = #rpbaaefoldfetchclocksnvalreq{}},
- #rpbaaefoldkeyclockresp{keys_clock = KeysClocks},
+ #rpbaaefoldkeyvalueresp{type = <<"clock">>} = Rsp,
State) ->
+ KeysNClocks = Rsp#rpbaaefoldkeyvalueresp.keys_value,
{reply,
- {ok, {keyclocks, lists:map(fun unpack_keyclock_fun/1, KeysClocks)}},
+ {ok, {keyclocks, lists:map(fun unpack_keyclock_fun/1, KeysNClocks)}},
State};
process_response(#request{msg = #rpbaaefoldmergetreesrangereq{tree_size = TS}},
#rpbaaefoldtreeresp{size = TS,
@@ -2258,10 +2259,11 @@ process_response(#request{msg = #rpbaaefoldmergetreesrangereq{tree_size = TS}},
{struct, lists:map(fun split_branch/1, Branches)}}]},
{reply, {ok, {tree, TreeToImport}}, State};
process_response(#request{msg = #rpbaaefoldfetchclocksrangereq{}},
- #rpbaaefoldkeyclockresp{keys_clock = KeysClocks},
+ #rpbaaefoldkeyvalueresp{type = <<"clock">>} = Rsp,
State) ->
+ KeysNClocks = Rsp#rpbaaefoldkeyvalueresp.keys_value,
{reply,
- {ok, {keyclocks, lists:map(fun unpack_keyclock_fun/1, KeysClocks)}},
+ {ok, {keyclocks, lists:map(fun unpack_keyclock_fun/1, KeysNClocks)}},
State};
process_response(#request{msg = #rpbaaefoldfindkeysreq{}},
#rpbaaefoldkeycountresp{keys_count = KeysCount},
@@ -2419,15 +2421,15 @@ response_type(_ReturnTerms, _ReturnBody) ->
unpack_keyclock_fun(RpbKeysClock) ->
- case RpbKeysClock#rpbkeysclock.type of
+ case RpbKeysClock#rpbkeysvalue.type of
undefined ->
- {RpbKeysClock#rpbkeysclock.bucket,
- RpbKeysClock#rpbkeysclock.key,
- RpbKeysClock#rpbkeysclock.value};
+ {RpbKeysClock#rpbkeysvalue.bucket,
+ RpbKeysClock#rpbkeysvalue.key,
+ RpbKeysClock#rpbkeysvalue.value};
T ->
- {{T, RpbKeysClock#rpbkeysclock.bucket},
- RpbKeysClock#rpbkeysclock.key,
- RpbKeysClock#rpbkeysclock.value}
+ {{T, RpbKeysClock#rpbkeysvalue.bucket},
+ RpbKeysClock#rpbkeysvalue.key,
+ RpbKeysClock#rpbkeysvalue.value}
end.
unpack_keycount_fun(RpbKeysCount) ->
From 3a003425ebd7cefd27b9c944dcf31e8f93789c0f Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Tue, 12 Nov 2019 21:28:49 +0000
Subject: [PATCH 17/42] Switch to response_type
---
src/riakc_pb_socket.erl | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/riakc_pb_socket.erl b/src/riakc_pb_socket.erl
index 72c41617..86418675 100644
--- a/src/riakc_pb_socket.erl
+++ b/src/riakc_pb_socket.erl
@@ -2240,7 +2240,7 @@ process_response(#request{msg = #rpbaaefoldmergebranchnvalreq{}},
State) ->
{reply, {ok, {branches, lists:map(fun unpack_branch/1, Branches)}}, State};
process_response(#request{msg = #rpbaaefoldfetchclocksnvalreq{}},
- #rpbaaefoldkeyvalueresp{type = <<"clock">>} = Rsp,
+ #rpbaaefoldkeyvalueresp{response_type = <<"clock">>} = Rsp,
State) ->
KeysNClocks = Rsp#rpbaaefoldkeyvalueresp.keys_value,
{reply,
@@ -2259,7 +2259,7 @@ process_response(#request{msg = #rpbaaefoldmergetreesrangereq{tree_size = TS}},
{struct, lists:map(fun split_branch/1, Branches)}}]},
{reply, {ok, {tree, TreeToImport}}, State};
process_response(#request{msg = #rpbaaefoldfetchclocksrangereq{}},
- #rpbaaefoldkeyvalueresp{type = <<"clock">>} = Rsp,
+ #rpbaaefoldkeyvalueresp{response_type = <<"clock">>} = Rsp,
State) ->
KeysNClocks = Rsp#rpbaaefoldkeyvalueresp.keys_value,
{reply,
From b8d6ced570300b6ee0659105d0e147777a0af94c Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Wed, 13 Nov 2019 10:48:32 +0000
Subject: [PATCH 18/42] Branches no longer encoded
---
src/riakc_pb_socket.erl | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/src/riakc_pb_socket.erl b/src/riakc_pb_socket.erl
index 86418675..d52ff1e1 100644
--- a/src/riakc_pb_socket.erl
+++ b/src/riakc_pb_socket.erl
@@ -2256,7 +2256,10 @@ process_response(#request{msg = #rpbaaefoldmergetreesrangereq{tree_size = TS}},
[{<<"level1">>,
base64:encode_to_string(Root)},
{<<"level2">>,
- {struct, lists:map(fun split_branch/1, Branches)}}]},
+ {struct,
+ lists:map(fun base64:encode_to_string/1,
+ lists:map(fun split_branch/1,
+ Branches))}}]},
{reply, {ok, {tree, TreeToImport}}, State};
process_response(#request{msg = #rpbaaefoldfetchclocksrangereq{}},
#rpbaaefoldkeyvalueresp{response_type = <<"clock">>} = Rsp,
@@ -2473,7 +2476,7 @@ stats_fold_fun({K, C}, Acc) ->
unpack_branch(BranchBin) ->
{I, CB} = split_branch(BranchBin),
- {I, zlib:uncompress(base64:decode(CB))}.
+ {I, zlib:uncompress(CB)}.
split_branch(<>) -> {I, CB}.
From f8c682beddcd6d9dca6efe0c8f54ef6627182bd9 Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Wed, 13 Nov 2019 11:16:24 +0000
Subject: [PATCH 19/42] Correct format of keys/clocks
---
src/riakc_pb_socket.erl | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/riakc_pb_socket.erl b/src/riakc_pb_socket.erl
index d52ff1e1..f9340e30 100644
--- a/src/riakc_pb_socket.erl
+++ b/src/riakc_pb_socket.erl
@@ -2244,7 +2244,7 @@ process_response(#request{msg = #rpbaaefoldfetchclocksnvalreq{}},
State) ->
KeysNClocks = Rsp#rpbaaefoldkeyvalueresp.keys_value,
{reply,
- {ok, {keyclocks, lists:map(fun unpack_keyclock_fun/1, KeysNClocks)}},
+ {ok, {keysclocks, lists:map(fun unpack_keyclock_fun/1, KeysNClocks)}},
State};
process_response(#request{msg = #rpbaaefoldmergetreesrangereq{tree_size = TS}},
#rpbaaefoldtreeresp{size = TS,
@@ -2266,7 +2266,7 @@ process_response(#request{msg = #rpbaaefoldfetchclocksrangereq{}},
State) ->
KeysNClocks = Rsp#rpbaaefoldkeyvalueresp.keys_value,
{reply,
- {ok, {keyclocks, lists:map(fun unpack_keyclock_fun/1, KeysNClocks)}},
+ {ok, {keysclocks, lists:map(fun unpack_keyclock_fun/1, KeysNClocks)}},
State};
process_response(#request{msg = #rpbaaefoldfindkeysreq{}},
#rpbaaefoldkeycountresp{keys_count = KeysCount},
@@ -2426,12 +2426,12 @@ response_type(_ReturnTerms, _ReturnBody) ->
unpack_keyclock_fun(RpbKeysClock) ->
case RpbKeysClock#rpbkeysvalue.type of
undefined ->
- {RpbKeysClock#rpbkeysvalue.bucket,
- RpbKeysClock#rpbkeysvalue.key,
+ {{RpbKeysClock#rpbkeysvalue.bucket,
+ RpbKeysClock#rpbkeysvalue.key},
RpbKeysClock#rpbkeysvalue.value};
T ->
- {{T, RpbKeysClock#rpbkeysvalue.bucket},
- RpbKeysClock#rpbkeysvalue.key,
+ {{{T, RpbKeysClock#rpbkeysvalue.bucket},
+ RpbKeysClock#rpbkeysvalue.key},
RpbKeysClock#rpbkeysvalue.value}
end.
From c9c10d7394cdf9b7f6cba0b692ac6ea840499d07 Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Wed, 13 Nov 2019 18:49:45 +0000
Subject: [PATCH 20/42] Correct split_branch
---
src/riakc_pb_socket.erl | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/src/riakc_pb_socket.erl b/src/riakc_pb_socket.erl
index f9340e30..40a9424f 100644
--- a/src/riakc_pb_socket.erl
+++ b/src/riakc_pb_socket.erl
@@ -2257,9 +2257,7 @@ process_response(#request{msg = #rpbaaefoldmergetreesrangereq{tree_size = TS}},
base64:encode_to_string(Root)},
{<<"level2">>,
{struct,
- lists:map(fun base64:encode_to_string/1,
- lists:map(fun split_branch/1,
- Branches))}}]},
+ lists:map(fun encode_branch/1, Branches)}}]},
{reply, {ok, {tree, TreeToImport}}, State};
process_response(#request{msg = #rpbaaefoldfetchclocksrangereq{}},
#rpbaaefoldkeyvalueresp{response_type = <<"clock">>} = Rsp,
@@ -2474,6 +2472,10 @@ stats_fold_fun({K, C}, Acc) ->
[{K, C}|Acc].
+encode_branch(BranchBin) ->
+ {I, CB} = split_branch(BranchBin),
+ {integer_to_binary(I), base64:encode_to_string(CB)}.
+
unpack_branch(BranchBin) ->
{I, CB} = split_branch(BranchBin),
{I, zlib:uncompress(CB)}.
From bd88d268ccb0399d79622b6f327a3696dd3a1f16 Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Thu, 14 Nov 2019 15:36:43 +0000
Subject: [PATCH 21/42] HashMethod wrong way round
---
src/riakc_pb_socket.erl | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/riakc_pb_socket.erl b/src/riakc_pb_socket.erl
index 40a9424f..0b8db925 100644
--- a/src/riakc_pb_socket.erl
+++ b/src/riakc_pb_socket.erl
@@ -1425,9 +1425,9 @@ aae_range_tree(Pid, BucketType, KeyRange, TreeSize,
{HM, IV} =
case HashMethod of
pre_hash ->
- {false, undefined};
+ {true, undefined};
{rehash, IV0} ->
- {true, IV0}
+ {false, IV0}
end,
{T, B} =
case BucketType of
From 350ddb33a54de49fd437f18061cb8ef19fa6e78a Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Thu, 14 Nov 2019 17:25:48 +0000
Subject: [PATCH 22/42] Update .travis.yml
Simplify travis
---
.travis.yml | 18 +++---------------
1 file changed, 3 insertions(+), 15 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index 1e0f9ce1..e8f7cb8d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,18 +1,6 @@
-sudo: required
-dist: trusty
language: erlang
+script:
+ - make dialyzer
+ - make test
otp_release:
- - 19.1
- - 18.3
- - 17.5
- R16B03
- - R15B03
-env:
- - RIAK_DOWNLOAD_URL=http://s3.amazonaws.com/downloads.basho.com/riak/2.0/2.0.7/ubuntu/trusty/riak_2.0.7-1_amd64.deb
- - RIAK_DOWNLOAD_URL=http://s3.amazonaws.com/downloads.basho.com/riak/2.1/2.1.4/ubuntu/trusty/riak_2.1.4-1_amd64.deb
-before_script:
- - sudo ./tools/travis-ci/riak-install -d "$RIAK_DOWNLOAD_URL"
- - sudo ./tools/setup-riak
-notifications:
- slack:
- secure: JVsrhRuWRTQauP7OjSc1XO6+P3eiOZtkjYhU2R53Hn9dK1KmJRBR5MzO1nq6BUs+bViXiAyW0YOoDTWF0eUw5gdd6sqnvx0+mYJVfYDTfbjp46yqj03Nj+J5HZ1KWPM78NSZ8jpZvdwk35ZpHqhsh/zWOY2RYmIVQKLB9EthHLU=
From ee563c687f9257e19673a24b440b82aa4f938cda Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Mon, 18 Nov 2019 12:49:17 +0000
Subject: [PATCH 23/42] Reset riak_pb branch
---
rebar.config | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rebar.config b/rebar.config
index 52bdf605..ec4cff48 100644
--- a/rebar.config
+++ b/rebar.config
@@ -13,7 +13,7 @@
]}.
{deps, [
- {riak_pb, ".*", {git, "https://github.com/basho/riak_pb", {branch, "mas-i1676-aaefoldpb"}}}
+ {riak_pb, ".*", {git, "https://github.com/basho/riak_pb", {branch, "develop-2.9"}}}
]}.
{edoc_opts, [
From 9f6df856f71168aa60be4d556a72c315c19aeed8 Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Mon, 18 Nov 2019 21:36:11 +0000
Subject: [PATCH 24/42] Switch riak_pb to basho repo
---
rebar.config | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rebar.config b/rebar.config
index 7444132e..6044485d 100644
--- a/rebar.config
+++ b/rebar.config
@@ -13,7 +13,7 @@
]}.
{deps, [
- {riak_pb, ".*", {git, "https://github.com/martinsumner/riak_pb", {tag, "mas-i1691-ttaaefullsync"}}}
+ {riak_pb, ".*", {git, "https://github.com/basho/riak_pb", {tag, "mas-i1691-ttaaefullsync"}}}
]}.
{edoc_opts, [
From 8bb24003800273b7dceab5cdd8f53acf3e0cedc0 Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Tue, 26 Nov 2019 15:26:11 +0000
Subject: [PATCH 25/42] Add range_replkeys
---
src/riakc_pb_socket.erl | 56 ++++++++++++++++++++++++++++++++++++++++-
1 file changed, 55 insertions(+), 1 deletion(-)
diff --git a/src/riakc_pb_socket.erl b/src/riakc_pb_socket.erl
index d90c705b..e7809784 100644
--- a/src/riakc_pb_socket.erl
+++ b/src/riakc_pb_socket.erl
@@ -72,7 +72,7 @@
get_index/4, get_index/5, get_index/6, get_index/7, %% @deprecated
get_index_eq/4, get_index_range/5, get_index_eq/5, get_index_range/6,
aae_merge_root/2, aae_merge_branches/3, aae_fetch_clocks/3,
- aae_range_tree/7, aae_range_clocks/5, % aae_range_replkeys/5,
+ aae_range_tree/7, aae_range_clocks/5, aae_range_replkeys/5,
aae_find_keys/5, aae_object_stats/4,
cs_bucket_fold/3,
default_timeout/1,
@@ -1519,6 +1519,55 @@ aae_range_clocks(Pid, BucketType, KeyRange, SegmentFilter, ModifiedRange) ->
last_mod_end = MRHigh},
Timeout}).
+
+%% @doc aae_range_repllkeys
+%% Fold over a range of keys and queue up those keys to be replicated to the
+%% other site. Once the keys are replicated the objects will then be fetched,
+%% as long as a site is consuming from that replication queue.
+%% Will return the number of keys which have been queued for replication.
+-spec aae_range_replkeys(pid(), riakc_obj:bucket(),
+ key_range(), modified_range(),
+ atom()) ->
+ {ok, non_neg_integer()} |
+ {error, any()}.
+aae_range_replkeys(Pid, BucketType, KeyRange, ModifiedRange, QueueName) ->
+ Timeout = default_timeout(get_coverage_timeout),
+ {KR, SK, EK} =
+ case KeyRange of
+ all ->
+ {false, undefined, undefined};
+ {SK0, EK0} ->
+ {true, SK0, EK0}
+ end,
+ {MR, MRLow, MRHigh} =
+ case ModifiedRange of
+ all ->
+ {false, undefined, undefined};
+ {MRL, MRH} ->
+ {true, MRL, MRH}
+ end,
+ {T, B} =
+ case BucketType of
+ B0 when is_binary(B0) ->
+ {undefined, B0};
+ {T0, B0} ->
+ {T0, B0}
+ end,
+ QN = atom_to_binary(QueueName, utf8),
+ call_infinity(Pid,
+ {req,
+ #rpbaaefoldreplkeysreq{type = T,
+ bucket = B,
+ key_range = KR,
+ start_key = SK,
+ end_key = EK,
+ modified_range = MR,
+ last_mod_start = MRLow,
+ last_mod_end = MRHigh,
+ queuename = QN},
+ Timeout}).
+
+
%% @doc aae_find_keys folds over the tictacaae store to get
%% operational information. `Rhc' is the client. `Bucket' is the
%% bucket to fold over. `KeyRange' as before is a two tuple of
@@ -2307,6 +2356,11 @@ process_response(#request{msg = #rpbaaefoldfindkeysreq{}},
{reply,
{ok, {keys, lists:map(fun unpack_keycount_fun/1, KeysCount)}},
State};
+process_response(#request{msg = #rpbaaefoldreplkeysreq{}},
+ #rpbaaefoldkeycountresp{keys_count = KeysCount},
+ State) ->
+ [{<<"dispatched_count">>, Count}] = KeysCount,
+ {reply, {ok, Count}, State};
process_response(#request{msg = #rpbaaefoldobjectstatsreq{}},
#rpbaaefoldkeycountresp{keys_count = KeysCount},
State) ->
From 8d894ca1efe30265377ca2c65a49f7885b1d151e Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Tue, 26 Nov 2019 16:22:46 +0000
Subject: [PATCH 26/42] Parse response to repl_range
---
src/riakc_pb_socket.erl | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/riakc_pb_socket.erl b/src/riakc_pb_socket.erl
index e7809784..001c489b 100644
--- a/src/riakc_pb_socket.erl
+++ b/src/riakc_pb_socket.erl
@@ -2357,10 +2357,10 @@ process_response(#request{msg = #rpbaaefoldfindkeysreq{}},
{ok, {keys, lists:map(fun unpack_keycount_fun/1, KeysCount)}},
State};
process_response(#request{msg = #rpbaaefoldreplkeysreq{}},
- #rpbaaefoldkeycountresp{keys_count = KeysCount},
+ #rpbaaefoldkeycountresp{keys_count = [DispatchCount]},
State) ->
- [{<<"dispatched_count">>, Count}] = KeysCount,
- {reply, {ok, Count}, State};
+ true = <<"dispatched_count">> == DispatchCount#rpbkeyscount.tag,
+ {reply, {ok, DispatchCount#rpbkeyscount.count}, State};
process_response(#request{msg = #rpbaaefoldobjectstatsreq{}},
#rpbaaefoldkeycountresp{keys_count = KeysCount},
State) ->
From 41c751276aaf50ab5a7a732b209dfcca56bb666e Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Mon, 9 Dec 2019 10:43:34 +0000
Subject: [PATCH 27/42] Update for additional aae_fold queries
---
src/riakc_pb_socket.erl | 246 +++++++++++++++++++++++++++++++++++++++-
1 file changed, 245 insertions(+), 1 deletion(-)
diff --git a/src/riakc_pb_socket.erl b/src/riakc_pb_socket.erl
index 001c489b..85bfccf0 100644
--- a/src/riakc_pb_socket.erl
+++ b/src/riakc_pb_socket.erl
@@ -73,7 +73,8 @@
get_index_eq/4, get_index_range/5, get_index_eq/5, get_index_range/6,
aae_merge_root/2, aae_merge_branches/3, aae_fetch_clocks/3,
aae_range_tree/7, aae_range_clocks/5, aae_range_replkeys/5,
- aae_find_keys/5, aae_object_stats/4,
+ aae_find_keys/5, aae_find_tombs/5, aae_reap_tombs/6, aae_erase_keys/6,
+ aae_object_stats/4,
cs_bucket_fold/3,
default_timeout/1,
tunnel/4,
@@ -138,6 +139,7 @@
-type modified_range() :: {ts(), ts()} | all.
-type ts() :: pos_integer().
-type hash_method() :: pre_hash | {rehash, non_neg_integer()}.
+-type change_method() :: {job, pos_integer()}|local|count.
-type tree_size() :: xxsmall| xsmall| small| medium| large| xlarge.
@@ -1632,6 +1634,226 @@ aae_find_keys(Pid, BucketType, KeyRange, ModifiedRange, Query) ->
Timeout}).
+%% @doc find_tombs will find tombstone keys in a given bucket and key_range
+%% returning the key and delete_hash, where the delete_hash is an integer that
+%% can be used in a reap request. The SegmentFilter is intended to be used as a
+%% mechanism for assiting in scheduling work - a way for splitting out the
+%% process of finding/reaping tombstones into batches without having
+%% inconsistencies within the AAE trees.
+-spec aae_find_tombs(pid(),
+ riakc_obj:bucket(), key_range(),
+ segment_filter(),
+ modified_range()) ->
+ {ok, {keys, list({riakc_obj:key(), pos_integer()})}} |
+ {error, any()}.
+aae_find_tombs(Pid, BucketType, KeyRange, SegmentFilter, ModifiedRange) ->
+ Timeout = default_timeout(get_coverage_timeout),
+ {KR, SK, EK} =
+ case KeyRange of
+ all ->
+ {false, undefined, undefined};
+ {SK0, EK0} ->
+ {true, SK0, EK0}
+ end,
+ {MR, MRLow, MRHigh} =
+ case ModifiedRange of
+ all ->
+ {false, undefined, undefined};
+ {MRL, MRH} ->
+ {true, MRL, MRH}
+ end,
+ {T, B} =
+ case BucketType of
+ B0 when is_binary(B0) ->
+ {undefined, B0};
+ {T0, B0} ->
+ {T0, B0}
+ end,
+ {SF, SFL, FTS} =
+ case SegmentFilter of
+ all ->
+ {false, [], undefined};
+ {SFL0, FTS0} ->
+ {true, SFL0, FTS0}
+ end,
+ call_infinity(Pid,
+ {req,
+ #rpbaaefoldfindtombsreq{type = T,
+ bucket = B,
+ key_range = KR,
+ start_key = SK,
+ end_key = EK,
+ segment_filter = SF,
+ id_filter = SFL,
+ filter_tree_size = FTS,
+ modified_range = MR,
+ last_mod_start = MRLow,
+ last_mod_end = MRHigh
+ },
+ Timeout}).
+
+%% @doc reap_tombs will find tombstone keys in a given bucket and key_range.
+%% The SegmentFilter is intended to be used as a mechanism for assiting in
+%% scheduling work - a way for splitting out the process of finding/reaping
+%% tombstones into batches without having inconsistencies within the AAE trees.
+%% reap_tombs can be passed a change_method of count if a count of matching
+%% tombstones is all that is required - this is an alternative to running
+%% find_tombs and taking the length of the list. To actually reap either
+%% `local` of `{ob, ID}` should be passed as the change_method. Using `local`
+%% will reap each tombstone from the node local to which it is discovered,
+%% whch will have the impact of distributing the reap load across the cluster
+%% and increasing parallelisation of reap activity. Otherwise a job id can be
+%% passed an a specific reaper will be started on the co-ordinating node of the
+%% query only. The Id will be a positive integer used to identify this reap
+%% task in logs.
+-spec aae_reap_tombs(pid(),
+ riakc_obj:bucket(), key_range(),
+ segment_filter(),
+ modified_range(),
+ change_method()) ->
+ {ok, {keys, list({riakc_obj:key(), pos_integer()})}} |
+ {error, any()}.
+aae_reap_tombs(Pid,
+ BucketType, KeyRange,
+ SegmentFilter, ModifiedRange,
+ ChangeMethod) ->
+ Timeout = default_timeout(get_coverage_timeout),
+ {KR, SK, EK} =
+ case KeyRange of
+ all ->
+ {false, undefined, undefined};
+ {SK0, EK0} ->
+ {true, SK0, EK0}
+ end,
+ {MR, MRLow, MRHigh} =
+ case ModifiedRange of
+ all ->
+ {false, undefined, undefined};
+ {MRL, MRH} ->
+ {true, MRL, MRH}
+ end,
+ {T, B} =
+ case BucketType of
+ B0 when is_binary(B0) ->
+ {undefined, B0};
+ {T0, B0} ->
+ {T0, B0}
+ end,
+ {SF, SFL, FTS} =
+ case SegmentFilter of
+ all ->
+ {false, [], undefined};
+ {SFL0, FTS0} ->
+ {true, SFL0, FTS0}
+ end,
+ {CM0, JobID} =
+ case ChangeMethod of
+ {job, ID} when is_integer(ID) ->
+ {job, ID};
+ count ->
+ {count, undefined};
+ local ->
+ {local, undefined}
+ end,
+ call_infinity(Pid,
+ {req,
+ #rpbaaefoldreaptombsreq{type = T,
+ bucket = B,
+ key_range = KR,
+ start_key = SK,
+ end_key = EK,
+ segment_filter = SF,
+ id_filter = SFL,
+ filter_tree_size = FTS,
+ modified_range = MR,
+ last_mod_start = MRLow,
+ last_mod_end = MRHigh,
+ change_method = CM0,
+ job_id = JobID
+ },
+ Timeout}).
+
+%% @doc erase_keys will find keys in a given bucket and key_range.
+%% The SegmentFilter is intended to be used as a mechanism for assiting in
+%% scheduling work - a way for splitting out the process of finding/reaping
+%% tombstones into batches without having inconsistencies within the AAE trees.
+%% erase_keys can be passed a change_method of count if a count of matching
+%% keys is all that is required - this is an alternative to running
+%% find_keys and taking the length of the list. To actually erase the object
+%% either `local` of `{ob, ID}` should be passed as the change_method. Using
+%% `local` will delete each object from the node local to which it is
+%% discovered, which will have the impact of distributing the delete load
+%% across the cluster and increasing parallelisation of delete activity.
+%% Otherwise a job id can be passed an a specific eraser process will be
+%% started on the co-ordinating node of the query only. The Id will be a
+%% positive integer used to identify this erase task in logs.
+-spec aae_erase_keys(pid(),
+ riakc_obj:bucket(), key_range(),
+ segment_filter(),
+ modified_range(),
+ change_method()) ->
+ {ok, {keys, list({riakc_obj:key(), pos_integer()})}} |
+ {error, any()}.
+aae_erase_keys(Pid,
+ BucketType, KeyRange,
+ SegmentFilter, ModifiedRange,
+ ChangeMethod) ->
+ Timeout = default_timeout(get_coverage_timeout),
+ {KR, SK, EK} =
+ case KeyRange of
+ all ->
+ {false, undefined, undefined};
+ {SK0, EK0} ->
+ {true, SK0, EK0}
+ end,
+ {MR, MRLow, MRHigh} =
+ case ModifiedRange of
+ all ->
+ {false, undefined, undefined};
+ {MRL, MRH} ->
+ {true, MRL, MRH}
+ end,
+ {T, B} =
+ case BucketType of
+ B0 when is_binary(B0) ->
+ {undefined, B0};
+ {T0, B0} ->
+ {T0, B0}
+ end,
+ {SF, SFL, FTS} =
+ case SegmentFilter of
+ all ->
+ {false, [], undefined};
+ {SFL0, FTS0} ->
+ {true, SFL0, FTS0}
+ end,
+ {CM0, JobID} =
+ case ChangeMethod of
+ {job, ID} when is_integer(ID) ->
+ {job, ID};
+ count ->
+ {count, undefined};
+ local ->
+ {local, undefined}
+ end,
+ call_infinity(Pid,
+ {req,
+ #rpbaaefolderasekeysreq{type = T,
+ bucket = B,
+ key_range = KR,
+ start_key = SK,
+ end_key = EK,
+ segment_filter = SF,
+ id_filter = SFL,
+ filter_tree_size = FTS,
+ modified_range = MR,
+ last_mod_start = MRLow,
+ last_mod_end = MRHigh,
+ change_method = CM0,
+ job_id = JobID
+ },
+ Timeout}).
+
%% @doc aae_object_stats folds over the tictacaae store to get
%% operational information. `Rhc' is the client. `Bucket' is the
%% bucket to fold over. `KeyRange' as before is a two tuple of
@@ -2356,6 +2578,28 @@ process_response(#request{msg = #rpbaaefoldfindkeysreq{}},
{reply,
{ok, {keys, lists:map(fun unpack_keycount_fun/1, KeysCount)}},
State};
+process_response(#request{msg = #rpbaaefoldfindtombsreq{}},
+ #rpbaaefoldkeycountresp{keys_count = KeysDH},
+ State) ->
+ %% In this case the integer value in each entry is not a count but a
+ %% delete hash
+ {reply,
+ {ok, {keys, lists:map(fun unpack_keycount_fun/1, KeysDH)}},
+ State};
+process_response(#request{msg = #rpbaaefoldreaptombsreq{}},
+ #rpbaaefoldkeycountresp{response_type = ReapTag,
+ keys_count = [ReapCount]},
+ State) ->
+ true = <<"reap_count">> == ReapTag,
+ true = <<"dispatched_count">> == ReapCount#rpbkeyscount.tag,
+ {reply, {ok, ReapCount#rpbkeyscount.count}, State};
+process_response(#request{msg = #rpbaaefolderasekeysreq{}},
+ #rpbaaefoldkeycountresp{response_type = EraseTag,
+ keys_count = [ReapCount]},
+ State) ->
+ true = <<"erase_keys">> == EraseTag,
+ true = <<"dispatched_count">> == ReapCount#rpbkeyscount.tag,
+ {reply, {ok, ReapCount#rpbkeyscount.count}, State};
process_response(#request{msg = #rpbaaefoldreplkeysreq{}},
#rpbaaefoldkeycountresp{keys_count = [DispatchCount]},
State) ->
From d60f5fa689ea9f282a00fc865d63df56526e8af4 Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Mon, 9 Dec 2019 12:18:17 +0000
Subject: [PATCH 28/42] Fix tag
---
src/riakc_pb_socket.erl | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/riakc_pb_socket.erl b/src/riakc_pb_socket.erl
index 85bfccf0..5b88bb2e 100644
--- a/src/riakc_pb_socket.erl
+++ b/src/riakc_pb_socket.erl
@@ -2590,7 +2590,7 @@ process_response(#request{msg = #rpbaaefoldreaptombsreq{}},
#rpbaaefoldkeycountresp{response_type = ReapTag,
keys_count = [ReapCount]},
State) ->
- true = <<"reap_count">> == ReapTag,
+ true = <<"reap_tombs">> == ReapTag,
true = <<"dispatched_count">> == ReapCount#rpbkeyscount.tag,
{reply, {ok, ReapCount#rpbkeyscount.count}, State};
process_response(#request{msg = #rpbaaefolderasekeysreq{}},
From 33693660ab8d8086e7579847f91c27758984f834 Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Tue, 10 Dec 2019 15:52:24 +0000
Subject: [PATCH 29/42] Add AAE Fold list_buckets
---
.gitignore | 1 +
src/riakc_pb_socket.erl | 31 +++++++++++++++++++++++++++++++
2 files changed, 32 insertions(+)
diff --git a/.gitignore b/.gitignore
index 8de2732a..55bdc170 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,4 @@ test-unchanged.escript
.idea
*.iml
*.dump
+.DS_Store
diff --git a/src/riakc_pb_socket.erl b/src/riakc_pb_socket.erl
index 5b88bb2e..5e61dca1 100644
--- a/src/riakc_pb_socket.erl
+++ b/src/riakc_pb_socket.erl
@@ -74,6 +74,7 @@
aae_merge_root/2, aae_merge_branches/3, aae_fetch_clocks/3,
aae_range_tree/7, aae_range_clocks/5, aae_range_replkeys/5,
aae_find_keys/5, aae_find_tombs/5, aae_reap_tombs/6, aae_erase_keys/6,
+ aae_list_buckets/1, aae_list_buckets/2,
aae_object_stats/4,
cs_bucket_fold/3,
default_timeout/1,
@@ -1913,6 +1914,24 @@ aae_object_stats(Pid, BucketType, KeyRange, ModifiedRange) ->
last_mod_end = MRHigh},
Timeout}).
+%% @doc
+%% List all the buckets with references in the AAE store. For reasonable
+%% (e.g. < o(1000)) this should be quick and efficient unless using the
+%% leveled_so parallel store. A minimum n_val can be passed if known. If
+%% there are buckets (with keys) below the minimum n_val they may not be
+%% detecting in the query. Will default to 1.
+-spec aae_list_buckets(pid()) -> list(riakc_obj:bucket()).
+aae_list_buckets(Pid) ->
+ Timeout = default_timeout(get_coverage_timeout),
+ call_infinity(Pid, {req, #rpbaaefoldlistbucketsreq{}, Timeout}).
+
+-spec aae_list_buckets(pid(), pos_integer()) -> list(riakc_obj:bucket()).
+aae_list_buckets(Pid, MinNVal) when is_integer(MinNVal), MinNVal > 0 ->
+ Timeout = default_timeout(get_coverage_timeout),
+ call_infinity(Pid,
+ {req,
+ #rpbaaefoldlistbucketsreq{n_val = MinNVal},
+ Timeout}).
%% ====================================================================
%% gen_server callbacks
@@ -2610,6 +2629,10 @@ process_response(#request{msg = #rpbaaefoldobjectstatsreq{}},
State) ->
RawStats = lists:map(fun unpack_keycount_fun/1, KeysCount),
{reply, {ok, {stats, stats_output(RawStats)}}, State};
+process_response(#request{msg = #rpbaaefoldlistbucketsreq{}},
+ #rpbaaefoldlistbucketsresp{bucket_list = BucketList},
+ State) ->
+ {repl, {ok, lists:map(fun unpack_bucket/1, BucketList)}, State};
process_response(#request{msg = #dtfetchreq{}}, #dtfetchresp{}=Resp,
State) ->
@@ -2754,6 +2777,14 @@ response_type(_ReturnTerms, _ReturnBody) ->
keys.
+unpack_bucket(RpbAaeFoldBucket) ->
+ case RpbAaeFoldBucket#rpbaaefoldbucket.type of
+ undefined ->
+ RpbAaeFoldBucket#rpbaaefoldbucket.bucket;
+ T ->
+ {T, RpbAaeFoldBucket#rpbaaefoldbucket.bucket}
+ end.
+
unpack_keyclock_fun(RpbKeysClock) ->
case RpbKeysClock#rpbkeysvalue.type of
undefined ->
From bab1b038ffd5c58325db6bb052033eb1201b736b Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Wed, 11 Dec 2019 13:23:42 +0000
Subject: [PATCH 30/42] Correct function return
---
src/riakc_pb_socket.erl | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/riakc_pb_socket.erl b/src/riakc_pb_socket.erl
index 5e61dca1..80a54d7c 100644
--- a/src/riakc_pb_socket.erl
+++ b/src/riakc_pb_socket.erl
@@ -2632,7 +2632,7 @@ process_response(#request{msg = #rpbaaefoldobjectstatsreq{}},
process_response(#request{msg = #rpbaaefoldlistbucketsreq{}},
#rpbaaefoldlistbucketsresp{bucket_list = BucketList},
State) ->
- {repl, {ok, lists:map(fun unpack_bucket/1, BucketList)}, State};
+ {reply, {ok, lists:map(fun unpack_bucket/1, BucketList)}, State};
process_response(#request{msg = #dtfetchreq{}}, #dtfetchresp{}=Resp,
State) ->
From 3a31dc5dcc68d88c88012a94a46b6508b122f927 Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Tue, 4 Feb 2020 12:05:21 +0000
Subject: [PATCH 31/42] Update spec with timeout return possibility
---
src/riakc_pb_socket.erl | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/riakc_pb_socket.erl b/src/riakc_pb_socket.erl
index 80a54d7c..fd278964 100644
--- a/src/riakc_pb_socket.erl
+++ b/src/riakc_pb_socket.erl
@@ -265,12 +265,12 @@ is_connected(Pid, Timeout) ->
%% @doc Ping the server
%% @equiv ping(Pid, default_timeout(ping_timeout))
--spec ping(pid()) -> pong.
+-spec ping(pid()) -> pong|{error, timeout}.
ping(Pid) ->
call_infinity(Pid, {req, rpbpingreq, default_timeout(ping_timeout)}).
%% @doc Ping the server specifying timeout
--spec ping(pid(), timeout()) -> pong.
+-spec ping(pid(), timeout()) -> pong|{error, timeout}.
ping(Pid, Timeout) ->
call_infinity(Pid, {req, rpbpingreq, Timeout}).
From ce4748bdec3050fb18cb5254bc467fe43bcfaebd Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Mon, 10 Feb 2020 15:51:42 +0000
Subject: [PATCH 32/42] Fix return spec
---
src/riakc_pb_socket.erl | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/src/riakc_pb_socket.erl b/src/riakc_pb_socket.erl
index fd278964..dcf9aec6 100644
--- a/src/riakc_pb_socket.erl
+++ b/src/riakc_pb_socket.erl
@@ -1712,8 +1712,7 @@ aae_find_tombs(Pid, BucketType, KeyRange, SegmentFilter, ModifiedRange) ->
segment_filter(),
modified_range(),
change_method()) ->
- {ok, {keys, list({riakc_obj:key(), pos_integer()})}} |
- {error, any()}.
+ {ok, non_neg_integer()} | {error, any()}.
aae_reap_tombs(Pid,
BucketType, KeyRange,
SegmentFilter, ModifiedRange,
@@ -1793,8 +1792,7 @@ aae_reap_tombs(Pid,
segment_filter(),
modified_range(),
change_method()) ->
- {ok, {keys, list({riakc_obj:key(), pos_integer()})}} |
- {error, any()}.
+ {ok, non_neg_integer()} | {error, any()}.
aae_erase_keys(Pid,
BucketType, KeyRange,
SegmentFilter, ModifiedRange,
From e0a195c5969308ef1deb327cc552569631f72dcd Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Tue, 11 Feb 2020 09:38:04 +0000
Subject: [PATCH 33/42] Revert to main updated branch
---
rebar.config | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rebar.config b/rebar.config
index 6044485d..f72fc900 100644
--- a/rebar.config
+++ b/rebar.config
@@ -13,7 +13,7 @@
]}.
{deps, [
- {riak_pb, ".*", {git, "https://github.com/basho/riak_pb", {tag, "mas-i1691-ttaaefullsync"}}}
+ {riak_pb, ".*", {git, "https://github.com/basho/riak_pb", {tag, "develop-2.9"}}}
]}.
{edoc_opts, [
From 5c5291944591f5027693813df419144f3cd7d52f Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Tue, 11 Feb 2020 09:40:26 +0000
Subject: [PATCH 34/42] Update rebar.config
---
rebar.config | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rebar.config b/rebar.config
index f72fc900..ec4cff48 100644
--- a/rebar.config
+++ b/rebar.config
@@ -13,7 +13,7 @@
]}.
{deps, [
- {riak_pb, ".*", {git, "https://github.com/basho/riak_pb", {tag, "develop-2.9"}}}
+ {riak_pb, ".*", {git, "https://github.com/basho/riak_pb", {branch, "develop-2.9"}}}
]}.
{edoc_opts, [
From 915caebaf555b842452b0a180d792aff25228ba5 Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Wed, 12 Feb 2020 10:27:21 +0000
Subject: [PATCH 35/42] Update rebar to tagged version
---
rebar.config | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rebar.config b/rebar.config
index ec4cff48..3977287f 100644
--- a/rebar.config
+++ b/rebar.config
@@ -13,7 +13,7 @@
]}.
{deps, [
- {riak_pb, ".*", {git, "https://github.com/basho/riak_pb", {branch, "develop-2.9"}}}
+ {riak_pb, ".*", {git, "https://github.com/basho/riak_pb", {tag, "riak_kv-2.9.1"}}}
]}.
{edoc_opts, [
From 12983fb1c6e39642aa5ab9221d9bfddaf7b37ed8 Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Wed, 19 Feb 2020 12:48:51 +0000
Subject: [PATCH 36/42] Add node_confirms on GET
---
rebar.config | 2 +-
src/riakc_pb_socket.erl | 6 ++++++
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/rebar.config b/rebar.config
index 3977287f..f9e03aa5 100644
--- a/rebar.config
+++ b/rebar.config
@@ -13,7 +13,7 @@
]}.
{deps, [
- {riak_pb, ".*", {git, "https://github.com/basho/riak_pb", {tag, "riak_kv-2.9.1"}}}
+ {riak_pb, ".*", {git, "https://github.com/basho/riak_pb", {branch, "mas-i1750-nodeconfirms"}}}
]}.
{edoc_opts, [
diff --git a/src/riakc_pb_socket.erl b/src/riakc_pb_socket.erl
index dcf9aec6..bd9d9c54 100644
--- a/src/riakc_pb_socket.erl
+++ b/src/riakc_pb_socket.erl
@@ -2172,6 +2172,9 @@ get_options([{n_val, N} | Rest], Req)
get_options([{sloppy_quorum, Bool} | Rest], Req)
when Bool == true; Bool == false ->
get_options(Rest, Req#rpbgetreq{sloppy_quorum = Bool});
+get_options([{node_confirms, NodeConfirms} | Rest], Req) ->
+ NCOpt = riak_pb_kv_codec:encode_quorum(NodeConfirms),
+ get_options(Rest, Req#rpbgetreq{node_confirms = NCOpt});
get_options([{_, _} | _Rest], _Req) ->
erlang:error(badarg).
@@ -2285,6 +2288,9 @@ counter_val_options([{r, R} | Rest], Req) ->
counter_val_options(Rest, Req#rpbcountergetreq{r=riak_pb_kv_codec:encode_quorum(R)});
counter_val_options([{pr, PR} | Rest], Req) ->
counter_val_options(Rest, Req#rpbcountergetreq{pr=riak_pb_kv_codec:encode_quorum(PR)});
+counter_val_options([{node_confirms, NodeConfirms} | Rest], Req) ->
+ NCOpt = riak_pb_kv_codec:encode_quorum(NodeConfirms),
+ counter_val_options(Rest, Req#rpbcountergetreq{node_confirms = NCOpt});
counter_val_options([_ | _Rest], _Req) ->
erlang:error(badarg).
From f7384059add42fb2ed05c434dabcfc2a27c11f6d Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Tue, 25 Feb 2020 08:56:12 +0000
Subject: [PATCH 37/42] Re-point to develop-2.9
---
rebar.config | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rebar.config b/rebar.config
index f9e03aa5..ec4cff48 100644
--- a/rebar.config
+++ b/rebar.config
@@ -13,7 +13,7 @@
]}.
{deps, [
- {riak_pb, ".*", {git, "https://github.com/basho/riak_pb", {branch, "mas-i1750-nodeconfirms"}}}
+ {riak_pb, ".*", {git, "https://github.com/basho/riak_pb", {branch, "develop-2.9"}}}
]}.
{edoc_opts, [
From 37676e86849893f5ceb993c2c74f50a2feb32371 Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Tue, 7 Apr 2020 12:08:55 +0100
Subject: [PATCH 38/42] Update rebar.config
---
rebar.config | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/rebar.config b/rebar.config
index ec4cff48..220fc315 100644
--- a/rebar.config
+++ b/rebar.config
@@ -13,7 +13,7 @@
]}.
{deps, [
- {riak_pb, ".*", {git, "https://github.com/basho/riak_pb", {branch, "develop-2.9"}}}
+ {riak_pb, ".*", {git, "https://github.com/basho/riak_pb", {tag, "riak_kv-2.9.2"}}}
]}.
{edoc_opts, [
From a01b951e1ee2619f48cd8c095e1a50c76117bd4d Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Wed, 8 Apr 2020 15:40:46 +0100
Subject: [PATCH 39/42] Initial change to 3.0 for compatability
---
.travis.yml | 21 +++++++--------------
rebar.config | 2 +-
2 files changed, 8 insertions(+), 15 deletions(-)
diff --git a/.travis.yml b/.travis.yml
index 1e0f9ce1..f1fbff05 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,18 +1,11 @@
-sudo: required
-dist: trusty
language: erlang
otp_release:
- - 19.1
- - 18.3
- - 17.5
- - R16B03
- - R15B03
-env:
- - RIAK_DOWNLOAD_URL=http://s3.amazonaws.com/downloads.basho.com/riak/2.0/2.0.7/ubuntu/trusty/riak_2.0.7-1_amd64.deb
- - RIAK_DOWNLOAD_URL=http://s3.amazonaws.com/downloads.basho.com/riak/2.1/2.1.4/ubuntu/trusty/riak_2.1.4-1_amd64.deb
-before_script:
- - sudo ./tools/travis-ci/riak-install -d "$RIAK_DOWNLOAD_URL"
- - sudo ./tools/setup-riak
+ - 20.3.8
+ - 21.3
+ - 22.1
+script:
+ - chmod u+x rebar3
+ - ./rebar3 do upgrade, compile, xref, dialyzer, eunit
notifications:
slack:
- secure: JVsrhRuWRTQauP7OjSc1XO6+P3eiOZtkjYhU2R53Hn9dK1KmJRBR5MzO1nq6BUs+bViXiAyW0YOoDTWF0eUw5gdd6sqnvx0+mYJVfYDTfbjp46yqj03Nj+J5HZ1KWPM78NSZ8jpZvdwk35ZpHqhsh/zWOY2RYmIVQKLB9EthHLU=
+ secure: SE0EMU9HenZlLBuNg7l6WLMxJPkfyAEGgodvAqMEuQmICtrh1nV019D/A8ykejYYPPsJafWVOfypOSDrSHCndzXvEZvU8l45nJ6XLdUdrDYEmvcSqN3EqmVSsuf9H3g99bvKygXaY27MkTS5ixLil7PzybG+YpwMnQGcQxYo5Eg=
diff --git a/rebar.config b/rebar.config
index a6184160..31aa0b2b 100644
--- a/rebar.config
+++ b/rebar.config
@@ -16,7 +16,7 @@
]}.
{deps, [
- {riak_pb, {git, "https://github.com/basho/riak_pb", {branch, "develop-3.0"}}}
+ {riak_pb, {git, "https://github.com/basho/riak_pb", {branch, "develop-3.0-292"}}}
]}.
{edoc_opts, [
From 6c572bbde98778e82a32a7011a91c3aee7ea6636 Mon Sep 17 00:00:00 2001
From: Martin Sumner
Date: Wed, 8 Apr 2020 16:05:21 +0100
Subject: [PATCH 40/42] Scrap some legacy
travis keeps running ./rebar get-deps
---
rebar | Bin 204449 -> 0 bytes
rebar.config.script | 17 -----------------
2 files changed, 17 deletions(-)
delete mode 100755 rebar
delete mode 100644 rebar.config.script
diff --git a/rebar b/rebar
deleted file mode 100755
index 13bcbf9a5c0966ca5e5fcd9b8ed74be4629c825a..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 204449
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