seomoz-riak-client 1.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. data/Gemfile +27 -0
  2. data/Guardfile +14 -0
  3. data/Rakefile +76 -0
  4. data/erl_src/riak_kv_test_backend.beam +0 -0
  5. data/erl_src/riak_kv_test_backend.erl +174 -0
  6. data/erl_src/riak_search_test_backend.beam +0 -0
  7. data/erl_src/riak_search_test_backend.erl +175 -0
  8. data/lib/active_support/cache/riak_store.rb +2 -0
  9. data/lib/riak.rb +21 -0
  10. data/lib/riak/bucket.rb +215 -0
  11. data/lib/riak/cache_store.rb +84 -0
  12. data/lib/riak/client.rb +415 -0
  13. data/lib/riak/client/beefcake/messages.rb +147 -0
  14. data/lib/riak/client/beefcake/object_methods.rb +92 -0
  15. data/lib/riak/client/beefcake_protobuffs_backend.rb +176 -0
  16. data/lib/riak/client/excon_backend.rb +65 -0
  17. data/lib/riak/client/http_backend.rb +203 -0
  18. data/lib/riak/client/http_backend/configuration.rb +46 -0
  19. data/lib/riak/client/http_backend/key_streamer.rb +43 -0
  20. data/lib/riak/client/http_backend/object_methods.rb +94 -0
  21. data/lib/riak/client/http_backend/request_headers.rb +34 -0
  22. data/lib/riak/client/http_backend/transport_methods.rb +218 -0
  23. data/lib/riak/client/net_http_backend.rb +79 -0
  24. data/lib/riak/client/protobuffs_backend.rb +97 -0
  25. data/lib/riak/client/pump.rb +30 -0
  26. data/lib/riak/client/search.rb +94 -0
  27. data/lib/riak/core_ext.rb +6 -0
  28. data/lib/riak/core_ext/blank.rb +53 -0
  29. data/lib/riak/core_ext/extract_options.rb +7 -0
  30. data/lib/riak/core_ext/json.rb +15 -0
  31. data/lib/riak/core_ext/slice.rb +18 -0
  32. data/lib/riak/core_ext/stringify_keys.rb +10 -0
  33. data/lib/riak/core_ext/symbolize_keys.rb +10 -0
  34. data/lib/riak/core_ext/to_param.rb +31 -0
  35. data/lib/riak/encoding.rb +6 -0
  36. data/lib/riak/failed_request.rb +81 -0
  37. data/lib/riak/i18n.rb +3 -0
  38. data/lib/riak/json.rb +28 -0
  39. data/lib/riak/link.rb +85 -0
  40. data/lib/riak/locale/en.yml +48 -0
  41. data/lib/riak/map_reduce.rb +206 -0
  42. data/lib/riak/map_reduce/filter_builder.rb +94 -0
  43. data/lib/riak/map_reduce/phase.rb +98 -0
  44. data/lib/riak/map_reduce_error.rb +7 -0
  45. data/lib/riak/robject.rb +290 -0
  46. data/lib/riak/search.rb +3 -0
  47. data/lib/riak/serializers.rb +74 -0
  48. data/lib/riak/stamp.rb +77 -0
  49. data/lib/riak/test_server.rb +252 -0
  50. data/lib/riak/util/escape.rb +45 -0
  51. data/lib/riak/util/fiber1.8.rb +48 -0
  52. data/lib/riak/util/headers.rb +53 -0
  53. data/lib/riak/util/multipart.rb +52 -0
  54. data/lib/riak/util/multipart/stream_parser.rb +62 -0
  55. data/lib/riak/util/tcp_socket_extensions.rb +58 -0
  56. data/lib/riak/util/translation.rb +19 -0
  57. data/lib/riak/walk_spec.rb +105 -0
  58. data/riak-client.gemspec +55 -0
  59. data/seomoz-riak-client.gemspec +55 -0
  60. data/spec/fixtures/cat.jpg +0 -0
  61. data/spec/fixtures/multipart-blank.txt +7 -0
  62. data/spec/fixtures/multipart-mapreduce.txt +10 -0
  63. data/spec/fixtures/multipart-with-body.txt +16 -0
  64. data/spec/fixtures/server.cert.crt +15 -0
  65. data/spec/fixtures/server.cert.key +15 -0
  66. data/spec/fixtures/test.pem +1 -0
  67. data/spec/integration/riak/cache_store_spec.rb +154 -0
  68. data/spec/integration/riak/http_backends_spec.rb +58 -0
  69. data/spec/integration/riak/protobuffs_backends_spec.rb +32 -0
  70. data/spec/integration/riak/test_server_spec.rb +161 -0
  71. data/spec/riak/beefcake_protobuffs_backend_spec.rb +59 -0
  72. data/spec/riak/bucket_spec.rb +205 -0
  73. data/spec/riak/client_spec.rb +517 -0
  74. data/spec/riak/core_ext/to_param_spec.rb +15 -0
  75. data/spec/riak/escape_spec.rb +69 -0
  76. data/spec/riak/excon_backend_spec.rb +64 -0
  77. data/spec/riak/headers_spec.rb +38 -0
  78. data/spec/riak/http_backend/configuration_spec.rb +51 -0
  79. data/spec/riak/http_backend/object_methods_spec.rb +217 -0
  80. data/spec/riak/http_backend/transport_methods_spec.rb +117 -0
  81. data/spec/riak/http_backend_spec.rb +269 -0
  82. data/spec/riak/link_spec.rb +71 -0
  83. data/spec/riak/map_reduce/filter_builder_spec.rb +32 -0
  84. data/spec/riak/map_reduce/phase_spec.rb +136 -0
  85. data/spec/riak/map_reduce_spec.rb +310 -0
  86. data/spec/riak/multipart_spec.rb +23 -0
  87. data/spec/riak/net_http_backend_spec.rb +16 -0
  88. data/spec/riak/robject_spec.rb +427 -0
  89. data/spec/riak/search_spec.rb +178 -0
  90. data/spec/riak/serializers_spec.rb +93 -0
  91. data/spec/riak/stamp_spec.rb +54 -0
  92. data/spec/riak/stream_parser_spec.rb +53 -0
  93. data/spec/riak/walk_spec_spec.rb +195 -0
  94. data/spec/spec_helper.rb +39 -0
  95. data/spec/support/drb_mock_server.rb +39 -0
  96. data/spec/support/http_backend_implementation_examples.rb +266 -0
  97. data/spec/support/integration_setup.rb +10 -0
  98. data/spec/support/mock_server.rb +81 -0
  99. data/spec/support/mocks.rb +4 -0
  100. data/spec/support/test_server.yml.example +2 -0
  101. data/spec/support/unified_backend_examples.rb +255 -0
  102. metadata +271 -0
