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   2336 use 5.010001;
  4         15  
4 4     4   22 use strict;
  4         7  
  4         87  
5 4     4   21 use warnings;
  4         8  
  4         128  
6              
7 4     4   1946 use Scalar::Util::Numeric qw(isint isfloat);
  4         2596  
  4         6919  
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.115'; # VERSION
13              
14             sub _array_is_numeric {
15 4     4   13 my $self = shift;
16 4         10 for (@{$_[0]}) {
  4         13  
17 8 100 66     72 return 0 if defined($_) && !isint($_) && !isfloat($_);
      66        
18             }
19 3         13 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 3007 my $self = shift;
29 15         103 $self->{cols_by_name};
30             }
31              
32             sub cols_by_idx {
33 15     15 1 34 my $self = shift;
34 15         75 $self->{cols_by_idx};
35             }
36              
37             sub col_exists {
38 42     42 1 7400 my ($self, $name_or_idx) = @_;
39 42 50       153 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         241 return exists $self->{cols_by_name}{$name_or_idx};
43             }
44             }
45              
46             sub col_name {
47 47     47 1 4529 my ($self, $name_or_idx) = @_;
48 47 100       207 if ($name_or_idx =~ /\A[0-9][1-9]*\z/) {
49 6         37 return $self->{cols_by_idx}[$name_or_idx];
50             } else {
51 41 100       214 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 4579 my ($self, $name_or_idx) = @_;
58 79 100       378 if ($name_or_idx =~ /\A[0-9][1-9]*\z/) {
59 23 100       44 return $name_or_idx < @{ $self->{cols_by_idx} } ? $name_or_idx : undef;
  23         100  
60             } else {
61 56         195 return $self->{cols_by_name}{$name_or_idx};
62             }
63             }
64              
65             sub col_count {
66 14     14 1 28 my $self = shift;
67 14         24 scalar @{ $self->{cols_by_idx} };
  14         68  
68             }
69              
70             sub col_content {
71 26     26 1 4505 my ($self, $name_or_idx) = @_;
72              
73 26         65 my $col_idx = $self->col_idx($name_or_idx);
74 26 100       95 return undef unless defined $col_idx; ## no critic: Subroutines::ProhibitExplicitReturnUndef
75              
76 18         59 my $row_count = $self->row_count;
77 18 50       44 return [] unless $row_count;
78              
79 18         33 my $col_content = [];
80 18         52 for my $i (0 .. $row_count-1) {
81 56         121 my $row = $self->row_as_aos($i);
82 56         122 $col_content->[$i] = $row->[$col_idx];
83             }
84 18         86 $col_content;
85             }
86              
87             sub _select {
88 28     28   74 my ($self, $_as, $cols0, $excl_cols, $func_filter_row, $sorts) = @_;
89              
90             # determine result's columns & spec
91 28         100 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       73 if ($cols0) {
96 24         69 $spec = {fields=>{}};
97 24         49 my $i = 0;
98 24         56 for my $col0 (@$cols0) {
99 35         129 my @add;
100 35 100       92 if ($col0 eq '*') {
101 4         8 @add = @{ $self->{cols_by_idx} };
  4         16  
102             } else {
103 31 100       80 die "Column '$col0' does not exist" unless $self->col_exists($col0);
104 27         66 @add = ($col0);
105             }
106              
107 31         58 for my $add (@add) {
108 35 100 100     100 next if $excl_cols && (grep {$add eq $_} @$excl_cols);
  6         30  
109 32         60 push @cols0, $add;
110 32         50 my $j = 1;
111 32         77 my $col = $add;
112 32         81 while (defined $newcols_to_origcols{$col}) {
113 8         16 $j++;
114 8         30 $col = "${add}_$j";
115             }
116 32         63 $newcols_to_origcols{$col} = $add;
117 32         51 push @newcols, $col;
118              
119             $spec->{fields}{$col} = {
120 32   50     46 %{$self->{spec}{fields}{$add} // {}},
  32         231  
121             pos=>$i,
122             };
123 32         99 $i++;
124             }
125             }
126 20         45 $cols0 = \@cols0;
127             } else {
128             # XXX excl_cols is not being observed
129 4         41 $spec = $self->{spec};
130 4         45 $cols0 = $self->{cols_by_idx};
131 4         12 @newcols = @{ $self->{cols_by_idx} };
  4         17  
132 4         33 for (@newcols) { $newcols_to_origcols{$_} = $_ }
  8         26  
133             }
134              
135 24         67 my $rows = [];
136              
137             # filter rows
138 24         55 for my $row (@{ $self->rows_as_aohos }) {
  24         139  
139 69 100 100     256 next unless !$func_filter_row || $func_filter_row->($self, $row);
140 61         166 push @$rows, $row;
141             }
142              
143             # sort rows
144 20 100 66     75 if ($sorts && @$sorts) {
145             # determine whether each column mentioned in $sorts is numeric, to
146             # decide whether to use <=> or cmp.
147 4         10 my %col_is_numeric;
148 4         14 for my $sortcol (@$sorts) {
149 4 50       41 my ($reverse, $col) = $sortcol =~ /\A(-?)(.+)/
150             or die "Invalid sort column specification '$sortcol'";
151 4 50       17 next if defined $col_is_numeric{$col};
152 4         13 my $sch = $self->{spec}{fields}{$col}{schema};
153 4 50       16 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         17 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         13 [map {$_->{$col_name}} @$rows]);
  9         76  
161             }
162             }
163              
164             $rows = [sort {
165 4         26 for my $sortcol (@$sorts) {
  5         16  
166 5         27 my ($reverse, $col) = $sortcol =~ /\A(-?)(.+)/;
167 5         17 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       28 ($a->{$name} cmp $b->{$name}));
    100          
172 5 50       55 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         40 my $rows2 = [];
  20         38  
181 20         69 for my $row0 (@$rows) {
182 61         92 my $row;
183 61 100       128 if ($_as eq 'aoaos') {
184 48         82 $row = [];
185 48         68 for my $i (0..$#{$cols0}) {
  48         105  
186 91         202 $row->[$i] = $row0->{$cols0->[$i]};
187             }
188             } else {
189 13         24 $row = {};
190 13         37 for my $i (0..$#newcols) {
191             $row->{$newcols[$i]} =
192 32         77 $row0->{$newcols_to_origcols{$newcols[$i]}};
193             }
194             }
195 61         116 push @$rows2, $row;
196             }
197 20         53 $rows = $rows2;
198             }
199              
200             # return result as object
201 20 100       49 if ($_as eq 'aoaos') {
202 16         2537 require Data::TableData::Object::aoaos;
203 16         96 return Data::TableData::Object::aoaos->new($rows, $spec);
204             } else {
205 4         1903 require Data::TableData::Object::aohos;
206 4         40 return Data::TableData::Object::aohos->new($rows, $spec);
207             }
208             }
209              
210             sub select_as_aoaos {
211 24     24 1 7574 my ($self, $cols, $excl_cols, $func_filter_row, $sorts) = @_;
212 24         88 $self->_select('aoaos', $cols, $excl_cols, $func_filter_row, $sorts);
213             }
214              
215             sub select_as_aohos {
216 4     4 1 16 my ($self, $cols, $excl_cols, $func_filter_row, $sorts) = @_;
217 4         15 $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 1483 sub del_col { die "Must be implemented by subclass" }
225              
226 2     2 1 520 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__