File Coverage

blib/lib/Catmandu/Store/DBI/Iterator.pm
Criterion Covered Total %
statement 12 71 16.9
branch 0 24 0.0
condition 0 13 0.0
subroutine 4 13 30.7
pod 0 4 0.0
total 16 125 12.8


line stmt bran cond sub pod time code
1             package Catmandu::Store::DBI::Iterator;
2              
3 7     7   52 use Catmandu::Sane;
  7         15  
  7         50  
4 7     7   1261 use Catmandu::Util qw(is_value is_string is_array_ref);
  7         20  
  7         479  
5 7     7   46 use Moo;
  7         20  
  7         55  
6 7     7   2631 use namespace::clean;
  7         25  
  7         57  
7              
8             our $VERSION = "0.09";
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 0     0     sub _build_binds {[]}
20 0     0     sub _build_start {0}
21              
22             sub _build_limit {
23 0     0     my ($self) = @_;
24 0           my $limit = 100;
25 0           my $total = $self->total;
26 0 0 0       if (defined $total && $total < $limit) {
27 0           $limit = $total;
28             }
29 0           $limit;
30             }
31              
32             sub generator {
33 0     0 0   my ($self) = @_;
34 0           my $bag = $self->bag;
35 0           my $store = $self->bag->store_with_table;
36 0           my $handler = $store->handler;
37 0           my $binds = $self->binds;
38 0           my $total = $self->total;
39 0           my $start = $self->start;
40 0           my $limit = $self->limit;
41 0           my $where = $self->where;
42              
43             sub {
44 0     0     state $rows;
45              
46 0 0 0       return if defined $total && !$total;
47              
48 0 0 0       unless (defined $rows && @$rows) {
49 0           my $dbh = $store->dbh;
50              
51             #DO NOT USE prepare_cached as it holds previous data in memory, leading to a memory leak!
52 0 0         my $sth
53             = $dbh->prepare(
54             $handler->select_sql($bag, $start, $limit, $where))
55             or Catmandu::Error->throw($dbh->errstr);
56 0 0         $sth->execute(@$binds) or Catmandu::Error->throw($sth->errstr);
57 0           $rows = $sth->fetchall_arrayref({});
58 0           $sth->finish;
59 0           $start += $limit;
60             }
61              
62 0   0       my $data = $bag->_row_to_data(shift(@$rows) // return);
63 0 0         $total-- if defined $total;
64 0           $data;
65 0           };
66             }
67              
68             sub count {
69 0     0 0   my ($self) = @_;
70 0           my $bag = $self->bag;
71 0           my $binds = $self->binds;
72 0           my $store = $bag->store_with_table;
73 0           my $dbh = $store->dbh;
74 0 0         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 0 0         $sth->execute(@$binds) or Catmandu::Error->throw($sth->errstr);
80 0           my ($n) = $sth->fetchrow_array;
81 0           $sth->finish;
82 0           $n;
83             }
84              
85             sub slice {
86 0     0 0   my ($self, $start, $total) = @_;
87 0   0       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 0     0 0   my ($self) = @_;
131 0           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 0     0     my ($self, $arg1, $arg2) = @_;
144 0           my $binds = [@{$self->binds}];
  0            
145 0 0         my $where = is_string($self->where) ? '(' . $self->where . ') AND ' : '';
146 0           my $map = $self->bag->mapping->{$arg1};
147 0           my $column = $map->{column};
148 0           my $q_column = $self->bag->_quote_id($column);
149              
150 0 0         if ($map->{array}) {
    0          
151 0 0         push @$binds, is_value($arg2) ? [$arg2] : $arg2;
152 0           $where .= "($q_column && ?)";
153             }
154             elsif (is_value($arg2)) {
155 0           push @$binds, $arg2;
156 0           $where .= "($q_column=?)";
157             }
158             else {
159 0           push @$binds, @$arg2;
160 0           $where .= "($q_column IN(" . join(',', ('?') x @$arg2) . '))';
161             }
162              
163             {
164 0           bag => $self->bag,
165             where => $where,
166             binds => $binds,
167             start => $self->start,
168             };
169             }
170              
171             1;