File Coverage

blib/lib/EntityModel/Storage.pm
Criterion Covered Total %
statement 6 119 5.0
branch 0 28 0.0
condition 0 2 0.0
subroutine 2 26 7.6
pod 18 20 90.0
total 26 195 13.3


line stmt bran cond sub pod time code
1             package EntityModel::Storage;
2             {
3             $EntityModel::Storage::VERSION = '0.102';
4             }
5             use EntityModel::Class {
6 14         176 _isa => [qw(Mixin::Event::Dispatch)],
7             entity => { type => 'array', subclass => 'EntityModel::Entity' },
8             transaction => { type => 'array', subclass => 'EntityModel::Transaction' },
9 14     14   75834 };
  14         79396  
10 14     14   6173 no if $] >= 5.017011, warnings => "experimental::smartmatch";
  14         45  
  14         113  
11              
12             =head1 NAME
13              
14             EntityModel::Storage - backend storage interface for L
15              
16             =head1 VERSION
17              
18             version 0.102
19              
20             =head1 SYNOPSIS
21              
22             See L.
23              
24             =head1 DESCRIPTION
25              
26             See L for more details.
27              
28             =head1 METHODS
29              
30             =cut
31              
32             =head2 register
33              
34             Register with L so that callbacks trigger when further definitions are loaded/processed.
35              
36             The base storage engine doesn't provide any callbacks - but we define the method anyway so that we don't
37             need to check for ->can.
38              
39             =cut
40              
41             sub register {
42 0     0 1   my $class = shift;
43             }
44              
45             =head2 apply_model
46              
47             Apply the given model.
48              
49             =cut
50              
51             sub apply_model {
52 0     0 1   my $self = shift;
53 0           my $model = shift;
54 0           $self->apply_model_and_schema($model);
55             }
56              
57             =head2 apply_model_and_schema
58              
59             Apply the given model to the storage layer.
60              
61             This delegates most of the work to L.
62              
63             =cut
64              
65             sub apply_model_and_schema {
66 0     0 1   my $self = shift;
67 0           my $model = shift;
68 0           my %args = @_;
69              
70             # Start off assuming that we need all the listed entities
71 0           my @pending = $model->entity->list;
72             # Nothing applied yet (should be $self->entity->list)
73 0           my %existing;
74 0           my @pendingNames = map { $_->name } @pending;
  0            
75              
76 0           my $code;
77             # Called when everything's been applied successfully
78             my $done; $done = sub {
79             # $done = sub { die "Tried to hit the same completion callback twice\n" };
80 0 0   0     $args{on_complete}->() if exists $args{on_complete};
81 0           0
82 0           };
83 0           my %incomplete;
84             # Process a single entity
85             $code = sub {
86             # We may be a leftover event, bail out if there's nothing to do
87 0 0   0     unless(@pending) {
88 0 0         return 1 if keys %incomplete; # try us again later
89 0           $done->();
90 0           return 0; # all complete
91             }
92              
93             # Next item in queue, no idea what state it's in yet
94 0           my $entity = shift(@pending);
95              
96             # Also remove current entry so we don't match ourselves when checking deps
97 0           shift(@pendingNames);
98              
99             # If we've seen this one before, assume it's done
100             # FIXME Diff the entities
101 0 0         return $code->() if exists $existing{$entity->name};
102              
103             { # Dependency handling
104 0           my @deps = map { $_->name } $entity->dependencies;
  0            
  0            
105             # Include ourselves in the list in case anyone else has a dependency on us
106 0           my %expected = %existing; @expected{map { $_->name } @pending, $entity} = ();
  0            
  0            
107 0 0         if(my @unresolved = grep { !exists $expected{$_} } @deps, $entity->name) {
  0            
108 0           logError("%s unresolved (pending %s, deps %s for %s)", $_, join(',', @pendingNames), join(',', @deps), $entity->name) for @unresolved;
109 0           die "Dependency error";
110             }
111              
112             # Check that all dependencies are complete
113 0           delete @expected{$entity->name, keys %existing};
114 0 0         if(my @unsatisfied = grep { exists $expected{$_} } @deps) {
  0            
115 0           logInfo("%s has %d unsatisfied deps, postponing: %s", $entity->name, scalar @unsatisfied, join(',',@unsatisfied));
116 0           push @pending, $entity;
117 0           push @pendingNames, $entity->name;
118 0           return $code->();
119             }
120             }
121              
122             # Apply this entity and add more detail to the error message if it fails:
123             return try {
124 0           $incomplete{$entity->name} = $entity;
125             $self->apply_entity(
126             $entity,
127             on_complete => sub {
128             # Record this entry so we pick it up in later dependency checks
129 0           $existing{$entity->name} = $entity;
130 0           $code->();
131 0           return 0;
132             }
133 0           );
134 0           return 0;
135 0           } catch {
136 0   0       die "Failed to apply entity " . $entity->name . " (pending " . join(',', @pendingNames) . "): " . ($_ // 'undef');
137 0           return 1;
138             };
139 0           };
140 0           1 while $code->();
141 0           return $self;
142             }
143              
144             =head2 read
145              
146             Reads the data for the given entity and returns hashref with the appropriate data.
147              
148             Parameters:
149              
150             =over 4
151              
152             =item * entity - L
153              
154             =item * id - ID to read data from
155              
156             =back
157              
158             =cut
159              
160             sub read {
161 0     0 1   my $self = shift;
162 0           my %args = @_;
163 0           die "Virtual!";
164             }
165              
166             =head2 create
167              
168             Creates new entry for the given L.
169              
170             Parameters:
171              
172             =over 4
173              
174             =item * entity - L
175              
176             =item * data - actual data values
177              
178             =back
179              
180             =cut
181              
182             sub create {
183 0     0 1   my $self = shift;
184 0           my %args = @_;
185 0           die "Virtual!";
186             }
187              
188             =head2 store
189              
190             Stores data to the given entity and ID.
191              
192             Parameters:
193              
194             =over 4
195              
196             =item * entity - L
197              
198             =item * id - ID to store data to
199              
200             =item * data - actual data values
201              
202             =back
203              
204             =cut
205              
206             sub store {
207 0     0 1   my $self = shift;
208 0           my %args = @_;
209 0           die "Virtual!";
210             }
211              
212             =head2 remove
213              
214             Removes given ID from storage.
215              
216             Parameters:
217              
218             =over 4
219              
220             =item * entity - L
221              
222             =item * id - ID to store data to
223              
224             =back
225              
226             =cut
227              
228             sub remove {
229 0     0 1   my $self = shift;
230 0           my %args = @_;
231 0           die "Virtual!";
232             }
233              
234             =head2 find
235              
236             Find some entities that match the spec.
237              
238             =cut
239              
240             sub find {
241 0     0 1   my $self = shift;
242 0           my %args = @_;
243 0           die "Virtual!";
244             }
245              
246             =head2 adjacent
247              
248             Returns the previous and next element for the given ID.
249              
250             =cut
251              
252             sub adjacent {
253 0     0 1   my $self = shift;
254 0           my %args = @_;
255 0           die "Virtual!";
256             }
257              
258             =head2 prev
259              
260             Returns previous element for the given ID.
261              
262             =cut
263              
264             sub prev {
265 0     0 1   my $self = shift;
266 0           my ($prev, $next) = $self->adjacent(@_);
267 0           return $prev;
268             }
269              
270             =head2 next
271              
272             Returns next element for the given ID.
273              
274             =cut
275              
276             sub next {
277 0     0 1   my $self = shift;
278 0           my ($prev, $next) = $self->adjacent(@_);
279 0           return $next;
280             }
281              
282             =head2 outer
283              
284             Returns first and last IDs for the given entity.
285              
286             =cut
287              
288             sub outer {
289 0     0 1   my $self = shift;
290 0           my %args = @_;
291 0           die "Virtual!";
292             }
293              
294             =head2 first
295              
296             Returns first active ID for the given entity.
297              
298             =cut
299              
300             sub first {
301 0     0 1   my $self = shift;
302 0           my ($first, $last) = $self->outer(@_);
303 0           return $first;
304             }
305              
306             =head2 last
307              
308             Returns last active ID for the given entity.
309              
310             =cut
311              
312             sub last {
313 0     0 1   my $self = shift;
314 0           my ($first, $last) = $self->outer(@_);
315 0           return $last;
316             }
317              
318             =head2 transaction_start
319              
320             Mark the start of a transaction.
321              
322             =cut
323              
324             sub transaction_start {
325 0     0 1   my $self = shift;
326 0           my $tran = shift;
327              
328             # TODO weaken?
329 0           $self->transaction->push($tran);
330 0           return $self;
331             }
332              
333             =head2 transaction_rollback
334              
335             Roll back a transaction.
336              
337             =cut
338              
339             sub transaction_rollback {
340 0     0 1   my $self = shift;
341 0           my $tran = shift;
342 0 0         die "No transaction in progress" unless $self->transaction->count;
343 0 0         die "Mismatched transaction" unless $tran ~~ $self->transaction->last;
344             }
345              
346             =head2 transaction_commit
347              
348             Commit this transaction to storage - makes everything done within the transaction permanent
349             (or at least to the level the storage class supports permanence).
350              
351             =cut
352              
353             sub transaction_commit {
354 0     0 1   my $self = shift;
355 0           my $tran = shift;
356 0 0         die "No transaction in progress" unless $self->transaction->count;
357 0 0         die "Mismatched transaction" unless $tran ~~ $self->transaction->last;
358             }
359              
360             =head2 transaction_end
361              
362             Release the transaction on completion.
363              
364             =cut
365              
366             sub transaction_end {
367 0     0 1   my $self = shift;
368 0           my $tran = shift;
369 0 0         die "No transaction in progress" unless $self->transaction->count;
370 0 0         die "Mismatched transaction" unless $tran ~~ $self->transaction->last;
371 0           $self->transaction->pop;
372 0           return $self;
373             }
374              
375 0     0 0   sub backend_ready { shift->{backend_ready} }
376              
377             sub wait_for_backend {
378 0     0 0   my $self = shift;
379 0           my $code = shift;
380 0 0         return $code->($self) if $self->backend_ready;
381 0     0     $self->add_handler_for_event( backend_ready => sub { $code->(@_); 0 });
  0            
  0            
382 0           return $self;
383             }
384              
385             sub DESTROY {
386 0     0     my $self = shift;
387 0 0         die "Active transactions" if $self->transaction->count;
388             }
389              
390             1;
391              
392             __END__