File Coverage

blib/lib/DataCube/Cube.pm
Criterion Covered Total %
statement 28 30 93.3
branch n/a
condition n/a
subroutine 10 10 100.0
pod n/a
total 38 40 95.0


line stmt bran cond sub pod time code
1              
2              
3              
4             package DataCube::Cube;
5              
6 1     1   6 use strict;
  1         3  
  1         41  
7 1     1   5 use warnings;
  1         2  
  1         33  
8 1     1   6 use Storable;
  1         1  
  1         103  
9 1     1   7 use Digest::MD5;
  1         2  
  1         34  
10 1     1   6 use Time::HiRes;
  1         3  
  1         8  
11 1     1   93 use Data::Dumper;
  1         1  
  1         51  
12 1     1   5 use Cwd qw(getcwd);
  1         2  
  1         43  
13              
14 1     1   587 use DataCube::MeasureUpdater;
  1         3  
  1         34  
15              
16 1     1   547 use DataCube::Cube::Style;
  1         1  
  1         21  
17 1     1   649 use DataCube::Cube::Style::HTML;
  0            
  0            
18             use DataCube::Cube::Style::HTML::CSS;
19              
20              
21             sub new {
22             my($class,%opts) = @_;
23             $opts{cube} ||= {};
24             my $self = bless {%opts}, ref($class) || $class;
25             $self->{schema}->initialize if $self->{schema};
26             return $self;
27             }
28              
29             sub is_empty {
30             my($self) = @_;
31             return 0 if $self->is_non_empty;
32             return 1;
33             }
34              
35             sub is_non_empty {
36             my($self) = @_;
37             return 1 if scalar(keys %{$self->{cube}});
38             return 0;
39             }
40              
41             sub merge_with {
42             my($self,$cube) = @_;
43            
44             return $self unless $cube->is_non_empty;
45            
46             my $schema = $self->{schema};
47             my $cube_data = $cube->{cube};
48             my $self_data = $self->{cube};
49            
50             my $updater = DataCube::MeasureUpdater->new($self->{schema});
51            
52             for(keys %{$cube->{cube}}){
53             $updater->update(
54             source => $cube_data,
55             target => $self_data,
56             source_key => $_,
57             target_key => $_,
58             );
59             }
60             return $self;
61             }
62              
63             sub commit {
64             my($self,%opts);
65            
66             commit_opts:{
67             ($self) = @_ and last commit_opts if @_ == 1;
68             ($self,%opts) = @_ and last commit_opts if @_ % 2 && @_ > 2;
69             ($self,$opts{target}) = @_ and last commit_opts;
70             }
71            
72             $opts{target} ||= getcwd;
73             $opts{prefix} ||= 3;
74            
75             my $digest = $self->{schema}->{name_digest};
76             my $target = $opts{target} . "/$digest";
77            
78             my $schema;
79             my $config;
80             my $digests;
81             my $digester = Digest::MD5->new;
82            
83             unless(-d( $target )){
84             mkdir($target)
85             or die "DataCube::Cube(commit):\ncant make directory: $target\n$!\n";
86             $schema = $self->{schema};
87             for(keys %{$self->{cube}}){
88             my $digest = $digester->add($_)->hexdigest;
89             $digests->{$_} = {
90             digest => $digest,
91             prefix => substr($digest,0,$opts{prefix})
92             };
93             }
94             Storable::nstore(\%opts, "$target/.config.working");
95             Storable::nstore($schema, "$target/.schema.working");
96             Storable::nstore($digests,"$target/.digests.working");
97             rename("$target/.config.working", "$target/.config") or die "DataCube::Cube(commit):\ncant rename config file:\n$target/.config.working \nto\n$target/.config \n$!\n";
98             rename("$target/.schema.working", "$target/.schema") or die "DataCube::Cube(commit):\ncant rename config file:\n$target/.schema.working \nto\n$target/.schema \n$!\n";
99             rename("$target/.digests.working", "$target/.digests") or die "DataCube::Cube(commit):\ncant rename config file:\n$target/.digests.working\nto\n$target/.digests\n$!\n";
100             } else {
101             $config = Storable::retrieve("$target/.config");
102             $schema = Storable::retrieve("$target/.schema");
103             $digests = Storable::retrieve("$target/.digests");
104             }
105            
106             my $updater = DataCube::MeasureUpdater->new($schema);
107            
108             my $prefices;
109             my $digests_changed = 0;
110            
111             for(keys %{$self->{cube}}){
112             if($digests->{$_}){
113             my $prefix = $digests->{$_}->{prefix};
114             $prefices->{$prefix}->{$digests->{$_}->{digest}} = $_;
115             } else {
116             my $digest = $digester->add($_)->hexdigest;
117             my $prefix = substr($digest,0,$config->{prefix});
118             $digests->{$_} = {
119             digest => $digest,
120             prefix => $prefix,
121             };
122             $prefices->{$prefix}->{$digest} = $_;
123             $digests_changed = 1;
124             }
125             }
126            
127             if($digests_changed){
128             Storable::nstore($digests,"$target/.digests.working");
129             rename("$target/.digests.working", "$target/.digests")
130             or die "DataCube::Cube(commit):\ncant rename config file:\n$target/.digests.working\nto\n$target/.digests\n$!\n";
131             }
132            
133             for(keys %$prefices){
134             my $cube_hunk;
135             $cube_hunk->{$_} = $self->{cube}->{$_} for values %{$prefices->{$_}};
136             my $target_file = $target . "/$_";
137             if(-f($target_file)){
138             my $existing_hunk = Storable::retrieve($target_file);
139             for(keys %$cube_hunk){
140             $updater->update(
141             source => $cube_hunk,
142             target => $existing_hunk,
143             source_key => $_,
144             target_key => $_,
145             );
146             }
147             Storable::nstore($existing_hunk, $target_file . '.working');
148             rename("$target_file.working", "$target_file")
149             or die "DataCube::Cube(commit):\ncant rename target file:\n$target_file.working\nto\n$target_file\n$!\n";
150             } else {
151             Storable::nstore($cube_hunk, $target_file . '.working');
152             rename("$target_file.working", "$target_file")
153             or die "DataCube::Cube(commit):\ncant rename target file:\n$target_file.working\nto\n$target_file\n$!\n";
154             }
155             }
156             return $self;
157             }
158              
159             sub report {
160             my($self,$dir,%opts);
161             purge_opts:{
162             ($self) = @_ and last purge_opts if @_ == 1;
163             ($self,%opts) = @_ and last purge_opts if @_ % 2 && @_ > 2;
164             ($self,$opts{dir}) = @_ and last purge_opts;
165             }
166             $dir = $opts{dir} || getcwd;
167             local $| = 1;
168             my $name = $self->{schema}->{name};
169             (my $file_name = $name) =~ s/\t+/__/g;
170             $file_name = $self->schema->safe_file_name;
171             my @sorted_measures = @{$self->{schema}->{measures}};
172             my @computed_measures = @{$self->{schema}->{computed_measures}};
173             for(@sorted_measures){ $_->[2] = 'count' if $_->[0] eq 'key_count' }
174             @sorted_measures = sort {$a->[2] cmp $b->[2]} @sorted_measures;
175             open(my $F, '>' , $dir . '/' . $file_name.'.dat')
176             or die "cant open purge file:\n$dir/$file_name.dat\n$!\n";
177             print $F join("\n", map { join("\t",@$_) } @{$self->to_table});
178             close $F;
179             return $self;
180             }
181              
182              
183             sub describe {
184             my($self,$cube) = @_;
185             my $schema = $self->{schema};
186              
187             my $name = $schema->{name};
188             my $meas = $schema->{measures};
189             my $hier = $schema->{hierarchies};
190             my $dige = $schema->{name_digest};
191             my $base = $cube->get_base_cube_name;
192            
193             my(@dims,@hiers);
194             my @meas = @$meas;
195            
196             for(@$hier){
197             if(@$_ == 1){
198             push @dims, $_
199             } else {
200             push @hiers, $_
201             }
202             }
203            
204             @dims = map { $_->[0] } @dims;
205             @hiers = map { join (", ", @$_) } @hiers;
206            
207             for(@meas){
208             if($_->[0] eq 'key_count'){
209             $_->[1] = '';
210             $_->[0] = 'count';
211             }
212             }
213            
214             print "\n table: $dige", $name eq $base ? " (base table)\n" : "\n";
215             print '-' x 80 ,"\n\n";
216            
217             for(sort { length($a->[0]) <=> length($b->[0]) || $a->[0] cmp $b->[0] || length($a->[1]) <=> length($b->[1])} @meas){
218             my $additive = 'additive';
219             my $measure_string = '';
220             if($_->[0] eq 'count' && $_->[1] eq ''){
221             $measure_string = 'count of occurances';
222             } elsif($_->[0] eq 'average') {
223             $measure_string = "average $_->[1]";
224             $additive = 'non-' . $additive;
225             } else {
226             if(defined($_->[1]) && length($_->[1]) && ($_->[0] eq 'count' || $_->[0] eq 'multi_count')){
227             $additive = 'non-' . $additive;
228             }
229             $measure_string = "$_->[0] of $_->[1]";
230             }
231             printf "\tmeasure: %-30s (%s)\n",$measure_string,$additive;
232             }
233             print "\n";
234            
235             for(sort { length($a) <=> length($b) || $a cmp $b } @dims){
236             print "\tdimension: $_\n";
237             }
238             print "\n" if @dims;
239            
240             for(sort { length($a) <=> length($b) || $a cmp $b } @hiers){
241             print "\thierarchy: $_\n";
242             }
243             print "\n" if @hiers;
244            
245             my $print_name = $schema->{lattice_point_name} || $name;
246             $print_name =~ s/\t/__/g;
247             printf "\treport: %-50s\n\n",$print_name;
248             return $self;
249             }
250              
251             sub report_html {
252             my($self,$dir,%opts);
253             purge_opts:{
254             ($self) = @_ and last purge_opts if @_ == 1;
255             ($self,%opts) = @_ and last purge_opts if @_ % 2 && @_ > 2;
256             ($self,$opts{dir}) = @_ and last purge_opts;
257             }
258             $dir = $opts{dir} || getcwd;
259             local $| = 1;
260             my $file_name = $self->schema->safe_file_name;
261             open(my $F, '>' , $dir . '/' . $file_name.'.html')
262             or die "cant open purge file:\n$dir/$file_name.html\n$!\n";
263             my $driver = DataCube::Cube::Style::HTML->new;
264             print $F $driver->html($self);
265             close $F;
266             return $self;
267             }
268              
269             sub to_table {
270             my($self) = @_;
271             my $table = [];
272             my $schema = $self->schema;
273            
274             my $name = $self->{schema}->{name};
275             (my $alias = $name) =~ s/\t+/__/g;
276             $alias = $self->{schema}->{lattice_point_name} if defined($self->{schema}->{lattice_point_name});
277             my @sorted_measures = $schema->measures;
278             my @computed_measures = @{$self->{schema}->{computed_measures}};
279             for(@sorted_measures){
280             $_->[2] = 'count' if $_->[0] eq 'key_count'
281             }
282             @sorted_measures = sort {$a->[2] cmp $b->[2]} @sorted_measures;
283            
284             my $data = $self->{cube};
285             my @keys = sort keys %$data;
286             my @fields = $schema->field_names;
287            
288             push @$table, [@fields, map { $_->[2] } @sorted_measures];
289            
290             for(my $i = 0; $i < @keys; ++$i){
291            
292             my $k = $i + 1;
293             my $key = $keys[$i];
294             my @key = split/\t/,$key,-1;
295            
296             @key = ('') if ! @key && $self->schema->field_count == 1;
297            
298             die "DataCube::Cube(to_table | alignment):\nthere is a data alignment problem here:\n\n\t" .
299             join("\t\n",@fields) . "\n" . '-' x 40 . "\n\t" . join("\t\n",@key)
300             unless $#key == $#fields;
301            
302             for(my $j = 0; $j < @fields; ++$j){
303             $table->[$k][$j] = $key[$j];
304             }
305            
306             measure_loop:
307             for(my $n = 0; $n < @sorted_measures; ++$n){
308            
309             my $j = $n + @fields;
310            
311             my $node = $sorted_measures[$n];
312            
313             measure_collect: {
314             if($node->[0] eq 'key_count'){
315             $table->[$k][$j] = $self->{cube}->{$key}->{key_count};
316             last measure_collect;
317             }
318             if($node->[0] eq 'count'){
319             $table->[$k][$j] = scalar(keys %{$self->{cube}->{$key}->{count}->{$node->[1]}});
320             last measure_collect;
321             }
322             if($node->[0] eq 'multi_count'){
323             $table->[$k][$j] = scalar(keys %{$self->{cube}->{$key}->{multi_count}->{$node->[1]}});
324             last measure_collect;
325             }
326             if($node->[0] eq 'sum'){
327             $table->[$k][$j] = $self->{cube}->{$key}->{sum}->{$node->[1]};
328             last measure_collect;
329             }
330             if($node->[0] eq 'min'){
331             $table->[$k][$j] = $self->{cube}->{$key}->{min}->{$node->[1]};
332             last measure_collect;
333             }
334             if($node->[0] eq 'max'){
335             $table->[$k][$j] = $self->{cube}->{$key}->{max}->{$node->[1]};
336             last measure_collect;
337             }
338             if($node->[0] eq 'average'){
339             $table->[$k][$j] = $self->{cube}->{$key}->{average}->{$node->[1]}->{sum_total} / $self->{cube}->{$key}->{average}->{$node->[1]}->{observations};
340             last measure_collect;
341             }
342             }
343             }
344             }
345             return wantarray ? @$table : $table;
346             }
347              
348             sub tsv_data {
349             my( $self ) = @_;
350             my @measures = $self->schema->measures;
351             for(@measures){ $_->[2] = 'count' if $_->[0] eq 'key_count' }
352             @measures = sort { $a->[2] cmp $b->[2] } @measures;
353             my $data = $self->{cube};
354             my @keys = keys %$data;
355             my @results;
356             for my $key( @keys ){
357             my @measure_values = $self->get_measure_values( $key, \@measures );
358             push @results, join("\t", $key, join("\t", @measure_values));
359             }
360             return @results;
361             }
362              
363             sub get_measure_values {
364             my( $self, $key, $measures ) = @_;
365             my @measures = @$measures;
366             my @results;
367             for( @measures ){
368             if($_->[0] eq 'key_count'){
369             push @results, $self->{cube}->{$key}->{key_count};
370             next;
371             }
372             if($_->[0] eq 'count'){
373             push @results, scalar(keys %{$self->{cube}->{$key}->{count}->{$_->[1]}});
374             next;
375             }
376             if($_->[0] eq 'multi_count'){
377             push @results, scalar(keys %{$self->{cube}->{$key}->{multi_count}->{$_->[1]}});
378             next;
379             }
380             if($_->[0] eq 'sum'){
381             push @results, $self->{cube}->{$key}->{sum}->{$_->[1]};
382             next;
383             }
384             if($_->[0] eq 'min'){
385             push @results, $self->{cube}->{$key}->{min}->{$_->[1]};
386             next;
387             }
388             if($_->[0] eq 'max'){
389             push @results, $self->{cube}->{$key}->{max}->{$_->[1]};
390             next;
391             }
392             if($_->[0] eq 'average'){
393             push @results, $self->{cube}->{$key}->{average}->{$_->[1]}->{sum_total} /
394             $self->{cube}->{$key}->{average}->{$_->[1]}->{observations};
395             next;
396             }
397             }
398             return @results;
399             }
400              
401             sub cube {
402             my($self) = @_;
403             return $self->{cube}
404             }
405              
406             sub data {
407             my($self) = @_;
408             return $self->{cube}
409             }
410              
411             sub name {
412             my($self) = @_;
413             return $self->{schema}->{name};
414             }
415              
416             sub schema {
417             my($self) = @_;
418             return $self->{schema};
419             }
420              
421             sub reset {
422             my($self) = @_;
423             $self->{cube} = {};
424             return $self;
425             }
426              
427             sub has_field {
428             my($self,$field) = @_;
429             my $fields = $self->schema->field_names;
430             return 1 if exists $fields->{$field};
431             return 0;
432             }
433              
434             sub is_the_base_table {
435             my($self) = @_;
436             return 1 if $self->{is_the_base_table};
437             return 0;
438             }
439              
440              
441              
442              
443             1;
444              
445              
446              
447              
448              
449             __DATA__