riak-client 0.9.8 → 1.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. data/.gitignore +32 -0
  2. data/Gemfile +17 -11
  3. data/Guardfile +14 -0
  4. data/Rakefile +18 -44
  5. data/erl_src/riak_kv_test_backend.beam +0 -0
  6. data/erl_src/riak_kv_test_backend.erl +461 -128
  7. data/erl_src/riak_search_test_backend.beam +0 -0
  8. data/erl_src/riak_search_test_backend.erl +175 -0
  9. data/lib/active_support/cache/riak_store.rb +0 -13
  10. data/lib/riak.rb +11 -16
  11. data/lib/riak/bucket.rb +59 -41
  12. data/lib/riak/cache_store.rb +1 -14
  13. data/lib/riak/client.rb +145 -73
  14. data/lib/riak/client/beefcake/messages.rb +36 -31
  15. data/lib/riak/client/beefcake/object_methods.rb +27 -19
  16. data/lib/riak/client/beefcake_protobuffs_backend.rb +27 -33
  17. data/lib/riak/client/excon_backend.rb +0 -13
  18. data/lib/riak/client/http_backend.rb +95 -60
  19. data/lib/riak/client/http_backend/configuration.rb +144 -19
  20. data/lib/riak/client/http_backend/key_streamer.rb +1 -14
  21. data/lib/riak/client/http_backend/object_methods.rb +16 -16
  22. data/lib/riak/client/http_backend/request_headers.rb +0 -13
  23. data/lib/riak/client/http_backend/transport_methods.rb +26 -56
  24. data/lib/riak/client/net_http_backend.rb +11 -13
  25. data/lib/riak/client/protobuffs_backend.rb +21 -19
  26. data/lib/riak/client/pump.rb +1 -15
  27. data/lib/riak/client/search.rb +85 -0
  28. data/lib/riak/cluster.rb +151 -0
  29. data/lib/riak/core_ext.rb +1 -0
  30. data/lib/riak/core_ext/deep_dup.rb +13 -0
  31. data/lib/riak/core_ext/json.rb +15 -0
  32. data/lib/riak/core_ext/stringify_keys.rb +1 -1
  33. data/lib/riak/core_ext/symbolize_keys.rb +1 -1
  34. data/lib/riak/encoding.rb +6 -0
  35. data/lib/riak/failed_request.rb +2 -15
  36. data/lib/riak/i18n.rb +0 -13
  37. data/lib/riak/json.rb +19 -8
  38. data/lib/riak/link.rb +18 -20
  39. data/lib/riak/locale/en.yml +13 -16
  40. data/lib/riak/map_reduce.rb +40 -20
  41. data/lib/riak/map_reduce/filter_builder.rb +14 -18
  42. data/lib/riak/map_reduce/phase.rb +0 -13
  43. data/lib/riak/map_reduce_error.rb +0 -13
  44. data/lib/riak/node.rb +38 -0
  45. data/lib/riak/node/configuration.rb +286 -0
  46. data/lib/riak/node/console.rb +139 -0
  47. data/lib/riak/node/control.rb +207 -0
  48. data/lib/riak/node/defaults.rb +70 -0
  49. data/lib/riak/node/generation.rb +99 -0
  50. data/lib/riak/node/log.rb +34 -0
  51. data/lib/riak/node/version.rb +37 -0
  52. data/lib/riak/robject.rb +45 -41
  53. data/lib/riak/search.rb +2 -161
  54. data/lib/riak/serializers.rb +74 -0
  55. data/lib/riak/stamp.rb +77 -0
  56. data/lib/riak/test_server.rb +56 -220
  57. data/lib/riak/util/escape.rb +58 -17
  58. data/lib/riak/util/headers.rb +2 -15
  59. data/lib/riak/util/multipart.rb +0 -13
  60. data/lib/riak/util/multipart/stream_parser.rb +0 -13
  61. data/lib/riak/util/tcp_socket_extensions.rb +1 -14
  62. data/lib/riak/util/translation.rb +0 -13
  63. data/lib/riak/version.rb +3 -0
  64. data/lib/riak/walk_spec.rb +0 -13
  65. data/riak-client.gemspec +27 -47
  66. data/spec/fixtures/multipart-with-marked-tombstones.txt +17 -0
  67. data/spec/fixtures/multipart-with-unmarked-tombstone.txt +16 -0
  68. data/spec/integration/riak/cache_store_spec.rb +2 -40
  69. data/spec/integration/riak/cluster_spec.rb +88 -0
  70. data/spec/integration/riak/http_backends_spec.rb +6 -30
  71. data/spec/integration/riak/node_spec.rb +184 -0
  72. data/spec/integration/riak/protobuffs_backends_spec.rb +2 -26
  73. data/spec/integration/riak/test_server_spec.rb +31 -167
  74. data/spec/riak/beefcake_protobuffs_backend_spec.rb +5 -4
  75. data/spec/riak/bucket_spec.rb +26 -36
  76. data/spec/riak/client_spec.rb +44 -38
  77. data/spec/riak/escape_spec.rb +56 -30
  78. data/spec/riak/excon_backend_spec.rb +4 -17
  79. data/spec/riak/headers_spec.rb +1 -14
  80. data/spec/riak/http_backend/configuration_spec.rb +211 -34
  81. data/spec/riak/http_backend/object_methods_spec.rb +52 -18
  82. data/spec/riak/http_backend/transport_methods_spec.rb +5 -38
  83. data/spec/riak/http_backend_spec.rb +84 -78
  84. data/spec/riak/link_spec.rb +19 -18
  85. data/spec/riak/map_reduce/filter_builder_spec.rb +1 -14
  86. data/spec/riak/map_reduce/phase_spec.rb +1 -14
  87. data/spec/riak/map_reduce_spec.rb +141 -43
  88. data/spec/riak/multipart_spec.rb +1 -14
  89. data/spec/riak/net_http_backend_spec.rb +2 -15
  90. data/spec/riak/robject_spec.rb +129 -97
  91. data/spec/riak/search_spec.rb +45 -62
  92. data/spec/riak/serializers_spec.rb +93 -0
  93. data/spec/riak/stamp_spec.rb +54 -0
  94. data/spec/riak/stream_parser_spec.rb +3 -16
  95. data/spec/riak/walk_spec_spec.rb +1 -14
  96. data/spec/spec_helper.rb +22 -27
  97. data/spec/support/http_backend_implementation_examples.rb +49 -79
  98. data/spec/support/integration_setup.rb +10 -0
  99. data/spec/support/mock_server.rb +0 -14
  100. data/spec/support/mocks.rb +0 -13
  101. data/spec/support/test_server.rb +30 -0
  102. data/spec/support/test_server.yml.example +14 -2
  103. data/spec/support/unified_backend_examples.rb +36 -27
  104. metadata +100 -31
  105. data/lib/riak/client/curb_backend.rb +0 -89
  106. data/spec/riak/curb_backend_spec.rb +0 -76
