File Coverage

blib/lib/DataCube.pm
Criterion Covered Total %
statement 37 39 94.8
branch n/a
condition n/a
subroutine 13 13 100.0
pod n/a
total 50 52 96.1


line stmt bran cond sub pod time code
1              
2              
3              
4             package DataCube;
5              
6 1     1   1262 use 5.008008;
  1         5  
  1         63  
7             our $VERSION = '0.01';
8              
9 1     1   7 use strict;
  1         2  
  1         43  
10 1     1   30 use warnings;
  1         3  
  1         40  
11              
12 1     1   5 use Fcntl;
  1         2  
  1         424  
13 1     1   1022 use URI::file;
  1         14386  
  1         41  
14 1     1   13 use Digest::MD5;
  1         3  
  1         50  
15 1     1   8 use Time::HiRes;
  1         2  
  1         11  
16 1     1   128 use Data::Dumper;
  1         2  
  1         64  
17 1     1   5 use Cwd qw(getcwd);
  1         3  
  1         54  
18 1     1   1429 use Storable qw(nstore);
  1         3959  
  1         105  
19 1     1   10 use Scalar::Util qw(reftype);
  1         3  
  1         94  
20              
21             use constant {
22 1         104 read_only => O_RDONLY,
23             write_only => O_WRONLY,
24             create_write => O_CREAT|O_WRONLY,
25             vip_read_write => O_CREAT|O_WRONLY|O_EXCL,
26 1     1   7 };
  1         1  
27              
28 1     1   859 use DataCube::Cube;
  0            
  0            
