riak-client 1.0.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,34 @@
1
1
  # Riak Ruby Client Release Notes
2
2
 
3
+ ## 1.0.3 Patch/Bugfix Release - 2012-04-17
4
+
5
+ Release 1.0.3 fixes some bugs and adds support for secondary indexes
6
+ when using `Riak::TestServer`.
7
+
8
+ * Added tests for secondary index features to the unified backend
9
+ examples.
10
+ * Added secondary index support to `riak_kv_test_backend`. Full
11
+ support for this feature will be available via
12
+ `riak_kv_memory_backend` in the next major Riak release. See
13
+ [riak_kv #314](https://github.com/basho/riak_kv/pull/314).
14
+ * The console log (`lager_console_backend`) is now enabled on
15
+ generated nodes.
16
+ * `Riak::Node::Console` no longer overrides the `SIGWINCH` signal
17
+ handler.
18
+ * [Excon](http://rubygems.org/gems/excon) versions >= 0.7.0 are now
19
+ supported.
20
+ * IO-style objects will now be emitted properly when using the
21
+ `NetHTTPBackend`. [#1](https://github.com/basho/riak-ruby-client/issues/1)
22
+ * The Riak version filter for integration specs is now more correct.
23
+ * `Riak::RObject#url` has been removed because its accuracy cannot be
24
+ maintained when connected to multiple Riak nodes or to Riak via
25
+ PBC. [#3](https://github.com/basho/riak-ruby-client/issues/3)
26
+ * Index entries on `Riak::RObject` can be mass-overwritten using
27
+ `Riak::RObject#indexes=` while maintaining the proper internal
28
+ semantics. [#17](https://github.com/basho/riak-ruby-client/issues/17)
29
+ * Nodes should now generate properly when the `riak` script is a
30
+ symlink (e.g. Homebrew). [#26](https://github.com/basho/riak-ruby-client/issues/26)
31
+
3
32
  ## 1.0.2 Repackaging - 2012-04-02
4
33
 
5
34
  Release 1.0.2 relaxes the multi_json dependency so that the client
@@ -97,16 +126,16 @@ The new gem and repository locations are below:
97
126
  * [`ripple`](http://rubygems.org/gems/ripple) —
98
127
  [seancribbs/ripple](https://github.com/seancribbs/ripple)
99
128
  * [`riak-sessions`](http://rubygems.org/gems/riak-sessions) —
100
- [seancribbs/riak-sessions](https://github.com/seancribbs/riak-sessions)
129
+ [seancribbs/riak-sessions](https://github.com/seancribbs/riak-sessions)
101
130
  * [`riak-cache`](http://rubygems.org/gems/riak-cache) —
102
- [seancribbs/riak-cache](https://github.com/seancribbs/riak-cache)
131
+ [seancribbs/riak-cache](https://github.com/seancribbs/riak-cache)
103
132
 
104
133
  ### Significant Known Issues
105
134
 
106
135
  Attempting to use the Protocol Buffers transport with a 0.14.x cluster
107
136
  may cause the connection to dump because of incompatibilities in
108
137
  certain protocol messages. This will be addressed in a future
109
- patch/bugfix release.
138
+ patch/bugfix release.
110
139
 
111
140
  The new node generation and test server intermittently fails on JRuby,
112
141
  specifically from deadlocks related to blocking opens for the console
@@ -1,6 +1,6 @@
1
1
  %% -------------------------------------------------------------------
2
2
  %%
3
- %% riak_memory_backend: storage engine using ETS tables
3
+ %% riak_kv_test_backend: storage engine using ETS tables, for use in testing.
4
4
  %%
5
5
  %% Copyright (c) 2007-2011 Basho Technologies, Inc. All Rights Reserved.
6
6
  %%
@@ -32,6 +32,7 @@
32
32
  %% <ul>
33
33
  %% <li>`ttl' - The time in seconds that an object should live before being expired.</li>
34
34
  %% <li>`max_memory' - The amount of memory in megabytes to limit the backend to.</li>
35
+ %% <li>`test' - When `true', exposes the internal ETS tables so that they can be efficiently cleared using {@link reset/3}.</li>
35
36
  %% </ul>
36
37
  %%
37
38
 
@@ -40,6 +41,8 @@
40
41
 
41
42
  %% KV Backend API
42
43
  -export([api_version/0,
44
+ capabilities/1,
45
+ capabilities/2,
43
46
  start/2,
44
47
  stop/1,
45
48
  get/3,
@@ -51,20 +54,30 @@
51
54
  fold_objects/4,
52
55
  is_empty/1,
53
56
  status/1,
54
- callback/3,
55
- reset/0,
56
- capabilities/1,
57
- capabilities/2]).
57
+ callback/3]).
58
+
59
+ %% "Testing" backend API
60
+ -export([reset/0]).
58
61
 
59
62
  -ifdef(TEST).
60
63
  -include_lib("eunit/include/eunit.hrl").
61
64
  -endif.
62
65
 
63
66
  -define(API_VERSION, 1).
64
- -define(CAPABILITIES, [async_fold]).
67
+ -define(CAPABILITIES, [async_fold, indexes]).
68
+
69
+ %% Macros for working with indexes
70
+ -define(DELETE_PTN(B,K), {{B,'_','_',K},'_'}).
65
71
 
66
- -record(state, {data_ref :: integer() | atom(),
67
- time_ref :: integer() | atom(),
72
+ %% ETS table name macros so we can break encapsulation for testing
73
+ %% mode
74
+ -define(DNAME(P), list_to_atom("riak_kv_"++integer_to_list(P))).
75
+ -define(INAME(P), list_to_atom("riak_kv_"++integer_to_list(P)++"_i")).
76
+ -define(TNAME(P), list_to_atom("riak_kv_"++integer_to_list(P)++"_t")).
77
+
78
+ -record(state, {data_ref :: ets:tid(),
79
+ index_ref :: ets:tid(),
80
+ time_ref :: ets:tid(),
68
81
  max_memory :: undefined | integer(),
69
82
  used_memory=0 :: integer(),
70
83
  ttl :: integer()}).
@@ -76,20 +89,11 @@
76
89
  %% Public API
77
90
  %% ===================================================================
78
91
 
79
- %% TestServer reset
80
-
81
- -spec reset() -> ok | {error, timeout}.
82
- reset() ->
83
- {ok, Ring} = riak_core_ring_manager:get_my_ring(),
84
- [ catch ets:delete_all_objects(list_to_atom("kv" ++ integer_to_list(P))) ||
85
- P <- riak_core_ring:my_indices(Ring) ],
86
- ok.
87
-
88
92
  %% KV Backend API
89
93
 
90
94
  %% @doc Return the major version of the
91
- %% current API and a capabilities list.
92
- -spec api_version() -> {ok, integer()} | {integer(), [atom()]}.
95
+ %% current API.
96
+ -spec api_version() -> {ok, integer()}.
93
97
  api_version() ->
94
98
  case lists:member({capabilities, 1}, riak_kv_backend:behaviour_info(callbacks)) of
95
99
  true -> % Using 1.1 API or later
@@ -110,23 +114,33 @@ capabilities(_, _) ->
110
114
 
111
115
  %% @doc Start the memory backend
112
116
  -spec start(integer(), config()) -> {ok, state()}.
113
- start(Partition, _Config) ->
114
- %% TTL = config_value(ttl, Config),
115
- %% MemoryMB = config_value(max_memory, Config),
116
- %% case MemoryMB of
117
- %% undefined ->
118
- %% MaxMemory = undefined,
119
- %% TimeRef = undefined;
120
- %% _ ->
121
- %% MaxMemory = MemoryMB * 1024 * 1024,
122
- %% TimeRef = ets:new(list_to_atom(integer_to_list(Partition)), [ordered_set])
123
- %% end,
124
- DataRef = ets:new(list_to_atom("kv" ++ integer_to_list(Partition)), [named_table, public]),
125
- {ok, #state{data_ref=DataRef
126
- %% max_memory=MaxMemory,
127
- %% time_ref=TimeRef,
128
- %% ttl=TTL
129
- }}.
117
+ %% Bug in riak_kv_vnode in 1.0
118
+ start(Partition, [{async_folds,_}=AFolds, Rest]) when is_list(Rest) ->
119
+ start(Partition, [AFolds|Rest]);
120
+ start(Partition, Config) ->
121
+ TTL = app_helper:get_prop_or_env(ttl, Config, memory_backend),
122
+ MemoryMB = app_helper:get_prop_or_env(max_memory, Config, memory_backend),
123
+ TableOpts = case app_helper:get_prop_or_env(test, Config, memory_backend) of
124
+ true ->
125
+ [ordered_set, public, named_table];
126
+ _ ->
127
+ [ordered_set]
128
+ end,
129
+ case MemoryMB of
130
+ undefined ->
131
+ MaxMemory = undefined,
132
+ TimeRef = undefined;
133
+ _ ->
134
+ MaxMemory = MemoryMB * 1024 * 1024,
135
+ TimeRef = ets:new(?TNAME(Partition), TableOpts)
136
+ end,
137
+ IndexRef = ets:new(?INAME(Partition), TableOpts),
138
+ DataRef = ets:new(?DNAME(Partition), TableOpts),
139
+ {ok, #state{data_ref=DataRef,
140
+ index_ref=IndexRef,
141
+ max_memory=MaxMemory,
142
+ time_ref=TimeRef,
143
+ ttl=TTL}}.
130
144
 
131
145
  %% @doc Stop the memory backend
132
146
  -spec stop(state()) -> ok.
@@ -148,14 +162,27 @@ stop(#state{data_ref=DataRef,
148
162
  {ok, not_found, state()} |
149
163
  {error, term(), state()}.
150
164
  get(Bucket, Key, State=#state{data_ref=DataRef,
165
+ index_ref=IndexRef,
166
+ used_memory=UsedMemory,
167
+ max_memory=MaxMemory,
151
168
  ttl=TTL}) ->
152
169
  case ets:lookup(DataRef, {Bucket, Key}) of
153
170
  [] -> {error, not_found, State};
154
- [{{Bucket, Key}, {{ts, Timestamp}, Val}}] ->
171
+ [{{Bucket, Key}, {{ts, Timestamp}, Val}}=Object] ->
155
172
  case exceeds_ttl(Timestamp, TTL) of
156
173
  true ->
157
- delete(Bucket, Key, undefined, State),
158
- {error, not_found, State};
174
+ %% Because we do not have the IndexSpecs, we must
175
+ %% delete the object directly and all index
176
+ %% entries blindly using match_delete.
177
+ ets:delete(DataRef, {Bucket, Key}),
178
+ ets:match_delete(IndexRef, ?DELETE_PTN(Bucket, Key)),
179
+ case MaxMemory of
180
+ undefined ->
181
+ UsedMemory1 = UsedMemory;
182
+ _ ->
183
+ UsedMemory1 = UsedMemory - object_size(Object)
184
+ end,
185
+ {error, not_found, State#state{used_memory=UsedMemory1}};
159
186
  false ->
160
187
  {ok, Val, State}
161
188
  end;
@@ -166,18 +193,15 @@ get(Bucket, Key, State=#state{data_ref=DataRef,
166
193
  end.
167
194
 
168
195
  %% @doc Insert an object into the memory backend.
169
- %% NOTE: The memory backend does not currently
170
- %% support secondary indexing and the _IndexSpecs
171
- %% parameter is ignored.
172
196
  -type index_spec() :: {add, Index, SecondaryKey} | {remove, Index, SecondaryKey}.
173
197
  -spec put(riak_object:bucket(), riak_object:key(), [index_spec()], binary(), state()) ->
174
- {ok, state()} |
175
- {error, term(), state()}.
176
- put(Bucket, PrimaryKey, _IndexSpecs, Val, State=#state{data_ref=DataRef,
177
- max_memory=MaxMemory,
178
- time_ref=TimeRef,
179
- ttl=TTL,
180
- used_memory=UsedMemory}) ->
198
+ {ok, state()}.
199
+ put(Bucket, PrimaryKey, IndexSpecs, Val, State=#state{data_ref=DataRef,
200
+ index_ref=IndexRef,
201
+ max_memory=MaxMemory,
202
+ time_ref=TimeRef,
203
+ ttl=TTL,
204
+ used_memory=UsedMemory}) ->
181
205
  Now = now(),
182
206
  case TTL of
183
207
  undefined ->
@@ -185,36 +209,29 @@ put(Bucket, PrimaryKey, _IndexSpecs, Val, State=#state{data_ref=DataRef,
185
209
  _ ->
186
210
  Val1 = {{ts, Now}, Val}
187
211
  end,
188
- case do_put(Bucket, PrimaryKey, Val1, DataRef) of
189
- {ok, Size} ->
190
- %% If the memory is capped update timestamp table
191
- %% and check if the memory usage is over the cap.
192
- case MaxMemory of
193
- undefined ->
194
- UsedMemory1 = UsedMemory;
195
- _ ->
196
- time_entry(Bucket, PrimaryKey, Now, TimeRef),
197
- Freed = trim_data_table(MaxMemory,
198
- UsedMemory + Size,
199
- DataRef,
200
- TimeRef,
201
- 0),
202
- UsedMemory1 = UsedMemory + Size - Freed
203
- end,
204
- {ok, State#state{used_memory=UsedMemory1}};
205
- {error, Reason} ->
206
- {error, Reason, State}
207
- end.
212
+ {ok, Size} = do_put(Bucket, PrimaryKey, Val1, IndexSpecs, DataRef, IndexRef),
213
+ case MaxMemory of
214
+ undefined ->
215
+ UsedMemory1 = UsedMemory;
216
+ _ ->
217
+ time_entry(Bucket, PrimaryKey, Now, TimeRef),
218
+ Freed = trim_data_table(MaxMemory,
219
+ UsedMemory + Size,
220
+ DataRef,
221
+ TimeRef,
222
+ IndexRef,
223
+ 0),
224
+ UsedMemory1 = UsedMemory + Size - Freed
225
+ end,
226
+ {ok, State#state{used_memory=UsedMemory1}}.
208
227
 
209
228
  %% @doc Delete an object from the memory backend
210
- %% NOTE: The memory backend does not currently
211
- %% support secondary indexing and the _IndexSpecs
212
- %% parameter is ignored.
213
229
  -spec delete(riak_object:bucket(), riak_object:key(), [index_spec()], state()) ->
214
230
  {ok, state()}.
215
- delete(Bucket, Key, _IndexSpecs, State=#state{data_ref=DataRef,
216
- time_ref=TimeRef,
217
- used_memory=UsedMemory}) ->
231
+ delete(Bucket, Key, IndexSpecs, State=#state{data_ref=DataRef,
232
+ index_ref=IndexRef,
233
+ time_ref=TimeRef,
234
+ used_memory=UsedMemory}) ->
218
235
  case TimeRef of
219
236
  undefined ->
220
237
  UsedMemory1 = UsedMemory;
@@ -231,6 +248,7 @@ delete(Bucket, Key, _IndexSpecs, State=#state{data_ref=DataRef,
231
248
  UsedMemory1 = UsedMemory
232
249
  end
233
250
  end,
251
+ update_indexes(Bucket, Key, IndexSpecs, IndexRef),
234
252
  ets:delete(DataRef, {Bucket, Key}),
235
253
  {ok, State#state{used_memory=UsedMemory1}}.
236
254
 
@@ -259,15 +277,33 @@ fold_buckets(FoldBucketsFun, Acc, Opts, #state{data_ref=DataRef}) ->
259
277
  any(),
260
278
  [{atom(), term()}],
261
279
  state()) -> {ok, term()} | {async, fun()}.
262
- fold_keys(FoldKeysFun, Acc, Opts, #state{data_ref=DataRef}) ->
263
- Bucket = proplists:get_value(bucket, Opts),
264
- FoldFun = fold_keys_fun(FoldKeysFun, Bucket),
280
+ fold_keys(FoldKeysFun, Acc, Opts, #state{data_ref=DataRef,
281
+ index_ref=IndexRef}) ->
282
+
283
+ %% Figure out how we should limit the fold: by bucket, by
284
+ %% secondary index, or neither (fold across everything.)
285
+ Bucket = lists:keyfind(bucket, 1, Opts),
286
+ Index = lists:keyfind(index, 1, Opts),
287
+
288
+ %% Multiple limiters may exist. Take the most specific limiter,
289
+ %% get an appropriate folder function.
290
+ Folder = if
291
+ Index /= false ->
292
+ FoldFun = fold_keys_fun(FoldKeysFun, Index),
293
+ get_index_folder(FoldFun, Acc, Index, DataRef, IndexRef);
294
+ Bucket /= false ->
295
+ FoldFun = fold_keys_fun(FoldKeysFun, Bucket),
296
+ get_folder(FoldFun, Acc, DataRef);
297
+ true ->
298
+ FoldFun = fold_keys_fun(FoldKeysFun, undefined),
299
+ get_folder(FoldFun, Acc, DataRef)
300
+ end,
301
+
265
302
  case lists:member(async_fold, Opts) of
266
303
  true ->
267
- {async, get_folder(FoldFun, Acc, DataRef)};
304
+ {async, Folder};
268
305
  false ->
269
- Acc0 = ets:foldl(FoldFun, Acc, DataRef),
270
- {ok, Acc0}
306
+ {ok, Folder()}
271
307
  end.
272
308
 
273
309
  %% @doc Fold over all the objects for one or all buckets.
@@ -289,8 +325,10 @@ fold_objects(FoldObjectsFun, Acc, Opts, #state{data_ref=DataRef}) ->
289
325
  %% @doc Delete all objects from this memory backend
290
326
  -spec drop(state()) -> {ok, state()}.
291
327
  drop(State=#state{data_ref=DataRef,
328
+ index_ref=IndexRef,
292
329
  time_ref=TimeRef}) ->
293
330
  ets:delete_all_objects(DataRef),
331
+ ets:delete_all_objects(IndexRef),
294
332
  case TimeRef of
295
333
  undefined ->
296
334
  ok;
@@ -308,14 +346,18 @@ is_empty(#state{data_ref=DataRef}) ->
308
346
  %% @doc Get the status information for this memory backend
309
347
  -spec status(state()) -> [{atom(), term()}].
310
348
  status(#state{data_ref=DataRef,
349
+ index_ref=IndexRef,
311
350
  time_ref=TimeRef}) ->
312
351
  DataStatus = ets:info(DataRef),
352
+ IndexStatus = ets:info(IndexRef),
313
353
  case TimeRef of
314
354
  undefined ->
315
- [{data_table_status, DataStatus}];
355
+ [{data_table_status, DataStatus},
356
+ {index_table_status, IndexStatus}];
316
357
  _ ->
317
358
  TimeStatus = ets:info(TimeRef),
318
359
  [{data_table_status, DataStatus},
360
+ {index_table_status, IndexStatus},
319
361
  {time_table_status, TimeStatus}]
320
362
  end.
321
363
 
@@ -324,6 +366,24 @@ status(#state{data_ref=DataRef,
324
366
  callback(_Ref, _Msg, State) ->
325
367
  {ok, State}.
326
368
 
369
+ %% @doc Resets state of all running memory backends on the local
370
+ %% node. The `riak_kv' environment variable `memory_backend' must
371
+ %% contain the `test' property, set to `true' for this to work.
372
+ -spec reset() -> ok | {error, reset_disabled}.
373
+ reset() ->
374
+ reset(app_helper:get_env(memory_backend, test, app_helper:get_env(riak_kv, test)), app_helper:get_env(riak_kv, storage_backend)).
375
+
376
+ reset(true, ?MODULE) ->
377
+ {ok, Ring} = riak_core_ring_manager:get_my_ring(),
378
+ [ begin
379
+ catch ets:delete_all_objects(?DNAME(I)),
380
+ catch ets:delete_all_objects(?INAME(I)),
381
+ catch ets:delete_all_objects(?TNAME(I))
382
+ end || I <- riak_core_ring:my_indices(Ring) ],
383
+ ok;
384
+ reset(_, _) ->
385
+ {error, reset_disabled}.
386
+
327
387
  %% ===================================================================
328
388
  %% Internal functions
329
389
  %% ===================================================================
@@ -349,32 +409,43 @@ fold_buckets_fun(FoldBucketsFun) ->
349
409
  %% Return a function to fold over keys on this backend
350
410
  fold_keys_fun(FoldKeysFun, undefined) ->
351
411
  fun({{Bucket, Key}, _}, Acc) ->
352
- FoldKeysFun(Bucket, Key, Acc)
412
+ FoldKeysFun(Bucket, Key, Acc);
413
+ (_, Acc) ->
414
+ Acc
353
415
  end;
354
- fold_keys_fun(FoldKeysFun, Bucket) ->
355
- fun({{B, Key}, _}, Acc) ->
356
- case B =:= Bucket of
357
- true ->
358
- FoldKeysFun(Bucket, Key, Acc);
359
- false ->
360
- Acc
361
- end
416
+ fold_keys_fun(FoldKeysFun, {bucket, FilterBucket}) ->
417
+ fun({{Bucket, Key}, _}, Acc) when Bucket == FilterBucket ->
418
+ FoldKeysFun(Bucket, Key, Acc);
419
+ (_, Acc) ->
420
+ Acc
421
+ end;
422
+ fold_keys_fun(FoldKeysFun, {index, FilterBucket, {eq, <<"$bucket">>, _}}) ->
423
+ %% 2I exact match query on special $bucket field...
424
+ fold_keys_fun(FoldKeysFun, {bucket, FilterBucket});
425
+ fold_keys_fun(FoldKeysFun, {index, FilterBucket, {range, <<"$key">>, _, _}}) ->
426
+ %% 2I range query on special $key field...
427
+ fold_keys_fun(FoldKeysFun, {bucket, FilterBucket});
428
+ fold_keys_fun(FoldKeysFun, {index, _FilterBucket, _Query}) ->
429
+ fun({{Bucket, _FilterField, _FilterTerm, Key}, _}, Acc) ->
430
+ FoldKeysFun(Bucket, Key, Acc);
431
+ (_, Acc) ->
432
+ Acc
362
433
  end.
363
434
 
435
+
364
436
  %% @private
365
437
  %% Return a function to fold over keys on this backend
366
438
  fold_objects_fun(FoldObjectsFun, undefined) ->
367
439
  fun({{Bucket, Key}, Value}, Acc) ->
368
- FoldObjectsFun(Bucket, Key, Value, Acc)
440
+ FoldObjectsFun(Bucket, Key, Value, Acc);
441
+ (_, Acc) ->
442
+ Acc
369
443
  end;
370
- fold_objects_fun(FoldObjectsFun, Bucket) ->
371
- fun({{B, Key}, Value}, Acc) ->
372
- case B =:= Bucket of
373
- true ->
374
- FoldObjectsFun(Bucket, Key, Value, Acc);
375
- false ->
376
- Acc
377
- end
444
+ fold_objects_fun(FoldObjectsFun, FilterBucket) ->
445
+ fun({{Bucket, Key}, Value}, Acc) when Bucket == FilterBucket->
446
+ FoldObjectsFun(Bucket, Key, Value, Acc);
447
+ (_, Acc) ->
448
+ Acc
378
449
  end.
379
450
 
380
451
  %% @private
@@ -384,29 +455,90 @@ get_folder(FoldFun, Acc, DataRef) ->
384
455
  end.
385
456
 
386
457
  %% @private
387
- do_put(Bucket, Key, Val, Ref) ->
388
- Object = {{Bucket, Key}, Val},
389
- true = ets:insert(Ref, Object),
390
- {ok, object_size(Object)}.
458
+ get_index_folder(Folder, Acc0, {index, Bucket, {eq, <<"$bucket">>, _}}, DataRef, _) ->
459
+ %% For the special $bucket index, turn it into a fold over the
460
+ %% data table.
461
+ fun() ->
462
+ key_range_folder(Folder, Acc0, DataRef, {Bucket, <<>>}, Bucket)
463
+ end;
464
+ get_index_folder(Folder, Acc0, {index, Bucket, {range, <<"$key">>, Min, Max}}, DataRef, _) ->
465
+ %% For the special range lookup on the $key index, turn it into a
466
+ %% fold on the data table
467
+ fun() ->
468
+ key_range_folder(Folder, Acc0, DataRef, {Bucket, Min}, {Bucket, Min, Max})
469
+ end;
470
+ get_index_folder(Folder, Acc0, {index, Bucket, {eq, Field, Term}}, _, IndexRef) ->
471
+ fun() ->
472
+ index_range_folder(Folder, Acc0, IndexRef, {Bucket, Field, Term, undefined}, {Bucket, Field, Term, Term})
473
+ end;
474
+ get_index_folder(Folder, Acc0, {index, Bucket, {range, Field, Min, Max}}, _, IndexRef) ->
475
+ fun() ->
476
+ index_range_folder(Folder, Acc0, IndexRef, {Bucket, Field, Min, undefined}, {Bucket, Field, Min, Max})
477
+ end.
478
+
391
479
 
480
+ %% Iterates over a range of keys, for the special $key and $bucket
481
+ %% indexes.
392
482
  %% @private
393
- config_value(Key, Config) ->
394
- config_value(Key, Config, undefined).
483
+ -spec key_range_folder(function(), term(), ets:tid(), {riak_object:bucket(), riak_object:key()}, binary() | {riak_object:bucket(), term(), term()}) -> term().
484
+ key_range_folder(Folder, Acc0, DataRef, {B,_}=DataKey, B) ->
485
+ case ets:lookup(DataRef, DataKey) of
486
+ [] ->
487
+ key_range_folder(Folder, Acc0, DataRef, ets:next(DataRef, DataKey), B);
488
+ [Object] ->
489
+ Acc = Folder(Object, Acc0),
490
+ key_range_folder(Folder, Acc, DataRef, ets:next(DataRef, DataKey), B)
491
+ end;
492
+ key_range_folder(Folder, Acc0, DataRef, {B,K}=DataKey, {B, Min, Max}=Query) when K >= Min, K =< Max ->
493
+ case ets:lookup(DataRef, DataKey) of
494
+ [] ->
495
+ key_range_folder(Folder, Acc0, DataRef, ets:next(DataRef, DataKey), Query);
496
+ [Object] ->
497
+ Acc = Folder(Object, Acc0),
498
+ key_range_folder(Folder, Acc, DataRef, ets:next(DataRef, DataKey), Query)
499
+ end;
500
+ key_range_folder(_Folder, Acc, _DataRef, _DataKey, _Query) ->
501
+ Acc.
502
+
503
+ %% Iterates over a range of index postings
504
+ index_range_folder(Folder, Acc0, IndexRef, {B, I, V, _K}=IndexKey, {B, I, Min, Max}=Query) when V >= Min, V =< Max ->
505
+ case ets:lookup(IndexRef, IndexKey) of
506
+ [] ->
507
+ %% This will happen on the first iteration, where the key
508
+ %% does not exist. In all other cases, ETS will give us a
509
+ %% real key from next/2.
510
+ index_range_folder(Folder, Acc0, IndexRef, ets:next(IndexRef, IndexKey), Query);
511
+ [Posting] ->
512
+ Acc = Folder(Posting, Acc0),
513
+ index_range_folder(Folder, Acc, IndexRef, ets:next(IndexRef, IndexKey), Query)
514
+ end;
515
+ index_range_folder(_Folder, Acc, _IndexRef, _IndexKey, _Query) ->
516
+ Acc.
517
+
395
518
 
396
519
  %% @private
397
- config_value(Key, Config, Default) ->
398
- case proplists:get_value(Key, Config) of
399
- undefined ->
400
- app_helper:get_env(memory_backend, Key, Default);
401
- Value ->
402
- Value
403
- end.
520
+ do_put(Bucket, Key, Val, IndexSpecs, DataRef, IndexRef) ->
521
+ Object = {{Bucket, Key}, Val},
522
+ true = ets:insert(DataRef, Object),
523
+ update_indexes(Bucket, Key, IndexSpecs, IndexRef),
524
+ {ok, object_size(Object)}.
404
525
 
405
526
  %% Check if this timestamp is past the ttl setting.
406
527
  exceeds_ttl(Timestamp, TTL) ->
407
528
  Diff = (timer:now_diff(now(), Timestamp) / 1000 / 1000),
408
529
  Diff > TTL.
409
530
 
531
+ update_indexes(_Bucket, _Key, undefined, _IndexRef) ->
532
+ ok;
533
+ update_indexes(_Bucket, _Key, [], _IndexRef) ->
534
+ ok;
535
+ update_indexes(Bucket, Key, [{remove, Field, Value}|Rest], IndexRef) ->
536
+ true = ets:delete(IndexRef, {Bucket, Field, Value, Key}),
537
+ update_indexes(Bucket, Key, Rest, IndexRef);
538
+ update_indexes(Bucket, Key, [{add, Field, Value}|Rest], IndexRef) ->
539
+ true = ets:insert(IndexRef, {{Bucket, Field, Value, Key}, <<>>}),
540
+ update_indexes(Bucket, Key, Rest, IndexRef).
541
+
410
542
  %% @private
411
543
  time_entry(Bucket, Key, Now, TimeRef) ->
412
544
  ets:insert(TimeRef, {Now, {Bucket, Key}}).
@@ -414,20 +546,21 @@ time_entry(Bucket, Key, Now, TimeRef) ->
414
546
  %% @private
415
547
  %% @doc Dump some entries if the max memory size has
416
548
  %% been breached.
417
- trim_data_table(MaxMemory, UsedMemory, _, _, Freed) when
549
+ trim_data_table(MaxMemory, UsedMemory, _, _, _, Freed) when
418
550
  (UsedMemory - Freed) =< MaxMemory ->
419
551
  Freed;
420
- trim_data_table(MaxMemory, UsedMemory, DataRef, TimeRef, Freed) ->
552
+ trim_data_table(MaxMemory, UsedMemory, DataRef, TimeRef, IndexRef, Freed) ->
421
553
  %% Delete the oldest object
422
- OldestSize = delete_oldest(DataRef, TimeRef),
554
+ OldestSize = delete_oldest(DataRef, TimeRef, IndexRef),
423
555
  trim_data_table(MaxMemory,
424
556
  UsedMemory,
425
557
  DataRef,
426
558
  TimeRef,
559
+ IndexRef,
427
560
  Freed + OldestSize).
428
561
 
429
562
  %% @private
430
- delete_oldest(DataRef, TimeRef) ->
563
+ delete_oldest(DataRef, TimeRef, IndexRef) ->
431
564
  OldestTime = ets:first(TimeRef),
432
565
  case OldestTime of
433
566
  '$end_of_table' ->
@@ -437,8 +570,10 @@ delete_oldest(DataRef, TimeRef) ->
437
570
  ets:delete(TimeRef, OldestTime),
438
571
  case ets:lookup(DataRef, OldestKey) of
439
572
  [] ->
440
- delete_oldest(DataRef, TimeRef);
573
+ delete_oldest(DataRef, TimeRef, IndexRef);
441
574
  [Object] ->
575
+ {Bucket, Key} = OldestKey,
576
+ ets:match_delete(IndexRef, ?DELETE_PTN(Bucket, Key)),
442
577
  ets:delete(DataRef, OldestKey),
443
578
  object_size(Object)
444
579
  end
@@ -522,8 +657,8 @@ eqc_test_() ->
522
657
  [
523
658
  {timeout, 60000,
524
659
  [?_assertEqual(true,
525
- backend_eqc:test(?MODULE, true))]}
526
- ]}]}]}.
660
+ backend_eqc:test(?MODULE, true))]}
661
+ ]}]}]}.
527
662
 
528
663
  setup() ->
529
664
  application:load(sasl),