@@ -0,0 +1,32 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage/**/*
18
+ rdoc/**/*
19
+ pkg/**/*
20
+
21
+ ## PROJECT::SPECIFIC
22
+ doc
23
+ .yardoc
24
+ .bundle
25
+ spec/support/test_server.yml
26
+ Gemfile.lock
27
+ **/bin
28
+ *.rbc
29
+ .rvmrc
30
+
31
+ .ripplenode
32
+ .riaktest
data/Gemfile CHANGED
@@ -1,25 +1,31 @@
1
1
  source :rubygems
2
2
 
3
- gem 'i18n'
4
- gem 'builder'
5
- gem 'rspec', "~>2.4.0"
6
- gem 'fakeweb', ">=1.2"
7
- gem 'rack', '>=1.0'
8
- gem 'rake'
3
+ gemspec
9
4
  gem 'bundler'
10
- gem 'excon', "~>0.6.1"
11
- gem 'beefcake', '~>0.3.1'
5
+ gem 'guard-rspec'
6
+ gem 'rb-fsevent'
7
+ gem 'growl'
12
8
 
13
9
  platforms :mri do
14
- gem 'curb', '>=0.6'
15
10
  gem 'yajl-ruby'
16
11
  end
17
12
 
18
13
  platforms :jruby do
19
- gem 'json'
20
14
  gem 'jruby-openssl'
21
15
  end
22
16
 
23
17
  group :integration do
24
- gem 'activesupport', '~>3.0'
18
+ if ENV['RAILS31']
19
+ gem 'activesupport', '~> 3.1.0'
20
+ else
21
+ gem 'activesupport', '~> 3.0.10'
22
+ end
23
+ end
24
+
25
+ platforms :mri_18, :jruby do
26
+ gem 'ruby-debug'
27
+ end
28
+
29
+ platforms :mri_19 do
30
+ gem 'ruby-debug19'
25
31
  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 => '--profile --tag "~slow"', :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
data/Rakefile CHANGED
@@ -1,50 +1,22 @@
1
1
  require 'rubygems'
2
- require 'rake/gempackagetask'
3
-
4
- gemspec = Gem::Specification.new do |gem|
5
- gem.name = "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.4.0"
13
- gem.add_development_dependency "fakeweb", ">=1.2"
14
- gem.add_development_dependency "rack", ">=1.0"
15
- gem.add_development_dependency "curb", ">=0.6"
16
- gem.add_development_dependency "excon", "~>0.5.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
-
21
- files = FileList["**/*"]
22
- # Editor and O/S files
23
- files.exclude ".DS_Store", "*~", /#/, "*.swp", "*.tmproj", "tmtags"
24
- # Generated artifacts
25
- files.exclude "coverage/*", "rdoc/*", "pkg/*", "doc/*", ".bundle", "*.rbc", ".rvmrc", ".watchr", ".rspec"
26
- # Project-specific
27
- files.exclude "Gemfile.lock", %r{spec/support/test_server.yml$}, "bin"
28
- # Remove directories
29
- files.exclude {|d| File.directory?(d) }
30
-
31
- gem.files = files.to_a
2
+ require 'rubygems/package_task'
3
+ require 'rspec/core'
4
+ require 'rspec/core/rake_task'
32
5
 
33
- gem.test_files = gem.files.grep(/spec\/.*_spec\.rb$/)
6
+ def gemspec
7
+ $riakclient_gemspec ||= Gem::Specification.load("riak-client.gemspec")
34
8
  end
