File Coverage

blib/lib/Data/RecordStore/Transaction.pm
Criterion Covered Total %
statement 110 112 98.2
branch 17 18 94.4
condition 6 6 100.0
subroutine 14 14 100.0
pod 6 6 100.0
total 153 156 98.0


line stmt bran cond sub pod time code
1             package Data::RecordStore::Transaction;
2              
3 9     9   53 use strict;
  9         18  
  9         209  
4 9     9   36 use warnings;
  9         18  
  9         191  
5 9     9   44 no warnings 'numeric';
  9         9  
  9         222  
6 9     9   44 no warnings 'uninitialized';
  9         10  
  9         329  
7              
8 9     9   93 use File::Path qw(make_path remove_tree);
  9         17  
  9         461  
9              
10 9     9   45 use vars qw($VERSION);
  9         17  
  9         610  
11             $VERSION = '0.01';
12              
13             use constant {
14 9         6844 RS_ACTIVE => 1,
15             RS_DEAD => 2,
16             RS_IN_TRANSACTION => 3,
17              
18             TR_ACTIVE => 1,
19             TR_IN_COMMIT => 2,
20             TR_IN_ROLLBACK => 3,
21             TR_COMPLETE => 4,
22 9     9   53 };
  9         18  
23              
24             #######################################################################
25             # Transactions use a stack silo to record what happens #
26             # to entries affected by the transaction. #
27             # #
28             # When an entry is deleted in a transaction, the stack silo #
29             # marks that entry for deletion. #
30             # #
31             # When entry data is stowed in a transaction, the transaction #
32             # stores the data in the store. It marks this location as well #
33             # as the original location of the entry data (if any). #
34             # #
35             # The stack silo keeps only the last record for an entry, so #
36             # if an entry is stowed, then deleted, then stowed again, only #
37             # the most recent action is recorded. The stack silo contains the #
38             # id, the state, the original silo id (if there is one ), #
39             # the original idx in the silo (if there is one ), the silo id that #
40             # the transaction stored data in, the idx in the silo that the #
41             # the transaction stored data in #
42             # #
43             # When fetch is used from a transaction, the stack silo is checked #
44             # to see if there was any action on the fetched id. If so, it returns #
45             # the transactional value. #
46             #######################################################################
47              
48              
49             sub create {
50 86     86 1 391 my( $cls, $store, $dir, $id ) = @_;
51              
52             # stack of id used
53 86         259 my $stack_silo_dir = "$dir/stack_silo";
54 86         6434 make_path( $stack_silo_dir, { error => \my $err } );
55 86 50       431 if( @$err ) { die join( ", ", map { values %$_ } @$err ) }
  0         0  
  0         0  
56              
57 86         483 my $stack_silo = Data::RecordStore::Silo->open_silo(
58             $stack_silo_dir,
59             'ILILIL', # action, id, trans silo id, trans id in silo, orig silo id, orig id in silo
60             0,
61             $store->max_file_size );
62              
63 86         1124 return bless {
64             directory => $dir,
65             id => $id,
66             stack_silo => $stack_silo,
67             changes => {},
68             store => $store,
69             state => TR_ACTIVE,
70             }, $cls;
71              
72             } #create
73              
74             sub commit {
75 46     46 1 109 my $self = shift;
76              
77 46         99 my $store = $self->{store};
78              
79 46         139 $store->transaction_silo->put_record( $self->{id}, [TR_IN_COMMIT], 'I' );
80 46         365 $self->{state} = TR_IN_COMMIT;
81              
82 46         171 my $store_index = $store->index_silo;
83 46         128 my $store_silos = $store->silos;
84              
85 46         91 my $stack_silo = $self->{stack_silo};
86 46         81 my $changes = $self->{changes};
87 46         404 for my $id (sort { $a <=> $b } keys %$changes) {
  599         836  
88 238         1093 my( $action, $rec_id, $orig_silo_id, $orig_idx_in_silo, $trans_silo_id, $trans_id_in_silo ) = @{$changes->{$id}};
  238         711  
89 238 100       527 if( $action == RS_ACTIVE ) {
90 198         736 $store_silos->[$trans_silo_id]->put_record( $trans_id_in_silo, [ RS_ACTIVE ], 'I' );
91 198         1596 $store_index->put_record( $rec_id, [$trans_silo_id,$trans_id_in_silo,time] );
92             }
93             else {
94 40         64 my( $s_id, $id_in_s ) = @{$store_index->get_record( $rec_id )};
  40         144  
95 40 100       176 if( $s_id ) {
96 32         128 $store->silos->[$s_id]->put_record( $id_in_s, [RS_DEAD], 'I' );
97             }
98 40         416 $store_index->put_record( $rec_id, [0,0,time] );
99             }
100             }
101 38         190 $store->transaction_silo->put_record( $self->{id}, [TR_COMPLETE], 'I' );
102              
103             # this is sort of linting. The transaction is complete, but this cleans up any records marked deleted.
104 38         287 for my $id (sort { $a <=> $b } keys %$changes) {
  551         637  
105 198         243 my( $action, $rec_id, $orig_silo_id, $orig_idx_in_silo ) = @{$changes->{$id}};
  198         353  
106 198 100 100     571 if( $action == RS_DEAD && $orig_silo_id ) {
107 24         112 $store->_vacate( $orig_silo_id, $orig_idx_in_silo );
108             }
109             }
110             } #commit
111              
112             sub rollback {
113 40     40 1 72 my $self = shift;
114              
115 40         96 my $store = $self->{store};
116 40         120 my $index = $store->index_silo;
117              
118 40         120 $store->transaction_silo->put_record( $self->{id}, [TR_IN_ROLLBACK], 'I' );
119 40         328 $self->{state} = TR_IN_ROLLBACK;
120              
121             # [RS_ACTIVE, $id, $orig_silo_id, $orig_id_in_silo, $trans_silo_id, $trans_id_in_silo];
122             # [RS_DEAD , $id, $orig_silo_id, $orig_id_in_silo, 0, 0];
123            
124             # go and mark dead any temporary stows
125 40         80 my $stack_silo = $self->{stack_silo};
126 40         144 my $count = $stack_silo->entry_count;
127              
128             # go backwards to remove items that may have been partially created.
129 40         144 for my $stack_id (reverse(1..$count)) {
130 168         264 my( $action, $rec_id, $orig_silo_id, $id_in_orig_silo, $trans_silo_id, $trans_id_in_silo ) = @{$stack_silo->get_record( $stack_id )};
  168         400  
131 168         2392 my( $reported_silo_id, $id_reported_silo ) = @{$index->get_record( $rec_id )};
  168         376  
132 160 100 100     2376 if( $reported_silo_id != $orig_silo_id || $id_reported_silo != $id_in_orig_silo ) {
133 32         128 $index->put_record( $rec_id, [$orig_silo_id,$id_in_orig_silo,time] );
134             }
135 160 100       1248 if( $trans_silo_id ) {
136 112         352 $store->_vacate( $trans_silo_id, $trans_id_in_silo );
137             }
138             }
139              
140 32         136 $store->transaction_silo->put_record( $self->{id}, [TR_COMPLETE], 'I' );
141             } #rollback
142              
143             sub fetch {
144 153     153 1 355 my( $self, $id ) = @_;
145              
146 153         226 my $store = $self->{store};
147              
148 153         250 my $changes = $self->{changes};
149 153 100       396 if( my $rec = $changes->{$id} ) {
150 137         323 my( $action, $rec_id, $a, $b, $trans_silo_id, $trans_id_in_silo ) = @$rec;
151 137 100       282 if( $action == RS_ACTIVE ) {
152 89         211 my $ret = $store->silos->[$trans_silo_id]->get_record( $trans_id_in_silo );
153 89         61928 return substr( $ret->[3], 0, $ret->[2] );
154             }
155 48         248 return undef;
156             }
157 16         72 return $store->fetch( $id, 'no-trans' );
158             } #fetch
159              
160             #####################################################################################################################
161             # given data and id, #
162             # #
163             # uses the data size to find the appropriate silo id (new-silo-id) to store the data. #
164             # #
165             # looks up the original silo-id, index-in-silo from the stores index, #
166             # which may or not exist #
167             # #
168             # push the new data value into the store silo with the new-silo-id from above #
169             # #
170             # sees if the id already has an entry in the stack silo #
171             # - if yes, it updates it to include STOW,id,new_silo_id,new_idx_in_silo,$original-silo-id,original-idx-in-silo #
172             # - if no, pushes on to it to STOW,id,new_silo_id,new_idx_in_silo,$original-silo-id,original-idx-in-silo #
173             #####################################################################################################################
174             sub stow {
175 263     263 1 415 my $self = $_[0];
176 263         377 my $id = $_[2];
177              
178 263         451 my $store = $self->{store};
179 263 100       628 if( $id == 0 ) {
180 48         168 $id = $store->next_id;
181             }
182              
183 9     9   55 my $data_write_size = do { use bytes; length( $_[1] ) };
  9         17  
  9         50  
  263         425  
  263         428  
184 263         755 my $trans_silo_id = $store->silo_id_for_size( $data_write_size );
185              
186 263         713 my $trans_silo = $store->silos->[$trans_silo_id];
187 263         639 my( $orig_silo_id, $orig_id_in_silo ) = @{$store->index_silo->get_record($id)};
  263         577  
188 263         1524 my $trans_id_in_silo = $trans_silo->push( [RS_IN_TRANSACTION, $id, $data_write_size, $_[1]] );
189              
190 263         624 my $stack_silo = $self->{stack_silo};
191 263         466 my $changes = $self->{changes};
192             # if( my $rec = $changes->{$id} ) {
193             # my( $action, $rec_id, $a, $b, $old_trans_silo_id, $old_idx_in_trans_silo ) = @$rec;
194             # if( $old_trans_silo_id ) {
195             # my $old_trans_silo = $store->silos->[$old_trans_silo_id];
196             # $old_trans_silo->put_record( $old_idx_in_trans_silo, [RS_DEAD], 'I' );
197             # }
198             # }
199 263         641 my $update = [RS_ACTIVE,$id,$orig_silo_id,$orig_id_in_silo,$trans_silo_id,$trans_id_in_silo];
200 263         1112 $changes->{$id} = $update;
201 263         850 $stack_silo->push( $update );
202 263         901 return $id;
203             } #stow
204              
205             sub delete_record {
206 72     72 1 192 my( $self, $id ) = @_;
207            
208 72         144 my( $orig_silo_id, $orig_id_in_silo ) = @{$self->{store}->index_silo->get_record($id)};
  72         264  
209            
210 72         216 my $stack_silo = $self->{stack_silo};
211 72         128 my $changes = $self->{changes};
212              
213             # if( my $rec = $changes->{$id} ) {
214             # my( $action, $rec_id, $a, $b, $old_trans_silo_id, $old_idx_in_trans_silo ) = @$rec;
215             # if( $old_trans_silo_id ) {
216             # my $old_trans_silo = $self->{store}->silos->[$old_trans_silo_id];
217             # $old_trans_silo->put_record( $old_idx_in_trans_silo, [RS_DEAD], 'I' );
218             # }
219             # }
220 72         296 my $update = [RS_DEAD,$id,$orig_silo_id,$orig_id_in_silo,0,0];
221 72         200 $changes->{$id} = $update;
222 72         192 $stack_silo->push( $update );
223              
224             } #delete_record
225              
226             "I think there comes a time when you start dropping expectations. Because the world doesn't owe you anything, and you don't owe the world anything in return. Things, feelings, are a very simple transaction. If you get it, be grateful. If you don't, be alright with it. - Fawad Khan";
227              
228             __END__