File Coverage

blib/lib/Data/TableData/Object/Base.pm
Criterion Covered Total %
statement 127 136 93.3
branch 39 46 84.7
condition 13 17 76.4
subroutine 17 21 80.9
pod 14 14 100.0
total 210 234 89.7


line stmt bran cond sub pod time code
1             package Data::TableData::Object::Base;
2              
3 4     4   2296 use 5.010001;
  4         16  
4 4     4   19 use strict;
  4         7  
  4         86  
5 4     4   20 use warnings;
  4         8  
  4         136  
6              
7 4     4   1966 use Scalar::Util::Numeric qw(isint isfloat);
  4         2855  
  4         6816  
8              
9             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
10             our $DATE = '2021-11-17'; # DATE
11             our $DIST = 'Data-TableData-Object'; # DIST
12             our $VERSION = '0.114'; # VERSION
13              
14             sub _array_is_numeric {
15 4     4   16 my $self = shift;
16 4         8 for (@{$_[0]}) {
  4         18  
17 8 100 66     71 return 0 if defined($_) && !isint($_) && !isfloat($_);
      66        
18             }
19 3         15 return 1;
20             }
21              
22             sub _list_is_numeric {
23 0     0   0 my $self = shift;
24 0         0 $self->_array_is_numeric(\@_);
25             }
26              
27             sub cols_by_name {
28 15     15 1 3321 my $self = shift;
29 15         113 $self->{cols_by_name};
30             }
31              
32             sub cols_by_idx {
33 15     15 1 38 my $self = shift;
34 15         79 $self->{cols_by_idx};
35             }
36              
37             sub col_exists {
38 42     42 1 5990 my ($self, $name_or_idx) = @_;
39 42 50       173 if ($name_or_idx =~ /\A[0-9][1-9]*\z/) {
40 0         0 return $name_or_idx <= @{ $self->{cols_by_idx} };
  0         0  
41             } else {
42 42         247 return exists $self->{cols_by_name}{$name_or_idx};
43             }
44             }
45              
46             sub col_name {
47 47     47 1 4904 my ($self, $name_or_idx) = @_;
48 47 100       229 if ($name_or_idx =~ /\A[0-9][1-9]*\z/) {
49 6         69 return $self->{cols_by_idx}[$name_or_idx];
50             } else {
51 41 100       220 return exists($self->{cols_by_name}{$name_or_idx}) ?
52             $name_or_idx : undef;
53             }
54             }
55              
56             sub col_idx {
57 79     79 1 4924 my ($self, $name_or_idx) = @_;
58 79 100       388 if ($name_or_idx =~ /\A[0-9][1-9]*\z/) {
59 23 100       51 return $name_or_idx < @{ $self->{cols_by_idx} } ? $name_or_idx : undef;
  23         117  
60             } else {
61 56         197 return $self->{cols_by_name}{$name_or_idx};
62             }
63             }
64              
65             sub col_count {
66 14     14 1 30 my $self = shift;
67 14         28 scalar @{ $self->{cols_by_idx} };
  14         51  
68             }
69              
70             sub col_content {
71 26     26 1 4884 my ($self, $name_or_idx) = @_;
72              
73 26         72 my $col_idx = $self->col_idx($name_or_idx);
74 26 100       94 return undef unless defined $col_idx; ## no critic: Subroutines::ProhibitExplicitReturnUndef
75              
76 18         71 my $row_count = $self->row_count;
77 18 50       48 return [] unless $row_count;
78              
79 18         38 my $col_content = [];
80 18         57 for my $i (0 .. $row_count-1) {
81 56         133 my $row = $self->row_as_aos($i);
82 56         116 $col_content->[$i] = $row->[$col_idx];
83             }
84 18         84 $col_content;
85             }
86              
87             sub _select {
88 28     28   80 my ($self, $_as, $cols0, $excl_cols, $func_filter_row, $sorts) = @_;
89              
90             # determine result's columns & spec
91 28         105 my $spec;
92             my %newcols_to_origcols;
93 28         0 my @cols0; # original column names but with '*' expanded
94 28         0 my @newcols;
95 28 100       80 if ($cols0) {
96 24         70 $spec = {fields=>{}};
97 24         53 my $i = 0;
98 24         60 for my $col0 (@$cols0) {
99 35         102 my @add;
100 35 100       120 if ($col0 eq '*') {
101 4         13 @add = @{ $self->{cols_by_idx} };
  4         18  
102             } else {
103 31 100       88 die "Column '$col0' does not exist" unless $self->col_exists($col0);
104 27         72 @add = ($col0);
105             }
106              
107 31         61 for my $add (@add) {
108 35 100 100     103 next if $excl_cols && (grep {$add eq $_} @$excl_cols);
  6         37  
109 32         65 push @cols0, $add;
110 32         52 my $j = 1;
111 32         52 my $col = $add;
112 32         97 while (defined $newcols_to_origcols{$col}) {
113 8         17 $j++;
114 8         30 $col = "${add}_$j";
115             }
116 32         72 $newcols_to_origcols{$col} = $add;
117 32         55 push @newcols, $col;
118              
119             $spec->{fields}{$col} = {
120 32   50     50 %{$self->{spec}{fields}{$add} // {}},
  32         197  
121             pos=>$i,
122             };
123 32         101 $i++;
124             }
125             }
126 20         43 $cols0 = \@cols0;
127             } else {
128             # XXX excl_cols is not being observed
129 4         37 $spec = $self->{spec};
130 4         47 $cols0 = $self->{cols_by_idx};
131 4         11 @newcols = @{ $self->{cols_by_idx} };
  4         18  
132 4         44 for (@newcols) { $newcols_to_origcols{$_} = $_ }
  8         26  
133             }
134              
135 24         55 my $rows = [];
136              
137             # filter rows
138 24         83 for my $row (@{ $self->rows_as_aohos }) {
  24         102  
139 69 100 100     263 next unless !$func_filter_row || $func_filter_row->($self, $row);
140 61         155 push @$rows, $row;
141             }
142              
143             # sort rows
144 20 100 66     86 if ($sorts && @$sorts) {
145             # determine whether each column mentioned in $sorts is numeric, to
146             # decide whether to use <=> or cmp.
147 4         11 my %col_is_numeric;
148 4         14 for my $sortcol (@$sorts) {
149 4 50       42 my ($reverse, $col) = $sortcol =~ /\A(-?)(.+)/
150             or die "Invalid sort column specification '$sortcol'";
151 4 50       19 next if defined $col_is_numeric{$col};
152 4         16 my $sch = $self->{spec}{fields}{$col}{schema};
153 4 50       19 if ($sch) {
154 0         0 require Data::Sah::Util::Type;
155 0         0 $col_is_numeric{$col} = Data::Sah::Util::Type::is_numeric($sch);
156             } else {
157 4         19 my $col_name = $self->col_name($col);
158 4 50       19 defined($col_name) or die "Unknown sort column '$col'";
159             $col_is_numeric{$col} = $self->_array_is_numeric(
160 4         26 [map {$_->{$col_name}} @$rows]);
  9         83  
161             }
162             }
163              
164             $rows = [sort {
165 4         26 for my $sortcol (@$sorts) {
  5         21  
166 5         31 my ($reverse, $col) = $sortcol =~ /\A(-?)(.+)/;
167 5         18 my $name = $self->col_name($col);
168             my $cmp = ($reverse ? -1:1) *
169             ($col_is_numeric{$col} ?
170             ($a->{$name} <=> $b->{$name}) :
171 5 100       33 ($a->{$name} cmp $b->{$name}));
    100          
172 5 50       67 return $cmp if $cmp;
173             }
174             0;
175             } @$rows];
176             } # sort rows
177              
178             # select columns & convert back to aoaos if that's the requested form
179             {
180 20         47 my $rows2 = [];
  20         46  
181 20         43 for my $row0 (@$rows) {
182 61         85 my $row;
183 61 100       139 if ($_as eq 'aoaos') {
184 48         73 $row = [];
185 48         73 for my $i (0..$#{$cols0}) {
  48         104  
186 91         192 $row->[$i] = $row0->{$cols0->[$i]};
187             }
188             } else {
189 13         21 $row = {};
190 13         44 for my $i (0..$#newcols) {
191             $row->{$newcols[$i]} =
192 32         77 $row0->{$newcols_to_origcols{$newcols[$i]}};
193             }
194             }
195 61         128 push @$rows2, $row;
196             }
197 20         58 $rows = $rows2;
198             }
199              
200             # return result as object
201 20 100       54 if ($_as eq 'aoaos') {
202 16         2310 require Data::TableData::Object::aoaos;
203 16         113 return Data::TableData::Object::aoaos->new($rows, $spec);
204             } else {
205 4         2176 require Data::TableData::Object::aohos;
206 4         48 return Data::TableData::Object::aohos->new($rows, $spec);
207             }
208             }
209              
210             sub select_as_aoaos {
211 24     24 1 7267 my ($self, $cols, $excl_cols, $func_filter_row, $sorts) = @_;
212 24         98 $self->_select('aoaos', $cols, $excl_cols, $func_filter_row, $sorts);
213             }
214              
215             sub select_as_aohos {
216 4     4 1 19 my ($self, $cols, $excl_cols, $func_filter_row, $sorts) = @_;
217 4         19 $self->_select('aohos', $cols, $excl_cols, $func_filter_row, $sorts);
218             }
219              
220 0     0 1 0 sub uniq_col_names { die "Must be implemented by subclass" }
221              
222 0     0 1 0 sub const_col_names { die "Must be implemented by subclass" }
223              
224 4     4 1 1216 sub del_col { die "Must be implemented by subclass" }
225              
226 2     2 1 496 sub rename_col { die "Must be implemented by subclass" }
227              
228 0     0 1   sub switch_cols { die "Must be implemented by subclass" }
229              
230             1;
231             # ABSTRACT: Base class for Data::TableData::Object::*
232              
233             __END__