35
9
 
36
- # Gem packaging tasks
37
- Rake::GemPackageTask.new(gemspec) do |pkg|
10
+ Gem::PackageTask.new(gemspec) do |pkg|
38
11
  pkg.need_zip = false
39
12
  pkg.need_tar = false
40
13
  end
41
14
 
42
15
  task :gem => :gemspec
43
16
 
44
- desc %{Build the gemspec file.}
17
+ desc %{Validate the gemspec file.}
45
18
  task :gemspec do
46
19
  gemspec.validate
47
- File.open("#{gemspec.name}.gemspec", 'w'){|f| f.write gemspec.to_ruby }
48
20
  end
49
21
 
50
22
  desc %{Release the gem to RubyGems.org}
@@ -52,24 +24,26 @@ task :release => :gem do
52
24
  system "gem push pkg/#{gemspec.name}-#{gemspec.version}.gem"
53
25
  end
54
26
 
55
- require 'rspec/core'
56
- require 'rspec/core/rake_task'
57
-
58
27
  desc "Run Unit Specs Only"
59
28
  RSpec::Core::RakeTask.new(:spec) do |spec|
60
- spec.pattern = "spec/riak/**/*_spec.rb"
29
+ spec.rspec_opts = %w[--profile --tag ~integration]
61
30
  end
62
31
 
63
32
  namespace :spec do
64
- desc "Run Integration Specs Only"
33
+ desc "Run Integration Specs Only (without explicitly slow specs)"
65
34
  RSpec::Core::RakeTask.new(:integration) do |spec|
66
- spec.pattern = "spec/integration/**/*_spec.rb"
35
+ spec.rspec_opts = %w[--profile --tag '~slow' --tag integration]
67
36
  end
68
37
 
69
- desc "Run All Specs"
38
+ desc "Run All Specs (without explicitly slow specs)"
70
39
  RSpec::Core::RakeTask.new(:all) do |spec|
71
- spec.pattern = "spec/**/*_spec.rb"
40
+ spec.rspec_opts = %w[--profile --tag '~slow']
72
41
  end
73
42
  end
74
43
 
75
- task :default => :spec
44
+ desc "Run All Specs (including slow specs)"
45
+ RSpec::Core::RakeTask.new(:ci) do |spec|
46
+ spec.rspec_opts = %w[--profile]
47
+ end
48
+
49
+ task :default => :ci
@@ -1,8 +1,8 @@
1
1
  %% -------------------------------------------------------------------
2
2
  %%
3
- %% riak_kv_test_backend: storage engine based on ETS tables
3
+ %% riak_memory_backend: storage engine using ETS tables
4
4
  %%
5
- %% Copyright (c) 2007-2010 Basho Technologies, Inc. All Rights Reserved.
5
+ %% Copyright (c) 2007-2011 Basho Technologies, Inc. All Rights Reserved.
6
6
  %%
7
7
  %% This file is provided to you under the Apache License,
8
8
  %% Version 2.0 (the "License"); you may not use this file
@@ -20,170 +20,503 @@
20
20
  %%
21
21
  %% -------------------------------------------------------------------
22
22
 
