File Coverage

blib/lib/Catmandu/Store/DBI/Iterator.pm
Criterion Covered Total %
statement 67 71 94.3
branch 11 24 45.8
condition 5 13 38.4
subroutine 13 13 100.0
pod 0 4 0.0
total 96 125 76.8


line stmt bran cond sub pod time code
1             package Catmandu::Store::DBI::Iterator;
2              
3 6     6   45 use Catmandu::Sane;
  6         14  
  6         58  
4 6     6   1114 use Catmandu::Util qw(is_value is_string is_array_ref);
  6         14  
  6         405  
5 6     6   39 use Moo;
  6         20  
  6         55  
6 6     6   2202 use namespace::clean;
  6         17  
  6         51  
7              
8             our $VERSION = "0.11";
9              
10             with 'Catmandu::Iterable';
11              
12             has bag => (is => 'ro', required => 1);
13             has where => (is => 'ro');
14             has binds => (is => 'lazy');
15             has total => (is => 'ro');
16             has start => (is => 'lazy');
17             has limit => (is => 'lazy');
18              
19 4     4   72 sub _build_binds {[]}
20 4     4   129 sub _build_start {0}
21              
22             sub _build_limit {
23 1     1   10 my ($self) = @_;
24 1         2 my $limit = 100;
25 1         3 my $total = $self->total;
26 1 50 33     9 if (defined $total && $total < $limit) {
27 1         3 $limit = $total;
28             }
29 1         4 $limit;
30             }
31              
32             sub generator {
33 1     1 0 65 my ($self) = @_;
34 1         4 my $bag = $self->bag;
35 1         21 my $store = $self->bag->store_with_table;
36 1         23 my $handler = $store->handler;
37 1         22 my $binds = $self->binds;
38 1         8 my $total = $self->total;
39 1         15 my $start = $self->start;
40 1         24 my $limit = $self->limit;
41 1         3 my $where = $self->where;
42              
43             sub {
44 1     1   1 state $rows;
45              
46 1 50 33     8 return if defined $total && !$total;
47              
48 1 50 33     4 unless (defined $rows && @$rows) {
49 1         5 my $dbh = $store->dbh;
50              
51             #DO NOT USE prepare_cached as it holds previous data in memory, leading to a memory leak!
52 1 50       6 my $sth
53             = $dbh->prepare(
54             $handler->select_sql($bag, $start, $limit, $where))
55             or Catmandu::Error->throw($dbh->errstr);
56 1 50       293 $sth->execute(@$binds) or Catmandu::Error->throw($sth->errstr);
57 1         33 $rows = $sth->fetchall_arrayref({});
58 1         124 $sth->finish;
59 1         26 $start += $limit;
60             }
61              
62 1   50     10 my $data = $bag->_row_to_data(shift(@$rows) // return);
63 1 50       4 $total-- if defined $total;
64 1         13 $data;
65 1         8 };
66             }
67              
68             sub count {
69 8     8 0 4595 my ($self) = @_;
70 8         29 my $bag = $self->bag;
71 8         163 my $binds = $self->binds;
72 8         171 my $store = $bag->store_with_table;
73 8         77 my $dbh = $store->dbh;
74 8 50       144 my $sth = $dbh->prepare_cached(
75             $store->handler->count_sql(
76             $bag, $self->start, $self->total, $self->where
77             )
78             ) or Catmandu::Error->throw($dbh->errstr);
79 8 50       1419 $sth->execute(@$binds) or Catmandu::Error->throw($sth->errstr);
80 8         143 my ($n) = $sth->fetchrow_array;
81 8         46 $sth->finish;
82 8         72 $n;
83             }
84              
85             sub slice {
86 3     3 0 2107 my ($self, $start, $total) = @_;
87 3   50     68 ref($self)->new(
88             {
89             bag => $self->bag,
90             where => $self->where,
91             binds => $self->binds,
92             total => $total,
93             start => $self->start + ($start // 0),
94             }
95             );
96             }
97              
98             around select => sub {
99             my ($orig, $self, $arg1, $arg2) = @_;
100             my $mapping = $self->bag->mapping;
101              
102             if ( is_string($arg1)
103             && $mapping->{$arg1}
104             && (is_value($arg2) || is_array_ref($arg2)))
105             {
106             my $opts = $self->_scope($arg1, $arg2);
107             return ref($self)->new($opts);
108             }
109              
110             $self->$orig($arg1, $arg2);
111             };
112              
113             around detect => sub {
114             my ($orig, $self, $arg1, $arg2) = @_;
115             my $mapping = $self->bag->mapping;
116              
117             if ( is_string($arg1)
118             && $mapping->{$arg1}
119             && (is_value($arg2) || is_array_ref($arg2)))
120             {
121             my $opts = $self->_scope($arg1, $arg2);
122             $opts->{total} = 1;
123             return ref($self)->new($opts)->generator->();
124             }
125              
126             $self->$orig($arg1, $arg2);
127             };
128              
129             sub first {
130 1     1 0 31 my ($self) = @_;
131 1         23 ref($self)->new(
132             {
133             bag => $self->bag,
134             where => $self->where,
135             binds => $self->binds,
136             total => 1,
137             start => $self->start,
138             }
139             )->generator->();
140             }
141              
142             sub _scope {
143 4     4   9 my ($self, $arg1, $arg2) = @_;
144 4         6 my $binds = [@{$self->binds}];
  4         73  
145 4 50       35 my $where = is_string($self->where) ? '(' . $self->where . ') AND ' : '';
146 4         14 my $map = $self->bag->mapping->{$arg1};
147 4         7 my $column = $map->{column};
148 4         16 my $q_column = $self->bag->_quote_id($column);
149              
150 4 50       134 if ($map->{array}) {
    50          
151 0 0       0 push @$binds, is_value($arg2) ? [$arg2] : $arg2;
152 0         0 $where .= "($q_column && ?)";
153             }
154             elsif (is_value($arg2)) {
155 4         11 push @$binds, $arg2;
156 4         13 $where .= "($q_column=?)";
157             }
158             else {
159 0         0 push @$binds, @$arg2;
160 0         0 $where .= "($q_column IN(" . join(',', ('?') x @$arg2) . '))';
161             }
162              
163             {
164 4         84 bag => $self->bag,
165             where => $where,
166             binds => $binds,
167             start => $self->start,
168             };
169             }
170              
171             1;