File Coverage

blib/lib/DR/Tarantool/Iterator.pm
Criterion Covered Total %
statement 127 128 99.2
branch 58 62 93.5
condition 4 6 66.6
subroutine 23 24 95.8
pod 19 19 100.0
total 231 239 96.6


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             DR::Tarantool::Iterator - an iterator and a container class for
4             L<DR::Tarantool>
5              
6             =head1 SYNOPSIS
7              
8             use DR::Tarantool::Iterator;
9              
10             my $iter = DR::Tarantool::Iterator->new([1, 2, 3]);
11              
12             my $item0 = $iter->item(0);
13              
14             my @all = $iter->all;
15             my $all = $iter->all;
16              
17             while(my $item = $iter->next) {
18             do_something_with_item( $item );
19             }
20              
21              
22             =head1 METHODS
23              
24             =cut
25              
26 11     11   77717 use utf8;
  11         23  
  11         65  
27 11     11   275 use strict;
  11         22  
  11         295  
28 11     11   79 use warnings;
  11         20  
  11         453  
29              
30             package DR::Tarantool::Iterator;
31 11     11   69 use Carp;
  11         21  
  11         907  
32 11     11   3831 use Data::Dumper;
  11         27468  
  11         21887  
33              
34              
35             =head2 new
36              
37             A constructor.
38              
39             =head3 Arguments
40              
41             =over
42              
43             =item *
44              
45             An array of tuples to iterate over.
46              
47             =item *
48              
49             A list of named arguments:
50              
51             =over
52              
53             =item item_class
54              
55             Name of the class to bless each tuple in the iterator with.
56             If the field is 'B<ARRAYREF>' then the first element of the array is
57             B<item_class>, and the second element is B<item_constructor>.
58              
59             =item item_constructor
60              
61             Name of a constructor to invoke for each tuple. If this value is
62             undefined and B<item_class> is defined, the iterator blesses each
63             tuple but does not invoke a constructor on it.
64              
65             The constructor is invoked on with three arguments: B<item>,
66             B<item_index> and B<iterator>, for example:
67              
68              
69             my $iter = DR::Tarantool::Iterator->new(
70             [ [1], [2], [3] ],
71             item_class => 'MyClass',
72             item_constructor => 'new'
73             );
74              
75             my $iter = DR::Tarantool::Iterator->new( # the same
76             [ [1], [2], [3] ],
77             item_class => [ 'MyClass', 'new' ]
78             );
79              
80              
81             my $item = $iter->item(0);
82             my $item = MyClass->new( [1], 0, $iter ); # the same
83              
84             my $item = $iter->item(2);
85             my $item = MyClass->new( [3], 2, $iter ); # the same
86              
87             =item data
88              
89             Application state to store in the iterator. Is useful
90             if additional state needs to be passed into tuple constructor.
91              
92             =back
93              
94             =back
95              
96             =cut
97              
98             sub new {
99 21     21 1 1485 my ($class, $items, %opts) = @_;
100              
101 21 100       232 croak 'usage: DR::Tarantool::Iterator->new([$item1, $item2, ... ], %opts)'
102             unless 'ARRAY' eq ref $items;
103              
104              
105 20   66     111 my $self = bless { items => $items } => ref($class) || $class;
106              
107 1         4 $self->item_class(
108             ('ARRAY' eq ref $opts{item_class}) ?
109 20 100       108 @{ $opts{item_class} } : $opts{item_class}
    100          
110             ) if exists $opts{item_class};
111              
112 20 100       71 $self->item_constructor($opts{item_constructor})
113             if exists $opts{item_constructor};
114              
115 20 100       63 $self->data( $opts{data} ) if exists $opts{data};
116 20         57 $self;
117             }
118              
119              
120             =head2 clone(%opt)
121              
122             Clone the iterator object, but do not clone the tuples.
123             This method can be used to create an iterator that has
124             a different B<item_class> and (or) B<item_constructor>.
125              
126             If B<clone_items> argument is true, the function clones the tuple
127             list as well.
128              
129             my $iter1 = $old_iter->clone(item_class => [ 'MyClass', 'new' ]);
130             my $iter2 = $old_iter->clone(item_class => [ 'MyClass', 'new' ],
131             clone_items => 1);
132              
133             $old_iter->sort(sub { $_[0]->name cmp $_[1]->name });
134             # $iter1 is sorted, too, but $iter2 is not
135              
136             =cut
137              
138             sub clone {
139              
140 4     4 1 15 my $self = shift;
141 4         7 my %opts;
142 4 100       10 if (@_ == 1) {
143 1         3 %opts = (clone_items => shift);
144             } else {
145 3         9 %opts = @_;
146             }
147              
148 4         10 my %pre = (
149             data => $self->data,
150             item_class => $self->item_class,
151             item_constructor => $self->item_constructor
152             );
153              
154 4         8 my $clone_items = delete $opts{clone_items};
155              
156 4 100       17 my $items = $clone_items ? [ @{ $self->{items} } ] : $self->{items};
  1         4  
157 4         15 $self = $self->new( $items, %pre, %opts );
158 4         13 $self;
159             }
160              
161              
162             =head2 count
163              
164             Return the number of tuples available through the iterator.
165              
166             =cut
167              
168             sub count {
169 548     548 1 2023 my ($self) = @_;
170 548         480 return scalar @{ $self->{items} };
  548         1511  
171             }
172              
173              
174             =head2 item
175              
176             Return one tuple from the iterator by its index
177             (or croak an error if the index is out of range).
178              
179             =cut
180              
181             sub item {
182 135     135 1 3023 my ($self, $no) = @_;
183              
184 135         288 my $item = $self->raw_item( $no );
185              
186 131 100       266 if (my $class = $self->item_class) {
187              
188 43 100       70 if (my $m = $self->item_constructor) {
189 39         139 return $class->$m( $item, $no, $self );
190             }
191              
192 4 50       10 return bless $item => $class if ref $item;
193 4         24 return bless \$item => $class;
194             }
195              
196 88         252 return $self->{items}[ $no ];
197             }
198              
199              
200             =head2 raw_item
201              
202             Return one raw tuple from the iterator by its index
203             (or croak error if the index is out of range).
204              
205             In other words, this method ignores B<item_class> and B<item_constructor>.
206              
207             =cut
208              
209             sub raw_item {
210 172     172 1 186 my ($self, $no) = @_;
211              
212 172         294 my $exists = $self->exists($no);
213 172 100       563 croak "wrong item number format: " . (defined($no) ? $no : 'undef')
    100          
214             unless defined $exists;
215 170 100       533 croak 'wrong item number: ' . $no unless $exists;
216              
217 168 100       237 if ($no >= 0) {
218 164 50       255 croak "iterator doesn't contain item with number $no"
219             unless $no < $self->count;
220             } else {
221 4 50       11 croak "iterator doesn't contain item with number $no"
222             unless $no >= -$self->count;
223             }
224              
225 168         393 return $self->{items}[ $no ];
226             }
227              
228              
229             =head2 raw_sort(&)
230              
231             Sort the contents referred to by the iterator (changes the current
232             iterator object).
233             The compare function receives two B<raw> objects:
234              
235             $iter->raw_sort(sub { $_[0]->field cmp $_[1]->field });
236              
237             =cut
238              
239             sub raw_sort {
240 1     1 1 8 my ($self, $cb) = @_;
241 1         2 my $items = $self->{items};
242 1         8 @$items = sort { &$cb($a, $b) } @$items;
  8         17  
243 1         5 return $self;
244             }
245              
246             =head2 sort(&)
247              
248             Sort the contents referred to by the iterator (changes the current object).
249             The compare function receives two constructed objects:
250              
251             $iter->sort(sub { $_[0]->field <=> $_[1]->field });
252              
253             =cut
254              
255             sub sort : method {
256 1     1 1 2 my ($self, $cb) = @_;
257 1         2 my $items = $self->{items};
258 1         3 my @bitems = map { $self->item( $_ ) } 0 .. $#$items;
  6         34  
259 1         12 my @isorted = sort { &$cb( $bitems[$a], $bitems[$b] ) } 0 .. $#$items;
  7         43  
260              
261 1         9 @$items = @$items[ @isorted ];
262 1         4 return $self;
263             }
264              
265              
266             =head2 grep(&)
267              
268             Find all objects in the set referred to by the iterator that
269             match a given search criteria (linear search).
270              
271             my $admins = $users->grep(sub { $_[0]->is_admin });
272              
273             =cut
274              
275             sub grep :method {
276 3     3 1 6 my ($self, $cb) = @_;
277 3         5 my $items = $self->{items};
278 3         8 my @bitems = map { $self->item( $_ ) } 0 .. $#$items;
  18         57  
279 3         14 my @igrepped = grep { &$cb( $bitems[$_] ) } 0 .. $#$items;
  18         64  
280 3         15 @igrepped = @$items[ @igrepped ];
281              
282 3         7 return $self->new(
283             \@igrepped,
284             item_class => $self->item_class,
285             item_constructor => $self->item_constructor,
286             data => $self->data
287             );
288             }
289              
290              
291             =head2 raw_grep(&)
292              
293             Same as grep, but works on raw objects.
294              
295             my $admins = $users->raw_grep(sub { $_[0]->is_admin });
296              
297             =cut
298              
299             sub raw_grep :method {
300 1     1 1 9 my ($self, $cb) = @_;
301 1         3 my $items = $self->{items};
302 1         3 my @igrepped = grep { &$cb($_) } @$items;
  6         20  
303              
304 1         7 return $self->new(
305             \@igrepped,
306             item_class => $self->item_class,
307             item_constructor => $self->item_constructor,
308             data => $self->data
309             );
310             }
311              
312              
313             =head2 get
314              
315             An alias for L<item> method.
316              
317             =cut
318              
319 0     0 1 0 sub get { goto \&item; }
320              
321              
322             =head2 exists
323              
324             Return B<true> if the iterator contains a tuple with the given
325             index.
326              
327             my $item = $iter->exists(10) ? $iter->get(10) : somethig_else();
328              
329             =cut
330              
331             sub exists : method{
332 172     172 1 178 my ($self, $no) = @_;
333 172 100       317 return undef unless defined $no;
334 171 100       641 return undef unless $no =~ /^-?\d+$/;
335 170 100       287 return 0 if $no >= $self->count;
336 169 100       276 return 0 if $no < -$self->count;
337 168         264 return 1;
338             }
339              
340              
341             =head2 next
342              
343             Return the next tuple, or B<undef> in case of eof.
344              
345             while(my $item = $iter->next) {
346             do_something_with( $item );
347             }
348              
349             Index of the current tuple can be queried with function 'L<iter>'.
350              
351             =cut
352              
353             sub next :method {
354 120     120 1 10459 my ($self) = @_;
355 120         216 my $iter = $self->iter;
356              
357 120 100       232 if (defined $self->{iter}) {
358 90         281 return $self->item(++$self->{iter})
359 90 100       142 if $self->iter < $#{ $self->{items} };
360 21         38 delete $self->{iter};
361 21         56 return undef;
362             }
363              
364 30 100       57 return $self->item($self->{iter} = 0) if $self->count;
365 1         3 return undef;
366             }
367              
368              
369             =head2 iter
370              
371             Return index of the tuple at the current iterator position.
372              
373             =cut
374              
375             sub iter {
376 210     210 1 186 my ($self) = @_;
377 210         292 return $self->{iter};
378             }
379              
380              
381             =head2 reset
382              
383             Reset iteration index, return the previous value of the index.
384              
385             =cut
386              
387             sub reset :method {
388 3     3 1 2492 my ($self) = @_;
389 3         10 return delete $self->{iter};
390             }
391              
392              
393             =head2 all
394              
395             Return all tuples available through the iterator.
396              
397             my @list = $iter->all;
398             my $list_aref = $iter->all;
399              
400             my @abc_list = map { $_->abc } $iter->all;
401             my @abc_list = $iter->all('abc'); # the same
402              
403              
404             my @list = map { [ $_->abc, $_->cde ] } $iter->all;
405             my @list = $iter->all('abc', 'cde'); # the same
406              
407              
408             my @list = map { $_->abc + $_->cde } $iter->all;
409             my @list = $iter->all(sub { $_[0]->abc + $_->cde }); # the same
410              
411             =cut
412              
413             sub all {
414 16     16 1 974 my ($self, @items) = @_;
415              
416 16 50       36 return unless defined wantarray;
417 16         19 my @res;
418              
419 16         29 local $self->{iter};
420              
421              
422 16 100       37 if (@items == 1) {
    100          
423 2         4 my $m = shift @items;
424              
425 2         8 while (defined(my $i = $self->next)) {
426 6         68 push @res => $i->$m;
427             }
428             } elsif (@items) {
429 1         4 while (defined(my $i = $self->next)) {
430 3         5 push @res => [ map { $i->$_ } @items ];
  6         17  
431             }
432             } else {
433 13         27 while (defined(my $i = $self->next)) {
434 56         128 push @res => $i;
435             }
436             }
437              
438 16 100       63 return @res if wantarray;
439 10         49 return \@res;
440             }
441              
442              
443              
444             =head2 item_class
445              
446             Set/return the tuple class. If the value is defined, the iterator
447             blesses tuples with it (and also calls L<item_constructor> if it is set).
448              
449             =cut
450              
451             sub item_class {
452 164     164 1 1098 my ($self, $v, $m) = @_;
453 164 100       339 $self->item_constructor($m) if @_ > 2;
454 164 100 66     389 return $self->{item_class} = ref($v) || $v if @_ > 1;
455 139         347 return $self->{item_class};
456             }
457              
458              
459             =head2 item_constructor
460              
461             Set/return the tuple constructor.
462             The value is used only if L<item_class> is defined.
463              
464             =cut
465              
466             sub item_constructor {
467 75     75 1 90 my ($self, $v) = @_;
468 75 100       171 return $self->{item_constructor} = $v if @_ > 1;
469 51         138 return $self->{item_constructor};
470             }
471              
472              
473             =head2 push
474              
475             Push a tuple into the iterator.
476              
477             =cut
478              
479             sub push :method {
480 2     2 1 3 my ($self, @i) = @_;
481 2         4 push @{ $self->{items}} => @i;
  2         5  
482 2         4 return $self;
483             }
484              
485              
486             =head2 data
487              
488             Return/set an application-specific context maintained in the iterator
489             object. This can be useful to pass additional state to B<item_constructor>.
490              
491             =cut
492              
493             sub data {
494 29     29 1 37 my ($self, $data) = @_;
495 29 100       69 $self->{data} = $data if @_ > 1;
496 29         69 return $self->{data};
497             }
498              
499             1;