23
- % @doc riak_kv_test_backend is a Riak storage backend using ets that
24
- % exposes a reset function for efficiently clearing stored data.
23
+ %% @doc riak_kv_memory_backend is a Riak storage backend that uses ets
24
+ %% tables to store all data in memory.
25
+ %%
26
+ %% === Configuration Options ===
27
+ %%
28
+ %% The following configuration options are available for the memory backend.
29
+ %% The options should be specified in the `memory_backend' section of your
30
+ %% app.config file.
31
+ %%
32
+ %% <ul>
33
+ %% <li>`ttl' - The time in seconds that an object should live before being expired.</li>
34
+ %% <li>`max_memory' - The amount of memory in megabytes to limit the backend to.</li>
35
+ %% </ul>
36
+ %%
25
37
 
26
38
  -module(riak_kv_test_backend).
27
39
  -behavior(riak_kv_backend).
28
- -behavior(gen_server).
40
+
41
+ %% KV Backend API
42
+ -export([api_version/0,
43
+ start/2,
44
+ stop/1,
45
+ get/3,
46
+ put/5,
47
+ delete/4,
48
+ drop/1,
49
+ fold_buckets/4,
50
+ fold_keys/4,
51
+ fold_objects/4,
52
+ is_empty/1,
53
+ status/1,
54
+ callback/3,
55
+ reset/0]).
56
+
29
57
  -ifdef(TEST).
30
58
  -include_lib("eunit/include/eunit.hrl").
31
59
  -endif.
32
- -export([start/2,stop/1,get/2,put/3,list/1,list_bucket/2,delete/2,
33
- is_empty/1, drop/1, fold/3, callback/3, reset/0]).
34
60
 
35
- -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
36
- terminate/2, code_change/3]).
61
+ -define(API_VERSION, 1).
62
+ -define(CAPABILITIES, [async_fold]).
63
+
64
+ -record(state, {data_ref :: integer() | atom(),
65
+ time_ref :: integer() | atom(),
66
+ max_memory :: undefined | integer(),
67
+ used_memory=0 :: integer(),
68
+ ttl :: integer()}).
37
69
 
70
+ -type state() :: #state{}.
71
+ -type config() :: [].
38
72
 
39
- % @type state() = term().
40
- -record(state, {t, p}).
73
+ %% ===================================================================
74
+ %% Public API
75
+ %% ===================================================================
41
76
 
42
- % @spec start(Partition :: integer(), Config :: proplist()) ->
43
- % {ok, state()} | {{error, Reason :: term()}, state()}
44
- start(Partition, _Config) ->
45
- gen_server:start_link(?MODULE, [Partition], []).
77
+ %% TestServer reset
46
78
 
47
- % @spec reset() -> ok | {error, timeout}
79
+ -spec reset() -> ok | {error, timeout}.
48
80
  reset() ->
49
- Pids = lists:foldl(fun(Item, Acc) ->
50
- case lists:prefix("test_backend", atom_to_list(Item)) of
51
- true -> [whereis(Item)|Acc];
52
- _ -> Acc
53
- end
54
- end, [], registered()),
55
- [gen_server:cast(Pid,{reset, self()})|| Pid <- Pids],
56
- receive_reset(Pids).
57
-
58
- receive_reset([]) -> ok;
59
- receive_reset(Pids) ->
60
- receive
61
- {reset, Pid} ->
62
- receive_reset(lists:delete(Pid, Pids))
63
- after 1000 ->
64
- {error, timeout}
65
- end.
81
+ {ok, Ring} = riak_core_ring_manager:get_my_ring(),
82
+ [ ets:delete_all_objects(list_to_atom("kv" ++ integer_to_list(P))) ||
83
+ P <- riak_core_ring:my_indices(Ring) ],
84
+ ok.
66
85
 
67
- %% @private
68
- init([Partition]) ->
69
- PName = list_to_atom("test_backend" ++ integer_to_list(Partition)),
70
- P = list_to_atom(integer_to_list(Partition)),
71
- register(PName, self()),
72
- {ok, #state{t=ets:new(P,[]), p=P}}.
86
+ %% KV Backend API
73
87
 
74
- %% @private
75
- handle_cast({reset,From}, State) ->
76
- ets:delete_all_objects(State#state.t),
77
- From ! {reset, self()},
78
- {noreply, State};
79
- handle_cast(_, State) -> {noreply, State}.
88
+ %% @doc Return the major version of the
89
+ %% current API and a capabilities list.
90
+ -spec api_version() -> {integer(), [atom()]}.
91
+ api_version() ->
92
+ {?API_VERSION, ?CAPABILITIES}.
80
93
 
81
- %% @private
82
- handle_call(stop,_From,State) -> {reply, srv_stop(State), State};
83
- handle_call({get,BKey},_From,State) -> {reply, srv_get(State,BKey), State};
84
- handle_call({put,BKey,Val},_From,State) ->
85
- {reply, srv_put(State,BKey,Val),State};
86
- handle_call({delete,BKey},_From,State) -> {reply, srv_delete(State,BKey),State};
87
- handle_call(list,_From,State) -> {reply, srv_list(State), State};
88
- handle_call({list_bucket,Bucket},_From,State) ->
89
- {reply, srv_list_bucket(State, Bucket), State};
90
- handle_call(is_empty, _From, State) ->
91
- {reply, ets:info(State#state.t, size) =:= 0, State};
92
- handle_call(drop, _From, State) ->
93
- ets:delete(State#state.t),
94
- {reply, ok, State};
95
- handle_call({fold, Fun0, Acc}, _From, State) ->
96
- Fun = fun({{B,K}, V}, AccIn) -> Fun0({B,K}, V, AccIn) end,
97
- Reply = ets:foldl(Fun, Acc, State#state.t),
98
- {reply, Reply, State}.
99
-
100
- % @spec stop(state()) -> ok | {error, Reason :: term()}
101
- stop(SrvRef) -> gen_server:call(SrvRef,stop).
102
- srv_stop(State) ->
103
- true = ets:delete(State#state.t),
94
+ %% @doc Start the memory backend
95
+ -spec start(integer(), config()) -> {ok, state()}.
96
+ start(Partition, Config) ->
97
+ TTL = config_value(ttl, Config),
98
+ MemoryMB = config_value(max_memory, Config),
99
+ case MemoryMB of
100
+ undefined ->
101
+ MaxMemory = undefined,
102
+ TimeRef = undefined;
103
+ _ ->
104
+ MaxMemory = MemoryMB * 1024 * 1024,
105
+ TimeRef = ets:new(list_to_atom(integer_to_list(Partition)), [ordered_set])
106
+ end,
107
+ DataRef = ets:new(list_to_atom("kv" ++ integer_to_list(Partition)), [named_table, public]),
108
+ {ok, #state{data_ref=DataRef,
109
+ max_memory=MaxMemory,
110
+ time_ref=TimeRef,
111
+ ttl=TTL}}.
112
+
113
+ %% @doc Stop the memory backend
114
+ -spec stop(state()) -> ok.
115
+ stop(#state{data_ref=DataRef,
116
+ max_memory=MaxMemory,
117
+ time_ref=TimeRef}) ->
118
+ catch ets:delete(DataRef),
119
+ case MaxMemory of
120
+ undefined ->
121
+ ok;
122
+ _ ->
123
+ catch ets:delete(TimeRef)
124
+ end,
104
125
  ok.
105
126
 
106
- % get(state(), riak_object:bkey()) ->
107
- % {ok, Val :: binary()} | {error, Reason :: term()}
108
- % key must be 160b
109
- get(SrvRef, BKey) -> gen_server:call(SrvRef,{get,BKey}).
110
- srv_get(State, BKey) ->
111
- case ets:lookup(State#state.t,BKey) of
112
- [] -> {error, notfound};
113
- [{BKey,Val}] -> {ok, Val};
114
- Err -> {error, Err}
127
+ %% @doc Retrieve an object from the memory backend
128
+ -spec get(riak_object:bucket(), riak_object:key(), state()) ->
129
+ {ok, any(), state()} |
130
+ {ok, not_found, state()} |
131
+ {error, term(), state()}.
132
+ get(Bucket, Key, State=#state{data_ref=DataRef,
133
+ ttl=TTL}) ->
134
+ case ets:lookup(DataRef, {Bucket, Key}) of
135
+ [] -> {error, not_found, State};
136
+ [{{Bucket, Key}, {{ts, Timestamp}, Val}}] ->
137
+ case exceeds_ttl(Timestamp, TTL) of
138
+ true ->
139
+ delete(Bucket, Key, undefined, State),
140
+ {error, not_found, State};
141
+ false ->
142
+ {ok, Val, State}
143
+ end;
144
+ [{{Bucket, Key}, Val}] ->
145
+ {ok, Val, State};
146
+ Error ->
147
+ {error, Error, State}
115
148
  end.
116
149
 
117
- % put(state(), riak_object:bkey(), Val :: binary()) ->
118
- % ok | {error, Reason :: term()}
119
- % key must be 160b
120
- put(SrvRef, BKey, Val) -> gen_server:call(SrvRef,{put,BKey,Val}).
121
- srv_put(State,BKey,Val) ->
122
- true = ets:insert(State#state.t, {BKey,Val}),
123
- ok.
150
+ %% @doc Insert an object into the memory backend.
151
+ %% NOTE: The memory backend does not currently
152
+ %% support secondary indexing and the _IndexSpecs
153
+ %% parameter is ignored.
154
+ -type index_spec() :: {add, Index, SecondaryKey} | {remove, Index, SecondaryKey}.
155
+ -spec put(riak_object:bucket(), riak_object:key(), [index_spec()], binary(), state()) ->
156
+ {ok, state()} |
157
+ {error, term(), state()}.
158
+ put(Bucket, PrimaryKey, _IndexSpecs, Val, State=#state{data_ref=DataRef,
159
+ max_memory=MaxMemory,
160
+ time_ref=TimeRef,
161
+ ttl=TTL,
162
+ used_memory=UsedMemory}) ->
163
+ Now = now(),
164
+ case TTL of
165
+ undefined ->
166
+ Val1 = Val;
167
+ _ ->
168
+ Val1 = {{ts, Now}, Val}
169
+ end,
170
+ case do_put(Bucket, PrimaryKey, Val1, DataRef) of
171
+ {ok, Size} ->
172
+ %% If the memory is capped update timestamp table
173
+ %% and check if the memory usage is over the cap.
174
+ case MaxMemory of
175
+ undefined ->
176
+ UsedMemory1 = UsedMemory;
177
+ _ ->
178
+ time_entry(Bucket, PrimaryKey, Now, TimeRef),
179
+ Freed = trim_data_table(MaxMemory,
180
+ UsedMemory + Size,
181
+ DataRef,
182
+ TimeRef,
183
+ 0),
184
+ UsedMemory1 = UsedMemory + Size - Freed
185
+ end,
186
+ {ok, State#state{used_memory=UsedMemory1}};
187
+ {error, Reason} ->
188
+ {error, Reason, State}
189
+ end.
124
190
 
125
- % delete(state(), riak_object:bkey()) ->
126
- % ok | {error, Reason :: term()}
127
- % key must be 160b
128
- delete(SrvRef, BKey) -> gen_server:call(SrvRef,{delete,BKey}).
129
- srv_delete(State, BKey) ->
130
- true = ets:delete(State#state.t, BKey),
131
- ok.
191
+ %% @doc Delete an object from the memory backend
192
+ %% NOTE: The memory backend does not currently
193
+ %% support secondary indexing and the _IndexSpecs
194
+ %% parameter is ignored.
195
+ -spec delete(riak_object:bucket(), riak_object:key(), [index_spec()], state()) ->
196
+ {ok, state()}.
197
+ delete(Bucket, Key, _IndexSpecs, State=#state{data_ref=DataRef,
198
+ time_ref=TimeRef,
199
+ used_memory=UsedMemory}) ->
200
+ case TimeRef of
201
+ undefined ->
202
+ UsedMemory1 = UsedMemory;
203
+ _ ->
204
+ %% Lookup the object so we can delete its
205
+ %% entry from the time table and account
206
+ %% for the memory used.
207
+ [Object] = ets:lookup(DataRef, {Bucket, Key}),
208
+ case Object of
209
+ {_, {{ts, Timestamp}, _}} ->
210
+ ets:delete(TimeRef, Timestamp),
211
+ UsedMemory1 = UsedMemory - object_size(Object);
212
+ _ ->
213
+ UsedMemory1 = UsedMemory
214
+ end
215
+ end,
216
+ ets:delete(DataRef, {Bucket, Key}),
217
+ {ok, State#state{used_memory=UsedMemory1}}.
132
218
 
133
- % list(state()) -> [riak_object:bkey()]
134
- list(SrvRef) -> gen_server:call(SrvRef,list).
135
- srv_list(State) ->
136
- MList = ets:match(State#state.t,{'$1','_'}),
137
- list(MList,[]).
138
- list([],Acc) -> Acc;
139
- list([[K]|Rest],Acc) -> list(Rest,[K|Acc]).
140
-
141
- % list_bucket(term(), Bucket :: riak_object:bucket()) -> [Key :: binary()]
142
- list_bucket(SrvRef, Bucket) ->
143
- gen_server:call(SrvRef,{list_bucket, Bucket}).
144
- srv_list_bucket(State, {filter, Bucket, Fun}) ->
145
- MList = lists:filter(Fun, ets:match(State#state.t,{{Bucket,'$1'},'_'})),
146
- list(MList,[]);
147
- srv_list_bucket(State, Bucket) ->
148
- case Bucket of
149
- '_' -> MatchSpec = {{'$1','_'},'_'};
150
- _ -> MatchSpec = {{Bucket,'$1'},'_'}
219
+ %% @doc Fold over all the buckets.
220
+ -spec fold_buckets(riak_kv_backend:fold_buckets_fun(),
221
+ any(),
222
+ [],
223
+ state()) -> {ok, any()}.
224
+ fold_buckets(FoldBucketsFun, Acc, Opts, #state{data_ref=DataRef}) ->
225
+ FoldFun = fold_buckets_fun(FoldBucketsFun),
226
+ case lists:member(async_fold, Opts) of
227
+ true ->
228
+ BucketFolder =
229
+ fun() ->
230
+ {Acc0, _} = ets:foldl(FoldFun, {Acc, sets:new()}, DataRef),
231
+ Acc0
232
+ end,
233
+ {async, BucketFolder};
234
+ false ->
235
+ {Acc0, _} = ets:foldl(FoldFun, {Acc, sets:new()}, DataRef),
236
+ {ok, Acc0}
237
+ end.
238
+
239
+ %% @doc Fold over all the keys for one or all buckets.
240
+ -spec fold_keys(riak_kv_backend:fold_keys_fun(),
241
+ any(),
242
+ [{atom(), term()}],
243
+ state()) -> {ok, term()} | {async, fun()}.
244
+ fold_keys(FoldKeysFun, Acc, Opts, #state{data_ref=DataRef}) ->
245
+ Bucket = proplists:get_value(bucket, Opts),
246
+ FoldFun = fold_keys_fun(FoldKeysFun, Bucket),
247
+ case lists:member(async_fold, Opts) of
248
+ true ->
249
+ {async, get_folder(FoldFun, Acc, DataRef)};
250
+ false ->
251
+ Acc0 = ets:foldl(FoldFun, Acc, DataRef),
252
+ {ok, Acc0}
253
+ end.
254
+
255
+ %% @doc Fold over all the objects for one or all buckets.
256
+ -spec fold_objects(riak_kv_backend:fold_objects_fun(),
257
+ any(),
258
+ [{atom(), term()}],
259
+ state()) -> {ok, any()} | {async, fun()}.
260
+ fold_objects(FoldObjectsFun, Acc, Opts, #state{data_ref=DataRef}) ->
261
+ Bucket = proplists:get_value(bucket, Opts),
262
+ FoldFun = fold_objects_fun(FoldObjectsFun, Bucket),
263
+ case lists:member(async_fold, Opts) of
264
+ true ->
265
+ {async, get_folder(FoldFun, Acc, DataRef)};
266
+ false ->
267
+ Acc0 = ets:foldl(FoldFun, Acc, DataRef),
268
+ {ok, Acc0}
269
+ end.
270
+
271
+ %% @doc Delete all objects from this memory backend
272
+ -spec drop(state()) -> {ok, state()}.
273
+ drop(State=#state{data_ref=DataRef,
274
+ time_ref=TimeRef}) ->
275
+ ets:delete_all_objects(DataRef),
276
+ case TimeRef of
277
+ undefined ->
278
+ ok;
279
+ _ ->
280
+ ets:delete_all_objects(TimeRef)
151
281
  end,
152
- MList = ets:match(State#state.t,MatchSpec),
153
- list(MList,[]).
282
+ {ok, State}.
283
+
284
+ %% @doc Returns true if this memory backend contains any
285
+ %% non-tombstone values; otherwise returns false.
286
+ -spec is_empty(state()) -> boolean().
287
+ is_empty(#state{data_ref=DataRef}) ->
288
+ ets:info(DataRef, size) =:= 0.
154
289
 
155
- is_empty(SrvRef) -> gen_server:call(SrvRef, is_empty).
290
+ %% @doc Get the status information for this memory backend
291
+ -spec status(state()) -> [{atom(), term()}].
292
+ status(#state{data_ref=DataRef,
293
+ time_ref=TimeRef}) ->
294
+ DataStatus = ets:info(DataRef),
295
+ case TimeRef of
296
+ undefined ->
297
+ [{data_table_status, DataStatus}];
298
+ _ ->
299
+ TimeStatus = ets:info(TimeRef),
300
+ [{data_table_status, DataStatus},
301
+ {time_table_status, TimeStatus}]
302
+ end.
156
303
 
157
- drop(SrvRef) -> gen_server:call(SrvRef, drop).
304
+ %% @doc Register an asynchronous callback
305
+ -spec callback(reference(), any(), state()) -> {ok, state()}.
306
+ callback(_Ref, _Msg, State) ->
307
+ {ok, State}.
158
308
 
159
- fold(SrvRef, Fun, Acc0) -> gen_server:call(SrvRef, {fold, Fun, Acc0}, infinity).
309
+ %% ===================================================================
310
+ %% Internal functions
311
+ %% ===================================================================
160
312
 
161
- %% Ignore callbacks for other backends so multi backend works
162
- callback(_State, _Ref, _Msg) ->
163
- ok.
313
+ %% @TODO Some of these implementations may be suboptimal.
314
+ %% Need to do some measuring and testing to refine the
315
+ %% implementations.
164
316
 
165
317
  %% @private
166
- handle_info(_Msg, State) -> {noreply, State}.
318
+ %% Return a function to fold over the buckets on this backend
319
+ fold_buckets_fun(FoldBucketsFun) ->
320
+ fun({{Bucket, _}, _}, {Acc, BucketSet}) ->
321
+ case sets:is_element(Bucket, BucketSet) of
322
+ true ->
323
+ {Acc, BucketSet};
324
+ false ->
325
+ {FoldBucketsFun(Bucket, Acc),
326
+ sets:add_element(Bucket, BucketSet)}
327
+ end
328
+ end.
167
329
 
168
330
  %% @private
169
- terminate(_Reason, _State) -> ok.
331
+ %% Return a function to fold over keys on this backend
332
+ fold_keys_fun(FoldKeysFun, undefined) ->
333
+ fun({{Bucket, Key}, _}, Acc) ->
334
+ FoldKeysFun(Bucket, Key, Acc)
335
+ end;
336
+ fold_keys_fun(FoldKeysFun, Bucket) ->
337
+ fun({{B, Key}, _}, Acc) ->
338
+ case B =:= Bucket of
339
+ true ->
340
+ FoldKeysFun(Bucket, Key, Acc);
341
+ false ->
342
+ Acc
343
+ end
344
+ end.
170
345
 
171
346
  %% @private
172
- code_change(_OldVsn, State, _Extra) -> {ok, State}.
347
+ %% Return a function to fold over keys on this backend
348
+ fold_objects_fun(FoldObjectsFun, undefined) ->
349
+ fun({{Bucket, Key}, Value}, Acc) ->
350
+ FoldObjectsFun(Bucket, Key, Value, Acc)
351
+ end;
352
+ fold_objects_fun(FoldObjectsFun, Bucket) ->
353
+ fun({{B, Key}, Value}, Acc) ->
354
+ case B =:= Bucket of
355
+ true ->
356
+ FoldObjectsFun(Bucket, Key, Value, Acc);
357
+ false ->
358
+ Acc
359
+ end
360
+ end.
361
+
362
+ %% @private
363
+ get_folder(FoldFun, Acc, DataRef) ->
364
+ fun() ->
365
+ ets:foldl(FoldFun, Acc, DataRef)
366
+ end.
367
+
368
+ %% @private
369
+ do_put(Bucket, Key, Val, Ref) ->
370
+ Object = {{Bucket, Key}, Val},
371
+ true = ets:insert(Ref, Object),
372
+ {ok, object_size(Object)}.
373
+
374
+ %% @private
375
+ config_value(Key, Config) ->
376
+ config_value(Key, Config, undefined).
377
+
378
+ %% @private
379
+ config_value(Key, Config, Default) ->
380
+ case proplists:get_value(Key, Config) of
381
+ undefined ->
382
+ app_helper:get_env(memory_backend, Key, Default);
383
+ Value ->
384
+ Value
385
+ end.
386
+
387
+ %% Check if this timestamp is past the ttl setting.
388
+ exceeds_ttl(Timestamp, TTL) ->
389
+ Diff = (timer:now_diff(now(), Timestamp) / 1000 / 1000),
390
+ Diff > TTL.
391
+
392
+ %% @private
393
+ time_entry(Bucket, Key, Now, TimeRef) ->
394
+ ets:insert(TimeRef, {Now, {Bucket, Key}}).
395
+
396
+ %% @private
397
+ %% @doc Dump some entries if the max memory size has
398
+ %% been breached.
399
+ trim_data_table(MaxMemory, UsedMemory, _, _, Freed) when
400
+ (UsedMemory - Freed) =< MaxMemory ->
401
+ Freed;
402
+ trim_data_table(MaxMemory, UsedMemory, DataRef, TimeRef, Freed) ->
403
+ %% Delete the oldest object
404
+ OldestSize = delete_oldest(DataRef, TimeRef),
405
+ trim_data_table(MaxMemory,
406
+ UsedMemory,
407
+ DataRef,
408
+ TimeRef,
409
+ Freed + OldestSize).
410
+
411
+ %% @private
412
+ delete_oldest(DataRef, TimeRef) ->
413
+ OldestTime = ets:first(TimeRef),
414
+ case OldestTime of
415
+ '$end_of_table' ->
416
+ 0;
417
+ _ ->
418
+ OldestKey = ets:lookup_element(TimeRef, OldestTime, 2),
419
+ ets:delete(TimeRef, OldestTime),
420
+ case ets:lookup(DataRef, OldestKey) of
421
+ [] ->
422
+ delete_oldest(DataRef, TimeRef);
423
+ [Object] ->
424
+ ets:delete(DataRef, OldestKey),
425
+ object_size(Object)
426
+ end
427
+ end.
428
+
429
+ %% @private
430
+ object_size(Object) ->
431
+ case Object of
432
+ {{Bucket, Key}, {{ts, _}, Val}} ->
433
+ ok;
434
+ {{Bucket, Key}, Val} ->
435
+ ok
436
+ end,
437
+ size(Bucket) + size(Key) + size(Val).
438
+
439
+ %% ===================================================================
440
+ %% EUnit tests
441
+ %% ===================================================================
173
442
 
174
- %%
175
- %% Test
176
- %%
177
443
  -ifdef(TEST).
178
444
 
179
- % @private
180
- simple_test() ->
445
+ simple_test_() ->
181
446
  riak_kv_backend:standard_test(?MODULE, []).
182
447
 
183
- -ifdef(EQC).
448
+ ttl_test_() ->
449
+ Config = [{ttl, 15}],
450
+ {ok, State} = start(42, Config),
451
+
452
+ Bucket = <<"Bucket">>,
453
+ Key = <<"Key">>,
454
+ Value = <<"Value">>,
455
+
456
+ [
457
+ %% Put an object
458
+ ?_assertEqual({ok, State}, put(Bucket, Key, [], Value, State)),
459
+ %% Wait 1 second to access it
460
+ ?_assertEqual(ok, timer:sleep(1000)),
461
+ ?_assertEqual({ok, Value, State}, get(Bucket, Key, State)),
462
+ %% Wait 3 seconds and access it again
463
+ ?_assertEqual(ok, timer:sleep(3000)),
464
+ ?_assertEqual({ok, Value, State}, get(Bucket, Key, State)),
465
+ %% Wait 15 seconds and it should expire
466
+ {timeout, 30000, ?_assertEqual(ok, timer:sleep(15000))},
467
+ %% This time it should be gone
468
+ ?_assertEqual({error, not_found, State}, get(Bucket, Key, State))
469
+ ].
470
+
184
471
  %% @private
185
- eqc_test() ->
186
- ?assertEqual(true, backend_eqc:test(?MODULE, true)).
472
+ max_memory_test_() ->
473
+ %% Set max size to 1.5kb
474
+ Config = [{max_memory, 1.5 * (1 / 1024)}],
475
+ {ok, State} = start(42, Config),
476
+
477
+ Bucket = <<"Bucket">>,
478
+ Key1 = <<"Key1">>,
479
+ Value1 = list_to_binary(string:copies("1", 1024)),
480
+ Key2 = <<"Key2">>,
481
+ Value2 = list_to_binary(string:copies("2", 1024)),
482
+
483
+ %% Write Key1 to the datastore
484
+ {ok, State1} = put(Bucket, Key1, [], Value1, State),
485
+ timer:sleep(timer:seconds(1)),
486
+ %% Write Key2 to the datastore
487
+ {ok, State2} = put(Bucket, Key2, [], Value2, State1),
488
+
489
+ [
490
+ %% Key1 should be kicked out
491
+ ?_assertEqual({error, not_found, State2}, get(Bucket, Key1, State2)),
492
+ %% Key2 should still be present
493
+ ?_assertEqual({ok, Value2, State2}, get(Bucket, Key2, State2))
494
+ ].
495
+
496
+ -ifdef(EQC).
497
+
498
+ eqc_test_() ->
499
+ {spawn,
500
+ [{inorder,
501
+ [{setup,
502
+ fun setup/0,
503
+ fun cleanup/1,
504
+ [
505
+ {timeout, 60000,
506
+ [?_assertEqual(true,
507
+ backend_eqc:test(?MODULE, true))]}
508
+ ]}]}]}.
509
+
510
+ setup() ->
511
+ application:load(sasl),
512
+ application:set_env(sasl, sasl_error_logger, {file, "riak_kv_memory_backend_eqc_sasl.log"}),
513
+ error_logger:tty(false),
514
+ error_logger:logfile({open, "riak_kv_memory_backend_eqc.log"}),
515
+ ok.
516
+
517
+ cleanup(_) ->
518
+ ok.
187
519
 
188
520
  -endif. % EQC
521
+
189
522
  -endif. % TEST