File Coverage

blib/lib/KiokuDB/Backend/Role/TXN/Memory.pm
Criterion Covered Total %
statement 99 105 94.2
branch 33 38 86.8
condition 1 3 33.3
subroutine 14 15 93.3
pod 1 8 12.5
total 148 169 87.5


line stmt bran cond sub pod time code
1             #!/usr/bin/perl
2              
3             package KiokuDB::Backend::Role::TXN::Memory;
4 21     21   10416 use Moose::Role;
  21         38  
  21         123  
5              
6 21     21   83298 use Carp qw(croak);
  21         44  
  21         1294  
7              
8 21     21   7354 use KiokuDB::Util qw(deprecate);
  21         56  
  21         131  
9              
10             with qw(KiokuDB::Backend::Role::TXN);
11              
12 21     21   4639 use namespace::clean -except => 'meta';
  21         36  
  21         101  
13              
14             requires qw(commit_entries get_from_storage);
15              
16             # extremely slow/shitty fallback method, will be deprecated eventually
17             sub exists_in_storage {
18 0     0 1 0 my ( $self, @uuids ) = @_;
19              
20 0         0 deprecate('0.37', 'exists_in_storage should be implemented in TXN::Memory using backends');
21              
22 0 0       0 map { $self->get_from_storage($_) ? 1 : '' } @uuids;
  0         0  
23             }
24              
25             has _txn_stack => (
26             isa => "ArrayRef",
27             is => "ro",
28             default => sub { [] },
29             );
30              
31             sub _new_frame {
32             return {
33 2415     2415   16056 'live' => {},
34             'log' => [],
35             'cleared' => !1,
36             };
37             }
38              
39             sub txn_begin {
40 2183     2183 0 3548 my $self = shift;
41              
42 2183         2710 push @{ $self->_txn_stack }, $self->_new_frame;
  2183         69812  
43             }
44              
45             sub txn_rollback {
46 301     301 0 568 my $self = shift;
47              
48 301 100       606 pop @{ $self->_txn_stack } || croak "no open transaction";
  301         10896  
49             }
50              
51             sub txn_commit {
52 1915     1915 0 2828 my $self = shift;
53              
54 1915         56828 my $stack = $self->_txn_stack;
55              
56 1915   33     6269 my $txn = pop @$stack || croak "no open transaction";
57              
58 1915 100       2439 if ( @{ $self->_txn_stack } ) {
  1915         53377  
59 99         475 $stack->[-1] = $self->_collapse_txn_frames($txn, $stack->[-1]);
60             } else {
61 1816 100       6651 $self->clear_storage if $txn->{cleared};
62 1816         2615 $self->commit_entries(@{ $txn->{log} });
  1816         9222  
63             }
64             }
65              
66             sub _collapsed_txn_stack {
67 925     925   1384 my $self = shift;
68              
69 925         1315 $self->_collapse_txn_frames(reverse @{ $self->_txn_stack });
  925         25191  
70             }
71              
72             sub _collapse_txn_frames {
73 1057     1057   2131 my ( $self, $head, @tail ) = @_;
74              
75 1057 50       2928 return $self->_new_frame unless $head;
76              
77 1057 100       4239 return $head unless @tail;
78              
79 132         275 my $next = shift @tail;
80              
81 132 100       424 if ( $head->{cleared} ) {
82 99         361 return $head;
83             } else {
84 33         99 my $merged = {
85             cleared => $next->{cleared},
86             log => [
87 33         129 @{ $next->{log} },
88 33         94 @{ $head->{log} },
89             ],
90             live => {
91 33         226 %{ $next->{live} },
92 33         87 %{ $head->{live} },
93             },
94             };
95              
96 33         114 return $self->_collapse_txn_frames( $merged, @tail );
97             }
98             }
99              
100             # FIXME remove duplication between get/exists
101             sub get {
102 3689     3689 0 8147 my ( $self, @uuids ) = @_;
103              
104 3689         4748 my %entries;
105 3689         7274 my %remaining = map { $_ => undef } @uuids;
  6634         16505  
106              
107 3689         105170 my $stack = $self->_txn_stack;
108              
109 3689         7541 foreach my $frame ( @$stack ) {
110             # try to find a modified entry for every remaining key
111 2309         7471 foreach my $id ( keys %remaining ) {
112 2918 100       9738 if ( my $entry = $frame->{live}{$id} ) {
113 320 100       8181 if ( $entry->deleted ) {
114 34         430 return ();
115             }
116 286         705 $entries{$id} = $entry;
117 286         1064 delete $remaining{$id};
118             }
119             }
120              
121             # if there are no more remaining keys, we can stop examining the
122             # transaction frames
123 2275 100       5854 last unless keys %remaining;
124              
125             # if the current frame has cleared the DB and there are still remaining
126             # keys, they are supposed to fail the lookup
127 1957 50       6043 return () if $frame->{cleared};
128             }
129              
130 3655 100       9469 if ( keys %remaining ) {
131 3335         14054 my @remaining = $self->get_from_storage(keys %remaining);
132              
133 3335 100       7363 if ( @remaining ) {
134 3300         9243 @entries{keys %remaining} = @remaining;
135 3300 100       9247 @{ $stack->[-1]{live} }{keys %remaining} = @remaining if @$stack;
  1924         5465  
136             } else {
137 35         466 return ();
138             }
139             }
140              
141 3620         22846 return @entries{@uuids};
142             }
143              
144             # FIXME remove duplication between get/exists
145             sub exists {
146 642     642 0 1336 my ( $self, @uuids ) = @_;
147              
148 642         704 my %exists;
149 642         1112 my %remaining = map { $_ => undef } @uuids;
  693         2436  
150              
151 642         907 foreach my $frame ( @{ $self->_txn_stack } ) {
  642         18654  
152 331         1199 foreach my $id ( keys %remaining ) {
153 364 100       1498 if ( my $entry = $frame->{live}{$id} ) {
154 133         3492 $exists{$id} = not $entry->deleted;
155 133         562 delete $remaining{$id};
156             }
157             }
158              
159 331 100       987 last unless keys %remaining;
160              
161 198 50       669 if ( $frame->{cleared} ) {
162 0         0 @exists{keys %remaining} = ('') x keys %remaining;
163 0         0 return @exists{@uuids};
164             }
165             }
166              
167 642 100       1815 if ( keys %remaining ) {
168 509         2062 @exists{keys %remaining} = $self->exists_in_storage(keys %remaining);
169             }
170              
171 642         3120 return @exists{@uuids};
172             }
173              
174             sub delete {
175 170     170 0 503 my ( $self, @ids_or_entries ) = @_;
176              
177 170         448 my @entries = grep { ref } @ids_or_entries;
  171         536  
178              
179 170         415 my @ids = grep { not ref } @ids_or_entries;
  171         457  
180              
181 170         560 my @new_entries = map { $_->deletion_entry } $self->get(@ids);
  133         607  
182              
183 170         816 $self->insert(@entries, @new_entries);
184              
185 170         1310 return @new_entries;
186             }
187              
188             sub insert {
189 1732     1732 0 4226 my ( $self, @entries ) = @_;
190              
191 1732 100       2180 if ( @{ $self->_txn_stack } ) {
  1732         49825  
192 1498         40183 my $head = $self->_txn_stack->[-1];
193 1498         2468 push @{ $head->{log} }, @entries;
  1498         4724  
194 1498         3957 @{$head->{live}}{map { $_->id } @entries} = @entries;
  1498         7461  
  3275         71581  
195             } else {
196 234         1067 $self->commit_entries(@entries);
197             }
198             }
199              
200             __PACKAGE__
201              
202             __END__
203              
204             =pod
205              
206             =head1 NAME
207              
208             KiokuDB::Backend::Role::TXN::Memory - In memory transactions.
209              
210             =head1 SYNOPSIS
211              
212             with qw(KiokuDB::Backend::Role::TXN::Memory);
213              
214             sub commit_entries {
215             my ( $self, @entries ) = @_;
216              
217             # atomically apply @entries
218              
219             # deleted entries have the deleted flag set
220             # if an entry has no 'prev' entry it's an insert
221             # otherwise it's an update
222             }
223              
224             =head1 DESCRIPTION
225              
226             This backend provides in memory transactions for backends which support atomic
227             modification of data, but not full commit/rollback support.
228              
229             This backend works by buffering all operations in memory. Entries are kept
230             alive allowing read operations go to the live entry even for objects that are
231             out of scope.
232              
233             This implementation provides repeatable read level isolation. Durability,
234             concurrency and atomicity are still the responsibility of the backend.
235              
236             =head1 REQUIRED METHODS
237              
238             =over 4
239              
240             =item commit_entries
241              
242             Insert, update or delete entries as specified.
243              
244             This operation should either fail or succeed atomically.
245              
246             Entries with C<deleted> should be removed from the database, entries with a
247             C<prev> entry should be inserted, and all other entries should be updated.
248              
249             Multiple entries may be given for a single object, for instance an object that
250             was first inserted and then modified will have an insert entry and an update
251             entry.
252              
253             =item get_from_storage
254              
255             Should be the same as L<KiokuDB::Backend/get>.
256              
257             When no memory buffered entries are available for the object one is fetched
258             from the backend.
259              
260             =item exists_in_storage
261              
262             Required as of L<KiokuDB> version 0.37.
263              
264             A fallback implementation is provided, but should not be used and will issue a
265             deprecation warning.
266              
267             =back
268              
269             =cut
270