29             use DataCube::Schema;
30             use DataCube::FileUtils;
31             use DataCube::CubeStore;
32             use DataCube::Controller;
33             use DataCube::PathWalker;
34             use DataCube::Connection;
35             use DataCube::FileUtils::FileReader;
36              
37             sub new {
38             my($class,@opts,%opts) = @_;
39             datacube_opts_collection: {
40             %opts = @opts and last datacube_opts_collection if @_ > 2 && @_ % 2;
41             $opts{schema} = $opts[0] and last datacube_opts_collection if @_ == 2;
42             }
43             my $self = bless {}, ref($class) || $class;
44             return $self->retrieve($opts[0]) if defined($opts[0]) && -f($opts[0]) && @opts == 1;
45             return $self unless @opts;
46             $self->{cube_store} = DataCube::CubeStore->new;
47             schema_initialization: {
48             last schema_initialization unless
49             $opts{schema}
50             && ref($opts{schema})
51             && ref($opts{schema}) =~ /^datacube::schema$/i;
52             die "DataCube(new : schema init):\nyour schema does not contain any measures\n$!\n"
53             unless $opts{schema}->{measures};
54             $opts{schema}->initialize;
55             $self->build_lattice_tables (
56             schema => $opts{schema},
57             );
58             }
59             $self->{updater} = DataCube::MeasureUpdater->new($opts{schema});
60             my $base_cube_name = $self->{controller}->{cube_stats}->{base_cube_name};
61             $self->{meta_data}->{system}->{base_cube_name} = $base_cube_name;
62             $self->{meta_data}->{system}->{base_schema} = $opts{schema};
63             $self->{base_cube} = $self->{cube_store}->{cubes}->{$base_cube_name};
64             return $self;
65             }
66              
67             # change to modify:
68             # ------------------------------------------------------------------------------
69             # DataCube::insert
70             # DataCube::reset_measures
71             # DataCube::MeasureUpdater::update
72             # DataCube::Cube::describe
73             # DataCube::Cube::to_table
74             # Warehouse::Builder::*
75             # ------------------------------------------------------------------------------
76              
77             sub insert {
78             my($self,$data) = @_;
79             my @keys;
80             push @keys, $data->{$_} for @{$self->{base_cube}->{schema}->{fields}};
81             my $key = join("\t",@keys);
82             measure_update:
83             for(@{$self->{base_cube}->{schema}->{measures}}){
84            
85             if($_->[0] eq 'key_count'){
86             ++$self->{base_cube}->{cube}->{$key}->{key_count};
87             next measure_update;
88             }
89            
90             my $data_key = $data->{$_->[1]};
91            
92             if($_->[0] eq 'count'){
93             $self->{base_cube}->{cube}->{$key}->{count}->{$_->[1]}->{$data_key} = undef;
94             next measure_update;
95             }
96            
97             if($_->[0] eq 'multi_count'){
98             ++$self->{base_cube}->{cube}->{$key}->{multi_count}->{$_->[1]}->{$data_key};
99             next measure_update;
100             }
101            
102             if($_->[0] eq 'sum'){
103             $self->{base_cube}->{cube}->{$key}->{sum}->{$_->[1]} += $data_key;
104             next measure_update;
105             }
106            
107             if($_->[0] eq 'average'){
108             $self->{base_cube}->{cube}->{$key}->{average}->{$_->[1]}->{sum_total} += $data_key;
109             ++$self->{base_cube}->{cube}->{$key}->{average}->{$_->[1]}->{observations};
110             next measure_update;
111             }
112            
113             if($_->[0] eq 'max'){
114             $self->{base_cube}->{cube}->{$key}->{max}->{$_->[1]} = $data_key unless defined $self->{base_cube}->{cube}->{$key}->{max}->{$_->[1]};
115             $self->{base_cube}->{cube}->{$key}->{max}->{$_->[1]} = $data_key if $data_key > $self->{base_cube}->{cube}->{$key}->{max}->{$_->[1]};
116             next measure_update;
117             }
118            
119             if($_->[0] eq 'min'){
120             $self->{base_cube}->{cube}->{$key}->{min}->{$_->[1]} = $data_key unless defined $self->{base_cube}->{cube}->{$key}->{min}->{$_->[1]};
121             $self->{base_cube}->{cube}->{$key}->{min}->{$_->[1]} = $data_key if $data_key < $self->{base_cube}->{cube}->{$key}->{min}->{$_->[1]};
122             next measure_update;
123             }
124            
125             if($_->[0] eq 'product'){
126             $self->{base_cube}->{cube}->{$key}->{product}->{$_->[1]} = 1 unless defined $self->{base_cube}->{cube}->{$key}->{product}->{$_->[1]};
127             $self->{base_cube}->{cube}->{$key}->{product}->{$_->[1]} *= $data_key;
128             next measure_update;
129             }
130            
131             }
132             return $key;
133             }
134              
135             sub store {
136             my($self,$path) = @_;
137             Storable::nstore($self,$path);
138             return $self;
139             }
140              
141             sub retrieve {
142             my($self,$path) = @_;
143             return $_[0] = Storable::retrieve($path);
144             }
145              
146             sub clone {
147             my($self,$clone) = @_;
148             return Storable::thaw(Storable::freeze($self));
149             }
150              
151             sub get_measures {
152             my($self,$data) = @_;
153             my @keys;
154             my @values;
155             for(@{$self->{base_cube}->{schema}->{fields}}){
156             if(defined $data->{$_}){
157             push @keys, $_;
158             push @values, $data->{$_};
159             }
160             }
161             return unless @keys;
162             my $table = join("\t",@keys);
163             my $datakey = join("\t",@values);
164             return $self->cube_store->fetch($table)->data->{$datakey};
165             }
166              
167             sub get_measures_by_id {
168             my($self,$key) = @_;
169             return $self->{base_cube}->{cube}->{$key};
170             }
171              
172             sub query_measures {
173             my($self,@table) = @_;
174            
175             push @table, sub{1}
176             unless @table
177             && ref($table[$#table])
178             && ref($table[$#table]) =~ /^code$/i;
179            
180             my $func = pop(@table);
181             my $name = join("\t", sort @table);
182            
183             my $table = $self->cube_store->fetch($name);
184            
185             return unless $table;
186            
187             my $data = $table->{cube};
188            
189             my @results;
190            
191             my @fields = $table->schema->fields;
192            
193             for(keys %$data){
194             my $key = $_;
195             my @key = split/\t/,$key,-1;
196             my %key = map { $fields[$_] => $key[$_] } (0 .. $#fields);
197             my $record = { datakey => \%key, measures => $data->{$_} };
198             push @results, $record if $func->($record);
199             }
200            
201             return @results;
202             }
203              
204              
205             sub delete {
206             my($self,$data) = @_;
207            
208             die "DataCube(delete):\n" .
209             "cannot delete rows in a cube that has been rolled up\n$!\n"
210             if $self->has_been_rolled_up;
211              
212             my @values;
213             for(@{$self->{base_cube}->{schema}->{fields}}){
214            
215             die "DataCube(delete):\n" .
216             "please pass a hash reference with defined entries in the base table:\n"
217             unless defined $data->{$_};
218            
219             push @values, $data->{$_};
220             }
221            
222             my $datakey = join("\t",@values);
223             delete $self->{base_cube}->{cube}->{$datakey};
224             return $self;
225             }
226              
227             sub reset {
228             my($self) = @_;
229             for(keys %{$self->cube_store->cubes}){
230             my $cube = $self->cube_store->fetch($_);
231             $cube->reset;
232             }
233             $self->{meta_data}->{system}->{has_been_rolled_up} = 0;
234             return $self;
235             }
236              
237             sub unroll {
238             my($self) = @_;
239             for(keys %{$self->cube_store->cubes}){
240             my $cube = $self->cube_store->fetch($_);
241             $cube->reset unless $cube->schema->name eq $self->base_cube_name;
242             }
243             delete $self->{meta_data}->{system}->{has_been_rolled_up};
244             return $self;
245             }
246              
247              
248             sub reset_measures {
249             my($self,$data) = @_;
250            
251             die "DataCube(reset_measures):\n" .
252             "cannot reset measures in a cube that has been rolled up\n$!\n"
253             if $self->has_been_rolled_up;
254              
255             my @values;
256             for(@{$self->{base_cube}->{schema}->{fields}}){
257            
258             die "DataCube(reset_measures):\n" .
259             "please pass a hash reference with defined entries in the base table:\n"
260             unless defined $data->{$_};
261            
262             push @values, $data->{$_};
263             }
264             my $table = $self->base_table;
265             my $key = join("\t",@values);
266            
267             measure_reset:
268             for(@{$self->{base_cube}->{schema}->{measures}}){
269             if($_->[0] eq 'key_count'){
270             $self->{base_cube}->{cube}->{$key}->{key_count} = 0;
271             next measure_reset;
272             }
273             if($_->[0] eq 'count'){
274             $self->{base_cube}->{cube}->{$key}->{count}->{$_->[1]} = {};
275             next measure_reset;
276             }
277             if($_->[0] eq 'multi_count'){
278             $self->{base_cube}->{cube}->{$key}->{multi_count}->{$_->[1]} = {};
279             next measure_reset;
280             }
281             if($_->[0] eq 'sum'){
282             $self->{base_cube}->{cube}->{$key}->{sum}->{$_->[1]} = 0;
283             next measure_reset;
284             }
285             if($_->[0] eq 'product'){
286             $self->{base_cube}->{cube}->{$key}->{product}->{$_->[1]} = 1;
287             next measure_reset;
288             }
289             if($_->[0] eq 'max'){
290             $self->{base_cube}->{cube}->{$key}->{max}->{$_->[1]} = undef;
291             next measure_reset;
292             }
293             if($_->[0] eq 'min'){
294             $self->{base_cube}->{cube}->{$key}->{min}->{$_->[1]} = undef;
295             next measure_reset;
296             }
297             if($_->[0] eq 'average'){
298             $self->{base_cube}->{cube}->{$key}->{average}->{$_->[1]}->{sum_total} = 0;
299             $self->{base_cube}->{cube}->{$key}->{average}->{$_->[1]}->{observations} = 0;
300             next measure_reset;
301             }
302            
303             }
304             return $self;
305             }
306              
307              
308             sub decrement_key_count {
309             my($self,$data) = @_;
310            
311             die "DataCube(decrement_key_count):\n" .
312             "cannot decrement_key_count in a cube that has been rolled up\n$!\n"
313             if $self->has_been_rolled_up;
314            
315             my $key = join("\t", map { $data->{$_} } @{$self->{base_cube}->{schema}->{fields}} );
316             --$self->{base_cube}->{cube}->{$key}->{key_count};
317             return $self;
318             }
319              
320             sub decrement_multi_count {
321             my($self,$field,$data) = @_;
322            
323             die "DataCube(decrement_multi_count):\n" .
324             "cannot decrement_multi_count in a cube that has been rolled up\n$!\n"
325             if $self->has_been_rolled_up;
326            
327             my $key = join("\t", map { $data->{$_} } @{$self->{base_cube}->{schema}->{fields}} );
328             --$self->{base_cube}->{cube}->{$key}->{multi_count}->{$field}->{$data->{$field}};
329             return $self;
330             }
331              
332             sub drop_count {
333             my($self,$field,$data) = @_;
334            
335             die "DataCube(drop_count):\n" .
336             "cannot drop_count in a cube that has been rolled up\n$!\n"
337             if $self->has_been_rolled_up;
338            
339             my $key = join("\t", map { $data->{$_} } @{$self->{base_cube}->{schema}->{fields}} );
340             delete $self->{base_cube}->{cube}->{$key}->{count}->{$field}->{$data->{$field}};
341             return $self;
342             }
343              
344             sub add_meta_data {
345             my($self,%meta_data) = @_;
346             $self->{meta_data}->{user_generated}->{$_} = $meta_data{$_} for keys %meta_data;
347             return $self;
348             }
349              
350              
351             sub load_data_infile {
352             my($self,$file,$line) = @_;
353             my $reader = DataCube::FileUtils::FileReader->new->read($file);
354             $self->insert($line) while $line = $reader->nextrow_hashref;
355             return $self;
356             }
357              
358             sub report_html {
359             my($self,$dir,$target) = @_;
360             return $self->report_commited_html($dir,$target) if $dir && $target && -d($dir) && -d($target);
361             mkdir($dir);
362             for(keys %{$self->cube_store->cubes}){
363             my $cube = $self->cube_store->fetch($_);
364             $cube->report_html($dir);
365             }
366             return $self;
367             }
368              
369             sub report {
370             my($self,$dir,$target) = @_;
371             return $self->report_commited($dir,$target) if $dir && $target && -d($dir) && -d($target);
372             mkdir($dir);
373             for(keys %{$self->cube_store->cubes}){
374             my $cube = $self->cube_store->fetch($_);
375             $cube->report($dir);
376             }
377             return $self;
378             }
379              
380             sub report_commited {
381             my($self,$source,$target) = @_;
382             mkdir($target);
383             my $connection = DataCube::Connection->new($source);
384             $connection->report($target);
385             return $self;
386             }
387              
388             sub report_html_commited {
389             my($self,$source,$target) = @_;
390             mkdir($target);
391             my $connection = DataCube::Connection->new($source);
392             $connection->report_html($target);
393             return $self;
394             }
395              
396             sub sync {
397             my($self,$target) = @_;
398             my $connection = DataCube::Connection->new($target);
399             $connection->sync;
400             return $self;
401             }
402              
403             sub lazy_rollup {
404             my($self,%opts) = @_;
405            
406             die "DataCube(lazy_rollup):\ncant lazy_rollup on a cube that has already been rolled up\n$!\n"
407             if $self->has_been_rolled_up;
408            
409             $self->initialize_lazy_rollup unless $self->can_lazy_rollup;
410             if($self->{lazy_rollup_list} && ! @{$self->{lazy_rollup_list}}){
411             delete $self->{lazy_rollup_list};
412             return;
413             }
414             if($self->{lazy_rollup_list}->[0] eq $self->base_cube_name) {
415             shift @{$self->{lazy_rollup_list}};
416             return $self->base_cube;
417             }
418             my $source = [split/\t/,$self->{meta_data}->{system}->{base_cube_name},-1];
419             my $target = [split/\t/,shift(@{$self->{lazy_rollup_list}}),-1];
420             return $self->rollup_from(
421             parent => $source,
422             child => $target,
423             );
424             }
425              
426             sub initialize_lazy_rollup {
427             my($self) = @_;
428             my @pending = sort {$a <=> $b} keys %{$self->{controller}->{cube_stats}->{field_count}};
429             for(@pending){
430             push @{$self->{lazy_rollup_list}},
431             sort @{$self->{controller}->{cube_stats}->{field_count}->{$_}};
432             }
433             return $self;
434             }
435              
436             sub can_lazy_rollup {
437             my($self) = @_;
438             return $self->{lazy_rollup_list};
439             }
440              
441             sub how_many_active_cubes {
442             my($self) = @_;
443             my $total = 0;
444             for(keys %{$self->{cube_store}->cubes}){
445             $total++ if scalar(keys(%{$self->{cube_store}->{cubes}->{$_}->{cube}}));
446             }
447             return $total;
448             }
449              
450             sub rollup_from {
451             my($self,%opts) = @_;
452            
453             $opts{child} = [split/\t/,$opts{child} ,-1] unless ref($opts{child});
454             $opts{parent} = [split/\t/,$opts{parent},-1] unless ref($opts{parent});
455            
456             my @parent = sort @{$opts{parent}};
457             my @child = sort @{$opts{child}};
458            
459             my %child = map { $_ => undef } @child;
460            
461             my $child_cube = $self->{cube_store}->{cubes}->{join("\t",@child)};
462             my $parent_cube = $self->{cube_store}->{cubes}->{join("\t",@parent)};
463            
464             die "DataCube(rollup_from):\ncould not locate child cube:@child\n"
465             unless $child_cube && $child_cube->{schema};
466            
467             my @index_map = ();
468             for(my $i = 0; $i < @parent; $i++){
469             push @index_map, $i if exists $child{$parent[$i]};
470             }
471            
472             die "DataCube(rollup_from):\nfound no compatible index mapping\n@parent\n@child\n"
473             unless(@index_map || ($#child == 0 && $child[0] eq 'overall'));
474            
475             my $cube = $child_cube->new(schema => $child_cube->{schema});
476             my $updater = DataCube::MeasureUpdater->new( $cube->{schema} );
477            
478             my $target_data = $cube->{cube};
479             my $source_data = $parent_cube->{cube};
480            
481            
482             for(keys %$source_data){
483            
484             my $old_key = $_;
485             my @old_key = split/\t/,$old_key,-1;
486            
487             local $SIG{__WARN__} = sub {
488             die "DataCube(rollup_from | warnings):\n".
489             "caught a fatal exception here:\n$_[0]\n" .
490             '-' x 80 . "\n" .
491             join("\n",
492             "old_key: $old_key",
493             'index_map: ' . join(", ",@index_map) ,
494             "parent_cube: $parent_cube",
495             "child_cube: $child_cube"),"\n";
496             };
497            
498             my $new_key = join("\t",@old_key[@index_map]);
499            
500            
501             $updater->update (
502             target => $target_data,
503             source => $source_data,
504             source_key => $old_key,
505             target_key => $new_key,
506             );
507             }
508             return $cube;
509             }
510              
511             sub rollup {
512             my($self,%opts) = @_;
513            
514             die "DataCube(rollup):\ncant rollup twice!\n"
515             if $self->has_been_rolled_up;
516            
517             my @measures = @{$self->{meta_data}->{system}->{base_schema}->{measures}};
518             my @pending_levels = sort { $b <=> $a } keys %{$self->{controller}->{cube_stats}->{field_count}};
519              
520             splice(@pending_levels, 0 ,1);
521            
522             for(@pending_levels){
523             my $level = $_;
524             my @next_cubes = @{$self->{controller}->{cube_stats}->{field_count}->{$level}};
525              
526             for(@next_cubes){
527             my $next_cube_name = $_;
528            
529             my $possible_parents = $self->{controller}->{cube_stats}->{possible_parents}->{$next_cube_name};
530            
531             unless($possible_parents && ref($possible_parents) =~ /^array$/i){
532             die "DataCube(rollup : possible_parents):\n" .
533             "cant find possible parent list for:\nnext_cube_name:\n$next_cube_name" .
534             "\nat level:\n$level\n";
535             }
536            
537             my @possible_parents = @$possible_parents;
538            
539             my $best_parent_name = $possible_parents[0];
540             my $best_size = scalar(keys %{$self->{cube_store}->{cubes}->{$possible_parents[0]}->{cube}});
541            
542             for(my $i = 1; $i < @possible_parents; $i++){
543             my $new_size = scalar(keys %{$self->{cube_store}->{cubes}->{$possible_parents[$i]}->{cube}});
544             ($best_parent_name, $best_size) = ($possible_parents[$i], $new_size) if $new_size < $best_size;
545             }
546            
547             my $next_cube = $self->{cube_store}->fetch($next_cube_name);
548             my $best_parent = $self->{cube_store}->fetch($best_parent_name);
549            
550             my @best_parent_index_map = ();
551            
552             if($next_cube_name ne 'overall'){
553             my @parent_fields = split/\t/,$best_parent_name;
554             my $child_fields = $next_cube->{schema}->{field_names};
555             index_map_collection:
556             for(my $i = 0; $i < @parent_fields; $i++){
557             next index_map_collection unless exists $child_fields->{$parent_fields[$i]};
558             push @best_parent_index_map, $i;
559             }
560             }
561            
562             my $updater = DataCube::MeasureUpdater->new( $next_cube->{schema} );
563            
564             my $target_data = $next_cube->{cube};
565             my $source_data = $best_parent->{cube};
566            
567             for(keys %$source_data){
568            
569             my $old_key = $_;
570             my @old_key = split/\t/,$old_key,-1;
571             my $new_key = join("\t",@old_key[@best_parent_index_map]);
572            
573             $updater->update (
574             target => $target_data,
575             source => $source_data,
576             source_key => $old_key,
577             target_key => $new_key,
578             );
579             }
580             }
581             }
582             $self->{meta_data}->{system}->{has_been_rolled_up} = 1;
583             return $self;
584             }
585              
586             sub commit {
587             my($self,$target) = @_;
588             mkdir($target);
589             my $cubes = $self->{cube_store}->cubes;
590             $cubes->{$_}->commit($target) for keys %$cubes;
591             return $self;
592             }
593              
594             sub lazy_rc {
595             my($self,$target) = @_;
596             while(my $next_cube = $self->lazy_rollup){
597             $next_cube->commit($target);
598             }
599             return $self;
600             }
601              
602             sub build_lattice_tables {
603             my($self,%opts) = @_;
604             my $schema = $opts{schema};
605             $schema->check_conflicts;
606             my $confine = $schema->is_confined;
607             my @hierarchies = @{$schema->{hierarchies}};
608             my @lattice;
609             for(my $i = 0; $i < @hierarchies; $i++){
610             my @hierarchy = @{$hierarchies[$i]};
611             for(my $j = 0; $j < @hierarchy; $j++){
612             $lattice[$i][$j] = [@hierarchy[0..$j]];
613             }
614             }
615            
616             my $path_walker = DataCube::Pathwalker->new(
617             lattice => [@lattice],
618             );
619            
620             path_walk:
621             while(my $next_path = $path_walker->next_path){
622            
623             my $next_schema = DataCube::Schema->new;
624            
625             $next_schema->add_measure(@$_) for @{$schema->{measures}};
626             $next_schema->add_computed_measure(@$_) for @{$schema->{computed_measures}};
627            
628             for(@$next_path){
629             my @path = grep{defined}@$_;
630             $next_schema->add_strict_hierarchy(@path) if @path;
631             }
632            
633             my $cube = DataCube::Cube->new(
634             schema => $next_schema,
635             );
636            
637             $cube->{is_the_base_table} = 1 if $cube->{schema}->{name} eq $schema->{name};
638            
639             if($schema->{confine_to_base}){
640             $self->{cube_store}->add_cube($cube) and last path_walk if $cube->{is_the_base_table};
641             next path_walk;
642             }
643            
644             if($schema->{lattice_point_names} && defined($schema->{lattice_point_names}->{$cube->{schema}->{name}}) ){
645             $cube->{schema}->{lattice_point_name} =
646             $schema->{lattice_point_names}->{$cube->{schema}->{name}};
647             }
648            
649             if(defined $confine){
650             next path_walk if $confine ne $cube->{schema}->{name};
651             $self->{cube_store}->add_cube($cube) and last path_walk
652             if $confine eq $cube->{schema}->{name};
653             }
654            
655             if( $schema->{lattice_point_filters} ){
656             my @fields = split/\t/,$cube->{schema}->{name};
657             my @filters = @{ $schema->{lattice_point_filters} };
658             for( @filters ){
659             next unless ref( $_ ) && ref( $_ ) eq 'CODE';
660             next path_walk if $_->( @fields ) && ! $cube->{is_the_base_table};
661             }
662             }
663            
664             if( $schema->{asserted_lattice_points} && ref($schema->{asserted_lattice_points}) ){
665             $self->{cube_store}->add_cube($cube)
666             if exists
667             $schema->{asserted_lattice_points}->{$cube->{schema}->{name}} ||
668             $cube->{schema}->{name} eq $schema->{name};
669             } else {
670             $self->{cube_store}->add_cube($cube)
671             unless exists
672             $schema->{suppressed_lattice_points}->{$cube->{schema}->{name}} &&
673             $cube->{schema}->{name} ne $schema->{name};
674             }
675            
676             }
677             $self->{controller} = DataCube::Controller->new_from_datacube($self);
678             return $self;
679              
680             }
681              
682             sub merge_with {
683             my($self,@data_cubes) = @_;
684             my $base_cube_name = $self->{meta_data}->{system}->{base_cube_name};
685             my $base_schema = $self->{cube_store}->fetch($base_cube_name)->{schema};
686             my $self_cubes = $self->{cube_store}->cubes;
687            
688             for(@data_cubes){
689             my $data_cube = $_;
690             my $parity_check = $data_cube->has_been_rolled_up + $self->has_been_rolled_up;
691            
692             die "DataCube(merge_with):\ncant merge a cube which has been rolled up with one that has not\n" if $parity_check % 2;
693             my $new_cubes = $data_cube->{cube_store}->cubes;
694            
695             for(keys %$self_cubes){ die "DataCube(merge_with : self_cubes):\ncube name mismatch:\n$_\n$!\n" unless defined $new_cubes->{$_} }
696             for(keys %$new_cubes) { die "DataCube(merge_with : new_cubes):\ncube name mismatch:\n$_\n$!\n" unless defined $self_cubes->{$_}}
697            
698             for(keys %$self_cubes){
699             $self_cubes->{$_}->merge_with( $new_cubes->{$_} );
700             }
701             }
702            
703             return $self;
704             }
705              
706             sub isa_copy_of {
707             my($self,$cube) = @_;
708             my @queue = ($self,$cube);
709             equality_check:
710             while(1){
711             last equality_check unless @queue;
712             my($source,$target) = splice(@queue,0,2);
713             return if (ref($source) && !ref($target)) || (!ref($source) && ref($target));
714             next equality_check unless defined($source) && defined($target);
715             if(!ref($source) && !ref($target)){
716             return unless $source eq $target;
717             next equality_check;
718             }
719             if(reftype($source) =~ /^array$/i && reftype($target) =~ /^array$/i){
720             return unless @$source == @$target;
721             push @queue, ($source->[$_],$target->[$_]) for (0..$#$source);
722             next equality_check;
723             }
724             if(reftype($source) =~ /^hash$/i && reftype($target) =~ /^hash$/i){
725             return unless keys %$source == keys %$target;
726             push @queue, ($source->{$_},$target->{$_}) for keys %$source;
727             next equality_check;
728             }
729             }
730             return 1;
731             }
732              
733             sub has_been_rolled_up {
734             my($self) = @_;
735             return $self->{meta_data}->{system}->{has_been_rolled_up} || 0;
736             }
737              
738             sub rollup_by_level {
739             my($self,%opts) = @_;
740             $self->initialize_rollup_by_level unless $self->can_rollup_by_level;
741             unless($self->{level_rollup_state}->{pending_levels}){
742             delete $self->{level_rollup_state};
743             return;
744             }
745             my $cube_name = $self->{level_rollup_state}->{current_cube};
746             my $parents = $self->{controller}->{cube_stats}->{possible_parents}->{$cube_name};
747             unless($parents) {
748             $self->nominate_next_level_cube;
749             return $self->{base_cube};
750             }
751             if(my $last_name = $self->{level_rollup_state}->{current_cleanup}){
752             unless($self->{level_rollup_state}->{parent_ref_count}->{$last_name}){
753             delete $self->{cube_store}->{cubes}->{$last_name}->{cube};
754             $self->{cube_store}->{cubes}->{$last_name}->{cube} = {};
755             }
756             }
757             my $parent = $self->{level_rollup_state}->{possible_parents}->{$cube_name};
758             $self->{cube_store}->{cubes}->{$cube_name} =
759             $self->rollup_from(
760             parent => $parent,
761             child => $cube_name,
762             );
763             $self->{level_rollup_state}->{parent_ref_count}->{$parent}--;
764             my $cleanup =
765             0 >= $self->{level_rollup_state}->{parent_ref_count}->{$parent}
766             && $parent ne $self->{meta_data}->{system}->{base_cube_name};
767             if($cleanup){
768             delete $self->{level_rollup_state}->{parent_ref_count}->{$parent};
769             delete $self->{cube_store}->{cubes}->{$parent}->{cube};
770             $self->{cube_store}->{cubes}->{$parent}->{cube} = {};
771             }
772             $self->nominate_next_level_cube;
773             $self->{level_rollup_state}->{current_cleanup} = $cube_name;
774             return $self->{cube_store}->fetch($cube_name);
775             }
776              
777             sub nominate_next_level_cube {
778             my($self) = @_;
779             my $current_level = $self->{level_rollup_state}->{current_level};
780             my @current_level = @{$self->{level_rollup_state}->{pending_levels}->{$current_level}};
781            
782             unless(@current_level){
783             delete $self->{level_rollup_state}->{pending_levels}->{$current_level};
784             my @pending_levels = sort {$b <=> $a} keys %{$self->{level_rollup_state}->{pending_levels}};
785             unless(@pending_levels){
786             delete $self->{level_rollup_state}->{pending_levels};
787             return $self;
788             }
789             my $child = shift(@{$self->{level_rollup_state}->{pending_levels}->{$pending_levels[0]}});
790             $self->{level_rollup_state}->{current_cube} = $child;
791             $self->{level_rollup_state}->{current_parent} = $self->{level_rollup_state}->{possible_parents}->{$child};
792             $self->{level_rollup_state}->{current_level} = $pending_levels[0];
793            
794             } else {
795             my $child = shift(@{$self->{level_rollup_state}->{pending_levels}->{$current_level}});
796             $self->{level_rollup_state}->{current_cube} = $child;
797             $self->{level_rollup_state}->{current_parent} = $self->{level_rollup_state}->{possible_parents}->{$child};
798             }
799             return $self;
800             }
801              
802             sub initialize_rollup_by_level {
803             my($self) = @_;
804             my $possible = $self->{controller}->{cube_stats}->{possible_parents};
805             for(keys %$possible){
806             my($child,@parents) = ($_,@{$possible->{$_}});
807             $self->{level_rollup_state}->{possible_parents}->{$child} = $parents[0];
808             $self->{level_rollup_state}->{parent_ref_count}->{$parents[0]}++;
809             }
810             for(keys %{$self->{controller}->{cube_stats}->{field_count}}){
811             my $level = $_;
812             my @cubes = @{$self->{controller}->{cube_stats}->{field_count}->{$level}};
813             $self->{level_rollup_state}->{pending_levels}->{$level} = [@cubes];
814             }
815             my @pending_levels = sort {$b <=> $a} keys %{$self->{level_rollup_state}->{pending_levels}};
816             $self->{level_rollup_state}->{current_cube} = shift(@{$self->{level_rollup_state}->{pending_levels}->{$pending_levels[0]}});
817             $self->{level_rollup_state}->{current_level} = $pending_levels[0];
818             return $self;
819             }
820              
821             sub can_rollup_by_level {
822             my($self) = @_;
823             return $self->{level_rollup_state};
824             }
825              
826              
827              
828             # user help funtions
829              
830             sub describe {
831             my($self) = @_;
832             my $cubes = $self->{cube_store}->cubes;
833             for(sort { length($a) <=> length($b) || $a cmp $b} keys %$cubes){
834             $cubes->{$_}->describe($self);
835             }
836             }
837              
838             sub get_base_cube_name {
839             my($self) = @_;
840             return $self->{controller}->{cube_stats}->{base_cube_name};
841             }
842              
843             sub base_cube_name {
844             my($self) = @_;
845             return $self->{controller}->{cube_stats}->{base_cube_name};
846             }
847              
848             sub cube_store {
849             my($self) = @_;
850             return $self->{cube_store};
851             }
852              
853             sub cube_list {
854             my($self) = @_;
855             $self->cube_store->cube_names;
856             }
857              
858             sub cube_names {
859             my($self) = @_;
860             $self->cube_store->cube_names;
861             }
862              
863             sub cubes {
864             my($self) = @_;
865             my @names = $self->cube_store->cube_names;
866             my $cube_hash = $self->cube_store->cubes;
867             return map { $cube_hash->{$_} } @names;
868             }
869              
870             sub table_store {
871             my($self) = @_;
872             return $self->{cube_store};
873             }
874              
875             sub base_cube {
876             my($self) = @_;
877             return $self->{base_cube};
878             }
879              
880             sub base_table {
881             my($self) = @_;
882             return $self->{base_cube};
883             }
884              
885             sub schema {
886             my($self) = @_;
887             return $self->{meta_data}->{system}->{base_schema};
888             }
889              
890             sub tables {
891             my($self) = @_;
892             return sort keys %{ $self->table_store->tables };
893             }
894              
895             sub table_count {
896             my($self) = @_;
897             return scalar( keys %{ $self->cube_store->cubes } )
898             }
899              
900             sub dmp {
901             use Data::Dumper;
902             print Dumper( \@_ );
903             }
904              
905              
906              
907              
908             1;
909              
910              
911              
912              
913              
914              
915             __DATA__