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