File Coverage

blib/lib/TableData/Object/Base.pm
Criterion Covered Total %
statement 117 126 92.8
branch 36 42 85.7
condition 13 17 76.4
subroutine 16 20 80.0
pod 13 13 100.0
total 195 218 89.4


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