File Coverage

blib/lib/Data/Model/Driver/Memory.pm
Criterion Covered Total %
statement 359 369 97.2
branch 135 164 82.3
condition 40 67 59.7
subroutine 39 40 97.5
pod 0 25 0.0
total 573 665 86.1


line stmt bran cond sub pod time code
1             package Data::Model::Driver::Memory;
2 44     44   15226915 use strict;
  44         116  
  44         1807  
3 44     44   299 use warnings;
  44         109  
  44         1365  
4 44     44   235 use base 'Data::Model::Driver';
  44         217  
  44         26357  
5              
6 44     44   272 use Carp ();
  44         90  
  44         258731  
7             $Carp::Internal{(__PACKAGE__)}++;
8              
9             ## data loader
10              
11             sub _load_data {
12 152     152   294 my($self, $model, $type, $name) = @_;
13              
14 152   50     556 $self->{models}->{$model} ||= +{};
15 152 100       392 if ($type eq 'data') {
16             return +{
17 64         495 records => +{},
18             seq => 0,
19             record_id => 0,
20             };
21             } else {
22             return +{
23 88         760 key => +{},
24             prefix => +{},
25             };
26             }
27             }
28              
29             sub load_data {
30 1108     1108 0 1595 my($self, $schema) = @_;
31 1108   66     4719 $self->{models}->{$schema->model}->{data} ||= $self->_load_data($schema->model, 'data');
32             }
33              
34             sub load_key {
35 829     829 0 1180 my($self, $schema) = @_;
36 829   66     2591 $self->{models}->{$schema->model}->{key} ||= $self->_load_data($schema->model, 'key');
37             }
38              
39             sub load_index {
40 103     103 0 177 my($self, $schema, $name) = @_;
41 103   66     355 $self->{models}->{$schema->model}->{index}->{$name} ||= $self->_load_data($schema->model, 'index', $name);
42             }
43              
44             sub load_unique {
45 68     68 0 110 my($self, $schema, $name) = @_;
46 68   66     221 $self->{models}->{$schema->model}->{unique}->{$name} ||= $self->_load_data($schema->model, 'unique', $name);
47             }
48              
49             sub new {
50 51     51 0 18977 my $class = shift;
51 51         622 bless {
52             models => +{},
53             }, $class;
54             }
55              
56 0     0 0 0 sub save {}
57              
58             sub generate_record_id {
59 242     242 0 370 my($self, $schema) = @_;
60 242         555 my $data = $self->load_data($schema);
61 242         643 ++($data->{record_id});
62             }
63              
64             sub generate_auto_increment {
65 84     84 0 126 my($self, $schema) = @_;
66 84         176 my $data = $self->load_data($schema);
67 84         311 ++($data->{seq});
68             }
69              
70             ## get, set, delete
71              
72             sub fetch {
73 380     380 0 788 my($self, $schema, $key, $columns, %args) = @_;
74              
75             # fetch record id
76 380         1148 my $result_id_list = $self->get_record_id_list($schema, $key, $columns);
77 380 100 66     1093 return unless $result_id_list && @{ $result_id_list };
  380         1392  
78              
79 336         1095 my $results = $self->get_result_list($schema, $columns, $result_id_list);
80 336 100 66     933 return unless $results && @{ $results };
  336         1197  
81              
82 332         470 $results = [ map { $_->[1] } @{ $results } ];
  494         1921  
  332         659  
83             }
84              
85              
86             sub lookup {
87 72     72 0 116 my $self = shift;
88 72         158 my $results = $self->fetch(@_);
89 72         244 $results->[0];
90             }
91              
92             sub lookup_multi {
93 23     23 0 47 my($self, $schema, $ids) = @_;
94              
95 23         34 my %resultlist;
96 23         33 for my $id (@{ $ids }) {
  23         50  
97 56         72 my $key = join "\0", @{ $id };
  56         133  
98 56         137 my $results = $self->fetch($schema, $id);
99 56 100       164 next unless $results;
100 50         162 $resultlist{$key} = $results->[0];
101             }
102 23         83 \%resultlist;
103             }
104              
105             sub get {
106 252     252 0 386 my $self = shift;
107 252         785 my $results = $self->fetch(@_);
108 252 100       876 return unless $results;
109 212         1218 return $self->_generate_result_iterator($results), +{};
110             }
111              
112             sub set {
113 246     246 0 543 my($self, $schema, $key, $columns, %args) = @_;
114              
115             # initilaize
116              
117             # check unique
118 246 100 100     283 if (@{ $schema->key } && grep { defined $_ } @{ $key }) {
  246         1003  
  204         848  
  234         709  
119 150         491 my $result_id_list = $self->get_record_id_list($schema, $key, +{});
120 150 100       230 Carp::croak 'not unique columns' if @{ $result_id_list };
  150         874  
121             }
122 244 100       417 if (scalar(%{ $schema->unique })) {
  244         702  
123 26         43 while (my($unique_name, $unique_columns) = each %{ $schema->unique }) {
  50         130  
124 26         45 my $index = [];
125 26         36 for my $column (@{ $unique_columns }) {
  26         53  
126 62         67 push @{ $index }, $columns->{$column};
  62         151  
127             }
128 26         122 my $result_id_list = $self->get_record_id_list($schema, undef, +{ index => { $unique_name => $ index } });
129 26 100       67 Carp::croak 'not unique columns' if @{ $result_id_list };
  26         387  
130             }
131             }
132              
133             # delete old record
134              
135             # record_id
136 242         712 my $record_id = $self->generate_record_id($schema);
137              
138             # auto_increment
139 242 100   84   1766 if ($self->_set_auto_increment($schema, $columns, sub { $self->generate_auto_increment($schema) })) {
  84         274  
140             # remake $key
141 84         244 $key = $schema->get_key_array_by_hash($columns);
142             }
143              
144             # write to index, key and unique
145 242         1257 $self->set_memory_index($schema, $key, $columns, $record_id);
146              
147             # write data
148 242         586 my $data = $self->load_data($schema);
149 242         678 $data->{records}->{$record_id} = +{ %{ $columns } };
  242         1776  
150             }
151              
152             sub replace {
153 4     4 0 12 my($self, $schema, $key, $columns, %args) = @_;
154 4         32 $self->delete($schema, $key, +{}, %args);
155 4         32 $self->set($schema, $key, $columns, %args);
156             }
157              
158             sub update {
159 18     18 0 47 my($self, $schema, $old_key, $key, $old_columns, $columns, $changed_columns, %args) = @_;
160              
161             # fetch record id
162 18         64 my $result_id_list = $self->get_record_id_list($schema, $old_key, +{});
163 18 50 33     76 return unless $result_id_list && @{ $result_id_list };
  18         78  
164 18 50       26 return if @{ $result_id_list } != 1; # not unique key
  18         64  
165 18         41 my $id = $result_id_list->[0];
166              
167             # reindex
168 18         1041 $self->delete_memory_index($schema, $old_key, $old_columns, $id);
169 18         60 $self->set_memory_index($schema, $key, $columns, $id);
170              
171             # set data
172 18         62 my $data = $self->load_data($schema);
173 18         33 $data->{records}->{$id} = +{ %{ $columns } };
  18         128  
174             }
175              
176             sub _uodate_delete_visitor {
177 58     58   1133 my($self, $schema, $key, $query, $code) = @_;
178              
179             # fetch record id
180 58         170 my $result_id_list = $self->get_record_id_list($schema, $key, $query);
181 58 100 66     198 return unless $result_id_list && @{ $result_id_list };
  58         272  
182              
183 52         175 my $results = $self->get_result_list($schema, $query, $result_id_list);
184 52 50 33     169 return unless $results && @{ $results };
  52         200  
185              
186             # delete data
187 52         142 my $data = $self->load_data($schema);
188 52         105 my @rows;
189 52         98 for my $id ( map { $_->[0] } @{ $results }) {
  56         221  
  52         116  
190 56         162 my @ret = $code->($data, $id);
191 56 50       257 push @rows, @ret if @ret;
192             }
193 52 50       566 return @rows ? [ @rows ] : undef;
194             }
195              
196             sub update_direct {
197 18     18 0 49 my($self, $schema, $key, $query, $columns, %args) = @_;
198              
199             $self->_uodate_delete_visitor(
200             $schema, $key, $query,
201             sub {
202 20     20   46 my($data, $id) = @_;
203 20         91 $self->delete_memory_index($schema, $key, $data->{records}->{$id}, $id);
204 20         40 while (my($key, $val) = each %{ $columns }) {
  50         204  
205 30         95 $data->{records}->{$id}->{$key} = $val;
206             }
207 20         100 $key = $schema->get_key_array_by_hash($data->{records}->{$id});
208 20         91 $self->set_memory_index($schema, $key, $data->{records}->{$id}, $id);
209             }
210 18         137 );
211             }
212              
213              
214             sub delete {
215 40     40 0 117 my($self, $schema, $key, $columns, %args) = @_;
216              
217             $self->_uodate_delete_visitor(
218             $schema, $key, $columns,
219             sub {
220 36     36   83 my($data, $id) = @_;
221 36         269 $self->delete_memory_index($schema, $key, $data->{records}->{$id}, $id);
222 36         128 delete $data->{records}->{$id};
223             }
224 40         522 );
225             }
226              
227             ## for memory index
228              
229             sub get_record_id_list {
230 632     632 0 1124 my($self, $schema, $key, $columns) = @_;
231              
232 632         1012 my $result_id_list = [];
233 632 100       1294 if ($key) {
234 475         1223 $result_id_list = $self->get_memory_index($schema, 'key', undef, $key);
235             } else {
236             # hash
237 157   100     394 $columns ||= +{};
238 157 100 66     820 if (exists $columns->{index} && ref($columns->{index}) eq 'HASH') {
239 75         90 my($index, $index_key) = %{ $columns->{index} };
  75         215  
240 75 100       256 $index_key = [ $index_key ] unless ref($index_key) eq 'ARRAY';
241 75         139 for my $index_type (qw/ unique index /) {
242 106 100       379 if (exists $schema->$index_type->{$index}) {
243 75         250 $result_id_list = $self->get_memory_index($schema, $index_type, $index, $index_key);
244 75         188 last;
245             }
246             }
247             } else {
248 82         272 my $data = $self->load_data($schema);
249 498         907 $result_id_list = [
250 82         172 sort { $a <=> $b } keys %{ $data->{records} }
  82         673  
251             ];
252             }
253             }
254 632         1505 $result_id_list;
255             }
256              
257             sub get_memory_index {
258 550     550 0 959 my($self, $schema, $index_type, $index, $key) = @_;
259 550 100       2033 my $columns = $index_type eq 'key' ? $schema->key : $schema->$index_type->{$index};
260              
261 550         1366 my $method = "load_$index_type";
262 550         1434 my $key_hash = $self->$method($schema, $index);
263 550         2357 my $key_data = $self->_generate_key_data($key);
264              
265 550 100       758 my $type = scalar(@{ $key }) == scalar(@{ $columns }) ? 'key' : 'prefix';
  550         761  
  550         1377  
266 550         1207 my $result = $key_hash->{$type}->{$key_data};
267 550 100       2325 $result ? ref($result) eq 'HASH' ? [ keys %{ $result } ] : [ $result ] : [];
  38 100       192  
268             }
269              
270             sub set_memory_index {
271 280     280 0 513 my($self, $schema, $key, $columns, $id) = @_;
272 280         831 $self->_set_memory_index($schema, 'key', undef, $key, $id);
273              
274 280         534 for my $index_type (qw/ unique index /) {
275 560         650 for my $index (keys %{ $schema->$index_type }) {
  560         1588  
276 144         352 my @index_key = map {
277 80         296 $columns->{$_}
278 80         117 } @{ $schema->$index_type->{$index} };
279 80         282 $self->_set_memory_index($schema, $index_type, $index, [ @index_key ], $id);
280             }
281             }
282             }
283              
284             sub _set_memory_index {
285 360     360   682 my($self, $schema, $index_type, $index, $key, $id) = @_;
286 360 100       1332 my $columns = $index_type eq 'key' ? $schema->key : $schema->{$index_type}->{$index};
287              
288 360         694 my $method = "load_$index_type";
289 360         957 my $key_hash = $self->$method($schema, $index);
290              
291 360         692 my @prefix = ();
292 360         549 for my $k (@{ $key }) {
  360         674  
293 462         626 push @prefix, $k;
294 462         1720 my $key_data = $self->_generate_key_data([ @prefix ]);
295              
296 462 100       885 my $type = scalar(@prefix) == scalar(@{ $key }) ? 'key' : 'prefix';
  462         1053  
297 462         772 my $hash = $key_hash->{$type};
298 462 100       931 if (exists $hash->{$key_data}) {
299 78 100       223 unless (ref($hash->{$key_data}) eq 'HASH') {
300 54         83 my $oid = $hash->{$key_data};
301 54         160 $hash->{$key_data} = +{
302             $oid => $oid,
303             };
304             }
305 78         271 $hash->{$key_data}->{$id} = $id;
306             } else {
307 384         1997 $hash->{$key_data} = $id;
308             }
309             }
310             }
311              
312             sub delete_memory_index {
313 74     74 0 152 my($self, $schema, $key, $columns, $id) = @_;
314 74         253 $self->_delete_memory_index($schema, 'key', undef, $key, $id);
315              
316 74         155 for my $index_type (qw/ unique index /) {
317 148         194 for my $index (keys %{ $schema->$index_type }) {
  148         521  
318 16         66 my @index_key = map {
319 16         58 $columns->{$_}
320 16         34 } @{ $schema->$index_type->{$index} };
321 16         64 $self->_delete_memory_index($schema, $index_type, $index, [ @index_key ], $id);
322             }
323             }
324             }
325              
326             sub _delete_memory_index {
327 90     90   180 my($self, $schema, $index_type, $index, $key, $id) = @_;
328 90 100       364 my $columns = $index_type eq 'key' ? $schema->key : $schema->{$index_type}->{$index};
329              
330 90         194 my $method = "load_$index_type";
331 90         256 my $key_hash = $self->$method($schema, $index);
332              
333 90         184 my @prefix = ();
334 90         121 for my $k (@{ $key }) {
  90         207  
335 82         142 push @prefix, $k;
336 82         318 my $key_data = $self->_generate_key_data([ @prefix ]);
337              
338 82 100       194 my $type = scalar(@prefix) == scalar(@{ $key }) ? 'key' : 'prefix';
  82         232  
339 82         162 my $hash = $key_hash->{$type};
340 82 100       220 if (ref($hash->{$key_data}) eq 'HASH') {
341 8         23 delete $hash->{$key_data}->{$id};
342 8 50       14 if (keys(%{ $hash->{$key_data} }) == 1) {
  8         71  
343 8         15 my($k) = keys %{ $hash->{$key_data} };
  8         22  
344 8         50 $hash->{$key_data} = $k;
345             }
346             } else {
347 74         382 delete $hash->{$key_data};
348             }
349             }
350             }
351              
352             # grep, sort, limit
353              
354             sub get_result_list {
355 388     388 0 645 my($self, $schema, $query, $id_list) = @_;
356              
357             # merge data
358 388         867 my $data = $self->load_data($schema);
359 388         719 my $results = [];
360 388         536 for my $id (@ { $id_list }) {
  388         779  
361 726         803 push @{ $results }, [ $id => $data->{records}->{$id} ];
  726         2690  
362             }
363              
364 388 100 66     1789 return $results unless $query && ref($query) eq 'HASH';
365 133         539 return $self->limit($schema, $query, $self->sort($schema, $query, $self->grep($schema, $query, $results)));
366             }
367              
368             ## grep
369             sub _grep_merge_and {
370 6     6   12 my($self, $l, $r) = @_;
371 6 50 33     12 return [] unless @{ $l } && @{ $r };
  6         25  
  6         26  
372 6 100       27 if ($l->[0]->[0] > $r->[0]->[0]) {
373 2         5 my $t = $l;
374 2         5 $l = $r;
375 2         3 $r = $t;
376             }
377              
378 6         16 my @results;
379 6         11 my $ridx = 0;
380 6         13 my $rmax = @{ $r };
  6         16  
381 6         14 for my $lrow (@{ $l }) {
  6         14  
382 18         28 my $lid = $lrow->[0];
383 18         46 while ( $ridx < $rmax) {
384 22         37 my $rid = $r->[$ridx]->[0];
385 22 100       69 if ($rid == $lid) {
    100          
386 10         17 push @results, $lrow;
387 10         28 $ridx++;
388             } elsif ($rid < $lid) {
389 4         9 $ridx++;
390             } else {
391 8         17 last;
392             }
393             }
394             }
395 6         27 return \@results;
396             }
397             sub _grep_merge_or {
398 6     6   15 my($self, $l, $r) = @_;
399 6 50 33     10 return $l if @{ $l } && !@{ $r };
  6         25  
  6         26  
400 6 50 33     9 return $r if !@{ $l } && @{ $r };
  6         22  
  0         0  
401 6 100       28 if ($l->[0]->[0] > $r->[0]->[0]) {
402 2         5 my $t = $l;
403 2         4 $l = $r;
404 2         6 $r = $t;
405             }
406              
407 6         12 my @results;
408 6         11 my $ridx = 0;
409 6         8 my $rmax = @{ $r };
  6         11  
410 6         8 for my $lrow (@{ $l }) {
  6         15  
411 16         46 my $lid = $lrow->[0];
412 16         36 while ( $ridx < $rmax) {
413 14         36 my $rid = $r->[$ridx]->[0];
414 14 100       51 if ($rid == $lid) {
    100          
415 4         7 $ridx++;
416 4         9 last;
417             } elsif ($rid < $lid) {
418 2         12 push @results, $r->[$ridx];
419 2         7 $ridx++;
420             } else {
421 8         14 last;
422             }
423             }
424 16         34 push @results, $lrow;
425             }
426 6         26 return \@results;
427             }
428              
429             sub _grep_grep {
430 78     78   154 my($self, $col, $val, $rows) = @_;
431 78         110 my @result;
432 78         115 for my $row (@{ $rows }) {
  78         291  
433 384         498 my $ok = 0;
434 384 50       968 unless (exists $row->[1]->{$col}) {
435 0         0 next;
436             }
437 384         656 my $rval = $row->[1]->{$col};
438 384 100       676 if (ref($val)) {
439 220 50       482 if (ref($val) eq 'HASH') {
440 220         256 my($op, $value) = (%{ $val });
  220         533  
441 220         407 $op = uc($op);
442 220 100 66     1070 if ($op eq 'LIKE') {
    50 33        
    100          
    100          
    100          
    50          
    50          
    50          
443 82         277 my $is_prefix = !($value =~ s/^%//);
444 82         214 my $is_suffix = !($value =~ s/%$//);
445 82         190 my $meta_str = join '.', map { quotemeta $_ } split '_', $value;
  92         234  
446 82 100       1220 $meta_str = '^' . $meta_str if $is_prefix;
447 82 100       181 $meta_str .= '$' if $is_suffix;
448 82 100       570 $ok = 1 if $rval =~ /$meta_str/;
449              
450             } elsif ($op eq '=') {
451 0 0       0 $ok = 1 if $rval eq $value;
452              
453             } elsif ($op eq '!=') {
454 32 100       111 $ok = 1 unless $rval eq $value;
455              
456             } elsif ($op eq '>') {
457 30 100       78 $ok = 1 if $rval > $value;
458              
459             } elsif ($op eq '<') {
460 20 100       62 $ok = 1 if $rval < $value;
461              
462             } elsif ($op eq '>=') {
463 0 0       0 $ok = 1 if $rval >= $value;
464              
465             } elsif ($op eq '<=') {
466 0 0       0 $ok = 1 if $rval <= $value;
467              
468             } elsif (($op eq 'IN' || $op eq 'NOT IN') && ref($value) eq 'ARRAY') {
469 56         72 for my $v (@{ $value }) {
  56         98  
470 112 100       291 $ok = 1 if $rval eq $v;
471             }
472 56 100       163 $ok = !$ok unless $op eq 'IN';
473             }
474             }
475             } else {
476 164 100       386 $ok = 1 if $rval eq $val;
477             }
478 384 100       1009 push @result, $row if $ok;
479             }
480 78         292 \@result;
481             }
482             sub _grep {
483 154     154   283 my($self, $col, $val, $rows) = @_;
484 154 100 100     762 if (lc($col) eq '-and' || lc($col) eq '-or') {
485 76         105 my $results;
486             my $ret;
487 76         130 while (my($ccol, $cval) = splice @{ $val }, 0, 2) {
  164         708  
488 88         331 $ret = $self->_grep( $ccol, $cval, $rows );
489 88 100       195 if ($results) {
490 12 100       62 $results = (lc($col) eq '-and') ? $self->_grep_merge_and($results, $ret) : $self->_grep_merge_or($results, $ret);
491             } else {
492 76         168 $results = $ret;
493             }
494             }
495 76 50       180 $results = $ret unless $results;
496 76         183 return $results;
497             } else {
498             ## xxx Need to support old range and transform behaviors.
499 78 50 33     479 Carp::croak("Invalid/unsafe column name $col") unless $col =~ /^[\w\.]+$/ || ref($col) eq 'SCALAR';
500 78 50 66     349 Carp::croak("Invalid/unsafe column value $col (unused Data::Model::SQL->_mk_term parse data)") unless !ref($val) || ref($val) eq 'HASH';
501 78         254 return $self->_grep_grep($col, $val, $rows);
502             }
503             }
504             sub grep {
505 133     133 0 238 my($self, $schema, $query, $rows) = @_;
506 133 100       564 return $rows unless exists $query->{where};
507 66         323 my $ret = $self->_grep( -and => $query->{where}, $rows );
508 66 50       176 return [] unless $ret;
509 66         266 return $ret;
510             }
511              
512             sub sort {
513 133     133 0 241 my($self, $schema, $query, $rows) = @_;
514 133 100       532 return $rows unless exists $query->{order};
515              
516 78         146 my $sort_data = [];
517 78         128 for my $data (@{ $query->{order} }) {
  78         193  
518 128         179 my($column, $vec) = (%{ $data });
  128         334  
519 128         180 push @{ $sort_data }, +{
  128         12165  
520             column => $column,
521             vec => uc($vec),
522             int => !!($schema->column_type($column) =~ /int/i),
523             };
524             }
525              
526 190         255 my @ordered = sort {
527 78         365 my $v = 0;
528 190         230 for my $data (@{ $sort_data }) {
  190         368  
529 264         403 my $column = $data->{column};
530 264 50       501 if ($data->{int}) {
531 0 0       0 next if $a->[1]->{$column} == $b->[1]->{$column};
532 0         0 $v = $a->[1]->{$column} <=> $b->[1]->{$column};
533             } else {
534 264 100       884 next if $a->[1]->{$column} eq $b->[1]->{$column};
535 166         391 $v = $a->[1]->{$column} cmp $b->[1]->{$column};
536             }
537 166 100       409 $v *= -1 if $data->{vec} eq 'DESC';
538 166         252 last;
539             }
540 190         424 $v;
541 78         159 } @{ $rows };
542 78         477 \@ordered;
543             }
544              
545             sub limit {
546 133     133 0 262 my($self, $schema, $query, $rows) = @_;
547 133 100 66     1029 return $rows unless exists $query->{limit} || exists $query->{offset};
548              
549 4         8 my @limitted;
550 4 100       16 if (exists $query->{offset}) {
551 2         52 for (1..$query->{offset}) {
552 4         8 shift @{ $rows };
  4         9  
553             }
554             }
555 4 50       13 if (exists $query->{limit}) {
556 4         12 for (1..$query->{limit}) {
557 6         7 push @limitted, shift @{ $rows };
  6         16  
558             }
559             } else {
560 0         0 push @limitted, @{ $rows };
  0         0  
561             }
562 4         13 return \@limitted;
563             }
564              
565             1;
566              
567             =head1 NAME
568              
569             Data::Model::Driver::Memory - storage driver for memory
570              
571             =head1 SYNOPSIS
572              
573             package MyDB;
574             use base 'Data::Model';
575             use Data::Model::Schema;
576             use Data::Model::Driver::Memory;
577            
578             my $dbi_connect_options = {};
579             my $driver = Data::Model::Driver::Memory->new;
580            
581             base_driver $driver;
582             install_model model_name => schema {
583             ....
584             };
585              
586             =head1 SEE ALSO
587              
588             L
589              
590             =head1 AUTHOR
591              
592             Kazuhiro Osawa Eyappo shibuya plE
593              
594             =head1 LICENSE
595              
596             This library is free software; you can redistribute it and/or modify
597             it under the same terms as Perl itself.
598              
599             =cut