File Coverage

lib/UR/DataSource/Default.pm
Criterion Covered Total %
statement 85 95 89.4
branch 24 30 80.0
condition n/a
subroutine 11 11 100.0
pod 3 4 75.0
total 123 140 87.8


line stmt bran cond sub pod time code
1             package UR::DataSource::Default;
2              
3             # NOTE: UR::DataSource::QueryPlan currently has conditional logic for this class
4              
5 210     210   7153 use strict;
  210         300  
  210         5702  
6 210     210   744 use warnings;
  210         266  
  210         4950  
7 210     210   698 use UR;
  210         268  
  210         1563  
8             our $VERSION = "0.46"; # UR $VERSION;
9              
10             class UR::DataSource::Default {
11             is => ['UR::DataSource','UR::Singleton'],
12             doc => 'allows the class to describe its own loading strategy'
13             };
14              
15              
16             sub create_iterator_closure_for_rule {
17 131     131 1 168 my($self,$rule) = @_;
18              
19 131         311 my $subject_class_name = $rule->subject_class_name;
20 131 50       552 unless ($subject_class_name->can('__load__')) {
21 0         0 Carp::croak("Can't load from class $subject_class_name: UR::DataSource::Default requires the class to implement __load__");
22             }
23              
24 131         1263 my $template = $rule->template;
25 131         592 my ($query_plan) = $self->_resolve_query_plan($template);
26            
27 131         323 my $expected_headers = $query_plan->{loading_templates}[0]{property_names};
28 131         537 my ($headers, $content) = $subject_class_name->__load__($rule,$expected_headers);
29              
30 128         3643 my $iterator;
31 128 100       381 if (ref($content) eq 'ARRAY') {
    50          
32             $iterator = sub {
33 268     268   387 my $next_row = shift @$content;
34 268 100       579 $content = undef if @$content == 0;
35 268         497 return $next_row;
36 124         544 };
37             }
38             elsif (ref($content) eq 'CODE') {
39 4         15 $iterator = $content;
40             }
41             else {
42 0         0 Carp::confess("Expected an arrayref of properties, and then content in the form of an arrayref (rows,columns) or coderef/iterator returning rows from $subject_class_name __load__!\n");
43             }
44              
45 128 100       592 if ("@$headers" ne "@$expected_headers") {
46             # translate the headers into the appropriate order
47 9         19 my @mapping = eval { _map_fields($headers,$expected_headers);};
  9         43  
48 9 50       23 if ($@) {
49 0         0 Carp::croak("Loading data for class $subject_class_name and boolexpr $rule failed: $@");
50             }
51             # print Data::Dumper::Dumper($headers,$expected_headers,\@mapping);
52 9         11 my $orig_iterator = $iterator;
53             $iterator = sub {
54 100026     100026   131612 my $result = $orig_iterator->();
55 100026 100       3808862 return unless $result;
56 100018         205786 my @result2 = @$result[@mapping];
57 100018         192448 return \@result2;
58 9         52 };
59             }
60              
61 128         411 return $iterator;
62             }
63              
64 54     54 0 127 sub can_savepoint { 0 }
65              
66             sub _map_fields {
67 9     9   15 my ($from,$to) = @_;
68 9         15 my $n = 0;
69 9         26 my %from = map { $_ => $n++ } @$from;
  34         82  
70 9         23 my @pos;
71 9         24 for my $field (@$to) {
72 35         37 my $pos = $from{$field};
73 35 50       56 unless (defined $pos) {
74             #print "@$from\n@$to\n" . Carp::longmess() . "\n";
75 0         0 die("Can't resolve value for '$field' from the headers returned by its __load__: ". join(', ', @$from));
76             }
77 35         41 push @pos, $pos;
78             }
79 9         37 return @pos;
80             }
81              
82             # Nothing to be done for rollback
83 12     12 1 42 sub rollback { 1;}
84              
85             my @saved_objects;
86             sub _sync_database {
87 4     4   7 my $self = shift;
88 4         8 my %params = @_;
89 4         6 my $changed_objects = $params{changed_objects};
90              
91 4         8 my %class_can_save;
92 4         6 my $err = do {
93 4         6 local $@;
94 4         6 eval {
95 4         12 for my $obj (@$changed_objects) {
96 8         29 my $obj_class = $obj->class;
97 8 100       20 unless (exists $class_can_save{$obj_class}) {
98 5         33 $class_can_save{$obj_class} = $obj->can('__save__');
99             }
100 8 100       111 if ($class_can_save{$obj_class}) {
101 7         9 push @saved_objects, $obj;
102 7         16 $obj->__save__;
103             }
104             }
105             };
106 4         9 $@;
107             };
108              
109 4 100       11 if ($err) {
110 2         3 my @failed_rollback;
111 2         4 do {
112 2         2 my $rollback_error;
113 2         6 while (my $obj = shift @saved_objects) {
114 2         3 local $@;
115 2         4 eval {
116 2         7 $obj->__rollback__;
117             };
118 2 100       9 if ($@) {
119 1         3 $rollback_error = $@;
120 1         4 push @failed_rollback, $obj;
121             }
122             }
123 2 100       5 if (@failed_rollback) {
124 1         8 $self->error_message('Rollback failed: ' . Data::Dumper::Dumper(\@failed_rollback));
125 1         23 Carp::croak "Failed to save, and ERRORS DURING ROLLBACK:\n$err\n $rollback_error\n";
126             }
127             };
128 1         5 die $err;
129             }
130              
131 2         5 return 1;
132             }
133              
134             sub commit {
135 45     45 1 157 my @failed_commit;
136 45         187 while (my $obj = shift @saved_objects) {
137 5         5 local $@;
138 5         4 eval {
139 5         15 $obj->__commit__;
140             };
141 5 50       16 if ($@) {
142 0         0 push @failed_commit, $@ => $obj;
143             }
144             }
145              
146 45 50       146 if (@failed_commit) {
147 0         0 my @failure_messages;
148 0         0 for (my $i = 0; $i < @failed_commit; $i += 2) {
149 0         0 my($exception, $obj) = @failed_commit[$i .. $i+1];
150 0         0 push @failure_messages, "$exception: ".Data::Dumper::Dumper($obj);
151             }
152 0         0 Carp::croak "Commit failed:\n" . join("\n", @failure_messages);
153             }
154              
155 45         124 return 1;
156             }
157              
158             1;
159