File Coverage

blib/lib/Data/Riak/Fast/Bucket.pm
Criterion Covered Total %
statement 27 100 27.0
branch 0 38 0.0
condition 0 24 0.0
subroutine 9 23 39.1
pod 8 14 57.1
total 44 199 22.1


line stmt bran cond sub pod time code
1             package Data::Riak::Fast::Bucket;
2             # ABSTRACT: A Data::Riak::Fast bucket, used for storing keys and values.
3              
4 22     22   880 use Mouse;
  22         34442  
  22         126  
5              
6 22     22   11951 use Data::Riak::Fast::Link;
  22         43  
  22         517  
7 22     22   14597 use Data::Riak::Fast::Util::MapCount;
  22         60  
  22         892  
8 22     22   20650 use Data::Riak::Fast::Util::ReduceCount;
  22         64  
  22         849  
9              
10 22     22   12937 use Data::Riak::Fast::MapReduce;
  22         122  
  22         805  
11 22     22   143 use Data::Riak::Fast::MapReduce::Phase::Reduce;
  22         47  
  22         440  
12              
13 22     22   25107 use HTTP::Headers::ActionPack::LinkList;
  22         44489  
  22         732  
14              
15 22     22   138 use JSON::XS qw/decode_json encode_json/;
  22         42  
  22         35467  
