File Coverage

blib/lib/Mojo/DB/Results/Role/MoreMethods.pm
Criterion Covered Total %
statement 9 245 3.6
branch 0 168 0.0
condition 0 73 0.0
subroutine 3 49 6.1
pod 23 23 100.0
total 35 558 6.2


line stmt bran cond sub pod time code
1             package Mojo::DB::Results::Role::MoreMethods;
2 1     1   635 use Mojo::Base -role;
  1         2  
  1         6  
3 1     1   804 use Mojo::Collection;
  1         3283  
  1         34  
4 1     1   6 use Mojo::Util ();
  1         2  
  1         3388  
5              
6             with 'Mojo::DB::Results::Role::Struct';
7              
8             our $VERSION = '0.03';
9              
10             requires qw(array arrays columns hash hashes);
11              
12             sub import {
13 0     0     my $class = shift;
14              
15 0   0       my (@mysql_indexes) = grep { ($_[$_] // '') eq '-mysql' } 0..$#_;
  0            
16 0 0         Carp::croak '-mysql flag provided more than once' if @mysql_indexes > 1;
17              
18 0           my $mysql_flag;
19 0 0         if (@mysql_indexes) {
20 0           splice @_, $mysql_indexes[0], 1;
21 0           $mysql_flag = 1;
22             }
23              
24 0   0       my (@pg_indexes) = grep { ($_[$_] // '') eq '-Pg' } 0..$#_;
  0            
25 0 0         Carp::croak '-Pg flag provided more than once' if @pg_indexes > 1;
26              
27 0           my $pg_flag;
28 0 0         if (@pg_indexes) {
29 0           splice @_, $pg_indexes[0], 1;
30 0           $pg_flag = 1;
31             }
32              
33 0           my %options = @_;
34 0 0 0       return unless %options or $mysql_flag or $pg_flag;
      0        
35              
36 0           my $results_class;
37 0 0         if (exists $options{results_class}) {
38 0           $results_class = delete $options{results_class};
39 0 0 0       Carp::croak 'results_class must be a defined and non-empty value' unless defined $results_class and $results_class ne '';
40              
41 0           my $ref = ref $results_class;
42 0 0 0       Carp::croak 'results_class must be a string or an arrayref' unless $ref eq '' or $ref eq 'ARRAY';
43              
44 0 0 0       if ($ref and $ref eq 'ARRAY') {
45 0 0         Carp::croak 'results_class array cannot be empty' unless @$results_class;
46             Carp::croak 'results_class array entries must be non-empty strings'
47 0 0 0       unless scalar(grep { defined and not ref $_ and $_ ne '' } @$results_class) == @$results_class
  0 0          
48             } else {
49 0           $results_class = [$results_class];
50             }
51             }
52              
53 0 0         Carp::croak 'unknown options provided to import: ' . Mojo::Util::dumper \%options if %options;
54              
55 0 0 0       if ($mysql_flag or $pg_flag) {
56 0   0       $results_class ||= [];
57 0 0         if ($mysql_flag) {
58 0 0         if (grep { $_ eq 'Mojo::mysql::Results' } @$results_class) {
  0            
59 0           Carp::croak 'cannot provide -mysql flag and provide Mojo::mysql::Results in result_class';
60             }
61              
62 0           push @$results_class, 'Mojo::mysql::Results';
63             }
64              
65 0 0         if ($pg_flag) {
66 0 0         if (grep { $_ eq 'Mojo::Pg::Results' } @$results_class) {
  0            
67 0           Carp::croak 'cannot provide -Pg flag and provide Mojo::Pg::Results in result_class';
68             }
69              
70 0           push @$results_class, 'Mojo::Pg::Results';
71             }
72             }
73              
74 0 0         if ($results_class) {
75 0           require Role::Tiny;
76              
77 0           my %seen;
78 0           for my $class (@$results_class) {
79 0 0         Carp::croak "$class provided more than once to result_class" if $seen{$class}++;
80              
81 0 0         do { eval "require $class"; 1 } or Carp::croak "Failed to require $class";
  0            
  0            
82 0           Role::Tiny->apply_roles_to_package($class, 'Mojo::DB::Results::Role::MoreMethods');
83             }
84             }
85             }
86              
87             sub get {
88 0     0 1   my $self = shift;
89              
90 0 0         my $options = ref $_[0] eq 'HASH' ? shift : {};
91 0           my $die = delete $options->{die};
92 0           my $one = delete $options->{one};
93 0           my @indexes = @_;
94              
95 0 0         if ($one) {
96 0 0         Carp::confess 'no rows returned' if $self->rows == 0;
97 0 0         Carp::confess 'multiple rows returned' if $self->rows > 1;
98             }
99              
100 0           my $wantarray = wantarray;
101 0 0         if (not defined $wantarray) {
    0          
102 0           Carp::cluck 'get or get variant called without using return value';
103              
104 0 0 0       if ($die or $one) {
105 0 0         Carp::croak 'no results' unless $self->array;
106             } else {
107 0           $self->array;
108 0           return;
109             }
110             } elsif ($wantarray) {
111 0           my $array = $self->array;
112 0 0         unless ($array) {
113 0 0 0       Carp::confess 'no results' if $die or $one;
114 0           return;
115             }
116              
117 0 0         if (@indexes) {
118 0           $self->_assert_indexes(@indexes);
119              
120 0           return @$array[@indexes];
121             } else {
122 0           return @$array;
123             }
124             } else {
125 0 0         Carp::confess 'multiple indexes passed for single requested get value' if @indexes > 1;
126              
127 0           my $array = $self->array;
128 0 0         unless ($array) {
129 0 0 0       Carp::confess 'no results' if $die or $one;
130 0           return;
131             }
132              
133 0   0       my $index = $indexes[0] // 0;
134 0           $self->_assert_indexes($index);
135              
136 0           return $array->[$index];
137             }
138             }
139              
140             sub get_by_name {
141 0     0 1   my $self = shift;
142              
143 0 0         my $options = ref $_[0] eq 'HASH' ? shift : {};
144 0           my @names = @_;
145 0 0         Carp::croak 'names required' unless @names;
146              
147 0           return $self->get($options, $self->_find_column_indexes(@names));
148             }
149              
150             sub c {
151 0 0   0 1   return shift->get(@_) if not defined wantarray;
152              
153 0           my @values = shift->get(@_);
154 0 0         return @values ? Mojo::Collection->new(@values) : undef;
155             }
156              
157             sub c_by_name {
158 0 0   0 1   return shift->get_by_name(@_) if not defined wantarray;
159              
160 0           my @values = shift->get_by_name(@_);
161 0 0         return @values ? Mojo::Collection->new(@values) : undef;
162             }
163              
164 0     0 1   sub collections { Mojo::Collection->new(map { Mojo::Collection->new(@$_) } @{ shift->arrays }) }
  0            
  0            
165              
166 0     0 1   sub flatten { shift->arrays->flatten }
167              
168             sub hashify {
169 0     0 1   my $self = shift;
170 0           my ($collection, $get_keys, $get_value) = $self->_parse_transform_options({}, @_);
171              
172 0           return $collection->with_roles('+Transform')->hashify($get_keys, $get_value);
173             }
174              
175             sub hashify_collect {
176 0     0 1   my ($collection, $get_keys, $get_value, $flatten) = shift->_parse_transform_options({flatten_allowed => 1}, @_);
177              
178 0           return $collection->with_roles('+Transform')->hashify_collect({flatten => $flatten}, $get_keys, $get_value);
179             }
180              
181             sub collect_by {
182 0     0 1   my ($collection, $get_keys, $get_value, $flatten) = shift->_parse_transform_options({flatten_allowed => 1}, @_);
183              
184 0           return $collection->with_roles('+Transform')->collect_by({flatten => $flatten}, $get_keys, $get_value);
185             }
186              
187             sub _parse_transform_options {
188 0     0     my $self = shift;
189 0           my $private_options = shift;
190 0 0         my $options = ref $_[0] eq 'HASH' ? shift : {};
191              
192 0           my ($key, $key_ref) = _parse_and_validate_transform_key(shift);
193 0           my ($value, $value_is_column, $value_ref) = _parse_and_validate_transform_value(@_);
194              
195 0           my ($type, $flatten) = _parse_and_validate_transform_options($private_options, $options);
196              
197             # if user will not access the rows and the type won't be used, default rows to arrays for speed
198 0 0 0       if (($value_is_column or $flatten) and $key_ref ne 'CODE' and $value_ref ne 'CODE') {
    0 0        
      0        
199 0 0 0       if ($type and $type ne 'array') {
200 0           Carp::cluck 'Useless type option provided. array will be used for performance.';
201             }
202 0           $type = 'array';
203             } elsif (not $type) {
204 0           $type = 'hash';
205             }
206              
207 0 0         my $get_keys = $key_ref eq 'CODE' ? $key : $self->_create_get_keys_sub($type, $key);
208             my $get_value = $value_ref eq 'CODE' ? $value
209             : $value_is_column ? $self->_create_column_value_getter($type, $value)
210             : $flatten ? $self->_create_flatten_value_getter($type)
211 0     0     : sub { $_ }
212 0 0         ;
    0          
    0          
213 0 0         my $collection = $type eq 'array' ? $self->arrays
    0          
    0          
214             : $type eq 'c' ? $self->collections
215             : $type eq 'hash' ? $self->hashes
216             : $self->structs
217             ;
218              
219 0           return $collection, $get_keys, $get_value, $flatten;
220             }
221              
222             sub _parse_and_validate_transform_key {
223 0     0     my ($key) = @_;
224              
225 0           my $key_ref = ref $key;
226 0 0         if ($key_ref) {
227 0 0 0       Carp::confess qq{key must be an arrayref, a sub or a non-empty string, but had ref '$key_ref'}
228             unless $key_ref eq 'ARRAY' or $key_ref eq 'CODE';
229              
230 0 0         if ($key_ref eq 'ARRAY') {
231 0 0         Carp::confess 'key array must not be empty' unless @$key;
232 0 0         Carp::confess 'key array elements must be defined and non-empty' if grep { not defined or $_ eq '' } @$key;
  0 0          
233             }
234             } else {
235 0 0 0       Carp::confess 'key was undefined or an empty string' unless defined $key and $key ne '';
236 0           $key = [$key];
237             }
238              
239 0           return $key, $key_ref;
240             }
241              
242             sub _parse_and_validate_transform_value {
243 0     0     my ($value, $value_is_column);
244              
245 0           my $value_ref;
246 0 0         if (@_ == 1) {
    0          
247 0           $value = shift;
248              
249 0           $value_ref = ref $value;
250 0 0 0       if ($value_ref) {
    0          
251 0 0         Carp::confess qq{value must be a sub or non-empty string, but was '$value_ref'} unless $value_ref eq 'CODE';
252             } elsif (not defined $value or $value eq '') {
253 0           Carp::confess 'value must not be undefined or an empty string';
254             } else {
255 0           $value_is_column = 1;
256             }
257             } elsif (@_ > 1) {
258 0           Carp::confess 'too many arguments provided (more than one value)';
259             }
260              
261 0   0       return $value, $value_is_column, $value_ref // '';
262             }
263              
264             sub _parse_and_validate_transform_options {
265 0     0     my ($private_options, $options) = @_;
266              
267 0           my $flatten;
268 0 0         if ($private_options->{flatten_allowed}) {
269 0           $flatten = delete $options->{flatten};
270             } else {
271 0 0         Carp::confess 'flatten not allowed' if exists $options->{flatten};
272             }
273              
274 0 0         my $flatten_allowed_text = $private_options->{flatten_allowed} ? 'In addition to flatten, ' : '';
275 0 0         Carp::confess "${flatten_allowed_text}one key/value pair is allowed for options"
276             if keys %$options > 1;
277              
278 0           my $type;
279 0 0         if (%$options) {
280 0           ($type) = keys %$options;
281              
282 0           my @valid_types = qw(array c hash struct);
283 0           Carp::confess "${flatten_allowed_text}option must be one of: @{[ join ', ', @valid_types ]}"
284 0 0         unless grep { $type eq $_ } @valid_types;
  0            
285             }
286              
287 0           return $type, $flatten;
288             }
289              
290             sub _create_get_keys_sub {
291 0     0     my ($self, $type, $key) = @_;
292              
293 0 0 0       if ($type eq 'array' or $type eq 'c') {
    0          
294 0           my @key_indexes = $self->_find_column_indexes(@$key);
295 0     0     return sub { @{$_}[@key_indexes] };
  0            
  0            
296             } elsif ($type eq 'hash') {
297             # assert columns exist
298 0           $self->_find_column_indexes(@$key);
299              
300 0     0     return sub { @{$_}{@$key} };
  0            
  0            
301             } else {
302             # assert columns exist
303 0           $self->_find_column_indexes(@$key);
304              
305             return sub {
306 0     0     map { $_[0]->${\$_} } @$key
  0            
  0            
307 0           };
308             }
309             }
310              
311             sub _create_column_value_getter {
312 0     0     my ($self, $type, $value) = @_;
313              
314 0 0 0       if ($type eq 'array' or $type eq 'c') {
    0          
315 0           my $column_index = $self->_find_column_indexes($value);
316 0     0     return sub { $_->[$column_index] };
  0            
317             } elsif ($type eq 'hash') {
318             # assert that column exists
319 0           $self->_find_column_indexes($value);
320              
321 0     0     return sub { $_->{$value} };
  0            
322             } else {
323             # assert that column exists
324 0           $self->_find_column_indexes($value);
325              
326 0     0     return sub { $_->${\$value} };
  0            
  0            
327             }
328             }
329              
330             sub _create_flatten_value_getter {
331 0     0     my ($self, $type) = @_;
332              
333 0 0 0       if ($type eq 'array' or $type eq 'c') {
    0          
334 0     0     return sub { @$_ };
  0            
335             } elsif ($type eq 'hash') {
336 0           my $columns = $self->columns;
337 0     0     return sub { @{$_}{@$columns} };
  0            
  0            
338             } else {
339 0           my $columns = $self->columns;
340             return sub {
341 0     0     my $struct = $_;
342 0           map { $struct->${\$_} } @$columns;
  0            
  0            
343 0           };
344             }
345             }
346              
347 0     0 1   sub get_or_die { shift->get({die => 1}, @_) }
348              
349 0     0 1   sub get_by_name_or_die { shift->get_by_name({die => 1}, @_) }
350              
351 0     0 1   sub c_or_die { shift->c({die => 1}, @_) }
352              
353 0     0 1   sub c_by_name_or_die { shift->c_by_name({die => 1}, @_) }
354              
355 0     0 1   sub struct_or_die { $_[0]->_type_or_die('struct') }
356              
357 0     0 1   sub array_or_die { $_[0]->_type_or_die('array') }
358              
359 0     0 1   sub hash_or_die { $_[0]->_type_or_die('hash') }
360              
361             sub _type_or_die {
362 0     0     my ($self, $type) = @_;
363 0 0         if (not defined wantarray) {
364 0           Carp::cluck "${type}_or_die called without using return value";
365             }
366              
367 0 0         Carp::croak 'no results' unless my $value = shift->$type;
368 0           return $value;
369             }
370              
371 0     0 1   sub one { shift->get({one => 1}, @_) }
372              
373 0     0 1   sub one_by_name { shift->get_by_name({one => 1}, @_) }
374              
375 0     0 1   sub one_c { shift->c({one => 1}, @_) }
376              
377 0     0 1   sub one_c_by_name { shift->c_by_name({one => 1}, @_) }
378              
379 0     0 1   sub one_struct { $_[0]->_one_type('struct') }
380              
381 0     0 1   sub one_array { $_[0]->_one_type('array') }
382              
383 0     0 1   sub one_hash { $_[0]->_one_type('hash') }
384              
385             sub _one_type {
386 0     0     my ($self, $type) = @_;
387              
388 0 0         Carp::confess 'no rows returned' if $self->rows == 0;
389 0 0         Carp::confess 'multiple rows returned' if $self->rows > 1;
390              
391 0           return $self->$type;
392             }
393              
394             sub _find_column_indexes {
395 0     0     my $columns = shift->columns;
396              
397 0           return map { _find_column_index($columns, $_) } @_;
  0            
398             }
399              
400             sub _find_column_index {
401 0     0     my ($columns, $column) = @_;
402              
403 0           my @indexes = grep { $columns->[$_] eq $column } 0..$#$columns;
  0            
404 0 0         Carp::confess "could not find column '$column' in returned columns" unless @indexes;
405 0 0         Carp::confess "more than one column named '$column' in returned columns" if @indexes > 1;
406              
407 0           return $indexes[0];
408             }
409              
410             sub _assert_indexes {
411 0     0     my ($self, @indexes) = @_;
412              
413 0           my $num_columns = @{ $self->columns };
  0            
414 0 0         Carp::croak 'cannot index into a size zero results array' if $num_columns == 0;
415              
416 0           for my $index (@indexes) {
417 0 0 0       Carp::croak "index out of valid range -$num_columns to @{[ $num_columns - 1 ]}"
  0            
418             unless $index >= -$num_columns and $index < $num_columns;
419             }
420             }
421              
422             1;
423             __END__