File Coverage

blib/lib/Data/TableData/Object/Base.pm
Criterion Covered Total %
statement 134 143 93.7
branch 41 48 85.4
condition 13 17 76.4
subroutine 19 23 82.6
pod 15 15 100.0
total 222 246 90.2


line stmt bran cond sub pod time code
1             package Data::TableData::Object::Base;
2              
3 4     4   1740 use 5.010001;
  4         10  
4 4     4   16 use strict;
  4         6  
  4         59  
5 4     4   13 use warnings;
  4         7  
  4         85  
6              
7 4     4   1425 use Scalar::Util::Numeric qw(isint isfloat);
  4         1863  
  4         5303  
8              
9             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
10             our $DATE = '2022-05-19'; # DATE
11             our $DIST = 'Data-TableData-Object'; # DIST
12             our $VERSION = '0.116'; # VERSION
13              
14             sub _array_is_numeric {
15 4     4   10 my $self = shift;
16 4         6 for (@{$_[0]}) {
  4         11  
17 8 100 66     65 return 0 if defined($_) && !isint($_) && !isfloat($_);
      66        
18             }
19 3         11 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 2451 my $self = shift;
29 15         73 $self->{cols_by_name};
30             }
31              
32             sub cols_by_idx {
33 15     15 1 30 my $self = shift;
34 15         57 $self->{cols_by_idx};
35             }
36              
37             sub col_exists {
38 42     42 1 4399 my ($self, $name_or_idx) = @_;
39 42 50       114 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         169 return exists $self->{cols_by_name}{$name_or_idx};
43             }
44             }
45              
46             sub col_name {
47 47     47 1 3534 my ($self, $name_or_idx) = @_;
48 47 100       151 if ($name_or_idx =~ /\A[0-9][1-9]*\z/) {
49 6         24 return $self->{cols_by_idx}[$name_or_idx];
50             } else {
51 41 100       153 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 3706 my ($self, $name_or_idx) = @_;
58 79 100       283 if ($name_or_idx =~ /\A[0-9][1-9]*\z/) {
59 23 100       31 return $name_or_idx < @{ $self->{cols_by_idx} } ? $name_or_idx : undef;
  23         85  
60             } else {
61 56         140 return $self->{cols_by_name}{$name_or_idx};
62             }
63             }
64              
65             sub col_count {
66 14     14 1 20 my $self = shift;
67 14         19 scalar @{ $self->{cols_by_idx} };
  14         39  
68             }
69              
70             sub col_content {
71 26     26 1 3582 my ($self, $name_or_idx) = @_;
72              
73 26         51 my $col_idx = $self->col_idx($name_or_idx);
74 26 100       83 return undef unless defined $col_idx; ## no critic: Subroutines::ProhibitExplicitReturnUndef
75              
76 18         49 my $row_count = $self->row_count;
77 18 50       36 return [] unless $row_count;
78              
79 18         26 my $col_content = [];
80 18         43 for my $i (0 .. $row_count-1) {
81 56         99 my $row = $self->row_as_aos($i);
82 56         89 $col_content->[$i] = $row->[$col_idx];
83             }
84 18         61 $col_content;
85             }
86              
87             sub _select {
88 28     28   56 my ($self, $_as, $cols0, $excl_cols, $func_filter_row, $sorts) = @_;
89              
90             # determine result's columns & spec
91 28         73 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       56 if ($cols0) {
96 24         50 $spec = {fields=>{}};
97 24         34 my $i = 0;
98 24         42 for my $col0 (@$cols0) {
99 35         60 my @add;
100 35 100       66 if ($col0 eq '*') {
101 4         8 @add = @{ $self->{cols_by_idx} };
  4         12  
102             } else {
103 31 100       52 die "Column '$col0' does not exist" unless $self->col_exists($col0);
104 27         59 @add = ($col0);
105             }
106              
107 31         49 for my $add (@add) {
108 35 100 100     82 next if $excl_cols && (grep {$add eq $_} @$excl_cols);
  6         25  
109 32         49 push @cols0, $add;
110 32         36 my $j = 1;
111 32         42 my $col = $add;
112 32         62 while (defined $newcols_to_origcols{$col}) {
113 8         12 $j++;
114 8         24 $col = "${add}_$j";
115             }
116 32         52 $newcols_to_origcols{$col} = $add;
117 32         44 push @newcols, $col;
118              
119             $spec->{fields}{$col} = {
120 32   50     36 %{$self->{spec}{fields}{$add} // {}},
  32         137  
121             pos=>$i,
122             };
123 32         72 $i++;
124             }
125             }
126 20         35 $cols0 = \@cols0;
127             } else {
128             # XXX excl_cols is not being observed
129 4         26 $spec = $self->{spec};
130 4         34 $cols0 = $self->{cols_by_idx};
131 4         16 @newcols = @{ $self->{cols_by_idx} };
  4         12  
132 4         26 for (@newcols) { $newcols_to_origcols{$_} = $_ }
  8         16  
133             }
134              
135 24         44 my $rows = [];
136              
137             # filter rows
138 24         32 for my $row (@{ $self->rows_as_aohos }) {
  24         61  
139 69 100 100     196 next unless !$func_filter_row || $func_filter_row->($self, $row);
140 61         113 push @$rows, $row;
141             }
142              
143             # sort rows
144 20 100 66     74 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         10 for my $sortcol (@$sorts) {
149 4 50       32 my ($reverse, $col) = $sortcol =~ /\A(-?)(.+)/
150             or die "Invalid sort column specification '$sortcol'";
151 4 50       21 next if defined $col_is_numeric{$col};
152 4         10 my $sch = $self->{spec}{fields}{$col}{schema};
153 4 50       14 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         13 my $col_name = $self->col_name($col);
158 4 50       18 defined($col_name) or die "Unknown sort column '$col'";
159             $col_is_numeric{$col} = $self->_array_is_numeric(
160 4         10 [map {$_->{$col_name}} @$rows]);
  9         50  
161             }
162             }
163              
164             $rows = [sort {
165 4         21 for my $sortcol (@$sorts) {
  5         12  
166 5         21 my ($reverse, $col) = $sortcol =~ /\A(-?)(.+)/;
167 5         13 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       32 ($a->{$name} cmp $b->{$name}));
    100          
172 5 50       31 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         30 my $rows2 = [];
  20         29  
181 20         32 for my $row0 (@$rows) {
182 61         64 my $row;
183 61 100       105 if ($_as eq 'aoaos') {
184 48         65 $row = [];
185 48         53 for my $i (0..$#{$cols0}) {
  48         77  
186 91         153 $row->[$i] = $row0->{$cols0->[$i]};
187             }
188             } else {
189 13         26 $row = {};
190 13         25 for my $i (0..$#newcols) {
191             $row->{$newcols[$i]} =
192 32         61 $row0->{$newcols_to_origcols{$newcols[$i]}};
193             }
194             }
195 61         90 push @$rows2, $row;
196             }
197 20         38 $rows = $rows2;
198             }
199              
200             # return result as object
201 20 100       37 if ($_as eq 'aoaos') {
202 16         1758 require Data::TableData::Object::aoaos;
203 16         65 return Data::TableData::Object::aoaos->new($rows, $spec);
204             } else {
205 4         1282 require Data::TableData::Object::aohos;
206 4         30 return Data::TableData::Object::aohos->new($rows, $spec);
207             }
208             }
209              
210             sub select_as_aoaos {
211 24     24 1 5574 my ($self, $cols, $excl_cols, $func_filter_row, $sorts) = @_;
212 24         60 $self->_select('aoaos', $cols, $excl_cols, $func_filter_row, $sorts);
213             }
214              
215             sub select_as_aohos {
216 4     4 1 13 my ($self, $cols, $excl_cols, $func_filter_row, $sorts) = @_;
217 4         13 $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 809 sub del_col { die "Must be implemented by subclass" }
225              
226 2     2 1 297 sub rename_col { die "Must be implemented by subclass" }
227              
228 0     0 1 0 sub switch_cols { die "Must be implemented by subclass" }
229              
230             sub iter {
231 5     5 1 69 my $self = shift;
232 5         9 my $i = 0;
233 5         15 my $count = $self->row_count;
234             sub {
235 17 100   17   53 if ($i < $count) {
236 12         35 $self->row($i++);
237             } else {
238 5         14 undef;
239             }
240 5         24 };
241             }
242              
243             1;
244             # ABSTRACT: Base class for Data::TableData::Object::*
245              
246             __END__