File Coverage

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