16              
17             with 'Data::Riak::Fast::Role::HasRiak';
18              
19             =head1 DESCRIPTION
20              
21             Data::Riak::Fast::Bucket is the primary interface that most people will use for Riak.
22             Adding and removing keys and values, adding links, querying keys; all of those
23             happen here.
24              
25             =head1 SYNOPSIS
26              
27             my $bucket = Data::Riak::Fast::Bucket->new({
28             name => 'my_bucket',
29             riak => $riak
30             });
31              
32             # Sets the value of "foo" to "bar", in my_bucket.
33             $bucket->add('foo', 'bar');
34              
35             # Gets the Result object for "foo" in my_bucket.
36             my $foo = $bucket->get('foo');
37              
38             # Returns "bar"
39             my $value = $foo->value;
40              
41             $bucket->create_alias({ key => 'foo', as => 'alias_to_foo' });
42             $bucket->create_alias({ key => 'foo', as => 'alias_to_foo', in => $another_bucket });
43              
44             # Returns "bar"
45             my $value = $bucket->resolve_alias('alias_to_foo');
46             my $value = $another_bucket->resolve_alias('alias_to_foo');
47              
48             $bucket->add('baz, 'value of baz', { links => [$bucket->create_link( riaktag => 'buddy', key =>'foo' )] });
49             my $resultset = $bucket->linkwalk('baz', [[ 'buddy', '_' ]]);
50             my $value = $resultset->first->value; # Will be "bar", the value of foo
51              
52             =cut
53              
54             has name => (
55             is => 'ro',
56             isa => 'Str',
57             required => 1
58             );
59              
60             =head1 METHOD
61             =head2 add ($key, $value, $opts)
62              
63             This will insert a key C<$key> into the bucket, with value C<$value>. The C<$opts>
64             can include links, allowed content types, or queries.
65              
66             =cut
67              
68             sub add {
69 0     0 0   my ($self, $key, $value, $opts) = @_;
70              
71 0   0       $opts ||= {};
72              
73 0           my $pack = HTTP::Headers::ActionPack::LinkList->new;
74 0 0         if($opts->{'links'}) {
75 0           foreach my $link (@{$opts->{'links'}}) {
  0            
76 0 0 0       if(blessed $link && $link->isa('Data::Riak::Fast::Link')) {
77 0           $pack->add($link->as_link_header);
78             }
79             else {
80 0           confess "Bad link type ($link)";
81             }
82             }
83             }
84              
85             # TODO:
86             # need to support other headers
87             # X-Riak-Vclock if the object already exists, the vector clock attached to the object when read.
88             # X-Riak-Meta-* - any additional metadata headers that should be stored with the object.
89             # see http://wiki.basho.com/HTTP-Store-Object.html
90             # - SL
91              
92 0 0         my $resultset = $self->riak->send_request({
    0          
    0          
93             method => 'PUT',
94             uri => sprintf('buckets/%s/keys/%s', $self->name, $key),
95             data => $value,
96             links => $pack,
97             (exists $opts->{'indexes'}
98             ? (indexes => $opts->{'indexes'})
99             : ()),
100             (exists $opts->{'content_type'}
101             ? (content_type => $opts->{'content_type'})
102             : ()),
103             (exists $opts->{'query'}
104             ? (query => $opts->{'query'})
105             : ()),
106             });
107              
108 0 0         return $resultset->first if $resultset;
109 0           return;
110             }
111              
112             =head2 remove ($key, $opts)
113              
114             This will remove a key C<$key> from the bucket.
115              
116             =cut
117              
118             sub remove {
119 0     0 1   my ($self, $key, $opts) = @_;
120              
121 0   0       $opts ||= {};
122              
123 0 0         return $self->riak->send_request({
124             method => 'DELETE',
125             uri => sprintf('buckets/%s/keys/%s', $self->name, $key),
126             (exists $opts->{'query'}
127             ? (query => $opts->{'query'})
128             : ()),
129             });
130             }
131              
132             =head2 get ($key, $opts)
133              
134             This will get a key C<$key> from the bucket, returning a L object.
135              
136             =cut
137              
138              
139             sub get {
140 0     0 1   my ($self, $key, $opts) = @_;
141              
142 0 0         die("This method requires a key") unless($key);
143              
144 0   0       $opts ||= {};
145              
146 0 0 0       confess "This method does not support multipart/mixed responses"
147             if exists $opts->{'accept'} && $opts->{'accept'} eq 'multipart/mixed';
148              
149 0 0         return $self->riak->send_request({
    0          
150             method => 'GET',
151             uri => sprintf('buckets/%s/keys/%s', $self->name, $key),
152             (exists $opts->{'accept'}
153             ? (accept => $opts->{'accept'})
154             : ()),
155             (exists $opts->{'query'}
156             ? (query => $opts->{'query'})
157             : ()),
158             })->first;
159             }
160              
161             =head2 list_keys
162              
163             List all the keys in the bucket. Warning: This is expensive, as it has to scan
164             every key in the system, so don't use it unless you mean it, and know what you're
165             doing.
166              
167             =cut
168              
169              
170             sub list_keys {
171 0     0 1   my $self = shift;
172              
173 0           my $result = $self->riak->send_request({
174             method => 'GET',
175             uri => sprintf('buckets/%s/keys', $self->name),
176             query => { keys => 'true' }
177             })->first;
178              
179 0           return decode_json( $result->value )->{'keys'};
180             }
181              
182             =head2 count
183              
184             Count all the keys in a bucket. This uses MapReduce to figure out the answer, so
185             it's expensive; Riak does not keep metadata on buckets for reasons that are beyond
186             the scope of this module (but are well documented, so if you are interested, read up).
187              
188             =cut
189              
190             sub count {
191 0     0 1   my $self = shift;
192 0           my $map_reduce = Data::Riak::Fast::MapReduce->new({
193             riak => $self->riak,
194             inputs => $self->name,
195             phases => [
196             Data::Riak::Fast::Util::MapCount->new,
197             Data::Riak::Fast::Util::ReduceCount->new
198             ]
199             });
200 0           my $map_reduce_results = $map_reduce->mapreduce;
201 0           my ( $result ) = $map_reduce_results->results->[0];
202 0   0       my ( $count ) = decode_json($result->value) || 0;
203 0           return $count->[0];
204             }
205              
206             =head2 remove_all
207              
208             Remove all the keys from a bucket. This involves a list_keys call, so it will be
209             slow on larger systems.
210              
211             =cut
212              
213             sub remove_all {
214 0     0 1   my $self = shift;
215 0           my $keys = $self->list_keys;
216 0 0 0       return unless ref $keys eq 'ARRAY' && @$keys;
217 0           foreach my $key ( @$keys ) {
218 0           $self->remove( $key );
219             }
220             }
221              
222             sub create_link {
223 0     0 0   my $self = shift;
224 0 0         my %opts = ref $_[0] eq 'HASH' ? %{$_[0]} : @_;
  0            
225 0 0         confess "You must provide a key for a link" unless exists $opts{key};
226 0 0         confess "You must provide a riaktag for a link" unless exists $opts{riaktag};
227 0 0         return Data::Riak::Fast::Link->new({
228             bucket => $self->name,
229             key => $opts{key},
230             riaktag => $opts{riaktag},
231             (exists $opts{params} ? (params => $opts{params}) : ())
232             });
233             }
234              
235             sub linkwalk {
236 0     0 0   my ($self, $object, $params) = @_;
237 0 0         return unless $params;
238 0           return $self->riak->linkwalk({
239             bucket => $self->name,
240             object => $object,
241             params => $params
242             });
243             }
244              
245             =head2 search_index
246              
247             Searches a Secondary Index to find results.
248              
249             =cut
250              
251             sub search_index {
252 0     0 1   my ($self, $opts) = @_;
253 0   0       my $field = $opts->{'field'} || die 'You must specify a field for searching Secondary indexes';
254 0   0       my $values = $opts->{'values'} || die 'You must specify values for searching Secondary indexes';
255              
256 0           my $inputs = { bucket => $self->name, index => $field };
257 0 0         if(ref($values) eq 'ARRAY') {
258 0           $inputs->{'start'} = $values->[0];
259 0           $inputs->{'end'} = $values->[1];
260             } else {
261 0           $inputs->{'key'} = $values;
262             }
263              
264 0           my $search_mr = Data::Riak::Fast::MapReduce->new({
265             riak => $self->riak,
266             inputs => $inputs,
267             phases => [
268             Data::Riak::Fast::MapReduce::Phase::Reduce->new({
269             language => 'erlang',
270             module => 'riak_kv_mapreduce',
271             function => 'reduce_identity',
272             keep => 1
273             })
274             ]
275             });
276 0           return $search_mr->mapreduce->results->[0]->value;
277             }
278              
279             # returns JUST the list of keys. human readable, not designed for MapReduce inputs.
280             sub pretty_search_index {
281 0     0 0   my ($self, $opts) = @_;
282 0           return [ sort map { $_->[1] } @{decode_json($self->search_index($opts))} ];
  0            
  0            
283             }
284              
285             sub props {
286 0     0 0   my $self = shift;
287              
288 0           my $result = $self->riak->send_request({
289             method => 'GET',
290             uri => sprintf('buckets/%s/props', $self->name)
291             })->first;
292              
293 0           return decode_json( $result->value )->{'props'};
294             }
295              
296             sub indexing {
297 0     0 0   my ($self, $enable) = @_;
298              
299 0           my $data;
300              
301 0 0         if($enable) {
302 0           $data->{props}->{precommit}->{mod} = 'riak_search_kv_hook';
303 0           $data->{props}->{precommit}->{fun} = 'precommit';
304             } else {
305 0           $data->{props}->{precommit}->{mod} = undef;
306 0           $data->{props}->{precommit}->{fun} = undef;
307             };
308              
309 0           return $self->riak->send_request({
310             method => 'PUT',
311             content_type => 'application/json',
312             uri => $self->name,
313             data => encode_json($data)
314             });
315             }
316              
317             =head2 create_alias ($opts)
318              
319             Creates an alias for a record using links. Helpful if your primary ID is a UUID or
320             some other automatically generated identifier. Can cross buckets, as well.
321              
322             $bucket->create_alias({ key => '123456', as => 'foo' });
323             $bucket->create_alias({ key => '123456', as => 'foo', in => $other_bucket });
324              
325             =cut
326              
327             sub create_alias {
328 0     0 1   my ($self, $opts) = @_;
329 0   0       my $bucket = $opts->{in} || $self;
330 0           $bucket->add($opts->{as}, $opts->{key}, { links => [ Data::Riak::Fast::Link->new( bucket => $bucket->name, riaktag => 'perl-data-riak-alias', key => $opts->{key} )] });
331             }
332              
333             =head2 resolve_alias ($alias)
334              
335             Returns the L that $alias points to.
336              
337             =cut
338              
339             sub resolve_alias {
340 0     0 1   my ($self, $alias) = @_;
341 0           return $self->linkwalk($alias, [[ 'perl-data-riak-alias', '_' ]])->first;
342             }
343              
344             __PACKAGE__->meta->make_immutable;
345 22     22   167 no Mouse;
  22         50  
  22         182  
346              
347             1;
348              
349             __END__