| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package UR::DataSource; | 
| 2 | 217 |  |  | 217 |  | 6174 | use strict; | 
|  | 217 |  |  |  |  | 297 |  | 
|  | 217 |  |  |  |  | 5973 |  | 
| 3 | 217 |  |  | 217 |  | 727 | use warnings; | 
|  | 217 |  |  |  |  | 306 |  | 
|  | 217 |  |  |  |  | 9373 |  | 
| 4 |  |  |  |  |  |  |  | 
| 5 |  |  |  |  |  |  | require UR; | 
| 6 |  |  |  |  |  |  | our $VERSION = "0.46"; # UR $VERSION; | 
| 7 | 217 |  |  | 217 |  | 823 | use Sys::Hostname; | 
|  | 217 |  |  |  |  | 271 |  | 
|  | 217 |  |  |  |  | 11873 |  | 
| 8 |  |  |  |  |  |  |  | 
| 9 |  |  |  |  |  |  | { | 
| 10 | 217 |  |  | 217 |  | 763 | no warnings 'once'; | 
|  | 217 |  |  |  |  | 270 |  | 
|  | 217 |  |  |  |  | 47516 |  | 
| 11 |  |  |  |  |  |  | *namespace = \&get_namespace; | 
| 12 |  |  |  |  |  |  | } | 
| 13 |  |  |  |  |  |  |  | 
| 14 |  |  |  |  |  |  | UR::Object::Type->define( | 
| 15 |  |  |  |  |  |  | class_name => 'UR::DataSource', | 
| 16 |  |  |  |  |  |  | is_abstract => 1, | 
| 17 |  |  |  |  |  |  | doc => 'A logical database, independent of prod/dev/testing considerations or login details.', | 
| 18 |  |  |  |  |  |  | has => [ | 
| 19 |  |  |  |  |  |  | namespace => { calculate_from => ['id'] }, | 
| 20 |  |  |  |  |  |  | is_connected => { is => 'Boolean', default_value => 0, is_optional => 1, is_transient => 1 }, | 
| 21 |  |  |  |  |  |  | get_default_handle => { | 
| 22 |  |  |  |  |  |  | is_calculated => 1, | 
| 23 |  |  |  |  |  |  | is_constant   => 1, | 
| 24 |  |  |  |  |  |  | doc => 'Underlying handle for this datasource', | 
| 25 |  |  |  |  |  |  | calculate => '$self->create_default_handle_wrapper', | 
| 26 |  |  |  |  |  |  | }, | 
| 27 |  |  |  |  |  |  | ], | 
| 28 |  |  |  |  |  |  | valid_signals => ['precreate_handle', 'create_handle', 'predisconnect_handle', 'disconnect_handle' ], | 
| 29 |  |  |  |  |  |  | ); | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  | our @CARP_NOT = qw(UR::Context UR::DataSource::QueryPlan); | 
| 32 |  |  |  |  |  |  |  | 
| 33 | 12 |  |  | 12 | 0 | 56 | sub define { shift->__define__(@_) } | 
| 34 |  |  |  |  |  |  |  | 
| 35 |  |  |  |  |  |  | sub get_namespace { | 
| 36 | 1667 |  |  | 1667 | 0 | 4611 | my $class = shift->class; | 
| 37 | 1667 |  |  |  |  | 5345 | return substr($class,0,index($class,"::DataSource")); | 
| 38 |  |  |  |  |  |  | } | 
| 39 |  |  |  |  |  |  |  | 
| 40 |  |  |  |  |  |  | sub get_name { | 
| 41 | 0 |  |  | 0 | 0 | 0 | my $class = shift->class; | 
| 42 | 0 |  |  |  |  | 0 | return lc(substr($class,index($class,"::DataSource")+14)); | 
| 43 |  |  |  |  |  |  | } | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  | # The default used to be to force table/column/constraint/etc names to | 
| 46 |  |  |  |  |  |  | # upper case when storing them in the MetaDB, and in the column_name | 
| 47 |  |  |  |  |  |  | # metadata for properties.  The new behavior is to just use whatever the | 
| 48 |  |  |  |  |  |  | # database supplies us when interrogating the data dictionary. | 
| 49 |  |  |  |  |  |  | # For datasources/clases that still need the old behavior, override this | 
| 50 |  |  |  |  |  |  | # to make the column_name metadata for properties forced to upper-case | 
| 51 | 3012 |  |  | 3012 | 0 | 6964 | sub table_and_column_names_are_upper_case { 0; } | 
| 52 |  |  |  |  |  |  |  | 
| 53 |  |  |  |  |  |  |  | 
| 54 |  |  |  |  |  |  | # Basic, dumb data sources do not support joins within a single | 
| 55 |  |  |  |  |  |  | # query.  Instead the Context logic can perform a cross datasource | 
| 56 |  |  |  |  |  |  | # join within irs own code | 
| 57 | 3 |  |  | 3 | 0 | 19 | sub does_support_joins { 0; } | 
| 58 |  |  |  |  |  |  |  | 
| 59 |  |  |  |  |  |  | # Many data sources do not support limit and offset. | 
| 60 |  |  |  |  |  |  | sub does_support_limit_offset { | 
| 61 |  |  |  |  |  |  | #my($self, $query_plan) = @_; | 
| 62 | 0 |  |  | 0 | 0 | 0 | 0 | 
| 63 |  |  |  |  |  |  | } | 
| 64 |  |  |  |  |  |  |  | 
| 65 |  |  |  |  |  |  | # Most datasources do not support recursive queries | 
| 66 |  |  |  |  |  |  | # Oracle and Postgres do, but in different ways | 
| 67 |  |  |  |  |  |  | # For data sources without support, it'll have to do multiple queries | 
| 68 |  |  |  |  |  |  | # to get all the data | 
| 69 | 10 |  |  | 10 | 0 | 31 | sub does_support_recursive_queries { ''; } | 
| 70 |  |  |  |  |  |  |  | 
| 71 |  |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  | { | 
| 73 | 217 |  |  | 217 |  | 893 | no warnings 'once'; | 
|  | 217 |  |  |  |  | 272 |  | 
|  | 217 |  |  |  |  | 194009 |  | 
| 74 |  |  |  |  |  |  | *create_dbh = \&create_default_handle_wrapper; | 
| 75 |  |  |  |  |  |  | } | 
| 76 |  |  |  |  |  |  |  | 
| 77 |  |  |  |  |  |  | sub create_default_handle_wrapper { | 
| 78 | 549 |  |  | 549 | 0 | 2036 | my $self = UR::Util::object(shift); | 
| 79 |  |  |  |  |  |  |  | 
| 80 | 549 |  |  |  |  | 2837 | $self->__signal_observers__('precreate_handle'); | 
| 81 | 549 |  |  |  |  | 2673 | my $h = $self->create_default_handle; | 
| 82 | 547 |  |  |  |  | 1843 | $self->__signal_observers__('create_handle', $h); | 
| 83 |  |  |  |  |  |  |  | 
| 84 |  |  |  |  |  |  | # Hack - This is to avoid infinite recursion in the case where the | 
| 85 |  |  |  |  |  |  | # handle initializers below try to get the hadle by calling $ds->get_default_handle. | 
| 86 |  |  |  |  |  |  | # The cached/calculated accessor code will look in this hash key and | 
| 87 |  |  |  |  |  |  | # return the handle instead of recursing back into the handle creation, and | 
| 88 |  |  |  |  |  |  | # back to here | 
| 89 | 547 |  |  |  |  | 1275 | $self->{get_default_handle} = $h; | 
| 90 |  |  |  |  |  |  |  | 
| 91 |  |  |  |  |  |  | # Backward compatability for older code that still uses _init_created_dbh | 
| 92 | 547 | 100 |  |  |  | 2310 | if ($self->can('_init_created_dbh')) { | 
| 93 | 172 |  |  |  |  | 2342 | $self->_init_created_dbh($h); | 
| 94 |  |  |  |  |  |  | } else { | 
| 95 | 375 |  |  |  |  | 27088 | $self->init_created_handle($h); | 
| 96 |  |  |  |  |  |  | } | 
| 97 |  |  |  |  |  |  |  | 
| 98 | 547 |  |  |  |  | 1863 | return $h; | 
| 99 |  |  |  |  |  |  | } | 
| 100 |  |  |  |  |  |  |  | 
| 101 |  |  |  |  |  |  | # basic, dumb datasources do not have a handle | 
| 102 | 0 |  |  | 0 | 0 | 0 | sub create_default_handle { undef } | 
| 103 |  |  |  | 0 | 0 |  | sub disconnect { } | 
| 104 |  |  |  |  |  |  |  | 
| 105 |  |  |  |  |  |  | # derived classes can implement this to do extra initialization after the | 
| 106 |  |  |  |  |  |  | # handle is created | 
| 107 | 375 |  |  | 375 | 0 | 435 | sub init_created_handle { 1;  } | 
| 108 |  |  |  |  |  |  |  | 
| 109 |  |  |  |  |  |  | # Peek into the object and see if there's anything in 'get_default_handle' without actually | 
| 110 |  |  |  |  |  |  | # creating a handle | 
| 111 |  |  |  |  |  |  | *has_default_dbh = \&has_default_handle; | 
| 112 |  |  |  |  |  |  | sub has_default_handle { | 
| 113 | 649 |  |  | 649 | 0 | 1451 | my $self = UR::Util::object(shift); | 
| 114 | 649 |  |  |  |  | 2294 | return exists($self->{get_default_handle}); | 
| 115 |  |  |  |  |  |  | } | 
| 116 |  |  |  |  |  |  |  | 
| 117 |  |  |  |  |  |  | *disconnect_default_dbh = \&disconnect_default_handle; | 
| 118 |  |  |  |  |  |  | sub disconnect_default_handle { | 
| 119 | 13 |  |  | 13 | 0 | 857 | my $self = shift; | 
| 120 |  |  |  |  |  |  |  | 
| 121 | 13 | 100 |  |  |  | 57 | if ($self->has_default_handle) { | 
| 122 | 3 |  |  |  |  | 14 | $self->__signal_observers__('predisconnect_handle'); | 
| 123 | 3 |  |  |  |  | 19 | $self->disconnect(); | 
| 124 | 3 |  |  |  |  | 43 | $self->__signal_observers__('disconnect_handle'); | 
| 125 |  |  |  |  |  |  | } | 
| 126 | 13 |  |  |  |  | 31 | 1; | 
| 127 |  |  |  |  |  |  | } | 
| 128 |  |  |  |  |  |  |  | 
| 129 |  |  |  |  |  |  | our $use_dummy_autogenerated_ids; | 
| 130 |  |  |  |  |  |  | *use_dummy_autogenerated_ids = \$ENV{UR_USE_DUMMY_AUTOGENERATED_IDS}; | 
| 131 |  |  |  |  |  |  | sub use_dummy_autogenerated_ids { | 
| 132 |  |  |  |  |  |  | # This allows the saved SQL from sync database to be comparable across executions. | 
| 133 |  |  |  |  |  |  | # It also | 
| 134 | 162 |  |  | 162 | 1 | 246 | my $class = shift; | 
| 135 | 162 | 50 |  |  |  | 486 | if (@_) { | 
| 136 | 0 |  |  |  |  | 0 | ($use_dummy_autogenerated_ids) = @_; | 
| 137 |  |  |  |  |  |  | } | 
| 138 | 162 |  | 50 |  |  | 1607 | $use_dummy_autogenerated_ids ||= 0;  # Replace undef with 0 | 
| 139 | 162 |  |  |  |  | 508 | return $use_dummy_autogenerated_ids; | 
| 140 |  |  |  |  |  |  | } | 
| 141 |  |  |  |  |  |  |  | 
| 142 |  |  |  |  |  |  | our $last_dummy_autogenerated_id; | 
| 143 |  |  |  |  |  |  | sub next_dummy_autogenerated_id { | 
| 144 | 0 | 0 |  | 0 | 1 | 0 | unless($last_dummy_autogenerated_id) { | 
| 145 | 0 |  |  |  |  | 0 | my $hostname = hostname(); | 
| 146 | 0 |  |  |  |  | 0 | $hostname =~ /(\d+)/; | 
| 147 | 0 | 0 |  |  |  | 0 | my $id = $1 ? $1 : 1; | 
| 148 | 0 |  |  |  |  | 0 | $last_dummy_autogenerated_id = ($id * -10_000_000) - ($$ * 1_000); | 
| 149 |  |  |  |  |  |  | } | 
| 150 |  |  |  |  |  |  |  | 
| 151 |  |  |  |  |  |  | #limit id to fit within 11 characters | 
| 152 | 0 |  |  |  |  | 0 | ($last_dummy_autogenerated_id) = $last_dummy_autogenerated_id =~ m/(-\d{1,10})/; | 
| 153 |  |  |  |  |  |  |  | 
| 154 | 0 |  |  |  |  | 0 | return --$last_dummy_autogenerated_id; | 
| 155 |  |  |  |  |  |  | } | 
| 156 |  |  |  |  |  |  |  | 
| 157 |  |  |  |  |  |  | sub autogenerate_new_object_id_for_class_name_and_rule { | 
| 158 | 0 |  |  | 0 | 1 | 0 | my $ds = shift; | 
| 159 |  |  |  |  |  |  |  | 
| 160 | 0 | 0 |  |  |  | 0 | if (ref $ds) { | 
| 161 | 0 |  |  |  |  | 0 | $ds = ref($ds) . " ID " . $ds->id; | 
| 162 |  |  |  |  |  |  | } | 
| 163 |  |  |  |  |  |  |  | 
| 164 |  |  |  |  |  |  | # Maybe we could use next_dummy_autogenerated_id instead? | 
| 165 | 0 |  |  |  |  | 0 | die "Data source $ds did not implement autogenerate_new_object_id_for_class_name_and_rule()"; | 
| 166 |  |  |  |  |  |  | } | 
| 167 |  |  |  |  |  |  |  | 
| 168 |  |  |  |  |  |  | # UR::Context needs to know if a data source supports savepoints | 
| 169 |  |  |  |  |  |  | sub can_savepoint { | 
| 170 | 0 |  |  | 0 | 0 | 0 | my $class = ref($_[0]); | 
| 171 | 0 |  |  |  |  | 0 | die "Class $class didn't supply can_savepoint()"; | 
| 172 |  |  |  |  |  |  | } | 
| 173 |  |  |  |  |  |  |  | 
| 174 |  |  |  |  |  |  | sub set_savepoint { | 
| 175 | 0 |  |  | 0 | 0 | 0 | my $class = ref($_[0]); | 
| 176 | 0 |  |  |  |  | 0 | die "Class $class didn't supply set_savepoint, but can_savepoint is true"; | 
| 177 |  |  |  |  |  |  | } | 
| 178 |  |  |  |  |  |  |  | 
| 179 |  |  |  |  |  |  | sub rollback_to_savepoint { | 
| 180 | 0 |  |  | 0 | 0 | 0 | my $class = ref($_[0]); | 
| 181 | 0 |  |  |  |  | 0 | die "Class $class didn't supply rollback_to_savepoint, but can_savepoint is true"; | 
| 182 |  |  |  |  |  |  | } | 
| 183 |  |  |  |  |  |  |  | 
| 184 |  |  |  |  |  |  |  | 
| 185 |  |  |  |  |  |  | sub _get_class_data_for_loading { | 
| 186 | 3897 |  |  | 3897 |  | 5875 | my ($self, $class_meta) = @_; | 
| 187 | 3897 |  |  |  |  | 6361 | my $class_data = $class_meta->{loading_data_cache}; | 
| 188 | 3897 | 50 |  |  |  | 8132 | unless ($class_data) { | 
| 189 | 3897 |  |  |  |  | 13346 | $class_data = $self->_generate_class_data_for_loading($class_meta); | 
| 190 |  |  |  |  |  |  | } | 
| 191 | 3897 |  |  |  |  | 10900 | return $class_data; | 
| 192 |  |  |  |  |  |  | } | 
| 193 |  |  |  |  |  |  |  | 
| 194 |  |  |  |  |  |  | sub _resolve_query_plan { | 
| 195 | 3838 |  |  | 3838 |  | 4605 | my ($self, $rule_template) = @_; | 
| 196 | 3838 |  |  |  |  | 14996 | my $qp = UR::DataSource::QueryPlan->get( | 
| 197 |  |  |  |  |  |  | rule_template => $rule_template, | 
| 198 |  |  |  |  |  |  | data_source => $self, | 
| 199 |  |  |  |  |  |  | ); | 
| 200 | 3838 | 100 |  |  |  | 12602 | $qp->_init() unless $qp->_is_initialized; | 
| 201 | 3838 |  |  |  |  | 9845 | return $qp; | 
| 202 |  |  |  |  |  |  | } | 
| 203 |  |  |  |  |  |  |  | 
| 204 |  |  |  |  |  |  | # Child classes can override this to return a different datasource | 
| 205 |  |  |  |  |  |  | # depending on the rule passed in | 
| 206 |  |  |  |  |  |  | sub resolve_data_sources_for_rule { | 
| 207 | 4488 |  |  | 4488 | 1 | 7352 | return $_[0]; | 
| 208 |  |  |  |  |  |  | } | 
| 209 |  |  |  |  |  |  |  | 
| 210 |  |  |  |  |  |  | sub _generate_class_data_for_loading { | 
| 211 | 3897 |  |  | 3897 |  | 4594 | my ($self, $class_meta) = @_; | 
| 212 |  |  |  |  |  |  |  | 
| 213 | 3897 |  |  |  |  | 11375 | my $class_name = $class_meta->class_name; | 
| 214 | 3897 |  |  |  |  | 17464 | my $ghost_class = $class_name->ghost_class; | 
| 215 |  |  |  |  |  |  |  | 
| 216 | 3897 |  |  |  |  | 15288 | my @all_id_property_names = $class_meta->all_id_property_names(); | 
| 217 | 3897 |  |  |  |  | 11702 | my @id_properties = $class_meta->id_property_names; | 
| 218 | 3897 |  |  |  |  | 13639 | my $id_property_sorter = $class_meta->id_property_sorter; | 
| 219 | 3897 |  |  |  |  | 9518 | my @class_hierarchy = ($class_meta->class_name,$class_meta->ancestry_class_names); | 
| 220 |  |  |  |  |  |  |  | 
| 221 | 3897 |  |  |  |  | 12581 | my @parent_class_objects = $class_meta->ancestry_class_metas; | 
| 222 | 3897 |  |  |  |  | 5238 | my $sub_classification_method_name; | 
| 223 | 3897 |  |  |  |  | 4302 | my ($sub_classification_meta_class_name, $subclassify_by); | 
| 224 |  |  |  |  |  |  |  | 
| 225 | 0 |  |  |  |  | 0 | my @all_properties; | 
| 226 | 0 |  |  |  |  | 0 | my $first_table_name; | 
| 227 | 0 |  |  |  |  | 0 | my %seen; | 
| 228 | 3897 |  |  |  |  | 6693 | for my $co ( $class_meta, @parent_class_objects ) { | 
| 229 | 12487 | 100 |  |  |  | 37098 | next if ($seen{ $co->id })++; | 
| 230 | 12485 |  | 100 |  |  | 32032 | my $table_name = $co->table_name || '__default__'; | 
| 231 |  |  |  |  |  |  |  | 
| 232 | 12485 |  | 66 |  |  | 26392 | $first_table_name ||= $table_name; | 
| 233 | 12485 |  | 100 |  |  | 40477 | $sub_classification_method_name ||= $co->sub_classification_method_name; | 
| 234 | 12485 |  | 33 |  |  | 38758 | $sub_classification_meta_class_name ||= $co->sub_classification_meta_class_name; | 
| 235 | 12485 |  | 100 |  |  | 38638 | $subclassify_by   ||= $co->subclassify_by; | 
| 236 |  |  |  |  |  |  |  | 
| 237 | 12485 |  |  | 29515 |  | 42812 | my $sort_sub = sub ($$) { return $_[0]->property_name cmp $_[1]->property_name }; | 
|  | 29515 |  |  |  |  | 43495 |  | 
| 238 | 12485 |  |  |  |  | 32681 | push @all_properties, map { [$co, $_, $table_name, 0]} sort $sort_sub UR::Object::Property->get(class_name => $co->class_name); | 
|  | 24210 |  |  |  |  | 68205 |  | 
| 239 |  |  |  |  |  |  | } | 
| 240 |  |  |  |  |  |  |  | 
| 241 | 3897 |  |  |  |  | 12019 | my $sub_typing_property = $class_meta->subclassify_by; | 
| 242 |  |  |  |  |  |  |  | 
| 243 | 3897 |  |  |  |  | 10322 | my $class_table_name = $class_meta->table_name; | 
| 244 |  |  |  |  |  |  |  | 
| 245 | 3897 |  |  |  |  | 14950 | my $class_data = { | 
| 246 |  |  |  |  |  |  | class_name                          => $class_name, | 
| 247 |  |  |  |  |  |  | ghost_class                         => $class_name->ghost_class, | 
| 248 |  |  |  |  |  |  |  | 
| 249 |  |  |  |  |  |  | parent_class_objects                => [$class_meta->ancestry_class_metas], ## | 
| 250 |  |  |  |  |  |  | sub_classification_method_name      => $sub_classification_method_name, | 
| 251 |  |  |  |  |  |  | sub_classification_meta_class_name  => $sub_classification_meta_class_name, | 
| 252 |  |  |  |  |  |  | subclassify_by    => $subclassify_by, | 
| 253 |  |  |  |  |  |  |  | 
| 254 |  |  |  |  |  |  | all_properties                      => \@all_properties, | 
| 255 |  |  |  |  |  |  | all_id_property_names               => [$class_meta->all_id_property_names()], | 
| 256 |  |  |  |  |  |  | id_properties                       => [$class_meta->id_property_names], | 
| 257 |  |  |  |  |  |  | id_property_sorter                  => $class_meta->id_property_sorter, | 
| 258 |  |  |  |  |  |  |  | 
| 259 |  |  |  |  |  |  | sub_typing_property                 => $sub_typing_property, | 
| 260 |  |  |  |  |  |  |  | 
| 261 |  |  |  |  |  |  | # these seem like they go in the RDBMS subclass, but for now the | 
| 262 |  |  |  |  |  |  | # "table" concept is stretched to mean any valid structure identifier | 
| 263 |  |  |  |  |  |  | # within the datasource. | 
| 264 |  |  |  |  |  |  | first_table_name                    => $first_table_name, | 
| 265 |  |  |  |  |  |  | class_table_name                    => $class_table_name, | 
| 266 |  |  |  |  |  |  | }; | 
| 267 |  |  |  |  |  |  |  | 
| 268 | 3897 |  |  |  |  | 20329 | return $class_data; | 
| 269 |  |  |  |  |  |  | } | 
| 270 |  |  |  |  |  |  |  | 
| 271 |  |  |  |  |  |  | sub _generate_loading_templates_arrayref { | 
| 272 |  |  |  |  |  |  | # Each entry represents a table alias in the query. | 
| 273 |  |  |  |  |  |  | # This accounts for different tables, or multiple occurrances | 
| 274 |  |  |  |  |  |  | # of the same table in a join, by grouping by alias instead of | 
| 275 |  |  |  |  |  |  | # table. | 
| 276 |  |  |  |  |  |  |  | 
| 277 | 785 |  |  | 785 |  | 1249 | my $class = shift; | 
| 278 | 785 |  |  |  |  | 1080 | my $db_cols = shift; | 
| 279 | 785 |  |  |  |  | 1048 | my $obj_joins = shift; | 
| 280 | 785 |  |  |  |  | 1133 | my $bxt = shift; | 
| 281 |  |  |  |  |  |  |  | 
| 282 | 217 |  |  | 217 |  | 1116 | use strict; | 
|  | 217 |  |  |  |  | 294 |  | 
|  | 217 |  |  |  |  | 3966 |  | 
| 283 | 217 |  |  | 217 |  | 714 | use warnings; | 
|  | 217 |  |  |  |  | 264 |  | 
|  | 217 |  |  |  |  | 443360 |  | 
| 284 |  |  |  |  |  |  |  | 
| 285 | 785 |  |  |  |  | 1003 | my %obj_joins_by_source_alias; | 
| 286 | 785 |  |  |  |  | 1244 | if (0) { # ($obj_joins) { | 
| 287 |  |  |  |  |  |  | my @obj_joins = @$obj_joins; | 
| 288 |  |  |  |  |  |  | while (@obj_joins) { | 
| 289 |  |  |  |  |  |  | my $foreign_alias = shift @obj_joins; | 
| 290 |  |  |  |  |  |  | my $data = shift @obj_joins; | 
| 291 |  |  |  |  |  |  | for my $foreign_property_name (sort keys %$data) { | 
| 292 |  |  |  |  |  |  | next if $foreign_property_name eq '-is_required'; | 
| 293 |  |  |  |  |  |  |  | 
| 294 |  |  |  |  |  |  | my $source_alias = $data->{$foreign_property_name}{'link_alias'}; | 
| 295 |  |  |  |  |  |  | my $detail = $obj_joins_by_source_alias{$source_alias}{$foreign_alias} ||= {}; | 
| 296 |  |  |  |  |  |  | # warnings come from the above because we don't have 'link_alias' in filters. | 
| 297 |  |  |  |  |  |  |  | 
| 298 |  |  |  |  |  |  | my $source_property_name = $data->{$foreign_property_name}{'link_property_name'}; | 
| 299 |  |  |  |  |  |  | if ($source_property_name) { | 
| 300 |  |  |  |  |  |  | # join | 
| 301 |  |  |  |  |  |  | my $links = $detail->{links} ||= []; | 
| 302 |  |  |  |  |  |  | push @$links, $foreign_property_name, $source_property_name; | 
| 303 |  |  |  |  |  |  | } | 
| 304 |  |  |  |  |  |  |  | 
| 305 |  |  |  |  |  |  | if (exists $data->{value}) { | 
| 306 |  |  |  |  |  |  | # filter | 
| 307 |  |  |  |  |  |  | my $operator = $data->{operator}; | 
| 308 |  |  |  |  |  |  | my $value = $data->{value}; | 
| 309 |  |  |  |  |  |  | my $filter = $detail->{filter} ||= []; | 
| 310 |  |  |  |  |  |  | my $key = $foreign_property_name; | 
| 311 |  |  |  |  |  |  | $key .= ' ' . $operator if $operator; | 
| 312 |  |  |  |  |  |  | push @$filter, $key, $value; | 
| 313 |  |  |  |  |  |  | } | 
| 314 |  |  |  |  |  |  | } | 
| 315 |  |  |  |  |  |  | } | 
| 316 |  |  |  |  |  |  | } | 
| 317 |  |  |  |  |  |  | else { | 
| 318 |  |  |  |  |  |  | #Carp::cluck("no obj joins???"); | 
| 319 |  |  |  |  |  |  | } | 
| 320 |  |  |  |  |  |  |  | 
| 321 | 785 |  |  |  |  | 1108 | my %templates; | 
| 322 | 785 |  |  |  |  | 1131 | my $pos = 0; | 
| 323 | 785 |  |  |  |  | 1008 | my @templates; | 
| 324 |  |  |  |  |  |  | my %alias_object_num; | 
| 325 | 785 |  |  |  |  | 1566 | for my $col_data (@$db_cols) { | 
| 326 | 3599 |  |  |  |  | 3970 | my ($class_obj, $prop, $table_alias, $object_num) = @$col_data; | 
| 327 | 3599 | 50 |  |  |  | 5140 | unless (defined $object_num) { | 
| 328 | 0 |  |  |  |  | 0 | die "No object num for loading template data?!"; | 
| 329 |  |  |  |  |  |  | } | 
| 330 |  |  |  |  |  |  | #Carp::confess() unless $table_alias; | 
| 331 | 3599 |  |  |  |  | 3309 | my $template = $templates[$object_num]; | 
| 332 | 3599 | 100 |  |  |  | 5180 | unless ($template) { | 
| 333 | 906 |  |  |  |  | 2875 | $template = { | 
| 334 |  |  |  |  |  |  | object_num => $object_num, | 
| 335 |  |  |  |  |  |  | table_alias => $table_alias, | 
| 336 |  |  |  |  |  |  | data_class_name => $class_obj->class_name, | 
| 337 |  |  |  |  |  |  | final_class_name => $class_obj->class_name, | 
| 338 |  |  |  |  |  |  | property_names => [], | 
| 339 |  |  |  |  |  |  | column_positions => [], | 
| 340 |  |  |  |  |  |  | id_property_names => undef, | 
| 341 |  |  |  |  |  |  | id_column_positions => [], | 
| 342 |  |  |  |  |  |  | id_resolver => undef, # subref | 
| 343 |  |  |  |  |  |  | }; | 
| 344 | 906 |  |  |  |  | 1991 | $templates[$object_num] = $template; | 
| 345 | 906 |  |  |  |  | 1859 | $alias_object_num{$table_alias} = $object_num; | 
| 346 |  |  |  |  |  |  | } | 
| 347 | 3599 |  |  |  |  | 2542 | push @{ $template->{property_names} }, $prop->property_name; | 
|  | 3599 |  |  |  |  | 6727 |  | 
| 348 | 3599 |  |  |  |  | 3068 | push @{ $template->{column_positions} }, $pos; | 
|  | 3599 |  |  |  |  | 3666 |  | 
| 349 | 3599 |  |  |  |  | 3951 | $pos++; | 
| 350 |  |  |  |  |  |  | } | 
| 351 |  |  |  |  |  |  |  | 
| 352 |  |  |  |  |  |  | # remove joins that resulted in no template, such as when it was to a table-less class | 
| 353 | 785 |  |  |  |  | 1435 | @templates = grep { $_ } @templates; | 
|  | 906 |  |  |  |  | 2025 |  | 
| 354 |  |  |  |  |  |  |  | 
| 355 |  |  |  |  |  |  | # Post-process the template objects a bit to get the exact id positions. | 
| 356 | 785 |  |  |  |  | 1470 | for my $template (@templates) { | 
| 357 | 906 |  |  |  |  | 1089 | my @id_property_names; | 
| 358 | 906 |  |  |  |  | 25629 | for my $id_class_name ($template->{data_class_name}, $template->{data_class_name}->inheritance) { | 
| 359 | 906 |  |  |  |  | 3565 | my $id_class_obj = UR::Object::Type->get(class_name => $id_class_name); | 
| 360 | 906 | 50 |  |  |  | 3469 | last if @id_property_names = $id_class_obj->id_property_names; | 
| 361 |  |  |  |  |  |  | } | 
| 362 | 906 |  |  |  |  | 2073 | $template->{id_property_names} = \@id_property_names; | 
| 363 |  |  |  |  |  |  |  | 
| 364 | 906 |  |  |  |  | 1184 | my @id_column_positions; | 
| 365 | 906 |  |  |  |  | 1705 | for my $id_property_name (@id_property_names) { | 
| 366 | 1364 |  |  |  |  | 1538 | for my $n (0..$#{ $template->{property_names} }) { | 
|  | 1364 |  |  |  |  | 3703 |  | 
| 367 | 3584 | 100 |  |  |  | 6310 | if ($template->{property_names}[$n] eq $id_property_name) { | 
| 368 | 1364 |  |  |  |  | 2138 | push @id_column_positions, $template->{column_positions}[$n]; | 
| 369 | 1364 |  |  |  |  | 1981 | last; | 
| 370 |  |  |  |  |  |  | } | 
| 371 |  |  |  |  |  |  | } | 
| 372 |  |  |  |  |  |  | } | 
| 373 | 906 |  |  |  |  | 1632 | $template->{id_column_positions} = \@id_column_positions; | 
| 374 |  |  |  |  |  |  |  | 
| 375 | 906 | 100 |  |  |  | 2653 | if (@id_column_positions == 1) { | 
|  |  | 50 |  |  |  |  |  | 
| 376 |  |  |  |  |  |  | $template->{id_resolver} = sub { | 
| 377 | 0 |  |  | 0 |  | 0 | return $_[0][$id_column_positions[0]]; | 
| 378 |  |  |  |  |  |  | } | 
| 379 | 679 |  |  |  |  | 3120 | } | 
| 380 |  |  |  |  |  |  | elsif (@id_column_positions > 1) { | 
| 381 | 227 |  |  |  |  | 464 | my $class_name = $template->{data_class_name}; | 
| 382 |  |  |  |  |  |  | $template->{id_resolver} = sub { | 
| 383 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 384 | 0 |  |  |  |  | 0 | return $class_name->__meta__->resolve_composite_id_from_ordered_values(@$self[@id_column_positions]); | 
| 385 |  |  |  |  |  |  | } | 
| 386 | 227 |  |  |  |  | 1481 | } | 
| 387 |  |  |  |  |  |  | else { | 
| 388 |  |  |  |  |  |  | Carp::croak("Can't determine which columns will hold the ID property data for class " | 
| 389 |  |  |  |  |  |  | . $template->{data_class_name} . ".  It's ID properties are (" . join(', ', @id_property_names) | 
| 390 | 0 |  |  |  |  | 0 | . ") which do not appear in the class' property list (" . join(', ', @{$template->{'property_names'}}).")"); | 
|  | 0 |  |  |  |  | 0 |  | 
| 391 |  |  |  |  |  |  | } | 
| 392 |  |  |  |  |  |  |  | 
| 393 | 906 |  |  |  |  | 1855 | my $source_alias = $template->{table_alias}; | 
| 394 | 906 |  |  |  |  | 1612 | if (0 and my $join_data_for_source_table = $obj_joins_by_source_alias{$source_alias}) { | 
| 395 |  |  |  |  |  |  | # there are joins which come from this entity to other entities | 
| 396 |  |  |  |  |  |  | # as these entities are loaded, remember the individual queries covered by this object returning | 
| 397 |  |  |  |  |  |  | # NOTE: when we join a <> b, we remember that we've loaded all of the b for a when _a_ loads, not b, | 
| 398 |  |  |  |  |  |  | # since it's possible that there ar zero of b, and we don't want to perform the query for b | 
| 399 |  |  |  |  |  |  | my $source_object_num = $template->{object_num}; | 
| 400 |  |  |  |  |  |  | my $source_class_name = $template->{data_class_name}; | 
| 401 |  |  |  |  |  |  | my $next_joins = $template->{next_joins} ||= []; | 
| 402 |  |  |  |  |  |  | for my $foreign_alias (keys %$join_data_for_source_table) { | 
| 403 |  |  |  |  |  |  | my $foreign_object_num = $alias_object_num{$foreign_alias}; | 
| 404 |  |  |  |  |  |  | Carp::confess("no alias for $foreign_alias?") if not defined $foreign_object_num; | 
| 405 |  |  |  |  |  |  | my $foreign_template = $templates[$foreign_object_num]; | 
| 406 |  |  |  |  |  |  | my $foreign_class_name = $foreign_template->{data_class_name}; | 
| 407 |  |  |  |  |  |  |  | 
| 408 |  |  |  |  |  |  | my $join_data = $join_data_for_source_table->{$foreign_alias}; | 
| 409 |  |  |  |  |  |  | my %links = map { $_ ? @$_ : () } $join_data->{links}; | 
| 410 |  |  |  |  |  |  | my %filters = map { $_ ? @$_ : () } $join_data->{filters}; | 
| 411 |  |  |  |  |  |  |  | 
| 412 |  |  |  |  |  |  | my @keys = sort (keys %links, keys %filters); | 
| 413 |  |  |  |  |  |  | my @value_position_source_property; | 
| 414 |  |  |  |  |  |  | for (my $n = 0; $n < @keys; $n++) { | 
| 415 |  |  |  |  |  |  | my $key = $keys[$n]; | 
| 416 |  |  |  |  |  |  | if ($links{$key} and $filters{$key}) { | 
| 417 |  |  |  |  |  |  | Carp::confess("unexpected same key $key in filters and joins"); | 
| 418 |  |  |  |  |  |  | } | 
| 419 |  |  |  |  |  |  | my $source_property_name = $links{$key}; | 
| 420 |  |  |  |  |  |  | next unless $source_property_name; | 
| 421 |  |  |  |  |  |  | push @value_position_source_property, $n, $source_property_name; | 
| 422 |  |  |  |  |  |  | } | 
| 423 |  |  |  |  |  |  | my $bx = $foreign_class_name->define_boolexpr(map { $_ => $filters{$_} } @keys); | 
| 424 |  |  |  |  |  |  | my ($bxt, @values) = $bx->template_and_values(); | 
| 425 |  |  |  |  |  |  | push @$next_joins, [ $bxt->id, \@values, \@value_position_source_property ]; | 
| 426 |  |  |  |  |  |  | } | 
| 427 |  |  |  |  |  |  | } | 
| 428 |  |  |  |  |  |  | } | 
| 429 |  |  |  |  |  |  |  | 
| 430 | 785 |  |  |  |  | 3046 | return \@templates; | 
| 431 |  |  |  |  |  |  | } | 
| 432 |  |  |  |  |  |  |  | 
| 433 |  |  |  |  |  |  | sub create_iterator_closure_for_rule_template_and_values { | 
| 434 | 0 |  |  | 0 | 1 | 0 | my ($self, $rule_template, @values) = @_; | 
| 435 | 0 |  |  |  |  | 0 | my $rule = $rule_template->get_rule_for_values(@values); | 
| 436 | 0 |  |  |  |  | 0 | return $self->create_iterator_closure_for_rule($rule); | 
| 437 |  |  |  |  |  |  | } | 
| 438 |  |  |  |  |  |  |  | 
| 439 |  |  |  |  |  |  | sub _reclassify_object_loading_info_for_new_class { | 
| 440 | 115 |  |  | 115 |  | 138 | my $self = shift; | 
| 441 | 115 |  |  |  |  | 130 | my $loading_info = shift; | 
| 442 | 115 |  |  |  |  | 138 | my $new_class = shift; | 
| 443 |  |  |  |  |  |  |  | 
| 444 | 115 |  |  |  |  | 133 | my $new_info; | 
| 445 | 115 |  |  |  |  | 338 | %$new_info = %$loading_info; | 
| 446 |  |  |  |  |  |  |  | 
| 447 | 115 |  |  |  |  | 247 | foreach my $template_id (keys %$loading_info) { | 
| 448 |  |  |  |  |  |  |  | 
| 449 | 150 |  |  |  |  | 190 | my $target_class_rules = $loading_info->{$template_id}; | 
| 450 | 150 |  |  |  |  | 268 | foreach my $rule_id (keys %$target_class_rules) { | 
| 451 | 163 |  |  |  |  | 520 | my $pos = index($rule_id,'/'); | 
| 452 | 163 |  |  |  |  | 571 | $new_info->{$template_id}->{$new_class . "/" . substr($rule_id,$pos+1)} = 1; | 
| 453 |  |  |  |  |  |  | } | 
| 454 |  |  |  |  |  |  | } | 
| 455 |  |  |  |  |  |  |  | 
| 456 | 115 |  |  |  |  | 231 | return $new_info; | 
| 457 |  |  |  |  |  |  | } | 
| 458 |  |  |  |  |  |  |  | 
| 459 |  |  |  |  |  |  | sub _get_object_loading_info { | 
| 460 | 115 |  |  | 115 |  | 174 | my $self = shift; | 
| 461 | 115 |  |  |  |  | 134 | my $obj  = shift; | 
| 462 | 115 |  |  |  |  | 138 | my %param_load_hash; | 
| 463 | 115 | 50 |  |  |  | 298 | if ($obj->{'__load'}) { | 
| 464 | 115 |  |  |  |  | 150 | while( my($template_id, $rules) = each %{ $obj->{'__load'} } ) { | 
|  | 252 |  |  |  |  | 787 |  | 
| 465 | 137 |  |  |  |  | 307 | foreach my $rule_id ( keys %$rules ) { | 
| 466 | 137 |  |  |  |  | 445 | $param_load_hash{$template_id}->{$rule_id} = $UR::Context::all_params_loaded->{$template_id}->{$rule_id}; | 
| 467 |  |  |  |  |  |  | } | 
| 468 |  |  |  |  |  |  | } | 
| 469 |  |  |  |  |  |  | } | 
| 470 | 115 |  |  |  |  | 285 | return \%param_load_hash; | 
| 471 |  |  |  |  |  |  | } | 
| 472 |  |  |  |  |  |  |  | 
| 473 |  |  |  |  |  |  |  | 
| 474 |  |  |  |  |  |  | sub _add_object_loading_info { | 
| 475 | 56 |  |  | 56 |  | 79 | my $self = shift; | 
| 476 | 56 |  |  |  |  | 71 | my $obj = shift; | 
| 477 | 56 |  |  |  |  | 72 | my $param_load_hash = shift; | 
| 478 |  |  |  |  |  |  |  | 
| 479 | 56 |  |  |  |  | 239 | while( my($template_id, $rules) = each %$param_load_hash) { | 
| 480 | 80 |  |  |  |  | 133 | foreach my $rule_id ( keys %$rules ) { | 
| 481 | 135 |  |  |  |  | 336 | $obj->{'__load'}->{$template_id}->{$rule_id} = $rules->{$rule_id}; | 
| 482 |  |  |  |  |  |  | } | 
| 483 |  |  |  |  |  |  | } | 
| 484 |  |  |  |  |  |  | } | 
| 485 |  |  |  |  |  |  |  | 
| 486 |  |  |  |  |  |  |  | 
| 487 |  |  |  |  |  |  | # same as add_object_loading_info, but manipulates the data in $UR::Context::all_params_loaded | 
| 488 |  |  |  |  |  |  | sub _record_that_loading_has_occurred { | 
| 489 | 147 |  |  | 147 |  | 174 | my $self = shift; | 
| 490 | 147 |  |  |  |  | 172 | my $param_load_hash = shift; | 
| 491 |  |  |  |  |  |  |  | 
| 492 | 147 |  |  |  |  | 487 | while( my($template_id, $rules) = each %$param_load_hash) { | 
| 493 | 186 |  |  |  |  | 301 | foreach my $rule_id ( keys %$rules ) { | 
| 494 |  |  |  |  |  |  | $UR::Context::all_params_loaded->{$template_id}->{$rule_id} ||= | 
| 495 | 301 |  | 100 |  |  | 1309 | $rules->{$rule_id}; | 
| 496 |  |  |  |  |  |  | } | 
| 497 |  |  |  |  |  |  | } | 
| 498 |  |  |  |  |  |  | } | 
| 499 |  |  |  |  |  |  |  | 
| 500 |  |  |  |  |  |  | sub _first_class_in_inheritance_with_a_table { | 
| 501 |  |  |  |  |  |  | # This is called once per subclass and cached in the subclass from then on. | 
| 502 | 78 |  |  | 78 |  | 106 | my $self = shift; | 
| 503 | 78 |  |  |  |  | 109 | my $class = shift; | 
| 504 | 78 | 50 |  |  |  | 191 | $class = ref($class) if ref($class); | 
| 505 |  |  |  |  |  |  |  | 
| 506 |  |  |  |  |  |  |  | 
| 507 | 78 | 50 |  |  |  | 179 | unless ($class) { | 
| 508 | 0 |  |  |  |  | 0 | Carp::confess("No class?"); | 
| 509 |  |  |  |  |  |  | } | 
| 510 | 78 |  |  |  |  | 283 | my $class_object = $class->__meta__; | 
| 511 | 78 |  |  |  |  | 132 | my $found = ""; | 
| 512 | 78 |  |  |  |  | 458 | for ($class_object, $class_object->ancestry_class_metas) | 
| 513 |  |  |  |  |  |  | { | 
| 514 | 139 | 100 |  |  |  | 676 | if ($_->has_direct_table) | 
| 515 |  |  |  |  |  |  | { | 
| 516 | 78 |  |  |  |  | 233 | $found = $_->class_name; | 
| 517 | 78 |  |  |  |  | 142 | last; | 
| 518 |  |  |  |  |  |  | } | 
| 519 |  |  |  |  |  |  | } | 
| 520 |  |  |  |  |  |  | #eval qq/ | 
| 521 |  |  |  |  |  |  | #    package $class; | 
| 522 |  |  |  |  |  |  | #    sub _first_class_in_inheritance_with_a_table { | 
| 523 |  |  |  |  |  |  | #        return '$found' if \$_[0] eq '$class'; | 
| 524 |  |  |  |  |  |  | #        shift->SUPER::_first_class_in_inheritance_with_a_table(\@_); | 
| 525 |  |  |  |  |  |  | #    } | 
| 526 |  |  |  |  |  |  | #/; | 
| 527 |  |  |  |  |  |  | #die "Error setting data in subclass: $@" if $@; | 
| 528 | 78 |  |  |  |  | 210 | return $found; | 
| 529 |  |  |  |  |  |  | } | 
| 530 |  |  |  |  |  |  |  | 
| 531 |  |  |  |  |  |  | sub _class_is_safe_to_rebless_from_parent_class { | 
| 532 | 78 |  |  | 78 |  | 155 | my ($self, $class, $was_loaded_as_this_parent_class) = @_; | 
| 533 | 78 |  |  |  |  | 391 | my $fcwt = $self->_first_class_in_inheritance_with_a_table($class); | 
| 534 | 78 | 50 |  |  |  | 206 | unless ($fcwt) { | 
| 535 | 0 |  |  |  |  | 0 | Carp::croak("Can't call _class_is_safe_to_rebless_from_parent_class(): Class $class has no parent classes with a table"); | 
| 536 |  |  |  |  |  |  | } | 
| 537 | 78 |  |  |  |  | 427 | return ($was_loaded_as_this_parent_class->isa($fcwt)); | 
| 538 |  |  |  |  |  |  | } | 
| 539 |  |  |  |  |  |  |  | 
| 540 |  |  |  |  |  |  | sub ur_datasource_class_for_dbi_connect_string { | 
| 541 | 70 |  |  | 70 | 0 | 99 | my($class, $dsn) = @_; | 
| 542 | 70 |  |  |  |  | 398 | my(undef, $driver) = DBI->parse_dsn($dsn); | 
| 543 | 70 | 50 |  |  |  | 1379 | $driver | 
| 544 |  |  |  |  |  |  | || Carp::croak("Could not parse DBI driver out of connect string $dsn"); | 
| 545 | 70 |  |  |  |  | 213 | return 'UR::DataSource::'.$driver; | 
| 546 |  |  |  |  |  |  | } | 
| 547 |  |  |  |  |  |  |  | 
| 548 |  |  |  |  |  |  | sub _get_current_entities { | 
| 549 | 125 |  |  | 125 |  | 195 | my $self = shift; | 
| 550 | 125 |  |  |  |  | 587 | my @class_meta = UR::Object::Type->is_loaded( | 
| 551 |  |  |  |  |  |  | data_source_id => $self->id | 
| 552 |  |  |  |  |  |  | ); | 
| 553 | 125 |  |  |  |  | 228 | my @objects; | 
| 554 | 125 |  |  |  |  | 256 | for my $class_meta (@class_meta) { | 
| 555 | 308 | 50 |  |  |  | 1112 | next unless $class_meta->generated();  # Ungenerated classes won't have any instances | 
| 556 | 308 |  |  |  |  | 558 | my $class_name = $class_meta->class_name; | 
| 557 | 308 |  |  |  |  | 570 | push @objects, $UR::Context::current->all_objects_loaded($class_name); | 
| 558 |  |  |  |  |  |  | } | 
| 559 | 125 |  |  |  |  | 1229 | return @objects; | 
| 560 |  |  |  |  |  |  | } | 
| 561 |  |  |  |  |  |  |  | 
| 562 |  |  |  |  |  |  |  | 
| 563 |  |  |  | 0 |  |  | sub _prepare_for_lob { }; | 
| 564 |  |  |  |  |  |  |  | 
| 565 |  |  |  |  |  |  | sub _set_specified_objects_saved_uncommitted { | 
| 566 | 52 |  |  | 52 |  | 104 | my ($self,$objects_arrayref) = @_; | 
| 567 |  |  |  |  |  |  | # Sets an objects as though the has been saved but tha changes have not been committed. | 
| 568 |  |  |  |  |  |  | # This is called automatically by _sync_databases. | 
| 569 |  |  |  |  |  |  |  | 
| 570 | 52 |  |  |  |  | 92 | my %objects_by_class; | 
| 571 |  |  |  |  |  |  | my $class_name; | 
| 572 | 52 |  |  |  |  | 139 | for my $object (@$objects_arrayref) { | 
| 573 | 134 |  |  |  |  | 216 | $class_name = ref($object); | 
| 574 | 134 |  | 100 |  |  | 475 | $objects_by_class{$class_name} ||= []; | 
| 575 | 134 |  |  |  |  | 122 | push @{ $objects_by_class{$class_name} }, $object; | 
|  | 134 |  |  |  |  | 255 |  | 
| 576 |  |  |  |  |  |  | } | 
| 577 |  |  |  |  |  |  |  | 
| 578 | 52 |  |  |  |  | 230 | for my $class_name (sort keys %objects_by_class) { | 
| 579 | 72 |  |  |  |  | 348 | my $class_object = $class_name->__meta__; | 
| 580 |  |  |  |  |  |  | my @property_names = | 
| 581 | 246 |  |  |  |  | 411 | map { $_->property_name } | 
| 582 | 72 |  |  |  |  | 574 | grep { $_->column_name } | 
|  | 371 |  |  |  |  | 624 |  | 
| 583 |  |  |  |  |  |  | $class_object->all_property_metas; | 
| 584 |  |  |  |  |  |  |  | 
| 585 | 72 |  |  |  |  | 142 | for my $object (@{ $objects_by_class{$class_name} }) { | 
|  | 72 |  |  |  |  | 189 |  | 
| 586 | 134 |  | 100 |  |  | 562 | $object->{db_saved_uncommitted} ||= {}; | 
| 587 | 134 |  |  |  |  | 183 | my $db_saved_uncommitted = $object->{db_saved_uncommitted}; | 
| 588 | 134 |  |  |  |  | 167 | for my $property ( @property_names ) { | 
| 589 | 464 |  |  |  |  | 996 | $db_saved_uncommitted->{$property} = $object->$property; | 
| 590 |  |  |  |  |  |  | } | 
| 591 |  |  |  |  |  |  | } | 
| 592 |  |  |  |  |  |  | } | 
| 593 | 52 |  |  |  |  | 283 | return 1; | 
| 594 |  |  |  |  |  |  | } | 
| 595 |  |  |  |  |  |  |  | 
| 596 |  |  |  |  |  |  | sub _set_all_objects_saved_committed { | 
| 597 |  |  |  |  |  |  | # called by UR::DBI on commit | 
| 598 | 103 |  |  | 103 |  | 174 | my $self = shift; | 
| 599 | 103 |  |  |  |  | 1204 | return $self->_set_specified_objects_saved_committed([ $self->_get_current_entities ]); | 
| 600 |  |  |  |  |  |  | } | 
| 601 |  |  |  |  |  |  |  | 
| 602 |  |  |  |  |  |  | sub _set_all_specified_objects_saved_committed { | 
| 603 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 604 | 0 |  |  |  |  | 0 | my($pkg, $file, $line) = caller; | 
| 605 | 0 |  |  |  |  | 0 | Carp::carp("Deprecated method _set_all_specified_objects_saved_committed called at file $file line $line.  The new name for this method is _set_specified_objects_saved_committed"); | 
| 606 | 0 |  |  |  |  | 0 | my @changed_objects = @_; | 
| 607 | 0 |  |  |  |  | 0 | $self->_set_specified_objects_saved_committed(\@changed_objects); | 
| 608 |  |  |  |  |  |  | } | 
| 609 |  |  |  |  |  |  |  | 
| 610 |  |  |  |  |  |  | sub _set_specified_objects_saved_committed { | 
| 611 | 108 |  |  | 108 |  | 186 | my $self = shift; | 
| 612 | 108 |  |  |  |  | 141 | my $objects = shift; | 
| 613 |  |  |  |  |  |  |  | 
| 614 |  |  |  |  |  |  | # Two step process... set saved and committed, then fire commit observers. | 
| 615 |  |  |  |  |  |  | # Doing so prevents problems should any of the observers themselves commit. | 
| 616 | 108 |  |  |  |  | 135 | my @saved_objects; | 
| 617 | 108 |  |  |  |  | 208 | for my $obj (@$objects) { | 
| 618 | 677 |  |  |  |  | 900 | my $saved = $self->_set_object_saved_committed($obj); | 
| 619 | 677 | 100 |  |  |  | 948 | push @saved_objects, $saved if $saved; | 
| 620 |  |  |  |  |  |  | } | 
| 621 |  |  |  |  |  |  |  | 
| 622 | 108 |  |  |  |  | 205 | for my $obj (@saved_objects) { | 
| 623 | 83 | 100 |  |  |  | 338 | next if $obj->isa('UR::DeletedRef'); | 
| 624 | 80 |  |  |  |  | 242 | $obj->__signal_change__('commit'); | 
| 625 | 80 | 100 |  |  |  | 312 | if ($obj->isa('UR::Object::Ghost')) { | 
| 626 | 20 |  |  |  |  | 67 | $UR::Context::current->_abandon_object($obj); | 
| 627 |  |  |  |  |  |  | } | 
| 628 |  |  |  |  |  |  | } | 
| 629 |  |  |  |  |  |  |  | 
| 630 | 108 |  | 100 |  |  | 940 | return scalar(@$objects) || "0 but true"; | 
| 631 |  |  |  |  |  |  | } | 
| 632 |  |  |  |  |  |  |  | 
| 633 |  |  |  |  |  |  | sub _set_object_saved_committed { | 
| 634 |  |  |  |  |  |  | # called by the above, and some test cases | 
| 635 | 677 |  |  | 677 |  | 506 | my ($self, $object) = @_; | 
| 636 | 677 | 100 |  |  |  | 851 | if ($object->{db_saved_uncommitted}) { | 
| 637 | 83 | 100 |  |  |  | 394 | unless ($object->isa('UR::Object::Ghost')) { | 
| 638 | 60 |  |  |  |  | 1928 | %{ $object->{db_committed} } = ( | 
| 639 | 24 |  |  |  |  | 92 | ($object->{db_committed} ? %{ $object->{db_committed} } : ()), | 
| 640 | 60 | 100 |  |  |  | 134 | %{ $object->{db_saved_uncommitted} } | 
|  | 60 |  |  |  |  | 153 |  | 
| 641 |  |  |  |  |  |  | ); | 
| 642 | 60 |  |  |  |  | 166 | delete $object->{db_saved_uncommitted}; | 
| 643 |  |  |  |  |  |  | } | 
| 644 | 83 |  |  |  |  | 119 | return $object; | 
| 645 |  |  |  |  |  |  | } | 
| 646 |  |  |  |  |  |  | else { | 
| 647 | 594 |  |  |  |  | 482 | return; | 
| 648 |  |  |  |  |  |  | } | 
| 649 |  |  |  |  |  |  | } | 
| 650 |  |  |  |  |  |  |  | 
| 651 |  |  |  |  |  |  | sub _set_all_objects_saved_rolled_back { | 
| 652 |  |  |  |  |  |  | # called by UR::DBI on commit | 
| 653 | 22 |  |  | 22 |  | 26 | my $self = shift; | 
| 654 | 22 |  |  |  |  | 74 | my @objects = $self->_get_current_entities; | 
| 655 | 22 |  |  |  |  | 55 | for my $obj (@objects)  { | 
| 656 | 203 | 50 |  |  |  | 226 | unless ($self->_set_object_saved_rolled_back($obj)) { | 
| 657 | 0 |  |  |  |  | 0 | die "An error occurred setting " . $obj->__display_name__ | 
| 658 |  |  |  |  |  |  | . " to match the rolled-back database state.  Exiting..."; | 
| 659 |  |  |  |  |  |  | } | 
| 660 |  |  |  |  |  |  | } | 
| 661 |  |  |  |  |  |  | } | 
| 662 |  |  |  |  |  |  |  | 
| 663 |  |  |  |  |  |  | sub _set_specified_objects_saved_rolled_back { | 
| 664 | 0 |  |  | 0 |  | 0 | my $self = shift; | 
| 665 | 0 |  |  |  |  | 0 | my $objects = shift; | 
| 666 | 0 |  |  |  |  | 0 | for my $obj (@$objects)  { | 
| 667 | 0 | 0 |  |  |  | 0 | unless ($self->_set_object_saved_rolled_back($obj)) { | 
| 668 | 0 |  |  |  |  | 0 | die "An error occurred setting " . $obj->__display_name__ | 
| 669 |  |  |  |  |  |  | . " to match the rolled-back database state.  Exiting..."; | 
| 670 |  |  |  |  |  |  | } | 
| 671 |  |  |  |  |  |  | } | 
| 672 |  |  |  |  |  |  | } | 
| 673 |  |  |  |  |  |  |  | 
| 674 |  |  |  |  |  |  |  | 
| 675 |  |  |  |  |  |  |  | 
| 676 |  |  |  |  |  |  | sub _set_object_saved_rolled_back { | 
| 677 |  |  |  |  |  |  | # called by the above, and some test cases | 
| 678 | 203 |  |  | 203 |  | 127 | my ($self,$object) = @_; | 
| 679 | 203 |  |  |  |  | 156 | delete $object->{db_saved_uncommitted}; | 
| 680 | 203 |  |  |  |  | 335 | return $object; | 
| 681 |  |  |  |  |  |  | } | 
| 682 |  |  |  |  |  |  |  | 
| 683 |  |  |  |  |  |  |  | 
| 684 |  |  |  |  |  |  | # These are part of the basic DataSource API.  Subclasses will want to override these | 
| 685 |  |  |  |  |  |  |  | 
| 686 |  |  |  |  |  |  | sub _sync_database { | 
| 687 | 0 |  |  | 0 |  | 0 | my $class = shift; | 
| 688 | 0 |  |  |  |  | 0 | my %args = @_; | 
| 689 | 0 |  | 0 |  |  | 0 | $class = ref($class) || $class; | 
| 690 |  |  |  |  |  |  |  | 
| 691 |  |  |  |  |  |  | $class->warning_message("Data source $class does not support saving objects to storage.  " . | 
| 692 | 0 |  |  |  |  | 0 | scalar(@{$args{'changed_objects'}}) . " objects will not be saved"); | 
|  | 0 |  |  |  |  | 0 |  | 
| 693 | 0 |  |  |  |  | 0 | return 1; | 
| 694 |  |  |  |  |  |  | } | 
| 695 |  |  |  |  |  |  |  | 
| 696 |  |  |  |  |  |  | sub commit { | 
| 697 | 5 |  |  | 5 | 1 | 6 | my $class = shift; | 
| 698 | 5 |  |  |  |  | 6 | my %args = @_; | 
| 699 | 5 |  | 33 |  |  | 14 | $class = ref($class) || $class; | 
| 700 |  |  |  |  |  |  |  | 
| 701 |  |  |  |  |  |  | #$class->warning_message("commit() ignored for data source $class"); | 
| 702 | 5 |  |  |  |  | 9 | return 1; | 
| 703 |  |  |  |  |  |  | } | 
| 704 |  |  |  |  |  |  |  | 
| 705 |  |  |  |  |  |  | sub rollback { | 
| 706 | 0 |  |  | 0 | 1 | 0 | my $class = shift; | 
| 707 | 0 |  |  |  |  | 0 | my %args = @_; | 
| 708 | 0 |  | 0 |  |  | 0 | $class = ref($class) || $class; | 
| 709 |  |  |  |  |  |  |  | 
| 710 | 0 |  |  |  |  | 0 | $class->warning_message("rollback() ignored for data source $class"); | 
| 711 | 0 |  |  |  |  | 0 | return 1; | 
| 712 |  |  |  |  |  |  | } | 
| 713 |  |  |  |  |  |  |  | 
| 714 |  |  |  |  |  |  | # When the class initializer is create property objects, it will | 
| 715 |  |  |  |  |  |  | # auto-fill-in column_name if the class definition has a table_name. | 
| 716 |  |  |  |  |  |  | # File-based data sources do not have tables (and so classes using them | 
| 717 |  |  |  |  |  |  | # do not have table_names), but the properties still need column_names | 
| 718 |  |  |  |  |  |  | # so loading works properly. | 
| 719 |  |  |  |  |  |  | # For now, only UR::DataSource::File and ::FileMux set this. | 
| 720 |  |  |  |  |  |  | # FIXME this method's existence is ugly.  Find a better way to fill in | 
| 721 |  |  |  |  |  |  | # column_name for those properties, or fix the data sources to not | 
| 722 |  |  |  |  |  |  | # require column_names to be set by the initializer | 
| 723 |  |  |  |  |  |  | sub initializer_should_create_column_name_for_class_properties { | 
| 724 | 2053 |  |  | 2053 | 0 | 8438 | return 0; | 
| 725 |  |  |  |  |  |  | } | 
| 726 |  |  |  |  |  |  |  | 
| 727 |  |  |  |  |  |  |  | 
| 728 |  |  |  |  |  |  | # Subclasses should override this. | 
| 729 |  |  |  |  |  |  | # It's called by the class initializer when the data_source property in a class | 
| 730 |  |  |  |  |  |  | # definition contains a hashref with an 'is' key.  The method should accept this | 
| 731 |  |  |  |  |  |  | # hashref, create a data_source instance (if appropriate) and return the class_name | 
| 732 |  |  |  |  |  |  | # of this new datasource. | 
| 733 |  |  |  |  |  |  | sub create_from_inline_class_data { | 
| 734 | 0 |  |  | 0 | 1 | 0 | my ($class,$class_data,$ds_data) = @_; | 
| 735 | 0 |  |  |  |  | 0 | my %ds_data = %$ds_data; | 
| 736 | 0 |  |  |  |  | 0 | my $ds_class_name = delete $ds_data{is}; | 
| 737 | 0 | 0 |  |  |  | 0 | unless (my $ds_class_meta = UR::Object::Type->get($ds_class_name)) { | 
| 738 | 0 |  |  |  |  | 0 | die "No class $ds_class_name found!"; | 
| 739 |  |  |  |  |  |  | } | 
| 740 | 0 |  |  |  |  | 0 | my $ds = $ds_class_name->__define__(%ds_data); | 
| 741 | 0 | 0 |  |  |  | 0 | unless ($ds) { | 
| 742 | 0 |  |  |  |  | 0 | die "Failed to construct $ds_class_name: " . $ds_class_name->error_message(); | 
| 743 |  |  |  |  |  |  | } | 
| 744 | 0 |  |  |  |  | 0 | return $ds; | 
| 745 |  |  |  |  |  |  | } | 
| 746 |  |  |  |  |  |  |  | 
| 747 |  |  |  |  |  |  | sub ur_data_type_for_data_source_data_type { | 
| 748 | 0 |  |  | 0 | 0 | 0 | my($class,$type) = @_; | 
| 749 |  |  |  |  |  |  |  | 
| 750 | 0 |  |  |  |  | 0 | return [undef,undef];   # The default that should give reasonable behavior | 
| 751 |  |  |  |  |  |  | } | 
| 752 |  |  |  |  |  |  |  | 
| 753 |  |  |  |  |  |  |  | 
| 754 |  |  |  |  |  |  | # prepare_for_fork, do_after_fork_in_child, and finish_up_after_fork are no-op | 
| 755 |  |  |  |  |  |  | # here in the UR::DataSource base class and should be implented in subclasses | 
| 756 |  |  |  |  |  |  | # as needed. | 
| 757 | 2 |  |  | 2 | 0 | 6 | sub prepare_for_fork { return 1 } | 
| 758 | 2 |  |  | 2 | 0 | 14 | sub do_after_fork_in_child { return 1 } | 
| 759 | 2 |  |  | 2 | 0 | 20 | sub finish_up_after_fork { return 1 } | 
| 760 |  |  |  |  |  |  |  | 
| 761 |  |  |  |  |  |  | sub _resolve_owner_and_table_from_table_name { | 
| 762 | 546 |  |  | 546 |  | 486 | my($self, $table_name) = @_; | 
| 763 |  |  |  |  |  |  | # Basic data sources don't know about owners/schemas | 
| 764 | 546 |  |  |  |  | 893 | return (undef, $table_name); | 
| 765 |  |  |  |  |  |  | } | 
| 766 |  |  |  |  |  |  |  | 
| 767 |  |  |  |  |  |  | sub _resolve_table_and_column_from_column_name { | 
| 768 | 546 |  |  | 546 |  | 712 | my($self, $column_name) = @_; | 
| 769 |  |  |  |  |  |  | # Basic data sources don't know about tables | 
| 770 | 546 |  |  |  |  | 1077 | return (undef,$column_name); | 
| 771 |  |  |  |  |  |  | } | 
| 772 |  |  |  |  |  |  |  | 
| 773 |  |  |  |  |  |  | 1; |