File Coverage

lib/Net/Amazon/DynamoDB.pm
Criterion Covered Total %
statement 53 638 8.3
branch 0 352 0.0
condition 0 199 0.0
subroutine 18 49 36.7
pod 16 16 100.0
total 87 1254 6.9


line stmt bran cond sub pod time code
1             package Net::Amazon::DynamoDB;
2             $Net::Amazon::DynamoDB::VERSION = '0.002000';
3              
4 1     1   1113 use Moose;
  1         407544  
  1         8  
5              
6 1     1   7506 use v5.10;
  1         6  
7              
8 1     1   10 use Carp qw/ croak /;
  1         3  
  1         83  
9 1     1   9 use Data::Dumper;
  1         8  
  1         68  
10 1     1   722 use DateTime::Format::Strptime;
  1         466584  
  1         6  
11 1     1   90 use DateTime;
  1         2  
  1         24  
12 1     1   456 use HTTP::Date qw/ time2str /;
  1         2686  
  1         66  
13 1     1   326 use Digest::SHA qw/ sha1_hex sha256_hex sha384_hex sha256 hmac_sha256_base64 /;
  1         1859  
  1         85  
14 1     1   263 use HTTP::Request;
  1         11989  
  1         31  
15 1     1   532 use JSON;
  1         6425  
  1         5  
16 1     1   594 use LWP::UserAgent;
  1         13893  
  1         38  
17 1     1   426 use LWP::ConnCache;
  1         840  
  1         29  
18 1     1   302 use Net::Amazon::AWSSign;
  1         1766  
  1         48  
19 1     1   338 use Time::HiRes qw/ usleep /;
  1         1002  
  1         4  
20 1     1   694 use XML::Simple qw/ XMLin /;
  1         7756  
  1         9  
21 1     1   93 use MIME::Base64;
  1         2  
  1         62  
22 1     1   501 use Encode;
  1         7756  
  1         1138  
