riak-client 0.9.8 → 1.0.0.beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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