data/Gemfile ADDED
@@ -0,0 +1,27 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+ gem 'bundler'
5
+ gem 'guard-rspec'
6
+ gem 'rb-fsevent'
7
+ gem 'growl'
8
+
9
+ platforms :mri do
10
+ gem 'yajl-ruby'
11
+ end
12
+
13
+ platforms :jruby do
14
+ gem 'jruby-openssl'
15
+ end
16
+
17
+ group :integration do
18
+ gem 'activesupport', '~>3.0'
19
+ end
20
+
21
+ platforms :mri_18, :jruby do
22
+ gem 'ruby-debug'
23
+ end
24
+
25
+ platforms :mri_19 do
26
+ gem 'ruby-debug19'
27
+ end
@@ -0,0 +1,14 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+ gemset = ENV['RVM_GEMSET'] || 'ripple'
4
+ gemset = "@#{gemset}" unless gemset.to_s == ''
5
+
6
+ rvms = %w[ 1.8.7 1.9.2 jruby ].map do |version|
7
+ "#{version}@#{gemset}"
8
+ end
9
+
10
+ guard 'rspec', :cli => '--tag ~integration', :rvm => rvms do
11
+ watch(%r{^spec/.+_spec\.rb$})
12
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
13
+ watch('spec/spec_helper.rb') { "spec/riak" }
14
+ end
@@ -0,0 +1,76 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+
4
+ gemspec = Gem::Specification.new do |gem|
5
+ gem.name = "seomoz-riak-client"
6
+ gem.summary = %Q{riak-client is a rich client for Riak, the distributed database by Basho.}
7
+ gem.description = %Q{riak-client is a rich client for Riak, the distributed database by Basho. It supports the full HTTP interface including storage operations, bucket configuration, link-walking and map-reduce.}
8
+ gem.version = File.read('../VERSION').strip
9
+ gem.email = "sean@basho.com"
10
+ gem.homepage = "http://seancribbs.github.com/ripple"
11
+ gem.authors = ["Sean Cribbs"]
12
+ gem.add_development_dependency "rspec", "~>2.6.0"
13
+ gem.add_development_dependency "fakeweb", ">=1.2"
14
+ gem.add_development_dependency "rack", ">=1.0"
15
+ gem.add_development_dependency "excon", "~>0.6.1"
16
+ gem.add_development_dependency 'rake', '~> 0.8.7'
17
+ gem.add_dependency "i18n", ">=0.4.0"
18
+ gem.add_dependency "builder", ">= 2.1.2"
19
+ gem.add_dependency "beefcake", "0.3.2"
20
+ gem.add_dependency "multi_json", "~>1.0.0"
21
+
22
+ files = FileList["**/*"]
23
+ # Editor and O/S files
24
+ files.exclude ".DS_Store", "*~", "\#*", ".\#*", "*.swp", "*.tmproj", "tmtags"
25
+ # Generated artifacts
26
+ files.exclude "coverage", "rdoc", "pkg", "doc", ".bundle", "*.rbc", ".rvmrc", ".watchr", ".rspec"
27
+ # Project-specific
28
+ files.exclude "Gemfile.lock", %r{spec/support/test_server.yml$}, "bin"
29
+ # Remove directories
30
+ files.exclude {|d| File.directory?(d) }
31
+
32
+ gem.files = files.to_a
33
+
34
+ gem.test_files = gem.files.grep(/_spec\.rb$/)
35
+ end
36
+
37
+ # Gem packaging tasks
38
+ Rake::GemPackageTask.new(gemspec) do |pkg|
39
+ pkg.need_zip = false
40
+ pkg.need_tar = false
41
+ end
42
+
43
+ task :gem => :gemspec
44
+
45
+ desc %{Build the gemspec file.}
46
+ task :gemspec do
47
+ gemspec.validate
48
+ File.open("#{gemspec.name}.gemspec", 'w'){|f| f.write gemspec.to_ruby }
49
+ end
50
+
51
+ desc %{Release the gem to RubyGems.org}
52
+ task :release => :gem do
53
+ system "gem push pkg/#{gemspec.name}-#{gemspec.version}.gem"
54
+ end
55
+
56
+ require 'rspec/core'
57
+ require 'rspec/core/rake_task'
58
+
59
+ desc "Run Unit Specs Only"
60
+ RSpec::Core::RakeTask.new(:spec) do |spec|
61
+ spec.pattern = "spec/riak/**/*_spec.rb"
62
+ spec.rspec_opts = %w[--profile]
63
+ end
64
+
65
+ namespace :spec do
66
+ desc "Run Integration Specs Only"
67
+ RSpec::Core::RakeTask.new(:integration) do |spec|
68
+ spec.pattern = "spec/integration/**/*_spec.rb"
69
+ spec.rspec_opts = %w[--profile]
70
+ end
71
+
72
+ desc "Run All Specs"
73
+ task :all => %w[ spec spec:integration ]
74
+ end
75
+
76
+ task :default => "spec:all"
@@ -0,0 +1,174 @@
1
+ %% -------------------------------------------------------------------
2
+ %%
3
+ %% riak_kv_test_backend: storage engine based on ETS tables
4
+ %%
5
+ %%
6
+ %% -------------------------------------------------------------------
7
+
8
+ % @doc riak_kv_test_backend is a Riak storage backend using ets that
9
+ % exposes a reset function for efficiently clearing stored data.
10
+
11
+ -module(riak_kv_test_backend).
12
+ -behavior(riak_kv_backend).
13
+ -behavior(gen_server).
14
+ -ifdef(TEST).
15
+ -include_lib("eunit/include/eunit.hrl").
16
+ -endif.
17
+ -export([start/2,stop/1,get/2,put/3,list/1,list_bucket/2,delete/2,
18
+ is_empty/1, drop/1, fold/3, callback/3, reset/0]).
19
+
20
+ -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
21
+ terminate/2, code_change/3]).
22
+
23
+
24
+ % @type state() = term().
25
+ -record(state, {t, p}).
26
+
27
+ % @spec start(Partition :: integer(), Config :: proplist()) ->
28
+ % {ok, state()} | {{error, Reason :: term()}, state()}
29
+ start(Partition, _Config) ->
30
+ gen_server:start_link(?MODULE, [Partition], []).
31
+
32
+ % @spec reset() -> ok | {error, timeout}
33
+ reset() ->
34
+ Pids = lists:foldl(fun(Item, Acc) ->
35
+ case lists:prefix("test_backend", atom_to_list(Item)) of
36
+ true -> [whereis(Item)|Acc];
37
+ _ -> Acc
38
+ end
39
+ end, [], registered()),
40
+ [gen_server:cast(Pid,{reset, self()})|| Pid <- Pids],
41
+ receive_reset(Pids).
42
+
43
+ receive_reset([]) -> ok;
44
+ receive_reset(Pids) ->
45
+ receive
46
+ {reset, Pid} ->
47
+ receive_reset(lists:delete(Pid, Pids))
48
+ after 1000 ->
49
+ {error, timeout}
50
+ end.
51
+
52
+ %% @private
53
+ init([Partition]) ->
54
+ PName = list_to_atom("test_backend" ++ integer_to_list(Partition)),
55
+ P = list_to_atom(integer_to_list(Partition)),
56
+ register(PName, self()),
57
+ {ok, #state{t=ets:new(P,[]), p=P}}.
58
+
59
+ %% @private
60
+ handle_cast({reset,From}, State) ->
61
+ ets:delete_all_objects(State#state.t),
62
+ From ! {reset, self()},
63
+ {noreply, State};
64
+ handle_cast(_, State) -> {noreply, State}.
65
+
66
+ %% @private
67
+ handle_call(stop,_From,State) -> {reply, srv_stop(State), State};
68
+ handle_call({get,BKey},_From,State) -> {reply, srv_get(State,BKey), State};
69
+ handle_call({put,BKey,Val},_From,State) ->
70
+ {reply, srv_put(State,BKey,Val),State};
71
+ handle_call({delete,BKey},_From,State) -> {reply, srv_delete(State,BKey),State};
72
+ handle_call(list,_From,State) -> {reply, srv_list(State), State};
73
+ handle_call({list_bucket,Bucket},_From,State) ->
74
+ {reply, srv_list_bucket(State, Bucket), State};
75
+ handle_call(is_empty, _From, State) ->
76
+ {reply, ets:info(State#state.t, size) =:= 0, State};
77
+ handle_call(drop, _From, State) ->
78
+ ets:delete(State#state.t),
79
+ {reply, ok, State};
80
+ handle_call({fold, Fun0, Acc}, _From, State) ->
81
+ Fun = fun({{B,K}, V}, AccIn) -> Fun0({B,K}, V, AccIn) end,
82
+ Reply = ets:foldl(Fun, Acc, State#state.t),
83
+ {reply, Reply, State}.
84
+
85
+ % @spec stop(state()) -> ok | {error, Reason :: term()}
86
+ stop(SrvRef) -> gen_server:call(SrvRef,stop).
87
+ srv_stop(State) ->
88
+ true = ets:delete(State#state.t),
89
+ ok.
90
+
91
+ % get(state(), riak_object:bkey()) ->
92
+ % {ok, Val :: binary()} | {error, Reason :: term()}
93
+ % key must be 160b
94
+ get(SrvRef, BKey) -> gen_server:call(SrvRef,{get,BKey}).
95
+ srv_get(State, BKey) ->
96
+ case ets:lookup(State#state.t,BKey) of
97
+ [] -> {error, notfound};
98
+ [{BKey,Val}] -> {ok, Val};
99
+ Err -> {error, Err}
100
+ end.
101
+
102
+ % put(state(), riak_object:bkey(), Val :: binary()) ->
103
+ % ok | {error, Reason :: term()}
104
+ % key must be 160b
105
+ put(SrvRef, BKey, Val) -> gen_server:call(SrvRef,{put,BKey,Val}).
106
+ srv_put(State,BKey,Val) ->
107
+ true = ets:insert(State#state.t, {BKey,Val}),
108
+ ok.
109
+
110
+ % delete(state(), riak_object:bkey()) ->
111
+ % ok | {error, Reason :: term()}
112
+ % key must be 160b
113
+ delete(SrvRef, BKey) -> gen_server:call(SrvRef,{delete,BKey}).
114
+ srv_delete(State, BKey) ->
115
+ true = ets:delete(State#state.t, BKey),
116
+ ok.
117
+
118
+ % list(state()) -> [riak_object:bkey()]
119
+ list(SrvRef) -> gen_server:call(SrvRef,list).
120
+ srv_list(State) ->
121
+ MList = ets:match(State#state.t,{'$1','_'}),
122
+ list(MList,[]).
123
+ list([],Acc) -> Acc;
124
+ list([[K]|Rest],Acc) -> list(Rest,[K|Acc]).
125
+
126
+ % list_bucket(term(), Bucket :: riak_object:bucket()) -> [Key :: binary()]
127
+ list_bucket(SrvRef, Bucket) ->
128
+ gen_server:call(SrvRef,{list_bucket, Bucket}).
129
+ srv_list_bucket(State, {filter, Bucket, Fun}) ->
130
+ MList = lists:filter(Fun, ets:match(State#state.t,{{Bucket,'$1'},'_'})),
131
+ list(MList,[]);
132
+ srv_list_bucket(State, Bucket) ->
133
+ case Bucket of
134
+ '_' -> MatchSpec = {{'$1','_'},'_'};
135
+ _ -> MatchSpec = {{Bucket,'$1'},'_'}
136
+ end,
137
+ MList = ets:match(State#state.t,MatchSpec),
138
+ list(MList,[]).
139
+
140
+ is_empty(SrvRef) -> gen_server:call(SrvRef, is_empty).
141
+
142
+ drop(SrvRef) -> gen_server:call(SrvRef, drop).
143
+
144
+ fold(SrvRef, Fun, Acc0) -> gen_server:call(SrvRef, {fold, Fun, Acc0}, infinity).
145
+
146
+ %% Ignore callbacks for other backends so multi backend works
147
+ callback(_State, _Ref, _Msg) ->
148
+ ok.
149
+
150
+ %% @private
151
+ handle_info(_Msg, State) -> {noreply, State}.
152
+
153
+ %% @private
154
+ terminate(_Reason, _State) -> ok.
155
+
156
+ %% @private
157
+ code_change(_OldVsn, State, _Extra) -> {ok, State}.
158
+
159
+ %%
160
+ %% Test
161
+ %%
162
+ -ifdef(TEST).
163
+
164
+ % @private
165
+ simple_test() ->
166
+ riak_kv_backend:standard_test(?MODULE, []).
167
+
168
+ -ifdef(EQC).
169
+ %% @private
170
+ eqc_test() ->
171
+ ?assertEqual(true, backend_eqc:test(?MODULE, true)).
172
+
173
+ -endif. % EQC
174
+ -endif. % TEST
@@ -0,0 +1,175 @@
1
+ %% -------------------------------------------------------------------
2
+ %%
3
+ %% Copyright (c) 2007-2010 Basho Technologies, Inc. All Rights Reserved.
4
+ %%
5
+ %% -------------------------------------------------------------------
6
+
7
+ -module(riak_search_test_backend).
8
+ -behavior(riak_search_backend).
9
+
10
+ -export([
11
+ reset/0,
12
+ start/2,
13
+ stop/1,
14
+ index/2,
15
+ delete/2,
16
+ stream/6,
17
+ range/8,
18
+ info/5,
19
+ fold/3,
20
+ is_empty/1,
21
+ drop/1
22
+ ]).
23
+ -export([
24
+ stream_results/3
25
+ ]).
26
+
27
+ -include("riak_search.hrl").
28
+
29
+ -record(state, {partition, table}).
30
+
31
+ reset() ->
32
+ {ok, Ring} = riak_core_ring_manager:get_my_ring(),
33
+ [ ets:delete_all_objects(list_to_atom(integer_to_list(P))) ||
34
+ P <- riak_core_ring:my_indices(Ring) ],
35
+ riak_search_config:clear(),
36
+ ok.
37
+
38
+ start(Partition, _Config) ->
39
+ Table = ets:new(list_to_atom(integer_to_list(Partition)),
40
+ [named_table, public, ordered_set]),
41
+ {ok, #state{partition=Partition, table=Table}}.
42
+
43
+ stop(State) ->
44
+ maybe_delete(State).
45
+
46
+ index(IFTVPKList, #state{table=Table}=State) ->
47
+ lists:foreach(
48
+ fun({I, F, T, V, P, K}) ->
49
+ Key = {b(I), b(F), b(T), b(V)},
50
+ case ets:lookup(Table, Key) of
51
+ [{_, _, ExistingKeyClock}] ->
52
+ if ExistingKeyClock > K ->
53
+ %% stored data is newer
54
+ ok;
55
+ true ->
56
+ %% stored data is older
57
+ ets:update_element(Table, Key,
58
+ [{2, P},{3, K}])
59
+ end;
60
+ [] ->
61
+ ets:insert(Table, {Key, P, K})
62
+ end
63
+ end,
64
+ IFTVPKList),
65
+ {reply, {indexed, node()}, State}.
66
+
67
+ delete(IFTVKList, State) ->
68
+ Table = State#state.table,
69
+ lists:foreach(fun(IFTVK) -> delete_fun(IFTVK, Table) end, IFTVKList),
70
+ {reply, {deleted, node()}, State}.
71
+
72
+ delete_fun({I, F, T, V, K}, Table) ->
73
+ Key = {b(I), b(F), b(T), b(V)},
74
+ case ets:lookup(Table, Key) of
75
+ [{Key, _Props, ExistingKeyClock}] ->
76
+ if ExistingKeyClock > K ->
77
+ %% stored data is newer
78
+ ok;
79
+ true ->
80
+ %% stored data is older
81
+ ets:delete(Table, Key)
82
+ end;
83
+ [] ->
84
+ ok
85
+ end;
86
+ delete_fun({I, F, T, V, _P, K}, Table) ->
87
+ %% copied idea from merge_index_backend
88
+ %% other operations include Props, though delete shouldn't
89
+ delete_fun({I, F, T, V, K}, Table).
90
+
91
+ info(Index, Field, Term, Sender, State) ->
92
+ Count = ets:select_count(State#state.table,
93
+ [{{{b(Index), b(Field), b(Term), '_'},
94
+ '_', '_'},
95
+ [],[true]}]),
96
+ riak_search_backend:info_response(Sender, [{Term, node(), Count}]),
97
+ noreply.
98
+
99
+ -define(STREAM_SIZE, 100).
100
+
101
+ range(Index, Field, StartTerm, EndTerm, _Size, FilterFun, Sender, State) ->
102
+ ST = b(StartTerm),
103
+ ET = b(EndTerm),
104
+ spawn(riak_search_ets_backend, stream_results,
105
+ [Sender,
106
+ FilterFun,
107
+ ets:select(State#state.table,
108
+ [{{{b(Index), b(Field), '$1', '$2'}, '$3', '_'},
109
+ [{'>=', '$1', ST}, {'=<', '$1', ET}],
110
+ [{{'$2', '$3'}}]}],
111
+ ?STREAM_SIZE)]),
112
+ noreply.
113
+
114
+ stream(Index, Field, Term, FilterFun, Sender, State) ->
115
+ spawn(riak_search_ets_backend, stream_results,
116
+ [Sender,
117
+ FilterFun,
118
+ ets:select(State#state.table,
119
+ [{{{b(Index), b(Field), b(Term), '$1'}, '$2', '_'},
120
+ [], [{{'$1', '$2'}}]}],
121
+ ?STREAM_SIZE)]),
122
+ noreply.
123
+
124
+ stream_results(Sender, FilterFun, {Results0, Continuation}) ->
125
+ case lists:filter(fun({V,P}) -> FilterFun(V, P) end, Results0) of
126
+ [] ->
127
+ ok;
128
+ Results ->
129
+ riak_search_backend:response_results(Sender, Results)
130
+ end,
131
+ stream_results(Sender, FilterFun, ets:select(Continuation));
132
+ stream_results(Sender, _, '$end_of_table') ->
133
+ riak_search_backend:response_done(Sender).
134
+
135
+ fold(FoldFun, Acc, State) ->
136
+ Fun = fun({{I,F,T,V},P,K}, {OuterAcc, {{I,{F,T}},InnerAcc}}) ->
137
+ %% same IFT, just accumulate doc/props/clock
138
+ {OuterAcc, {{I,{F,T}},[{V,P,K}|InnerAcc]}};
139
+ ({{I,F,T,V},P,K}, {OuterAcc, {FoldKey, VPKList}}) ->
140
+ %% finished a string of IFT, send it off
141
+ %% (sorted order is assumed)
142
+ NewOuterAcc = FoldFun(FoldKey, VPKList, OuterAcc),
143
+ {NewOuterAcc, {{I,{F,T}},[{V,P,K}]}};
144
+ ({{I,F,T,V},P,K}, {OuterAcc, undefined}) ->
145
+ %% first round through the fold - just start building
146
+ {OuterAcc, {{I,{F,T}},[{V,P,K}]}}
147
+ end,
148
+ {OuterAcc0, Final} = ets:foldl(Fun, {Acc, undefined}, State#state.table),
149
+ OuterAcc = case Final of
150
+ {FoldKey, VPKList} ->
151
+ %% one last IFT to send off
152
+ FoldFun(FoldKey, VPKList, OuterAcc0);
153
+ undefined ->
154
+ %% this partition was empty
155
+ OuterAcc0
156
+ end,
157
+ {reply, OuterAcc, State}.
158
+
159
+ is_empty(State) ->
160
+ 0 == ets:info(State#state.table, size).
161
+
162
+ drop(State) ->
163
+ maybe_delete(State).
164
+
165
+ maybe_delete(State) ->
166
+ case lists:member(State#state.table, ets:all()) of
167
+ true ->
168
+ ets:delete(State#state.table),
169
+ ok;
170
+ false ->
171
+ ok
172
+ end.
173
+
174
+ b(Binary) when is_binary(Binary) -> Binary;
175
+ b(List) when is_list(List) -> iolist_to_binary(List).