23              
24              
25             has tables => ( isa => 'HashRef[HashRef]', is => 'rw', required => 1, trigger => sub {
26             my ( $self ) = @_;
27              
28             # check table
29             while( my ( $table, $table_ref ) = each %{ $self->{ tables } } ) {
30              
31             # determine primary keys
32             my @check_pk = ( 'hash' );
33             push @check_pk, 'range'
34             if defined $table_ref->{ range_key };
35              
36             # check primary keys
37             foreach my $check_pk( @check_pk ) {
38             my $key_pk = "${check_pk}_key";
39             my $name_pk = $table_ref->{ $key_pk };
40             croak "Missing '$key_pk' attribute in '$table' table definition\n"
41             unless defined $table_ref->{ $key_pk };
42             croak "Missing $check_pk key attribute in '$table' table attribute declaration: "
43             . "{ $table => { attributes => { '$name_pk' => 'S|N' } }\n"
44             unless defined $table_ref->{ attributes }->{ $name_pk };
45             croak "Wrong data type for $check_pk key attribute. Got '$table_ref->{ attributes }->{ $name_pk }',"
46             . " expect 'S' or 'N'"
47             unless $table_ref->{ attributes }->{ $name_pk } =~ /^(S|N)$/;
48             }
49              
50             # check attributes
51             while( my( $attr_name, $attr_type ) = each %{ $table_ref->{ attributes } } ) {
52             croak "Wrong data type for attribute '$attr_name' in table '$table': Got '$attr_type' was"
53             . " expecting 'S', 'N', 'B', 'SS', 'NS', or 'BS'"
54             unless $attr_type =~ /^[BNS]S?$/;
55             }
56             }
57              
58             # no need to go further, if no namespace given
59             return unless $self->namespace;
60              
61             # update table definitions with namespace
62             my %new_table = ();
63             my $updated = 0;
64             foreach my $table( keys %{ $self->{ tables } } ) {
65             my $table_updated = index( $table, $self->namespace ) == 0 ? $table : $self->_table_name( $table );
66             $new_table{ $table_updated } = $self->{ tables }->{ $table };
67             $updated ++ unless $table_updated eq $table;
68             }
69             if ( $updated ) {
70             $self->{ tables } = \%new_table;
71             }
72             } );
73              
74              
75             has use_keep_alive => ( isa => 'Int', is => 'rw', default => 0 );
76              
77              
78             has lwp => ( isa => 'LWP::UserAgent', is => 'rw', lazy => 1, default => sub { my ($self) = @_; LWP::UserAgent->new( timeout => 5, keep_alive => $self->use_keep_alive ) } );
79             has _lwpcache => ( isa => 'LWP::ConnCache', is => 'ro', lazy => 1, default => sub { my ($self) = @_; $self->lwp->conn_cache(); } );
80              
81              
82             has json => ( isa => 'JSON', is => 'rw', default => sub { JSON->new()->canonical( 1 )->allow_nonref( 1 )->utf8( 1 ) }, trigger => sub {
83             shift->json->canonical( 1 )->allow_nonref( 1 )->utf8( 1 );
84             } );
85              
86              
87             has host => ( isa => 'Str', is => 'rw', default => 'dynamodb.us-east-1.amazonaws.com' );
88              
89              
90             has access_key => ( isa => 'Str', is => 'rw', required => 1 );
91              
92              
93             has secret_key => ( isa => 'Str', is => 'rw', required => 1 );
94              
95              
96             has api_version => ( isa => 'Str', is => 'rw', default => '20111205' );
97              
98              
99             has read_consistent => ( isa => 'Bool', is => 'rw', default => 0 );
100              
101              
102             has namespace => ( isa => 'Str', is => 'ro', default => '' );
103              
104              
105             has raise_error => ( isa => 'Bool', is => 'rw', default => 0 );
106              
107              
108             has max_retries => ( isa => 'Int', is => 'rw', default => 1 );
109              
110              
111             has derive_table => ( isa => 'Bool', is => 'rw', default => 0 );
112              
113              
114             has retry_timeout => ( isa => 'Num', is => 'rw', default => 0.1 );
115              
116              
117             has cache => ( isa => 'Cache', is => 'rw', predicate => 'has_cache' );
118              
119              
120             has cache_disabled => ( isa => 'Bool', is => 'rw', default => 0 );
121              
122              
123             has cache_key_method => ( is => 'rw', default => sub { \&Digest::SHA::sha1_hex }, trigger => sub {
124             my ( $self, $method ) = @_;
125             if ( ( ref( $method ) ) ne 'CODE' ) {
126             if ( $method eq 'sha1_hex' ) {
127             $self->{ cache_key_method } = \&Digest::SHA::sha1_hex();
128             }
129             elsif ( $method eq 'sha256_hex' ) {
130             $self->{ cache_key_method } = \&Digest::SHA::sha256_hex();
131             }
132             elsif ( $method eq 'sha384_hex' ) {
133             $self->{ cache_key_method } = \&Digest::SHA::sha384_hex();
134             }
135             }
136             } );
137              
138              
139             has request_id => ( isa => 'Str', is => 'rw' );
140              
141             #
142             # _aws_signer
143             # Contains C<Net::Amazon::AWSSign> instance.
144             #
145              
146             has _aws_signer => ( isa => 'Net::Amazon::AWSSign', is => 'rw', predicate => '_has_aws_signer' );
147              
148             #
149             # _security_token_url
150             # URL for receiving security token
151             #
152              
153             has _security_token_url => ( isa => 'Str', is => 'rw', default => 'https://sts.amazonaws.com/?Action=GetSessionToken&Version=2011-06-15' );
154              
155             #
156             # _credentials
157             # Contains credentials received by GetSession
158             #
159              
160             has _credentials => ( isa => 'HashRef[Str]', is => 'rw', predicate => '_has_credentials' );
161              
162             #
163             # _credentials_expire
164             # Time of credentials exiration
165             #
166              
167             has _credentials_expire => ( isa => 'DateTime', is => 'rw' );
168              
169             #
170             # _error
171             # Contains credentials received by GetSession
172             #
173              
174             has _error => ( isa => 'Str', is => 'rw', predicate => '_has_error' );
175              
176              
177             sub create_table {
178 0     0 1   my ( $self, $table, $read_amount, $write_amount ) = @_;
179 0           $table = $self->_table_name( $table );
180 0   0       $read_amount ||= 10;
181 0   0       $write_amount ||= 5;
182              
183             # check & get table definition
184 0           my $table_ref = $self->_check_table( "create_table", $table );
185              
186             # init create definition
187 0           my %create = (
188             TableName => $table,
189             ProvisionedThroughput => {
190             ReadCapacityUnits => $read_amount + 0,
191             WriteCapacityUnits => $write_amount + 0,
192             }
193             );
194              
195             # build keys
196             $create{ KeySchema } = {
197             HashKeyElement => {
198             AttributeName => $table_ref->{ hash_key },
199             AttributeType => $table_ref->{ attributes }->{ $table_ref->{ hash_key } }
200             }
201 0           };
202 0 0         if ( defined $table_ref->{ range_key } ) {
203             $create{ KeySchema }->{ RangeKeyElement } = {
204             AttributeName => $table_ref->{ range_key },
205             AttributeType => $table_ref->{ attributes }->{ $table_ref->{ range_key } }
206 0           };
207             }
208              
209             # perform create
210 0           my ( $res, $res_ok, $json_ref ) = $self->request( CreateTable => \%create );
211              
212             # got res
213 0 0 0       if ( $res_ok && defined $json_ref->{ TableDescription } ) {
214             return {
215             status => $json_ref->{ TableDescription }->{ TableStatus },
216             created => int( $json_ref->{ TableDescription }->{ CreationDateTime } ),
217             read_amount => $json_ref->{ TableDescription }->{ ProvisionedThroughput }->{ ReadCapacityUnits },
218             write_amount => $json_ref->{ TableDescription }->{ ProvisionedThroughput }->{ WriteCapacityUnits },
219             hash_key => $json_ref->{ Table }->{ KeySchema }->{ HashKeyElement }->{ AttributeName },
220             hash_key_type => $json_ref->{ Table }->{ KeySchema }->{ HashKeyElement }->{ AttributeType },
221             ( defined $json_ref->{ Table }->{ KeySchema }->{ RangeKeyElement }
222             ? (
223             range_key => $json_ref->{ Table }->{ KeySchema }->{ RangeKeyElement }->{ AttributeName },
224             range_key_type => $json_ref->{ Table }->{ KeySchema }->{ RangeKeyElement }->{ AttributeType },
225             )
226 0 0         : ()
227             ),
228             }
229             }
230              
231             # set error
232 0           $self->error( 'create_table failed: '. $self->_extract_error_message( $res ) );
233 0           return ;
234             }
235              
236              
237              
238              
239             sub delete_table {
240 0     0 1   my ( $self, $table ) = @_;
241 0           $table = $self->_table_name( $table );
242              
243             # check & get table definition
244 0           my $table_ref = $self->_check_table( delete_table => $table );
245              
246             # perform create
247 0           my ( $res, $res_ok, $json_ref ) = $self->request( DeleteTable => { TableName => $table } );
248              
249             # got result
250 0 0 0       if ( $res_ok && defined $json_ref->{ TableDescription } ) {
251 0           return $json_ref->{ TableDescription }->{ TableStatus } eq 'DELETING';
252             }
253              
254             # set error
255 0           $self->error( 'delete_table failed: '. $self->_extract_error_message( $res ) );
256 0           return ;
257             }
258              
259              
260              
261              
262             sub describe_table {
263 0     0 1   my ( $self, $table ) = @_;
264 0           $table = $self->_table_name( $table );
265              
266             # check table definition
267 0           $self->_check_table( "describe_table", $table );
268              
269 0           my ( $res, $res_ok, $json_ref ) = $self->request( DescribeTable => { TableName => $table } );
270             # got result
271 0 0         if ( $res_ok ) {
272 0 0         if ( defined $json_ref->{ Table } ) {
273 1     1   10 no warnings 'uninitialized';
  1         2  
  1         6300  
274             return {
275             existing => 1,
276             size => $json_ref->{ Table }->{ TableSizeBytes },
277             count => $json_ref->{ Table }->{ ItemCount },
278             status => $json_ref->{ Table }->{ TableStatus },
279             created => int( $json_ref->{ Table }->{ CreationDateTime } ),
280             read_amount => $json_ref->{ Table }->{ ProvisionedThroughput }->{ ReadCapacityUnits },
281             write_amount => $json_ref->{ Table }->{ ProvisionedThroughput }->{ WriteCapacityUnits },
282             hash_key => $json_ref->{ Table }->{ KeySchema }->{ HashKeyElement }->{ AttributeName },
283             hash_key_type => $json_ref->{ Table }->{ KeySchema }->{ HashKeyElement }->{ AttributeType },
284             ( defined $json_ref->{ Table }->{ KeySchema }->{ RangeKeyElement }
285             ? (
286             range_key => $json_ref->{ Table }->{ KeySchema }->{ RangeKeyElement }->{ AttributeName },
287             range_key_type => $json_ref->{ Table }->{ KeySchema }->{ RangeKeyElement }->{ AttributeType },
288             )
289 0 0         : ()
290             ),
291             };
292             }
293             else {
294             return {
295 0           existing => 0
296             }
297             }
298             }
299              
300             # set error
301 0           $self->error( 'describe_table failed: '. $self->_extract_error_message( $res ) );
302 0           return ;
303             }
304              
305              
306              
307             sub update_table {
308 0     0 1   my ( $self, $table, $read_amount, $write_amount ) = @_;
309 0           $table = $self->_table_name( $table );
310              
311 0           my ( $res, $res_ok, $json_ref ) = $self->request( UpdateTable => {
312             TableName => $table,
313             ProvisionedThroughput => {
314             ReadCapacityUnits => $read_amount + 0,
315             WriteCapacityUnits => $write_amount + 0,
316             }
317             } );
318              
319 0 0         if ( $res_ok ) {
320 0           return 1;
321             }
322              
323             # set error
324 0           $self->error( 'update_table failed: '. $self->_extract_error_message( $res ) );
325 0           return ;
326             }
327              
328              
329              
330              
331             sub exists_table {
332 0     0 1   my ( $self, $table ) = @_;
333 0           $table = $self->_table_name( $table );
334              
335             # check table definition
336 0           $self->_check_table( "exists_table", $table );
337              
338 0           my ( $res, $res_ok, $json_ref );
339 0           eval {
340 0           ( $res, $res_ok, $json_ref ) = $self->request( DescribeTable => { TableName => $table } );
341             };
342              
343 0 0 0       return defined $json_ref->{ Table } && defined $json_ref->{ Table }->{ ItemCount } ? 1 : 0
    0          
344             if $res_ok;
345              
346             # set error
347 0           return 0;
348             }
349              
350              
351              
352              
353             sub list_tables {
354 0     0 1   my ( $self ) = @_;
355              
356 0           my ( $res, $res_ok, $json_ref ) = $self->request( ListTables => {} );
357 0 0         if ( $res_ok ) {
358 0           my $ns_length = length( $self->namespace );
359             my @table_names = map {
360 0           substr( $_, $ns_length );
361             } grep {
362 0 0         ! $self->namespace || index( $_, $self->namespace ) == 0
363 0           } @{ $json_ref->{ TableNames } };
  0            
364 0 0         return wantarray ? @table_names : \@table_names;
365             }
366              
367             # set error
368 0           $self->error( 'list_tables failed: '. $self->_extract_error_message( $res ) );
369 0           return ;
370             }
371              
372              
373              
374              
375             sub put_item {
376 0     0 1   my ( $self, $table, $item_ref, $where_ref, $args_ref ) = @_;
377 0   0       $args_ref ||= {
378             return_old => 0,
379             no_cache => 0,
380             use_cache => 0,
381             max_retries => undef
382             };
383 0           $table = $self->_table_name( $table );
384              
385             # check definition
386 0           my $table_ref = $self->_check_table( "put_item", $table );
387              
388             # check primary keys
389             croak "put_item: Missing value for hash key '$table_ref->{ hash_key }'"
390             unless defined $item_ref->{ $table_ref->{ hash_key } }
391 0 0 0       && length( $item_ref->{ $table_ref->{ hash_key } } );
392              
393             # check other attributes
394 0           $self->_check_keys( "put_item: item values", $table, $item_ref );
395              
396             # having where -> check now
397 0 0         $self->_check_keys( "put_item: where clause", $table, $where_ref ) if $where_ref;
398              
399             # build put
400 0           my %put = (
401             TableName => $table,
402             Item => {}
403             );
404              
405             # build the item
406 0           foreach my $key( keys %$item_ref ){
407 0           my $type = $self->_attrib_type( $table, $key );
408 0           my $value = $self->_build_value($item_ref->{ $key },$type);
409 0           $put{ Item }->{ $key } = { $type => $value };
410             }
411              
412             # build possible where clause
413 0 0         if ( $where_ref ) {
414 0           $self->_build_attrib_filter( $table, $where_ref, $put{ Expected } = {} );
415             }
416              
417             # add return value, if set
418 0 0         $put{ ReturnValues } = 'ALL_OLD' if $args_ref->{ return_old };
419              
420             # perform create
421             my ( $res, $res_ok, $json_ref ) = $self->request( PutItem => \%put, {
422             max_retries => $args_ref->{ max_retries },
423 0           } );
424              
425             # get result
426 0 0         if ( $res_ok ) {
427              
428             # clear cache
429 0 0         if ( $self->_cache_enabled( $args_ref ) ) {
430 0           my $cache_key = $self->_cache_key_single( $table, $item_ref );
431 0           $self->cache->remove( $cache_key );
432             }
433              
434 0 0         if ( $args_ref->{ return_old } ) {
435             return defined $json_ref->{ Attributes }
436             ? $self->_format_item( $table, $json_ref->{ Attributes } )
437 0 0         : undef;
438             }
439             else {
440 0           return $json_ref->{ ConsumedCapacityUnits } > 0;
441             }
442             }
443              
444             # set error
445 0           $self->error( 'put_item failed: '. $self->_extract_error_message( $res ) );
446 0           return ;
447             }
448              
449              
450              
451             sub batch_write_item {
452 0     0 1   my ( $self, $tables_ref, $args_ref ) = @_;
453 0   0       $args_ref ||= {
454             process_all => 0,
455             max_retries => undef
456             };
457              
458             # check definition
459 0           my %table_map;
460 0           foreach my $table( keys %$tables_ref ) {
461 0           $table = $self->_table_name( $table );
462 0           my $table_ref = $self->_check_table( "batch_write_item", $table );
463 0           $table_map{ $table } = $table_ref;
464             }
465              
466 0           my %write = ( RequestItems => {} );
467 0           foreach my $table( keys %table_map ) {
468 0           my $table_out = $self->_table_name( $table, 1 );
469 0           my $t_ref = $tables_ref->{ $table_out };
470 0           my $table_requests_ref = $write{ RequestItems }->{ $table } = [];
471              
472 0           foreach my $operation( qw/ put delete / ) {
473 0 0         next unless defined $t_ref->{ $operation };
474             my @operations = ref( $t_ref->{ $operation } ) eq 'ARRAY'
475 0           ? @{ $t_ref->{ $operation } }
476 0 0         : ( $t_ref->{ $operation } );
477              
478             # put ..
479 0 0         if ( $operation eq 'put' ) {
480 0           foreach my $put_ref( @operations ) {
481 0           push @$table_requests_ref, { 'PutRequest' => { Item => my $request_ref = {} } };
482              
483             # build the item
484 0           foreach my $key( keys %$put_ref ){
485 0           my $type = $self->_attrib_type( $table, $key );
486 0           my $value = $self->_build_value($put_ref->{ $key },$type);
487 0           $request_ref->{ $key } = { $type => $value };
488             }
489              
490             # no matter success or failure, remove cache first
491 0 0         if ( $self->_cache_enabled( $args_ref ) ) {
492 0           my $cache_key = $self->_cache_key_single( $table, $put_ref );
493 0           $self->cache->remove( $cache_key );
494             }
495             }
496             }
497              
498             # delete ..
499             else {
500 0           foreach my $delete_ref( @operations ) {
501             # no matter success or failure, remove cache first
502             # call before _build_pk_filter since $delete_ref will be empty afterwards
503 0 0         if ( $self->_cache_enabled( $args_ref ) ) {
504 0           my $cache_key = $self->_cache_key_single( $table, $delete_ref );
505 0           $self->cache->remove( $cache_key );
506             }
507              
508 0           push @$table_requests_ref, { 'DeleteRequest' => { Key => my $request_ref = {} } };
509 0           $self->_build_pk_filter( $table, $delete_ref, $request_ref );
510             }
511             }
512             }
513             }
514              
515             # perform create
516             my ( $res, $res_ok, $json_ref ) = $self->request( BatchWriteItem => \%write, {
517             max_retries => $args_ref->{ max_retries },
518 0           } );
519              
520             # having more to process
521 0   0       while ( $args_ref->{ process_all }
      0        
      0        
522             && $res_ok
523             && defined $json_ref->{ UnprocessedItems }
524 0           && scalar( keys %{ $json_ref->{ UnprocessedItems } } )
525             ) {
526             ( $res, $res_ok, $json_ref ) = $self->request( BatchWriteItem => {
527             RequestItems => $json_ref->{ UnprocessedItems }
528             }, {
529             max_retries => $args_ref->{ max_retries },
530 0           } );
531             }
532              
533             # count unprocessed
534 0           my $unprocessed_count = 0;
535 0           my %next_query;
536 0 0 0       if ( $res_ok && defined $json_ref->{ UnprocessedItems } ) {
537 0           foreach my $table( keys %{ $json_ref->{ UnprocessedItems } } ) {
  0            
538 0           my @operations = @{ $json_ref->{ UnprocessedItems }->{ $table } };
  0            
539 0 0         next unless @operations;
540 0           $unprocessed_count += scalar( @operations );
541 0           $next_query{ $table } = {};
542 0           foreach my $operation_ref( @operations ) {
543             my ( $item_ref, $operation_name ) = defined $operation_ref->{ PutRequest }
544             ? ( $operation_ref->{ PutRequest }->{ Item }, 'put' )
545 0 0         : ( $operation_ref->{ DeleteRequest }->{ Key }, 'delete' );
546             #print Dumper( [ $operation_ref, $operation_name, $item_ref ] );
547 0   0       push @{ $next_query{ $table }->{ $operation_name } ||= [] },
  0            
548             $self->_format_item( $table, $item_ref )
549             }
550             }
551             }
552              
553 0 0         return wantarray ? ( $res_ok, $unprocessed_count, \%next_query ) : $res_ok;
554             }
555              
556              
557              
558              
559             sub update_item {
560 0     0 1   my ( $self, $table, $update_ref, $where_ref, $args_ref ) = @_;
561 0   0       $args_ref ||= {
562             return_mode => '',
563             no_cache => 0,
564             use_cache => 0,
565             max_retries => undef
566             };
567 0           $table = $self->_table_name( $table );
568              
569             # check definition
570 0           my $table_ref = $self->_check_table( "update_item", $table );
571              
572             croak "update_item: Cannot update hash key value, do not set it in update-clause"
573 0 0         if defined $update_ref->{ $table_ref->{ hash_key } };
574              
575             croak "update_item: Cannot update range key value, do not set it in update-clause"
576             if defined $table_ref->{ range_key }
577 0 0 0       && defined $update_ref->{ $table_ref->{ range_key } };
578              
579             # check primary keys
580             croak "update_item: Missing value for hash key '$table_ref->{ hash_key }' in where-clause"
581             unless defined $where_ref->{ $table_ref->{ hash_key } }
582 0 0 0       && length( $where_ref->{ $table_ref->{ hash_key } } );
583             croak "update_item: Missing value for range key '$table_ref->{ hash_key }' in where-clause"
584             if defined $table_ref->{ range_key } && !(
585             defined $where_ref->{ $table_ref->{ range_key } }
586             && length( $where_ref->{ $table_ref->{ range_key } } )
587 0 0 0       );
      0        
588              
589             # check other attributes
590 0           $self->_check_keys( "update_item: item values", $table, $update_ref );
591             croak "update_item: Cannot update hash key '$table_ref->{ hash_key }'. You have to delete and put the item!"
592 0 0         if defined $update_ref->{ $table_ref->{ hash_key } };
593             croak "update_item: Cannot update range key '$table_ref->{ hash_key }'. You have to delete and put the item!"
594 0 0 0       if defined $table_ref->{ range_key } && defined $update_ref->{ $table_ref->{ range_key } };
595              
596             # having where -> check now
597 0           $self->_check_keys( "update_item: where clause", $table, $where_ref );
598              
599             # build put
600 0           my %update = (
601             TableName => $table,
602             AttributeUpdates => {},
603             Key => {}
604             );
605              
606             # build the item
607 0           foreach my $key( keys %$update_ref ) {
608 0           my $type = $self->_attrib_type( $table, $key );
609 0           my $value = $update_ref->{ $key };
610              
611             # delete
612 0 0 0       if ( ! defined $value ) {
    0 0        
    0 0        
    0          
613 0           $update{ AttributeUpdates }->{ $key } = {
614             Action => 'DELETE'
615             };
616             }
617              
618             # if ++N or --N on numeric type, ADD to get inc/dec behavior
619             elsif ( $type eq 'N' && $value =~ /^(--|\+\+)(\d+)$/ ) {
620 0 0         $update{ AttributeUpdates }->{ $key } = {
621             Value => { $type => ($1 eq '--') ? "-$2" : "$2" },
622             Action => 'ADD'
623             };
624             }
625              
626             # replace for scalar
627             elsif ( $type eq 'N' || $type eq 'S' || $type eq 'B' ) {
628 0           $update{ AttributeUpdates }->{ $key } = {
629             Value => { $type => $self->_build_value($value,$type) },
630             Action => 'PUT'
631             };
632             }
633              
634             # replace or add for array types
635             elsif ( $type =~ /^([NSB])S$/ ) {
636 0           my $base_type = $1;
637              
638             # add \[ qw/ value1 value2 / ]
639 0 0         if ( ref( $value ) eq 'REF' ) {
640             $update{ AttributeUpdates }->{ $key } = {
641 0           Value => { $type => [ map { $self->_build_value($_,$base_type) } @$$value ] },
  0            
642             Action => 'ADD'
643             };
644             }
645              
646             # replace [ qw/ value1 value2 / ]
647             else {
648             $update{ AttributeUpdates }->{ $key } = {
649 0           Value => { $type => [ map { $self->_build_value($_,$base_type) } @$value ] },
  0            
650             Action => 'PUT'
651             };
652             }
653             }
654             }
655              
656             # build possible where clause
657 0           my %where = %$where_ref;
658              
659             # primary key
660 0           $self->_build_pk_filter( $table, \%where, $update{ Key } );
661              
662             # additional filters
663 0 0         if ( keys %where ) {
664 0           $self->_build_attrib_filter( $table, \%where, $update{ Expected } = {} );
665             }
666              
667             # add return value, if set
668 0 0         if ( $args_ref->{ return_mode } ) {
669             $update{ ReturnValues } = "$args_ref->{ return_mode }" =~ /^(?:ALL_OLD|UPDATED_OLD|ALL_NEW|UPDATED_NEW)$/i
670             ? uc( $args_ref->{ return_mode } )
671 0 0         : "ALL_OLD";
672             }
673              
674             # perform create
675             my ( $res, $res_ok, $json_ref ) = $self->request( UpdateItem => \%update, {
676             max_retries => $args_ref->{ max_retries },
677 0           } );
678              
679             # get result
680 0 0         if ( $res_ok ) {
681              
682             # clear cache
683 0 0         if ( $self->_cache_enabled( $args_ref ) ) {
684 0           my $cache_key = $self->_cache_key_single( $table, $where_ref );
685 0           $self->cache->remove( $cache_key );
686             }
687              
688 0 0         if ( $args_ref->{ return_mode } ) {
689             return defined $json_ref->{ Attributes }
690             ? $self->_format_item( $table, $json_ref->{ Attributes } )
691 0 0         : undef;
692             }
693             else {
694 0           return $json_ref->{ ConsumedCapacityUnits } > 0;
695             }
696             }
697              
698             # set error
699 0           $self->error( 'put_item failed: '. $self->_extract_error_message( $res ) );
700 0           return ;
701             }
702              
703              
704              
705              
706             sub get_item {
707 0     0 1   my ( $self, $table, $pk_ref, $args_ref ) = @_;
708 0           $table = $self->_table_name( $table );
709 0   0       $args_ref ||= {
710             consistent => undef,
711             attributes => undef,
712             no_cache => 0,
713             use_cache => 0,
714             max_retries => undef
715             };
716 0   0       $args_ref->{ consistent } //= $self->read_consistent;
717              
718             # check definition
719 0           my $table_ref = $self->_check_table( "get_item", $table );
720              
721             # check primary keys
722             croak "get_item: Missing value for hash key '$table_ref->{ hash_key }'"
723             unless defined $pk_ref->{ $table_ref->{ hash_key } }
724 0 0 0       && length( $pk_ref->{ $table_ref->{ hash_key } } );
725             croak "get_item: Missing value for Range Key '$table_ref->{ range_key }'"
726             if defined $table_ref->{ range_key } && !(
727             defined $pk_ref->{ $table_ref->{ range_key } }
728             && length( $pk_ref->{ $table_ref->{ hash_key } } )
729 0 0 0       );
      0        
730              
731             # use cache
732 0           my $use_cache = $self->_cache_enabled( $args_ref );
733 0           my $cache_key;
734 0 0         if ( $use_cache ) {
735 0           $cache_key = $self->_cache_key_single( $table, $pk_ref );
736 0           my $cached = $self->cache->thaw( $cache_key );
737 0 0         return $cached if defined $cached;
738             }
739              
740              
741             # build get
742             my %get = (
743             TableName => $table,
744             ( defined $args_ref->{ attributes } ? ( AttributesToGet => $args_ref->{ attributes } ) : () ),
745             ConsistentRead => $args_ref->{ consistent } ? \1 : \0,
746             Key => {
747             HashKeyElement => {
748             $self->_attrib_type( $table, $table_ref->{ hash_key } ) =>
749             $pk_ref->{ $table_ref->{ hash_key } }
750             }
751             }
752 0 0         );
    0          
753              
754             # add range key ?
755 0 0         if ( defined $table_ref->{ range_key } ) {
756             $get{ Key }->{ RangeKeyElement } = {
757             $self->_attrib_type( $table, $table_ref->{ range_key } ) =>
758             $pk_ref->{ $table_ref->{ range_key } }
759 0           };
760             }
761              
762             # perform create
763             my ( $res, $res_ok, $json_ref ) = $self->request( GetItem => \%get, {
764             max_retries => $args_ref->{ max_retries },
765 0           } );
766              
767             # return on success
768 0 0         if ($res_ok) {
769 0 0         if (defined $json_ref->{ Item }) {
770 0           my $item_ref = $self->_format_item( $table, $json_ref->{ Item } );
771 0 0         if ( $use_cache ) {
772 0           $self->cache->freeze( $cache_key, $item_ref );
773             }
774 0           return $item_ref;
775             }
776 0           return;
777             # return on success, but nothing received
778             }
779              
780             # set error
781 0           $self->error( 'get_item failed: '. $self->_extract_error_message( $res ) );
782 0           return ;
783             }
784              
785              
786              
787              
788             sub batch_get_item {
789 0     0 1   my ( $self, $tables_ref, $args_ref ) = @_;
790 0   0       $args_ref ||= {
791             max_retries => undef,
792             process_all => undef,
793             consistent => undef
794             };
795 0   0       $args_ref->{ consistent } //= $self->read_consistent();
796              
797             # check definition
798 0           my %table_map;
799 0           foreach my $table( keys %$tables_ref ) {
800 0           $table = $self->_table_name( $table );
801 0           my $table_ref = $self->_check_table( "batch_get_item", $table );
802 0           $table_map{ $table } = $table_ref;
803             }
804              
805 0           my %get = ( RequestItems => {} );
806 0           foreach my $table( keys %table_map ) {
807 0           my $table_out = $self->_table_name( $table, 1 );
808 0           my $t_ref = $tables_ref->{ $table_out };
809              
810             # init items for table
811 0           $get{ RequestItems }->{ $table } = {};
812              
813             # init / get keys
814 0           my $k_ref = $get{ RequestItems }->{ $table }->{ Keys } = [];
815             my @keys = ref( $t_ref ) eq 'ARRAY'
816             ? @$t_ref
817 0 0         : @{ $t_ref->{ keys } };
  0            
818              
819             # get mapping for table
820 0           my $m_ref = $table_map{ $table };
821              
822             # get hash key
823 0           my $hash_key = $m_ref->{ hash_key };
824 0           my $hash_key_type = $self->_attrib_type( $table, $hash_key );
825              
826             # get range key?
827 0           my ( $range_key, $range_key_type );
828 0 0         if ( defined $m_ref->{ range_key } ) {
829 0           $range_key = $m_ref->{ range_key };
830 0           $range_key_type = $self->_attrib_type( $table, $range_key );
831             }
832              
833             # build request items
834 0           foreach my $key_ref( @keys ) {
835             push @$k_ref, {
836             HashKeyElement => { $hash_key_type => $key_ref->{ $hash_key }. '' },
837 0 0         ( defined $range_key ? ( RangeKeyElement => { $range_key_type => $key_ref->{ $range_key }. '' } ) : () )
838             };
839             }
840              
841             # having attributes limitation?
842 0 0 0       if ( ref( $t_ref ) eq 'HASH' && defined $t_ref->{ attributes } ) {
843 0           $get{ RequestItems }->{ $table }->{ AttributesToGet } = $t_ref->{ attributes };
844             }
845              
846             # using consistent read?
847 0 0         if ( $args_ref->{ consistent } ) {
848 0           $get{ RequestItems }->{ $table }->{ ConsistentRead } = \1;
849             }
850             }
851              
852             # perform create
853             my ( $res, $res_ok, $json_ref ) = $self->request( BatchGetItem => \%get, {
854             max_retries => $args_ref->{ max_retries },
855 0           } );
856              
857             # return on success
858 0 0 0       if ( $res_ok && defined $json_ref->{ Responses } ) {
859              
860 0 0 0       if ( $args_ref->{ process_all } && defined( my $ukeys_ref = $json_ref->{ UnprocessedKeys } ) ) {
861 0           while ( $ukeys_ref ) {
862             ( $res, $res_ok, my $ujson_ref ) = $self->request( BatchGetItem =>
863             {
864             RequestItems => $ukeys_ref
865             }, {
866             max_retries => $args_ref->{ max_retries },
867 0           } );
868 0 0 0       if ( $res_ok && defined $ujson_ref->{ Responses } ) {
869 0           foreach my $table_out( keys %$tables_ref ) {
870 0           my $table = $self->_table_name( $table_out );
871 0 0 0       if ( defined $ujson_ref->{ Responses }->{ $table } && defined $ujson_ref->{ Responses }->{ $table }->{ Items } ) {
872 0   0       $json_ref->{ Responses }->{ $table } ||= {};
873 0   0       push @{ $json_ref->{ Responses }->{ $table }->{ Items } ||= [] },
874 0           @{ $ujson_ref->{ Responses }->{ $table }->{ Items } };
  0            
875             }
876             }
877             }
878             $ukeys_ref = $res_ok && defined $ujson_ref->{ UnprocessedKeys }
879             ? $ujson_ref->{ UnprocessedKeys }
880 0 0 0       : undef;
881             }
882             }
883              
884 0           my %res;
885 0           foreach my $table_out( keys %$tables_ref ) {
886 0           my $table = $self->_table_name( $table_out );
887 0 0 0       next unless defined $json_ref->{ Responses }->{ $table } && defined $json_ref->{ Responses }->{ $table }->{ Items };
888 0           my $items_ref = $json_ref->{ Responses }->{ $table };
889 0           $res{ $table_out } = [];
890 0           foreach my $item_ref( @{ $items_ref->{ Items } } ) {
  0            
891 0           push @{ $res{ $table_out } }, $self->_format_item($table,$item_ref);
  0            
892             }
893             }
894 0           return \%res;
895             }
896              
897             # set error
898 0           $self->error( 'batch_get_item failed: '. $self->_extract_error_message( $res ) );
899 0           return ;
900             }
901              
902              
903              
904              
905             sub delete_item {
906 0     0 1   my ( $self, $table, $where_ref, $args_ref ) = @_;
907 0   0       $args_ref ||= {
908             return_old => 0,
909             no_cache => 0,
910             use_cache => 0,
911             max_retries => undef
912             };
913 0           $table = $self->_table_name( $table );
914              
915             # check definition
916 0           my $table_ref = $self->_check_table( "delete_item", $table );
917              
918             # check primary keys
919             croak "delete_item: Missing value for hash key '$table_ref->{ hash_key }'"
920             unless defined $where_ref->{ $table_ref->{ hash_key } }
921 0 0 0       && length( $where_ref->{ $table_ref->{ hash_key } } );
922             croak "delete_item: Missing value for Range Key '$table_ref->{ range_key }'"
923             if defined $table_ref->{ range_key } && ! (
924             defined $where_ref->{ $table_ref->{ range_key } }
925             && length( $where_ref->{ $table_ref->{ range_key } } )
926 0 0 0       );
      0        
927              
928             # check other attributes
929 0           $self->_check_keys( "delete_item: where-clause", $table, $where_ref );
930              
931             # build delete
932             my %delete = (
933             TableName => $table,
934             Key => {},
935 0 0         ( $args_ref->{ return_old } ? ( ReturnValues => 'ALL_OLD' ) : () )
936             );
937              
938             # setup pk
939 0           my %where = %$where_ref;
940              
941             # for hash key
942 0           my $hash_value = delete $where{ $table_ref->{ hash_key } };
943             $delete{ Key }->{ HashKeyElement } = {
944 0           $self->_attrib_type( $table, $table_ref->{ hash_key } ) => $hash_value
945             };
946              
947             # for range key
948 0 0         if ( defined $table_ref->{ range_key } ) {
949 0           my $range_value = delete $where{ $table_ref->{ range_key } };
950             $delete{ Key }->{ RangeKeyElement } = {
951 0           $self->_attrib_type( $table, $table_ref->{ range_key } ) => $range_value
952             };
953             }
954              
955             # build filter for other attribs
956 0 0         if ( keys %where ) {
957 0           $self->_build_attrib_filter( $table, \%where, $delete{ Expected } = {} );
958             }
959              
960             # perform create
961             my ( $res, $res_ok, $json_ref ) = $self->request( DeleteItem => \%delete, {
962             max_retries => $args_ref->{ max_retries },
963 0           } );
964              
965 0 0         if ( $res_ok ) {
966              
967             # use cache
968 0 0         if ( $self->_cache_enabled( $args_ref ) ) {
969 0           my $cache_key = $self->_cache_key_single( $table, $where_ref );
970 0           $self->cache->remove( $cache_key );
971             }
972              
973 0 0         if ( defined $json_ref->{ Attributes } ) {
974 0           my %res;
975 0           foreach my $attrib( $self->_attribs( $table ) ) {
976 0 0         next unless defined $json_ref->{ Attributes }->{ $attrib };
977 0           $res{ $attrib } = $json_ref->{ Attributes }->{ $attrib }->{ $self->_attrib_type( $table, $attrib ) };
978             }
979 0           return \%res;
980             }
981 0           return {};
982             }
983              
984 0           $self->error( 'delete_item failed: '. $self->_extract_error_message( $res ) );
985 0           return;
986             }
987              
988              
989              
990              
991             sub query_items {
992 0     0 1   my ( $self, $table, $filter_ref, $args_ref ) = @_;
993 0           my $table_orig = $table;
994 0           $table = $self->_table_name( $table );
995 0   0       $args_ref ||= {
996             limit => undef, # amount of items
997             consistent => 0, # default: eventually, not hard, conistent
998             backward => 0, # default: forward
999             start_key => undef, # eg { pk_name => 123, pk_other => 234 }
1000             attributes => undef, # eq [ qw/ attrib1 attrib2 / ]
1001             count => 0, # returns amount instead of the actual result
1002             all => 0, # read all entries (runs possibly multiple queries)
1003             max_retries => undef, # overwrite default max rewrites
1004             };
1005              
1006             # check definition
1007             croak "query_items: Table '$table' does not exist in table definition"
1008 0 0         unless defined $self->tables->{ $table };
1009 0           my $table_ref = $self->tables->{ $table };
1010              
1011             # die "query_items: Can run query_items only on tables with range key! '$table' does not have a range key.."
1012             # unless defined $table_ref->{ range_key };
1013              
1014             # build put
1015             my %query = (
1016             TableName => $table,
1017             ConsistentRead => $args_ref->{ consistent } ? \1 : \0,
1018             ScanIndexForward => $args_ref->{ backward } ? \0 : \1,
1019 0 0         ( defined $args_ref->{ limit } ? ( Limit => $args_ref->{ limit } ) : () ),
    0          
    0          
1020             );
1021              
1022             # using filter
1023 0           my %filter = %$filter_ref;
1024              
1025 0 0         if ( defined $filter{ $table_ref->{ hash_key } } ) {
1026             croak "query_items: Missing hash key value in filter-clause"
1027 0 0         unless defined $filter{ $table_ref->{ hash_key } };
1028             $query{ HashKeyValue } = {
1029             $self->_attrib_type( $table, $table_ref->{ hash_key } ) =>
1030 0           ( delete $filter{ $table_ref->{ hash_key } } ) . ''
1031             };
1032             }
1033              
1034             # adding range to filter
1035 0 0         if ( defined $table_ref->{ range_key }) {
1036             croak "query_items: Missing range key value in filter-clause"
1037 0 0         unless defined $filter{ $table_ref->{ range_key } };
1038             # r_ref = { GT => 1 } OR { BETWEEN => [ 1, 5 ] } OR { EQ => [ 1 ] } OR 5 FOR { EQ => 5 }
1039 0           my $r_ref = delete $filter{ $table_ref->{ range_key } };
1040 0 0         $r_ref = { EQ => $r_ref } unless ref( $r_ref );
1041 0           my ( $op, $vals_ref ) = %$r_ref;
1042 0 0         $vals_ref = [ $vals_ref ] unless ref( $vals_ref );
1043 0           my $type = $self->_attrib_type( $table, $table_ref->{ range_key } );
1044             $query{ RangeKeyCondition } = {
1045             AttributeValueList => [ map {
1046 0           { $type => $_. '' }
  0            
1047             } @$vals_ref ],
1048             ComparisonOperator => uc( $op )
1049             };
1050             }
1051              
1052             # too much keys
1053 0 0         croak "query_items: Cannot use keys ". join( ', ', sort keys %filter ). " in in filter - only hash and range key allowed."
1054             if keys %filter;
1055              
1056              
1057             # with start key?
1058 0 0         if( defined( my $start_key_ref = $args_ref->{ start_key } ) ) {
1059 0           $self->_check_keys( "query_items: start_key", $table, $start_key_ref );
1060 0           my $e_ref = $query{ ExclusiveStartKey } = {};
1061              
1062             # add hash key
1063 0 0         if ( defined $start_key_ref->{ $table_ref->{ hash_key } } ) {
1064 0           my $type = $self->_attrib_type( $table, $table_ref->{ hash_key } );
1065 0           $e_ref->{ HashKeyElement } = { $type => $start_key_ref->{ $table_ref->{ hash_key } } };
1066             }
1067              
1068             # add range key?
1069 0 0 0       if ( defined $table_ref->{ range_key } && defined $start_key_ref->{ $table_ref->{ range_key } } ) {
1070 0           my $type = $self->_attrib_type( $table, $table_ref->{ range_key } );
1071 0           $e_ref->{ RangeKeyElement } = { $type => $start_key_ref->{ $table_ref->{ range_key } } };
1072             }
1073             }
1074              
1075             # only certain attributes
1076 0 0         if ( defined( my $attribs_ref = $args_ref->{ attributes } ) ) {
    0          
1077 0           my @keys = $self->_check_keys( "query_items: attributes", $table, $attribs_ref );
1078 0           $query{ AttributesToGet } = \@keys;
1079             }
1080              
1081             # or count?
1082             elsif ( $args_ref->{ count } ) {
1083 0           $query{ Count } = \1;
1084             }
1085              
1086             # perform query
1087             #print Dumper( { QUERY => \%query } );
1088             my ( $res, $res_ok, $json_ref ) = $self->request( Query => \%query, {
1089             max_retries => $args_ref->{ max_retries },
1090 0           } );
1091              
1092             # format & return result
1093 0 0 0       if ( $res_ok && defined $json_ref->{ Items } ) {
1094 0           my @res;
1095 0           foreach my $from_ref( @{ $json_ref->{ Items } } ) {
  0            
1096 0           push @res, $self->_format_item( $table, $from_ref );
1097             }
1098 0           my $count = $json_ref->{ Count };
1099              
1100             # build start key for return or use
1101 0           my $next_start_key_ref;
1102 0 0         if ( defined $json_ref->{ LastEvaluatedKey } ) {
1103 0           $next_start_key_ref = {};
1104              
1105             # add hash key to start key
1106 0           my $hash_type = $self->_attrib_type( $table, $table_ref->{ hash_key } );
1107 0           $next_start_key_ref->{ $table_ref->{ hash_key } } = $json_ref->{ LastEvaluatedKey }->{ HashKeyElement }->{ $hash_type };
1108              
1109             # add range key to start key
1110 0 0 0       if ( defined $table_ref->{ range_key } && defined $json_ref->{ LastEvaluatedKey }->{ RangeKeyElement } ) {
1111 0           my $range_type = $self->_attrib_type( $table, $table_ref->{ range_key } );
1112 0           $next_start_key_ref->{ $table_ref->{ range_key } } = $json_ref->{ LastEvaluatedKey }->{ RangeKeyElement }->{ $range_type };
1113             }
1114             }
1115              
1116             # cycle through all?
1117 0 0 0       if ( $args_ref->{ all } && $next_start_key_ref ) {
1118              
1119             # make sure we do not run into a loop by comparing last and current start key
1120 0           my $new_start_key = join( ';', map { sprintf( '%s=%s', $_, $next_start_key_ref->{ $_ } ) } sort keys %$next_start_key_ref );
  0            
1121 0 0         my %key_cache = defined $args_ref->{ _start_key_cache } ? %{ $args_ref->{ _start_key_cache } } : ();
  0            
1122             #print Dumper( { STARTKEY => $next_start_key_ref, LASTEVAL => $json_ref->{ LastEvaluatedKey }, KEYS => [ \%key_cache, $new_start_key ] } );
1123              
1124 0 0         if ( ! defined $key_cache{ $new_start_key } ) {
1125 0           $key_cache{ $new_start_key } = 1;
1126              
1127             # perform sub-query
1128 0           my ( $sub_count, $sub_res_ref ) = $self->query_items( $table_orig, $filter_ref, {
1129             %$args_ref,
1130             _start_key_cache => \%key_cache,
1131             start_key => $next_start_key_ref
1132             } );
1133             #print Dumper( { SUB_COUNT => $sub_count } );
1134              
1135             # add result
1136 0 0         if ( $sub_count ) {
1137 0           $count += $sub_count;
1138 0           push @res, @$sub_res_ref;
1139             }
1140             }
1141             }
1142              
1143 0 0         return wantarray ? ( $count, \@res, $next_start_key_ref ) : \@res;
1144             }
1145              
1146             # error
1147 0           $self->error( 'query_items failed: '. $self->_extract_error_message( $res ) );
1148 0           return;
1149             }
1150              
1151              
1152              
1153              
1154             sub scan_items {
1155 0     0 1   my ( $self, $table, $filter_ref, $args_ref ) = @_;
1156 0           my $table_orig = $table;
1157 0           $table = $self->_table_name( $table );
1158 0   0       $args_ref ||= {
1159             limit => undef, # amount of items
1160             start_key => undef, # eg { hash_key => 1, range_key => "bla" }
1161             attributes => undef, # eq [ qw/ attrib1 attrib2 / ]
1162             count => 0, # returns amount instead of the actual result
1163             all => 0, # read all entries (runs possibly multiple queries)
1164             max_retries => undef, # overwrite default max retries
1165             };
1166              
1167             # check definition
1168             croak "scan_items: Table '$table' does not exist in table definition"
1169 0 0         unless defined $self->tables->{ $table };
1170 0           my $table_ref = $self->tables->{ $table };
1171              
1172             # build put
1173             my %query = (
1174             TableName => $table,
1175             ScanFilter => {},
1176 0 0         ( defined $args_ref->{ limit } ? ( Limit => $args_ref->{ limit } ) : () ),
1177             );
1178              
1179             # using filter
1180 0 0 0       if ( $filter_ref && keys %$filter_ref ) {
1181 0           my @filter_keys = $self->_check_keys( "scan_items: filter keys", $table, $filter_ref );
1182 0           my $s_ref = $query{ ScanFilter };
1183 0           foreach my $key( @filter_keys ) {
1184 0           my $type = $self->_attrib_type( $table, $key );
1185 0           my $val_ref = $filter_ref->{ $key };
1186 0   0       my $rvalue = ref( $val_ref ) || '';
1187 0 0         if ( $rvalue eq 'HASH' ) {
    0          
1188 0           my ( $op, $value ) = %$val_ref;
1189 0           $s_ref->{ $key } = {
1190             AttributeValueList => [ { $type => $value. '' } ],
1191             ComparisonOperator => uc( $op )
1192             };
1193             }
1194             elsif( $rvalue eq 'ARRAY' ) {
1195 0           $s_ref->{ $key } = {
1196             AttributeValueList => [ { $type => $val_ref } ],
1197             ComparisonOperator => 'IN'
1198             };
1199             }
1200             else {
1201 0           $s_ref->{ $key } = {
1202             AttributeValueList => [ { $type => $val_ref. '' } ],
1203             ComparisonOperator => 'EQ'
1204             };
1205             }
1206             }
1207             }
1208              
1209             # with start key?
1210 0 0         if( defined( my $start_key_ref = $args_ref->{ start_key } ) ) {
1211 0           $self->_check_keys( "scan_items: start_key", $table, $start_key_ref );
1212 0           my $e_ref = $query{ ExclusiveStartKey } = {};
1213              
1214             # add hash key
1215 0 0         if ( defined $start_key_ref->{ $table_ref->{ hash_key } } ) {
1216 0           my $type = $self->_attrib_type( $table, $table_ref->{ hash_key } );
1217 0           $e_ref->{ HashKeyElement } = { $type => $start_key_ref->{ $table_ref->{ hash_key } } };
1218             }
1219              
1220             # add range key?
1221 0 0 0       if ( defined $table_ref->{ range_key } && defined $start_key_ref->{ $table_ref->{ range_key } } ) {
1222 0           my $type = $self->_attrib_type( $table, $table_ref->{ range_key } );
1223 0           $e_ref->{ RangeKeyElement } = { $type => $start_key_ref->{ $table_ref->{ range_key } } };
1224             }
1225             }
1226              
1227             # only certain attributes
1228 0 0         if ( defined( my $attribs_ref = $args_ref->{ attributes } ) ) {
    0          
1229 0           my @keys = $self->_check_keys( "scan_items: attributes", $table, $attribs_ref );
1230 0           $query{ AttributesToGet } = \@keys;
1231             }
1232              
1233             # or count?
1234             elsif ( $args_ref->{ count } ) {
1235 0           $query{ Count } = \1;
1236             }
1237              
1238             # perform query
1239             my ( $res, $res_ok, $json_ref ) = $self->request( Scan => \%query, {
1240             max_retries => $args_ref->{ max_retries },
1241 0           } );
1242              
1243             # format & return result
1244 0 0 0       if ( $res_ok && defined $json_ref->{ Items } ) {
1245 0           my @res;
1246 0           foreach my $from_ref( @{ $json_ref->{ Items } } ) {
  0            
1247 0           push @res, $self->_format_item( $table, $from_ref );
1248             }
1249              
1250 0           my $count = $json_ref->{ Count };
1251              
1252             # build start key for return or use
1253 0           my $next_start_key_ref;
1254 0 0         if ( defined $json_ref->{ LastEvaluatedKey } ) {
1255 0           $next_start_key_ref = {};
1256              
1257             # add hash key to start key
1258 0           my $hash_type = $self->_attrib_type( $table, $table_ref->{ hash_key } );
1259 0           $next_start_key_ref->{ $table_ref->{ hash_key } } = $json_ref->{ LastEvaluatedKey }->{ HashKeyElement }->{ $hash_type };
1260              
1261             # add range key to start key
1262 0 0 0       if ( defined $table_ref->{ range_key } && defined $json_ref->{ LastEvaluatedKey }->{ RangeKeyElement } ) {
1263 0           my $range_type = $self->_attrib_type( $table, $table_ref->{ range_key } );
1264 0           $next_start_key_ref->{ $table_ref->{ range_key } } = $json_ref->{ LastEvaluatedKey }->{ RangeKeyElement }->{ $range_type };
1265             }
1266             }
1267              
1268             # cycle through all?
1269 0 0 0       if ( $args_ref->{ all } && $next_start_key_ref ) {
1270              
1271             # make sure we do not run into a loop by comparing last and current start key
1272 0           my $new_start_key = join( ';', map { sprintf( '%s=%s', $_, $next_start_key_ref->{ $_ } ) } sort keys %$next_start_key_ref );
  0            
1273 0 0         my %key_cache = defined $args_ref->{ _start_key_cache } ? %{ $args_ref->{ _start_key_cache } } : ();
  0            
1274             #print Dumper( { STARTKEY => $next_start_key_ref, LASTEVAL => $json_ref->{ LastEvaluatedKey }, KEYS => [ \%key_cache, $new_start_key ] } );
1275              
1276 0 0         if ( ! defined $key_cache{ $new_start_key } ) {
1277 0           $key_cache{ $new_start_key } = 1;
1278              
1279             # perform sub-query
1280 0           my ( $sub_count, $sub_res_ref ) = $self->scan_items( $table_orig, $filter_ref, {
1281             %$args_ref,
1282             _start_key_cache => \%key_cache,
1283             start_key => $next_start_key_ref
1284             } );
1285             #print Dumper( { SUB_COUNT => $sub_count } );
1286              
1287             # add result
1288 0 0         if ( $sub_count ) {
1289 0           $count += $sub_count;
1290 0           push @res, @$sub_res_ref;
1291             }
1292             }
1293             }
1294              
1295 0 0         return wantarray ? ( $count, \@res, $next_start_key_ref ) : \@res;
1296             }
1297              
1298 0 0         if ( $args_ref->{ count } ) { # do not have $json_ref->{ Items }
1299 0 0         return $json_ref if $res_ok;
1300             }
1301              
1302             # error
1303 0           $self->error( 'scan_items failed: '. $self->_extract_error_message( $res ) );
1304 0           return;
1305             }
1306              
1307              
1308              
1309              
1310             sub request {
1311 0     0 1   my ( $self, $target, $json, $args_ref ) = @_;
1312 0   0       $args_ref ||= {
1313             max_retries => undef
1314             };
1315              
1316             # assure security token existing
1317 0 0         unless( $self->_init_security_token() ) {
1318 0           my %error = ( error => $self->error() );
1319 0 0         return wantarray ? ( undef, 0, \%error ) : \%error;
1320             }
1321              
1322             # convert to string, if required
1323 0 0         $json = $self->json->encode( $json ) if ref $json;
1324              
1325             # get date
1326 0           my $http_date = time2str( time );
1327              
1328             # build signable content
1329             #$json is already utf8 encoded via json encode
1330             my $sign_content = encode_utf8(join( "\n",
1331             'POST', '/', '',
1332             'host:'. $self->host,
1333             'x-amz-date:'. $http_date,
1334             'x-amz-security-token:'. $self->_credentials->{ SessionToken },
1335 0           'x-amz-target:DynamoDB_20111205.'. $target,
1336             ''
1337             )) . "\n" . $json ;
1338 0           my $signature = hmac_sha256_base64( sha256( $sign_content ), $self->_credentials->{ SecretAccessKey } );
1339 0           $signature .= '=' while( length( $signature ) % 4 != 0 );
1340              
1341             # build request
1342 0           my $request = HTTP::Request->new( POST => 'http://'. $self->host. '/' );
1343              
1344             # .. setup headers
1345 0           $request->header( host => $self->host );
1346 0           $request->header( 'x-amz-date' => $http_date );
1347 0           $request->header( 'x-amz-target', 'DynamoDB_'. $self->api_version. '.'. $target );
1348             $request->header( 'x-amzn-authorization' => join( ',',
1349             'AWS3 AWSAccessKeyId='. $self->_credentials->{ AccessKeyId },
1350 0           'Algorithm=HmacSHA256',
1351             'SignedHeaders=host;x-amz-date;x-amz-security-token;x-amz-target',
1352             'Signature='. $signature
1353             ) );
1354 0           $request->header( 'x-amz-security-token' => $self->_credentials->{ SessionToken } );
1355 0           $request->header( 'content-type' => 'application/x-amz-json-1.0' );
1356              
1357             # .. add content
1358 0           $request->content( $json );
1359              
1360 0           my ( $json_ref, $response );
1361             my $tries = defined $args_ref->{ max_retries }
1362             ? $args_ref->{ max_retries }
1363 0 0         : $self->max_retries + 1;
1364 0           while( 1 ) {
1365              
1366             # run request
1367 0           $response = $self->lwp->request( $request );
1368              
1369             # pull out the RequestID header
1370 0           $self->request_id( $response->header( 'x-amzn-RequestId' ) );
1371              
1372 0 0         $ENV{ DYNAMO_DB_DEBUG } && warn Dumper( $response );
1373 0 0         $ENV{ DYNAMO_DB_DEBUG_KEEPALIVE } && warn " LWP keepalives in use: ", scalar($self->_lwpcache()->get_connections()), "/", $self->_lwpcache()->total_capacity(), "\n";
1374              
1375             # get json
1376             $json_ref = $response
1377 0 0 0       ? eval { $self->json->decode( $response->decoded_content ) } || { error => "Failed to parse JSON result" }
1378             : { error => "Failed to get result" };
1379 0 0 0       if ( defined $json_ref->{ __type } && $json_ref->{ __type } =~ /ProvisionedThroughputExceededException/ && $tries-- > 0 ) {
      0        
1380 0 0         $ENV{ DYNAMO_DB_DEBUG_RETRY } && warn "Retry $target: $json\n";
1381 0           usleep( $self->retry_timeout * 1_000_000 );
1382 0           next;
1383             }
1384 0           last;
1385             }
1386              
1387              
1388             # handle error
1389 0 0 0       if ( defined $json_ref->{ error } && $json_ref->{ error } ) {
    0 0        
      0        
1390 0           $self->error( $json_ref->{ error } );
1391             }
1392              
1393             # handle exception
1394             elsif ( defined $json_ref->{ __type } && $json_ref->{ __type } =~ /Exception/ && $json_ref->{ Message } ) {
1395 0           $self->error( $json_ref->{ Message } );
1396             }
1397              
1398 0 0         return wantarray ? ( $response, $response ? $response->is_success : 0, $json_ref ) : $json_ref;
    0          
1399             }
1400              
1401              
1402              
1403              
1404             sub error {
1405 0     0 1   my ( $self, $str ) = @_;
1406 0 0         if ( $str ) {
1407 0 0         croak $str if $self->raise_error();
1408 0           $self->_error( $str );
1409             }
1410 0 0         return $self->_error if $self->_has_error;
1411 0           return ;
1412             }
1413              
1414             #
1415             # _build_value
1416             # Creates the value for inclusion in JSON going to Dynamo
1417             sub _build_value {
1418 0     0     my ( $self, $value, $type ) = @_;
1419              
1420             # Deal with sets of other types
1421 0 0         if ( $type =~ /^(.)S$/ ) {
    0          
1422 0           my @values = map { $self->_build_value($_,$1) }
1423 0 0         ( ref( $value ) ? @{ $value } : () );
  0            
1424 0           return \@values;
1425             }
1426             # Binary Types: Base 64 Encode
1427             elsif ( $type eq 'B' ) {
1428 0           return encode_base64($value,'') . '';
1429             }
1430             # Numeric and String: Force to string
1431             else {
1432 0           return $value . '';
1433             }
1434             }
1435              
1436             #
1437             # _init_security_token
1438             # Creates new temporary security token (, access and secret key), if not exist
1439             #
1440              
1441             sub _init_security_token {
1442 0     0     my ( $self ) = @_;
1443              
1444             # wheter has valid credentials
1445 0 0         if ( $self->_has_credentials() ) {
1446 0           my $dt = DateTime->now( time_zone => 'local' )->add( seconds => 5 );
1447 0 0         return 1 if $dt < $self->_credentials_expire;
1448             }
1449              
1450             # build aws signed request
1451 0 0         $self->_aws_signer( Net::Amazon::AWSSign->new(
1452             $self->access_key, $self->secret_key ) )
1453             unless $self->_has_aws_signer;
1454 0           my $url = $self->_aws_signer->addRESTSecret( $self->_security_token_url );
1455              
1456             # get token
1457 0           my $res = $self->lwp->get( $url );
1458              
1459             # got response
1460 0 0         if ( $res->is_success) {
1461 0           my $content = $res->decoded_content;
1462 0           my $result_ref = XMLin( $content );
1463              
1464             # got valid result
1465 0 0 0       if( ref $result_ref && defined $result_ref->{ GetSessionTokenResult }
      0        
      0        
1466             && defined $result_ref->{ GetSessionTokenResult }
1467             && defined $result_ref->{ GetSessionTokenResult }->{ Credentials }
1468             ) {
1469             # SessionToken, AccessKeyId, Expiration, SecretAccessKey
1470 0           my $cred_ref = $result_ref->{ GetSessionTokenResult }->{ Credentials };
1471 0 0 0       if ( ref( $cred_ref )
      0        
      0        
      0        
1472             && defined $cred_ref->{ SessionToken }
1473             && defined $cred_ref->{ AccessKeyId }
1474             && defined $cred_ref->{ SecretAccessKey }
1475             && defined $cred_ref->{ Expiration }
1476             ) {
1477             # parse expiration date
1478 0           my $pattern = DateTime::Format::Strptime->new(
1479             pattern => '%FT%T',
1480             time_zone => 'UTC'
1481             );
1482 0           my $expire = $pattern->parse_datetime( $cred_ref->{ Expiration } );
1483 0           $expire->set_time_zone( 'local' );
1484 0           $self->_credentials_expire( $expire );
1485              
1486             # set credentials
1487 0           $self->_credentials( $cred_ref );
1488 0           return 1;
1489             }
1490             }
1491             else {
1492 0           $self->error( "Failed to fetch credentials: ". $res->status_line. " ($content)" );
1493             }
1494             }
1495             else {
1496 0   0       my $content = eval { $res->decoded_content } || "No Content";
1497 0           $self->error( "Failed to fetch credentials: ". $res->status_line. " ($content)" );
1498             }
1499              
1500 0           return 0;
1501             }
1502              
1503              
1504             #
1505             # _check_table $table
1506             # Check whether table exists and returns definition
1507             #
1508              
1509             sub _check_table {
1510 0     0     my ( $self, $meth, $table ) = @_;
1511 0 0         unless( $table ) {
1512 0           $table = $meth;
1513 0           $meth = "check_table";
1514             }
1515             croak "$meth: Table '$table' not defined"
1516 0 0         unless defined $self->tables->{ $table };
1517              
1518 0           return $self->tables->{ $table };
1519             }
1520              
1521              
1522             #
1523             # _check_keys $meth, $table, $key_ref
1524             # Check attributes. Dies on invalid (not registererd) attributes.
1525             #
1526              
1527             sub _check_keys {
1528 0     0     my ( $self, $meth, $table, $key_ref ) = @_;
1529 0           my $table_ref = $self->_check_table( $meth, $table );
1530              
1531 0 0         my @keys = ref( $key_ref )
    0          
1532             ? ( ref( $key_ref ) eq 'ARRAY'
1533             ? @$key_ref
1534             : keys %$key_ref
1535             )
1536             : ( $key_ref )
1537             ;
1538              
1539 0           my @invalid_keys = grep { ! defined $table_ref->{ attributes }->{ $_ } } @keys;
  0            
1540 0 0         croak "$meth: Invalid keys: ". join( ', ', @invalid_keys )
1541             if @invalid_keys;
1542              
1543 0 0         return wantarray ? @keys : \@keys;
1544             }
1545              
1546              
1547             #
1548             # _build_pk_filter $table, $where_ref, $node_ref
1549             # Build attribute filter "HashKeyElement" and "RangeKeyElement".
1550             # Hash key and range key will be deleted from where clause
1551             #
1552              
1553             sub _build_pk_filter {
1554 0     0     my ( $self, $table, $where_ref, $node_ref ) = @_;
1555             # primary key
1556 0           my $table_ref = $self->_check_table( $table );
1557 0           my $hash_value = delete $where_ref->{ $table_ref->{ hash_key } };
1558 0           my $hash_type = $self->_attrib_type( $table, $table_ref->{ hash_key } );
1559 0           $node_ref->{ HashKeyElement } = { $hash_type => $hash_value . '' };
1560 0 0         if ( defined $table_ref->{ range_key } ) {
1561 0           my $range_value = delete $where_ref->{ $table_ref->{ range_key } };
1562 0           my $range_type = $self->_attrib_type( $table, $table_ref->{ range_key } );
1563 0           $node_ref->{ RangeKeyElement } = { $range_type => $range_value . '' };
1564             }
1565             }
1566              
1567              
1568             #
1569             # _build_attrib_filter $table, $where_ref, $node_ref
1570             # Build attribute filter "Expected" from given where-clause-ref
1571             # {
1572             # attrib1 => 'somevalue', # -> { attrib1 => { Value => { S => 'somevalue' } } }
1573             # attrib2 => \1, # -> { attrib2 => { Exists => true } }
1574             # attrib3 => { # -> { attrib3 => { Value => { S => 'bla' } } }
1575             # value => 'bla'
1576             # }
1577             # }
1578             #
1579              
1580             sub _build_attrib_filter {
1581 0     0     my ( $self, $table, $where_ref, $node_ref ) = @_;
1582 0           my $table_ref = $self->_check_table( $table );
1583 0           foreach my $key( keys %$where_ref ){
1584 0           my $type = $table_ref->{ attributes }->{ $key };
1585 0           my %cur;
1586 0 0         unless( ref( $where_ref->{ $key } ) ) {
1587 0           $where_ref->{ $key } = { value => $where_ref->{ $key } };
1588             }
1589 0 0         if ( ref( $where_ref->{ $key } ) eq 'SCALAR' ) {
1590 0           $cur{ Exists } = $where_ref->{ $key };
1591             }
1592             else {
1593 0 0         if ( defined( my $value = $where_ref->{ $key }->{ value } ) ) {
1594 0           $cur{ Value } = { $type => $value. '' };
1595             }
1596 0 0         if ( defined $where_ref->{ $key }->{ exists } ) {
1597 0 0         $cur{ Exists } = $where_ref->{ $key }->{ exists } ? \1 : \0;
1598             }
1599             }
1600 0 0         $node_ref->{ $key } = \%cur if keys %cur;
1601             }
1602             }
1603              
1604              
1605             #
1606             # _attrib_type $table, $key
1607             # Returns type ("S", "N", "B", "NS", "SS", "BS") of existing attribute in table
1608             #
1609              
1610             sub _attrib_type {
1611 0     0     my ( $self, $table, $key ) = @_;
1612 0           my $table_ref = $self->_check_table( $table );
1613 0 0         return defined $table_ref->{ attributes }->{ $key } ? $table_ref->{ attributes }->{ $key } : "S";
1614             }
1615              
1616              
1617             #
1618             # _attribs $table
1619             # Returns list of attributes in table
1620             #
1621              
1622             sub _attribs {
1623 0     0     my ( $self, $table ) = @_;
1624 0           my $table_ref = $self->_check_table( $table );
1625 0           return sort keys %{ $table_ref->{ attributes } };
  0            
1626             }
1627              
1628              
1629             #
1630             # _format_item $table, $from_ref
1631             #
1632             # Formats result item into simpler format
1633             # {
1634             # attrib => { S => "bla" }
1635             # }
1636             #
1637             # to
1638             # {
1639             # attrib => 'bla'
1640             # }
1641             #
1642              
1643             sub _format_item {
1644 0     0     my ( $self, $table, $from_ref ) = @_;
1645 0           my $table_ref = $self->_check_table( format_item => $table );
1646 0           my %formatted;
1647 0 0         if ( defined $from_ref->{ HashKeyElement } ) {
1648 0           my @keys = ( 'hash' );
1649 0 0         push @keys, 'range' if defined $table_ref->{ range_key };
1650 0           foreach my $key( @keys ) {
1651 0           my $key_name = $table_ref->{ "${key}_key" };
1652 0           my $key_type = $table_ref->{ attributes }->{ $key_name };
1653 0           $formatted{ $key_name } = $from_ref->{ ucfirst( $key ). 'KeyElement' }->{ $key_type };
1654              
1655 0 0         if ( $key_type eq 'B' ) {
    0          
1656 0           $formatted{ $key_name } = decode_base64( $formatted{ $key_name } );
1657             }
1658             elsif ( $key_name = 'BS' ) {
1659 0           $formatted{ $key_name } = [ map { decode_base64($_) } @{ $formatted{ $key_name } } ];
  0            
  0            
1660             }
1661             }
1662             }
1663             else {
1664 0 0         if ( $self->derive_table() ) {
1665 0           while ( my ( $key, $value ) = each %$from_ref ) {
1666 0 0         if ( exists($value->{B}) ) {
    0          
1667 0           $formatted{$key} = decode_base64($value->{B});
1668             }
1669             elsif ( exists($value->{BS}) ) {
1670 0           $formatted{$key} = [ map { decode_base64($_) } @{ $value->{BS} } ];
  0            
  0            
1671             }
1672             else {
1673 0   0       $formatted{$key} = ( $value->{'S'} || $value->{'N'} || $value->{'NS'} || $value->{'SS'} );
1674             }
1675             }
1676             }
1677             else {
1678 0           while( my( $attrib, $type ) = each %{ $table_ref->{ attributes } } ) {
  0            
1679 0 0         next unless defined $from_ref->{ $attrib };
1680 0 0         if ( $type eq 'BS' ) {
    0          
1681 0           $formatted{ $attrib } = $self->_decode_binary_set( $from_ref->{ $attrib }->{ $type } );
1682             }
1683             elsif ( $type eq 'B' ) {
1684 0           $formatted{ $attrib } = decode_base64( $from_ref->{ $attrib }->{ $type } );
1685             }
1686             else {
1687 0           $formatted{ $attrib } = $from_ref->{ $attrib }->{ $type };
1688             }
1689             }
1690             }
1691             }
1692 0           return \%formatted;
1693             }
1694              
1695             sub _decode_binary_set {
1696 0     0     my ( $self, $bs_ref ) = shift;
1697              
1698 0           return [ map { decode_base64($_) } @$bs_ref ];
  0            
1699             }
1700              
1701              
1702             #
1703             # _table_name
1704             # Returns prefixed table name
1705             #
1706              
1707             sub _table_name {
1708 0     0     my ( $self, $table, $remove ) = @_;
1709 0 0         return $remove ? substr( $table, length( $self->namespace ) ) : $self->namespace. $table;
1710             }
1711              
1712              
1713             #
1714             # _extract_error_message
1715             #
1716              
1717             sub _extract_error_message {
1718 0     0     my ( $self, $response ) = @_;
1719 0           my $msg = '';
1720 0 0         if ( $response ) {
1721 0   0       my $json = eval { $self->json->decode( $response->decoded_content ) } || { error => "Failed to parse JSON result" };
1722 0 0         if ( defined $json->{ __type } ) {
1723 0           $msg = join( ' ** ',
1724             "ErrorType: $json->{ __type }",
1725             "ErrorMessage: $json->{ message }",
1726             );
1727             }
1728             else {
1729 0           $msg = $json->{ error };
1730             }
1731             }
1732             else {
1733 0           $msg = 'No response received. DynamoDB down?'
1734             }
1735             }
1736              
1737             #
1738             # _cache_enabled
1739             #
1740              
1741             sub _cache_enabled {
1742 0     0     my ( $self, $args_ref ) = @_;
1743             return $self->has_cache && ! $args_ref->{ no_cache }
1744 0   0       && ( $args_ref->{ use_cache } || ! $self->cache_disabled );
1745             }
1746              
1747             #
1748             # _cache_key_single
1749             #
1750              
1751             sub _cache_key_single {
1752 0     0     my ( $self, $table, $hash_ref ) = @_;
1753 0           my $table_ref = $self->_check_table( $table );
1754 0           my @keys = ( $table_ref->{ hash_key } );
1755 0 0         push @keys, $table_ref->{ range_key } if defined $table_ref->{ range_key };
1756 0   0       my %pk = map { ( $_ => $hash_ref->{ $_ } || '' ) } @keys;
  0            
1757 0           return $self->_cache_key( $table, 'single', \%pk );
1758             }
1759              
1760             #
1761             # _cache_key
1762             #
1763              
1764             sub _cache_key {
1765 0     0     my ( $self, $table, $name, $id_ref ) = @_;
1766 0           my $method = $self->cache_key_method();
1767 0           return sprintf( '%s-%s-%s', $table, $name, $method->( $self->json->encode( $id_ref ) ) );
1768             }
1769              
1770             __PACKAGE__->meta->make_immutable;
1771              
1772              
1773              
1774             1;
1775              
1776             __END__
1777              
1778             =pod
1779              
1780             =encoding UTF-8
1781              
1782             =head1 NAME
1783              
1784             Net::Amazon::DynamoDB
1785              
1786             =head1 VERSION
1787              
1788             version 0.002000
1789              
1790             =head1 SYNOPSIS
1791              
1792             my $ddb = Net::Amazon::DynamoDB->new(
1793             access_key => $my_access_key,
1794             secret_key => $my_secret_key,
1795             tables => {
1796              
1797             # table with only hash key
1798             sometable => {
1799             hash_key => 'id',
1800             attributes => {
1801             id => 'N',
1802             name => 'S',
1803             binary_data => 'B'
1804             }
1805             },
1806              
1807             # table with hash and reange key key
1808             othertable => {
1809             hash_key => 'id',
1810             range_key => 'range_id',
1811             attributes => {
1812             id => 'N',
1813             range_id => 'N',
1814             attrib1 => 'S',
1815             attrib2 => 'S'
1816             }
1817             }
1818             }
1819             );
1820              
1821             # create both tables with 10 read and 5 write units
1822             $ddb->exists_table( $_ ) || $ddb->create_table( $_, 10, 5 )
1823             for qw/ sometable othertable /;
1824              
1825             # insert something into tables
1826             $ddb->put_item( sometable => {
1827             id => 5,
1828             name => 'bla',
1829             binary_data => $some_data
1830             } ) or die $ddb->error;
1831             $ddb->put_item( othertable => {
1832             id => 5,
1833             range_id => 7,
1834             attrib1 => 'It is now '. localtime(),
1835             attrib2 => 'Or in unix timstamp '. time(),
1836             } ) or die $ddb->error;
1837              
1838             =head1 DESCRIPTION
1839              
1840             Simple to use interface for Amazon DynamoDB
1841              
1842             If you want an ORM-like interface with real objects to work with, this is implementation
1843             is not for you. If you just want to access DynamoDB in a simple/quick manner - you are welcome.
1844              
1845             See L<https://github.com/ukautz/Net-Amazon-DynamoDB> for latest release.
1846              
1847             =head1 NAME
1848              
1849             Net::Amazon::DynamoDB - Simple interface for Amazon DynamoDB
1850              
1851             =head1 CLASS ATTRIBUTES
1852              
1853             =head2 tables
1854              
1855             The table definitions
1856              
1857             =head2 use_keep_alive
1858              
1859             Use keep_alive connections to AWS (Uses C<LWP::ConnCache> experimental mechanism). 0 to disable, positive number sets value for C<LWP::UserAgent> attribute 'keep_alive'
1860             Default: 0
1861              
1862             =head2 lwp
1863              
1864             Contains C<LWP::UserAgent> instance.
1865              
1866             =head2 json
1867              
1868             Contains C<JSON> instance for decoding/encoding json.
1869              
1870             JSON object needs to support: canonical, allow_nonref and utf8
1871              
1872             =head2 host
1873              
1874             DynamoDB API Hostname. Your table will be in this region only. Table names do not
1875             have to be unique across regions. This is how you specify other regions. See L<Amazon's documentation for other available endpoints|https://docs.aws.amazon.com/general/latest/gr/rande.html#ddb_region>.
1876              
1877             Default: C<dynamodb.us-east-1.amazonaws.com>
1878              
1879             =head2 access_key
1880              
1881             AWS API access key
1882              
1883             Required!
1884              
1885             =head2 secret_key
1886              
1887             AWS API secret key
1888              
1889             Required!
1890              
1891             =head2 api_version
1892              
1893             AWS API Version. Use format "YYYYMMDD"
1894              
1895             Default: 20111205
1896              
1897             =head2 read_consistent
1898              
1899             Whether reads (get_item, batch_get_item) consistent per default or not. This does not affect scan_items or query_items, which are always eventually consistent.
1900              
1901             Default: 0 (eventually consistent)
1902              
1903             =head2 namespace
1904              
1905             Table prefix, prepended before table name on usage
1906              
1907             Default: ''
1908              
1909             =head2 raise_error
1910              
1911             Whether database errors (eg 4xx Response from DynamoDB) raise errors or not.
1912              
1913             Default: 0
1914              
1915             =head2 max_retries
1916              
1917             Amount of retries a query will be tries if ProvisionedThroughputExceededException is raised until final error.
1918              
1919             Default: 0 (do only once, no retries)
1920              
1921             =head2 derive_table
1922              
1923             Whether we parse results using table definition (faster) or without a known definition (still requires table definition for indexes)
1924              
1925             Default: 0
1926              
1927             =head2 retry_timeout
1928              
1929             Wait period in seconds between tries. Float allowed.
1930              
1931             Default: 0.1 (100ms)
1932              
1933             =head2 cache
1934              
1935             Cache object using L<Cache> interface, eg L<Cache::File> or L<Cache::Memcached>
1936              
1937             If set, caching is used for get_item, put_item, update_item and batch_get_item.
1938              
1939             Default: -
1940              
1941             =head2 cache_disabled
1942              
1943             If cache is set, you still can disable it per default and enable it per operation with "use_cache" option (see method documentation)
1944             This way you have a default no-cache policy, but still can use cache in choosen operations.
1945              
1946             Default: 0
1947              
1948             =head2 cache_key_method
1949              
1950             Which one to use. Either sha1_hex, sha256_hex, sha384_hex or coderef
1951              
1952             Default: sha1_hex
1953              
1954             =head2 request_id
1955              
1956             The x-amzn-RequestId header returned by the service. This is needed by
1957             Amazon tech support for debugging service issues
1958              
1959             =head1 METHODS
1960              
1961             =head2 create_table $table_name, $read_amount, $write_amount
1962              
1963             Create a new Table. Returns description of the table
1964              
1965             my $desc_ref = $ddb->create_table( 'table_name', 10, 5 )
1966             $desc_ref = {
1967             count => 123, # amount of "rows"
1968             status => 'CREATING', # or 'ACTIVE' or 'UPDATING' or some error state?
1969             created => 1328893776, # timestamp
1970             read_amount => 10, # amount of read units
1971             write_amount => 5, # amount of write units
1972             hash_key => 'id', # name of the hash key attribute
1973             hash_key_type => 'S', # or 'N',
1974             #range_key => 'id', # name of the hash key attribute (optional)
1975             #range_key_type => 'S', # or 'N' (optional)
1976             }
1977              
1978             =head2 delete_table $table
1979              
1980             Delete an existing (and defined) table.
1981              
1982             Returns bool whether table is now in deleting state (succesfully performed)
1983              
1984             =head2 describe_table $table
1985              
1986             Returns table information
1987              
1988             my $desc_ref = $ddb->describe_table( 'my_table' );
1989             $desc_ref = {
1990             existing => 1,
1991             size => 123213, # data size in bytes
1992             count => 123, # amount of "rows"
1993             status => 'ACTIVE', # or 'DELETING' or 'CREATING' or 'UPDATING' or some error state
1994             created => 1328893776, # timestamp
1995             read_amount => 10, # amount of read units
1996             write_amount => 5, # amount of write units
1997             hash_key => 'id', # name of the hash key attribute
1998             hash_key_type => 'S', # or 'N',
1999             #range_key => 'id', # name of the hash key attribute (optional)
2000             #range_key_type => 'S', # or 'N' (optional)
2001             }
2002              
2003             If no such table exists, return is
2004              
2005             {
2006             existing => 0
2007             }
2008              
2009             =head2 update_table $table, $read_amount, $write_amount
2010              
2011             Update read and write amount for a table
2012              
2013             =head2 exists_table $table
2014              
2015             Returns bool whether table exists or not
2016              
2017             =head2 list_tables
2018              
2019             Returns tables names as arrayref (or array in array context)
2020              
2021             =head2 put_item $table, $item_ref, [$where_ref], [$args_ref]
2022              
2023             Write a single item to table. All primary keys are required in new item.
2024              
2025             # just write
2026             $ddb->put_item( my_table => {
2027             id => 123,
2028             some_attrib => 'bla',
2029             other_attrib => 'dunno'
2030             } );
2031              
2032             # write conditionally
2033             $ddb->put_item( my_table => {
2034             id => 123,
2035             some_attrib => 'bla',
2036             other_attrib => 'dunno'
2037             }, {
2038             some_attrib => { # only update, if some_attrib has the value 'blub'
2039             value => 'blub'
2040             },
2041             other_attrib => { # only update, if a value for other_attrib exists
2042             exists => 1
2043             }
2044             } );
2045              
2046             =over
2047              
2048             =item * $table
2049              
2050             Name of the table
2051              
2052             =item * $item_ref
2053              
2054             Hashref containing the values to be inserted
2055              
2056             =item * $where_ref [optional]
2057              
2058             Filter containing expected values of the (existing) item to be updated
2059              
2060             =item * $args_ref [optional]
2061              
2062             HashRef with options
2063              
2064             =over
2065              
2066             =item * return_old
2067              
2068             If true, returns old value
2069              
2070             =item * no_cache
2071              
2072             Force not using cache, if enabled per default
2073              
2074             =item * use_cache
2075              
2076             Force using cache, if disabled per default but setupped
2077              
2078             =back
2079              
2080             =back
2081              
2082             =head2 batch_write_item $tables_ref, [$args_ref]
2083              
2084             Batch put / delete items into one ore more tables.
2085              
2086             Caution: Each batch put / delete cannot process more operations than you have write capacity for the table.
2087              
2088             Example:
2089              
2090             my ( $ok, $unprocessed_count, $next_query_ref ) = $ddb->batch_write_item( {
2091             table_name => {
2092             put => [
2093             {
2094             attrib1 => "Value 1",
2095             attrib2 => "Value 2",
2096             },
2097             # { .. } ..
2098             ],
2099             delete => [
2100             {
2101             hash_key => "Hash Key Value",
2102             range_key => "Range Key Value",
2103             },
2104             # { .. } ..
2105             ]
2106             },
2107             # table2_name => ..
2108             } );
2109              
2110             if ( $ok ) {
2111             if ( $unprocessed_count ) {
2112             print "Ok, but $unprocessed_count still not processed\n";
2113             $ddb->batch_write_item( $next_query_ref );
2114             }
2115             else {
2116             print "All processed\n";
2117             }
2118             }
2119              
2120             =over
2121              
2122             =item $tables_ref
2123              
2124             HashRef in the form
2125              
2126             { table_name => { put => [ { attribs }, .. ], delete => [ { primary keys } ] } }
2127              
2128             =item $args_ref
2129              
2130             HashRef
2131              
2132             =over
2133              
2134             =item * process_all
2135              
2136             Keep processing everything which is returned as unprocessed (if you send more operations than your
2137             table has write capability or you surpass the max amount of operations OR max size of request (see AWS API docu)).
2138              
2139             Caution: Error handling
2140              
2141             Default: 0
2142              
2143             =back
2144              
2145             =back
2146              
2147             =head2 update_item $table, $update_ref, $where_ref, [$args_ref]
2148              
2149             Update existing item in database. All primary keys are required in where clause
2150              
2151             # update existing
2152             $ddb->update_item( my_table => {
2153             some_attrib => 'bla',
2154             other_attrib => 'dunno'
2155             }, {
2156             id => 123,
2157             } );
2158              
2159             # write conditionally
2160             $ddb->update_item( my_table => {
2161             some_attrib => 'bla',
2162             other_attrib => 'dunno'
2163             }, {
2164             id => 123,
2165             some_attrib => { # only update, if some_attrib has the value 'blub'
2166             value => 'blub'
2167             },
2168             other_attrib => { # only update, if a value for other_attrib exists
2169             exists => 1
2170             }
2171             } );
2172              
2173             =over
2174              
2175             =item * $table
2176              
2177             Name of the table
2178              
2179             =item * $update_ref
2180              
2181             Hashref containing the updates.
2182              
2183             =over
2184              
2185             =item * delete a single values
2186              
2187             { attribname => undef }
2188              
2189             =item * replace a values
2190              
2191             {
2192             attribname1 => 'somevalue',
2193             attribname2 => [ 1, 2, 3 ]
2194             }
2195              
2196             =item * add values (arrays only)
2197              
2198             { attribname => \[ 4, 5, 6 ] }
2199              
2200             =back
2201              
2202             =item * $where_ref [optional]
2203              
2204             Filter HashRef
2205              
2206             =item * $args_ref [optional]
2207              
2208             HashRef of options
2209              
2210             =over
2211              
2212             =item * return_mode
2213              
2214             Can be set to on of "ALL_OLD", "UPDATED_OLD", "ALL_NEW", "UPDATED_NEW"
2215              
2216             =item * no_cache
2217              
2218             Force not using cache, if enabled per default
2219              
2220             =item * use_cache
2221              
2222             Force using cache, if disabled per default but setupped
2223              
2224             =back
2225              
2226             =back
2227              
2228             =head2 get_item $table, $pk_ref, [$args_ref]
2229              
2230             Read a single item by hash (and range) key.
2231              
2232             # only with hash key
2233             my $item1 = $ddb->get_item( my_table => { id => 123 } );
2234             print "Got $item1->{ some_key }\n";
2235              
2236             # with hash and range key, also consistent read and only certain attributes in return
2237             my $item2 = $ddb->get_item( my_other_table =>, {
2238             id => $hash_value, # the hash value
2239             title => $range_value # the range value
2240             }, {
2241             consistent => 1,
2242             attributes => [ qw/ attrib1 attrib2 ]
2243             } );
2244             print "Got $item2->{ attrib1 }\n";
2245              
2246             =over
2247              
2248             =item * $table
2249              
2250             Name of the table
2251              
2252             =item * $pk_ref
2253              
2254             HashRef containing all primary keys
2255              
2256             # only hash key
2257             {
2258             $hash_key => $hash_value
2259             }
2260              
2261             # hash and range key
2262             {
2263             $hash_key => $hash_value,
2264             $range_key => $range_value
2265             }
2266              
2267             =item * $args_ref [optional]
2268              
2269             HashRef of options
2270              
2271             =over
2272              
2273             =item * consistent
2274              
2275             Whether read shall be consistent. If set to 0 and read_consistent is globally enabled, this read will not be consistent
2276              
2277             =item * attributes
2278              
2279             ArrayRef of attributes to read. If not set, all attributes are returned.
2280              
2281             =item * no_cache
2282              
2283             Force not using cache, if enabled per default
2284              
2285             =item * use_cache
2286              
2287             Force using cache, if disabled per default but setupped
2288              
2289             =back
2290              
2291             =back
2292              
2293             =head2 batch_get_item $tables_ref, [$args_ref]
2294              
2295             Read multiple items (possible accross multiple tables) identified by their hash and range key (if required).
2296              
2297             my $res = $ddb->batch_get_item( {
2298             table_name => [
2299             { $hash_key => $value1 },
2300             { $hash_key => $value2 },
2301             { $hash_key => $value3 },
2302             ],
2303             other_table_name => {
2304             keys => [
2305             { $hash_key => $value1, $range_key => $rvalue1 },
2306             { $hash_key => $value2, $range_key => $rvalue2 },
2307             { $hash_key => $value3, $range_key => $rvalue3 },
2308             ],
2309             attributes => [ qw/ attrib1 attrib2 / ]
2310             ]
2311             } );
2312              
2313             foreach my $table( keys %$res ) {
2314             foreach my $item( @{ $res->{ $table } } ) {
2315             print "$item->{ some_attrib }\n";
2316             }
2317             }
2318              
2319             =over
2320              
2321             =item $tables_ref
2322              
2323             HashRef of tablename => primary key ArrayRef
2324              
2325             =item $args_ref
2326              
2327             HashRef
2328              
2329             =over
2330              
2331             =item * process_all
2332              
2333             Batch request might not fetch all requested items at once. This switch enforces
2334             to batch get the unprocessed items.
2335              
2336             Default: 0
2337              
2338             =back
2339              
2340             =back
2341              
2342             =head2 delete_item $table, $where_ref, [$args_ref]
2343              
2344             Deletes a single item by primary key (hash or hash+range key).
2345              
2346             # only with hash key
2347              
2348             =over
2349              
2350             =item * $table
2351              
2352             Name of the table
2353              
2354             =item * $where_ref
2355              
2356             HashRef containing at least primary key. Can also contain additional attribute filters
2357              
2358             =item * $args_ref [optional]
2359              
2360             HashRef containing options
2361              
2362             =over
2363              
2364             =item * return_old
2365              
2366             Bool whether return old, just deleted item or not
2367              
2368             Default: 0
2369              
2370             =item * no_cache
2371              
2372             Force not using cache, if enabled per default
2373              
2374             =item * use_cache
2375              
2376             Force using cache, if disabled per default but setupped
2377              
2378             =back
2379              
2380             =back
2381              
2382             =head2 query_items $table, $where, $args
2383              
2384             Search in a table with hash AND range key.
2385              
2386             my ( $count, $items_ref, $next_start_keys_ref )
2387             = $ddb->query_items( some_table => { id => 123, my_range_id => { GT => 5 } } );
2388             print "Found $count items, where last id is ". $items_ref->[-1]->{ id }. "\n";
2389              
2390             # iterate through al all "pages"
2391             my $next_start_keys_ref;
2392             do {
2393             ( my $count, my $items_ref, $next_start_keys_ref )
2394             = $ddb->query_items( some_table => { id => 123, my_range_id => { GT => 5 } }, {
2395             start_key => $next_start_keys_ref
2396             } );
2397             } while( $next_start_keys_ref );
2398              
2399             =over
2400              
2401             =item * $table
2402              
2403             Name of the table
2404              
2405             =item * $where
2406              
2407             Search condition. Has to contain a value of the primary key and a search-value for the range key.
2408              
2409             Search-value for range key can be formated in two ways
2410              
2411             =over
2412              
2413             =item * Scalar
2414              
2415             Eg
2416              
2417             { $range_key_name => 123 }
2418              
2419             Performs and EQ (equal) search
2420              
2421             =item * HASHREF
2422              
2423             Eg
2424              
2425             { $range_key_name => { GT => 1 } }
2426             { $range_key_name => { CONTAINS => "Bla" } }
2427             { $range_key_name => { IN => [ 1, 2, 5, 7 ] } }
2428              
2429             See L<http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html>
2430              
2431             =back
2432              
2433             =item * $args
2434              
2435             {
2436             limit => 5,
2437             consistent => 0,
2438             backward => 0,
2439             #start_key => { .. }
2440             attributes => [ qw/ attrib1 attrib2 / ],
2441             #count => 1
2442             }
2443              
2444             HASHREF containing:
2445              
2446             =over
2447              
2448             =item * limit
2449              
2450             Amount of items to return
2451              
2452             Default: unlimited
2453              
2454             =item * consistent
2455              
2456             If set to 1, consistent read is performed
2457              
2458             Default: 0
2459              
2460             =item * backward
2461              
2462             Whether traverse index backward or forward.
2463              
2464             Default: 0 (=forward)
2465              
2466             =item * start_key
2467              
2468             Contains start key, as return in C<LastEvaluatedKey> from previous query. Allows to iterate above a table in pages.
2469              
2470             { $hash_key => 5, $range_key => "something" }
2471              
2472             =item * attributes
2473              
2474             Return only those attributes
2475              
2476             [ qw/ attrib attrib2 / ]
2477              
2478             =item * count
2479              
2480             Instead of returning the actual result, return the count.
2481              
2482             Default: 0 (=return result)
2483              
2484             =item * all
2485              
2486             Iterate through all pages (see link to API above) and return them all.
2487              
2488             Can take some time. Also: max_retries might be needed to set, as a scan/query create lot's of read-units, and an immediate reading of the next "pages" lead to an Exception due to too many reads.
2489              
2490             Default: 0 (=first "page" of items)
2491              
2492             =back
2493              
2494             =back
2495              
2496             =head2 scan_items $table, $filter, $args
2497              
2498             Performs scan on table. The result is B<eventually consistent>. Non hash or range keys are allowed in the filter.
2499              
2500             See query_items for argument description.
2501              
2502             Main difference to query_items: A whole table scan is performed, which is much slower. Also the amount of data scanned is limited in size; see L<http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html>
2503              
2504             =head2 request
2505              
2506             Arbitrary request to DynamoDB API
2507              
2508             =head2 error [$str]
2509              
2510             Get/set last error
2511              
2512             =head1 AUTHOR
2513              
2514             =over
2515              
2516             =item * Ulrich Kautz <uk@fortrabbit.de>
2517              
2518             =item * Thanks to MadHacker L<http://stackoverflow.com/users/1139526/madhacker> (the signing code in request method)
2519              
2520             =item * Benjamin Abbott-Scoot <benjamin@abbott-scott.net> (Keep Alive patch)
2521              
2522             =back
2523              
2524             =head1 COPYRIGHT
2525              
2526             Copyright (c) 2012 the L</AUTHOR> as listed above
2527              
2528             =head1 LICENCSE
2529              
2530             Same license as Perl itself.
2531              
2532             =head1 AUTHORS
2533              
2534             =over 4
2535              
2536             =item *
2537              
2538             Arthur Axel "fREW" Schmidt <frioux+cpan@gmail.com>
2539              
2540             =item *
2541              
2542             Ulrich Kautz <uk@fortrabbit.de>
2543              
2544             =back
2545              
2546             =head1 COPYRIGHT AND LICENSE
2547              
2548             This software is copyright (c) 2017 by Ulrich Kautz.
2549              
2550             This is free software; you can redistribute it and/or modify it under
2551             the same terms as the Perl 5 programming language system itself.
2552              
2553             =cut