File Coverage

lib/DBIx/EAV/ResultSet.pm
Criterion Covered Total %
statement 117 134 87.3
branch 41 50 82.0
condition 11 14 78.5
subroutine 27 31 87.1
pod 7 19 36.8
total 203 248 81.8


line stmt bran cond sub pod time code
1             package DBIx::EAV::ResultSet;
2              
3 10     10   32 use Moo;
  10         10  
  10         41  
4 10     10   1872 use DBIx::EAV::Entity;
  10         18  
  10         168  
5 10     10   2881 use DBIx::EAV::Cursor;
  10         18  
  10         271  
6 10     10   45 use Data::Dumper;
  10         9  
  10         433  
7 10     10   33 use Carp qw/ croak confess /;
  10         11  
  10         445  
8             use overload
9 10         68 '0+' => "_to_num",
10             'bool' => "_to_bool",
11 10     10   46 fallback => 1;
  10         10  
12              
13             my $sql = SQL::Abstract->new;
14              
15             has 'eav', is => 'ro', required => 1;
16             has 'type', is => 'ro', required => 1;
17             has '_query', is => 'rw', default => sub { [] }, init_arg => 'query';
18             has '_options', is => 'rw', default => sub { {} }, init_arg => 'options';
19             has 'cursor', is => 'rw',
20             lazy => 1,
21             init_arg => undef,
22             predicate => '_has_cursor',
23             clearer => '_clear_cursor',
24             builder => '_build_cursor';
25              
26             has 'entity_class', is => 'ro', init_arg => undef, lazy => 1, default => sub {
27             my $self = shift;
28             $self->eav->_resolve_entity_class($self->type->name) || 'DBIx::EAV::Entity';
29             };
30              
31 3     3   136 sub _to_num { $_[0]->count }
32              
33 2     2   3837 sub _to_bool { 1 }
34              
35              
36             sub _build_cursor {
37 99     99   2781 my $self = shift;
38              
39 99         1493 DBIx::EAV::Cursor->new(
40             eav => $self->eav,
41             type => $self->type,
42             query => $self->_query,
43             options => $self->_options,
44             );
45             }
46              
47              
48             sub new_entity {
49 92     92 1 136 my ($self, $data) = @_;
50 92         1497 my $entity = $self->entity_class->new( eav => $self->eav, type => $self->type );
51 92 50       654 $entity->set($data) if ref $data eq 'HASH';
52 92         206 $entity;
53             }
54              
55              
56             sub inflate_entity {
57 111     111 0 114 my ($self, $data) = @_;
58 111         161 my $type = $self->type;
59             $type = $self->eav->type_by_id($data->{entity_type_id})
60 111 100 100     552 if $data->{entity_type_id} && $data->{entity_type_id} != $type->id;
61              
62 111         2076 my $entity = $self->entity_class->new( eav => $self->eav, type => $type, raw => $data );
63 111         911 $entity->load_attributes;
64 111         345 $entity;
65             }
66              
67              
68             {
69 10     10   3451 no warnings;
  10         17  
  10         10282  
70             *create = \&insert;
71             }
72              
73             sub insert {
74 87     87 1 353 my ($self, $data) = @_;
75 87         134 $self->new_entity($data)->save;
76             }
77              
78              
79             sub populate {
80 16     16 1 228 my ($self, $data) = @_;
81 16 50       44 die 'Call populate(\@items)' unless ref $data eq 'ARRAY';
82              
83 16         16 my @result;
84 16         26 foreach my $item (@$data) {
85 46         81 push @result, $self->insert($item);
86             }
87              
88 16 100       61 return wantarray ? @result : \@result;
89             }
90              
91              
92             sub update {
93 0     0 0 0 my ($self, $data, $where) = @_;
94              
95 0   0     0 $where //= {};
96 0         0 $where->{entity_type_id} = $self->type->id;
97              
98             # do a direct update for static attributes
99              
100             }
101              
102              
103             sub delete {
104 3     3 1 29 my $self = shift;
105 3         10 my $eav = $self->eav;
106 3         5 my $type = $self->type;
107 3         57 my $entities_table = $eav->table('entities');
108              
109             # Call delete_all for SQLite since it doesn't
110             # support delete with joins.
111             # Better solution welcome.
112 3 50       96 return $self->delete_all if
113             $self->eav->schema->db_driver_name eq 'SQLite';
114              
115 0 0       0 unless ($eav->schema->database_cascade_delete) {
116              
117             # delete links by relationship id
118 0         0 my @ids = map { $_->{id} } $type->relationships;
  0         0  
119              
120 0         0 $eav->table('entity_relationships')->delete(
121             {
122             relationship_id => \@ids,
123             $entities_table->name.'.entity_type_id' => $type->id
124             },
125             { join => { $entities_table->name => [{ 'me.left_entity_id' => 'their.id' }, { 'me.right_entity_id' => 'their.id' }] } }
126             );
127              
128             # delete attributes:
129             # - group attrs by data type so only one DELETE command is sent per data type
130             # - restrict by entity_type_id so we dont delete parent/sibiling/child data
131 0         0 my %types;
132 0         0 push @{ $types{$_->{data_type}} }, $_->{id}
133 0         0 for $type->attributes(no_static => 1);
134              
135 0         0 while (my ($data_type, $ids) = each %types) {
136              
137 0         0 my $value_table = $eav->table('value_'.$data_type);
138 0         0 $value_table->delete(
139             {
140             attribute_id => $ids,
141             $entities_table->name.'.entity_type_id' => $type->id
142             },
143             { join => { $entities_table->name => { 'me.entity_id' => 'their.id' } } }
144             );
145             }
146             }
147              
148 0         0 $entities_table->delete({ entity_type_id => $type->id });
149             }
150              
151              
152             sub delete_all {
153 6     6 1 19 my $self = shift;
154              
155 6 50       18 my $rs = scalar @_ > 0 ? $self->search_rs(@_) : $self;
156 6         7 my $i = 0;
157              
158 6         14 while (my $entity = $rs->next) {
159 14         45 $entity->delete;
160 14         56 $i++;
161             }
162              
163 6         47 $i;
164             }
165              
166              
167             sub find {
168 11     11 0 1568 my ($self, $criteria, $options) = @_;
169              
170 11 50       27 croak "Missing find() criteria."
171             unless defined $criteria;
172              
173             # simple id search
174 11 100       22 return $self->search_rs({ id => $criteria }, $options)->next
175             unless ref $criteria;
176              
177 10         20 my $rs = $self->search_rs($criteria, $options);
178 10         172 my $result = $rs->next;
179              
180             # criteria is a search query, die if this query returns multiple items
181 10 100 100     219 croak "find() returned more than one entity. If this is what you want, use search or search_rs."
182             if defined $result && defined $rs->cursor->next;
183              
184 9         222 $result;
185             }
186              
187              
188             sub search {
189 66     66 0 1019 my ($self, $query, $options) = @_;
190              
191 66         117 my $rs = $self->search_rs($query, $options);
192              
193 66 100       1380 return wantarray ? $rs->all : $rs;
194             }
195              
196             sub search_rs {
197 78     78 0 81 my ($self, $query, $options) = @_;
198              
199             # simple combine queries using AND
200 78         62 my @new_query = @{ $self->_query };
  78         165  
201 78 100       169 push @new_query, $query if $query;
202              
203             # merge options
204 78         114 my $merged_options = $self->_merge_options($options);
205              
206 78         1443 (ref $self)->new(
207             eav => $self->eav,
208             type => $self->type,
209             query => \@new_query,
210             options => $merged_options
211             );
212             }
213              
214              
215             sub _merge_options {
216 78     78   98 my ($self, $options) = @_;
217              
218 78         59 my %merged = %{ $self->_options };
  78         184  
219              
220 78 100       192 return \%merged
221             unless defined $options;
222              
223 19 50       53 confess "WTF" if $options eq '';
224              
225 19         51 foreach my $opt (keys %$options) {
226              
227             # doesnt even exist, just copy
228 39 100       75 if (not exists $merged{$opt}) {
    100          
    100          
229 36         62 $merged{$opt} = $options->{$opt};
230             }
231              
232             # having: combine queries using AND
233             elsif ($opt eq 'having') {
234              
235 1         3 $merged{$opt} = [$merged{$opt}, $options->{$opt}];
236             }
237              
238             # merge array
239             elsif (ref $merged{$opt} eq 'ARRAY') {
240              
241             $merged{$opt} = [
242 1         4 @{$merged{$opt}},
243 1 50       1 ref $options->{$opt} eq 'ARRAY' ? @{$options->{$opt}} : $options->{$opt}
  1         3  
244             ];
245             }
246              
247             else {
248 1         1 $merged{$opt} = $options->{$opt};
249             }
250             }
251              
252 19         37 \%merged;
253             }
254              
255              
256             sub count {
257 17     17 1 39 my $self = shift;
258 17 100       37 return $self->search(@_)->count if @_;
259              
260             # from DBIx::Class::ResultSet::count()
261             # this is a little optimization - it is faster to do the limit
262             # adjustments in software, instead of a subquery
263 14         23 my $options = $self->_options;
264 14         25 my ($limit, $offset) = @$options{qw/ limit offset /};
265              
266 14         27 my $count = $self->_count_rs($options)->cursor->next->{count};
267              
268 14 100       186 $count -= $offset if $offset;
269 14 100       24 $count = 0 if $count < 0;
270 14 100 100     35 $count = $limit if $limit && $count > $limit;
271              
272 14         101 $count;
273             }
274              
275              
276             sub _count_rs {
277 14     14   15 my ($self, $options) = @_;
278 14         41 my %tmp_options = ( %$options, select => [\'COUNT(*) AS count'] );
279              
280             # count using subselect if needed
281             $tmp_options{from} = $self->as_query
282 14 100 66     54 if $options->{group_by} || $options->{distinct};
283              
284 14         30 delete @tmp_options{qw/ limit offset order_by group_by distinct /};
285              
286             (ref $self)->new(
287             eav => $self->eav,
288             type => $self->type,
289 14         31 query => [@{ $self->_query }],
  14         256  
290             options => \%tmp_options
291             );
292             }
293              
294              
295             sub as_query {
296 4     4 0 6 my $self = shift;
297 4         59 $self->cursor->as_query;
298             }
299              
300              
301             sub reset {
302 54     54 0 49 my $self = shift;
303 54         847 $self->_clear_cursor;
304 54         1783 $self;
305             }
306              
307             sub first {
308 3     3 0 4 $_[0]->reset->next;
309             }
310              
311             sub next {
312 165     165 0 184 my $self = shift;
313              
314             # fetch next
315 165         2963 my $entity_row = $self->cursor->next;
316 165 100       2410 return unless defined $entity_row;
317              
318             # instantiate entity
319 111         208 $self->inflate_entity($entity_row);
320             }
321              
322             sub all {
323 25     25 0 42 my $self = shift;
324 25         23 my @entities;
325              
326 25         57 $self->reset;
327              
328 25         47 while (my $entity = $self->next) {
329 69         162 push @entities, $entity;
330             }
331              
332 25         52 $self->reset;
333              
334 25 100       113 return wantarray ? @entities : \@entities;
335             }
336              
337             sub pager {
338 0     0 0   die "pager() not implemented";
339             }
340              
341             sub distinct {
342 0     0 1   die "distinct() not implemented";
343             }
344              
345             sub storage_size {
346 0     0 0   die "storage_size() not implemented";
347             }
348              
349              
350             1;
351              
352             __END__