File Coverage

lib/UR/DataSource/QueryPlan.pm
Criterion Covered Total %
statement 833 1064 78.2
branch 289 446 64.8
condition 116 192 60.4
subroutine 30 31 96.7
pod 0 5 0.0
total 1268 1738 72.9


line stmt bran cond sub pod time code
1             package UR::DataSource::QueryPlan;
2 143     143   4819 use strict;
  143         196  
  143         4277  
3 143     143   529 use warnings;
  143         172  
  143         3706  
4 143     143   482 use UR;
  143         164  
  143         777  
5             our $VERSION = "0.46"; # UR $VERSION;
6              
7             # this class is an evolving attempt to formalize
8             # the blob of cached value used for query construction
9              
10             class UR::DataSource::QueryPlan {
11             is => 'UR::Value',
12             id_by => [
13             rule_template => { is => 'UR::BoolExpr::Template', id_by => ['subject_class_name','logic_type','logic_detail','constant_values_id'] },
14             data_source => { is => 'UR::DataSource', id_by => 'data_source_id' },
15             ],
16             has => [
17             limit => { is => 'Integer', via => 'rule_template', to => 'limit' },
18             offset => { is => 'Integer', via => 'rule_template', to => 'offset' },
19             ],
20             has_transient => [
21             _is_initialized => { is => 'Boolean' },
22              
23             needs_further_boolexpr_evaluation_after_loading => { is => 'Boolean' },
24            
25             # data tracked for the whole query by property,alias,join_id
26             _delegation_chain_data => { is => 'HASH' },
27             _alias_data => { is => 'HASH' },
28             _join_data => { is => 'HASH' },
29              
30             # the old $alias_num
31             _alias_count => { is => 'Number' },
32            
33             # the old @sql_joins
34             _db_joins => { is => 'ARRAY' },
35            
36             # the new @obj_joins
37             _obj_joins => { is => 'ARRAY' },
38              
39             # the old all_table_properties, which has a small array of loading info
40             _db_column_data => { is => 'ARRAY' },
41              
42             # the old hashes by the same names
43             _group_by_property_names => { is => 'HASH' },
44             _order_by_property_names => { is => 'HASH' },
45              
46             _sql_filters => { is => 'ARRAY' },
47             _sql_params => { is => 'ARRAY' },
48            
49             lob_column_names => {},
50             lob_column_positions => {},
51             query_config => {},
52             post_process_results_callback => {},
53            
54             select_clause => {},
55             select_hint => {},
56             from_clause => {},
57             where_clause => {},
58             connect_by_clause => {},
59             group_by_clause => {},
60             order_by_columns => {},
61             order_by_non_column_data => {}, # flag that's true if asked to order_by something not in the data source
62            
63             sql_params => {},
64             filter_specs => {},
65            
66             property_names_in_resultset_order => {},
67              
68             rule_template_id => {},
69             rule_template_id_without_recursion_desc => {},
70             rule_template_without_recursion_desc => {},
71              
72             joins => {},
73             recursion_desc => {},
74             recurse_property_on_this_row => {},
75             recurse_property_referencing_other_rows => {},
76             recurse_resolution_by_iteration => {}, # For data sources that don't support recursive queries
77            
78             joins_across_data_sources => {}, # context _resolve_query_plan_for_ds_and_bxt
79             loading_templates => {},
80             class_name => {},
81             rule_matches_all => {},
82             rule_template_is_id_only => {},
83              
84             sub_typing_property => {},
85             class_table_name => {},
86             rule_template_specifies_value_for_subtype => {},
87             sub_classification_meta_class_name => {},
88             ]
89             };
90              
91              
92             sub _load {
93 3838     3838   5220 my $class = shift;
94 3838         4269 my $rule = shift;
95              
96             # See if the requested object is loaded.
97 3838         12469 my @loaded = $UR::Context::current->get_objects_for_class_and_rule($class,$rule,0);
98 3838 100       15291 return $class->context_return(@loaded) if @loaded;
99              
100             # Auto generate the object on the fly.
101 798         2185 my $id = $rule->value_for_id;
102 798 50       2116 unless (defined $id) {
103             #$DB::single = 1;
104 0         0 Carp::croak "No id specified for loading members of an infinite set ($class)!"
105             }
106 798         2313 my $class_meta = $class->__meta__;
107 798         1883 my @p = (id => $id);
108 798 50       2571 if (my $alt_ids = $class_meta->{id_by}) {
109 798 50       2243 if (@$alt_ids == 1) {
110 0         0 push @p, $alt_ids->[0] => $id;
111             }
112             else {
113 798         2992 my ($rule, %extra) = UR::BoolExpr->resolve_normalized($class, $rule);
114 798         2358 push @p, $rule->params_list;
115             }
116             }
117              
118 798         4194 my $obj = $UR::Context::current->_construct_object($class, @p);
119              
120 798 50       4338 if (my $method_name = $class_meta->sub_classification_method_name) {
121 0         0 my($rule, %extra) = UR::BoolExpr->resolve_normalized($class, $rule);
122 0         0 my $sub_class_name = $obj->$method_name;
123 0 0       0 if ($sub_class_name ne $class) {
124             # delegate to the sub-class to create the object
125 0         0 $UR::Context::current->_abandon_object($obj);
126 0         0 $obj = $UR::Context::current->_construct_object($sub_class_name,$rule);
127 0         0 $obj->__signal_change__("load");
128 0         0 return $obj;
129             }
130             # fall through if the class names match
131             }
132              
133 798         3226 $obj->__signal_change__("load");
134 798         2805 return $obj;
135             }
136              
137             # these hash keys are probably removable
138             # because they are not above, they will be deleted if _init sets them
139             # this exists primarily as a cleanup target list
140             my @extra = qw(
141             id_properties
142             direct_table_properties
143             all_table_properties
144             sub_classification_method_name
145             subclassify_by
146             properties_meta_in_resultset_order
147             all_properties
148             rule_specifies_id
149             all_id_property_names
150             id_property_sorter
151             properties_for_params
152             first_table_name
153             base_joins
154             parent_class_objects
155             );
156              
157             sub _init {
158 798     798   1336 my $self = shift;
159            
160 798 50       2011 Carp::confess("already initialized???") if $self->_is_initialized;
161              
162             # We could have this sub-classify by data source type, but right
163             # now it's conditional logic because we'll likely remove the distinctions.
164             # This will work because we'll separate out the ds-specific portion
165             # and call methods on the DS to get that part.
166 798         2995 my $ds = $self->data_source;
167 798 100       5477 if ($ds->isa("UR::DataSource::RDBMS")) {
    100          
168 655         2646 $self->_init_light();
169 655         2890 $self->_init_rdbms();
170             }
171             elsif ($ds->isa('UR::DataSource::Filesystem')) {
172 33         121 $self->_init_core();
173 33         129 $self->_init_filesystem();
174             }
175             else {
176             # Once all callers are using the API for this we won't need "_init".
177 110         437 $self->_init_core();
178 110 100       851 $self->_init_default() if $ds->isa("UR::DataSource::Default");
179             #$self->_init_remote_cache() if $ds->isa("UR::DataSource::RemoteCache");
180             }
181              
182             # This object is currently still used as a hashref, but the properties
183             # are a declaration of the part of the hashref data we are still dependent upon.
184             # This removes the other properties to ensure this is the case.
185             # Next steps are to clean up the code below to not produce the data,
186             # then this loop can throw an exception if extra untracked data is found.
187 798         5257 for my $key (keys %$self) {
188 43368 100       145583 next if $self->can($key);
189 10666         381428 delete $self->{$key};
190             }
191              
192 798         6515 $self->_is_initialized(1);
193 798         1690 return $self;
194             }
195              
196              
197             sub _determine_complete_order_by_list {
198 688     688   1224 my($self, $rule_template, $class_data, $db_property_data) = @_;
199              
200 688         1987 my $class_meta = $rule_template->subject_class_name->__meta__;
201 688   50     2336 my $order_by_columns = $class_data->{order_by_columns} || [];
202 688         1803 my $order_by = $rule_template->order_by;
203 688         2277 my $ds = $self->data_source;
204              
205 688         1107 my %order_by_property_names;
206             my $order_by_non_column_data;
207 688 100       1880 if ($order_by) {
208 43         101 my %db_property_data_map = map { $_->[1]->property_name => $_ } @$db_property_data;
  135         253  
209              
210             # we only pull back columns we're ordering by if there is ordering happening
211 43         89 my %is_descending;
212             my @column_data;
213 43         95 for my $name (@$order_by) {
214 48         81 my $order_by_prop = $name;
215 48 100       250 if ($order_by_prop =~ m/^(-|\+)(.*)$/) {
216 20         83 $order_by_prop = $2;
217 20         65 $is_descending{$order_by_prop} = $1 eq '-';
218             }
219              
220 48         216 my($order_by_prop_meta) = $class_meta->_concrete_property_meta_for_class_and_name($order_by_prop);
221 48 50       114 unless ($order_by_prop_meta) {
222 0         0 Carp::croak("Cannot order by '$name': Class "
223             . $class_meta->class_name
224             . " has no property named '$order_by_prop'");
225             }
226              
227 48 100       201 $name = ( $is_descending{$order_by_prop} ? '-' : '' ) . $order_by_prop_meta->property_name;
228 48 100       117 if ($order_by_property_names{$name} = $db_property_data_map{$order_by_prop_meta->property_name}) { # yes, single =
229 45         77 push @column_data, $order_by_property_names{$name};
230              
231 45         229 my $table_column_names = $ds->_select_clause_columns_for_table_property_data($column_data[-1]);
232 45         116 $is_descending{$table_column_names->[0]} = $is_descending{$order_by_prop}; # copy for table.column designation
233 45         133 $order_by_property_names{$table_column_names->[0]} = $order_by_property_names{$name};
234             } else {
235 3         10 $order_by_non_column_data = 1;
236             }
237             }
238              
239 43 100       135 if (@column_data) {
240 37         95 my $additional_order_by_columns = $ds->_select_clause_columns_for_table_property_data(@column_data);
241              
242             # Strip out columns named in the original $order_by_columns list that now appear in the
243             # additional order by list so we don't duplicate columns names, and the additional columns
244             # appear earlier in the list
245 37         69 my %additional_order_by_columns = map { $_ => 1 } @$additional_order_by_columns;
  45         117  
246 37         67 my @existing_order_by_columns = grep { ! $additional_order_by_columns{$_} } @$order_by_columns;
  37         110  
247 37 100       81 $order_by_columns = [ map { $is_descending{$_} ? '-'. $_ : $_ } ( @$additional_order_by_columns, @existing_order_by_columns ) ];
  76         245  
248             }
249             }
250 688         2678 $self->_order_by_property_names(\%order_by_property_names);
251 688         1734 return ($order_by_columns, $order_by_non_column_data);
252             }
253              
254              
255             sub _init_rdbms {
256 655     655   1052 my $self = shift;
257 655         2510 my $rule_template = $self->rule_template;
258 655         2180 my $ds = $self->data_source;
259              
260             # class-based values
261 655         2009 my $class_name = $rule_template->subject_class_name;
262 655         2703 my $class_meta = $class_name->__meta__;
263 655         2685 my $class_data = $ds->_get_class_data_for_loading($class_meta);
264              
265 655         1216 my @parent_class_objects = @{ $class_data->{parent_class_objects} };
  655         2132  
266 655         970 my @all_id_property_names = @{ $class_data->{all_id_property_names} };
  655         1733  
267 655         832 my @id_properties = @{ $class_data->{id_properties} };
  655         1497  
268            
269             #my $first_table_name = $class_data->{first_table_name};
270            
271             #my $id_property_sorter = $class_data->{id_property_sorter};
272             #my @lob_column_names = @{ $class_data->{lob_column_names} };
273 655         924 my @lob_column_positions = @{ $class_data->{lob_column_positions} };
  655         1605  
274             #my $query_config = $class_data->{query_config};
275             #my $post_process_results_callback = $class_data->{post_process_results_callback};
276             #my $class_table_name = $class_data->{class_table_name};
277              
278             # individual template based
279 655         3065 my $hints = $rule_template->hints;
280 655         1964 my %hints = map { $_ => 1 } @$hints;
  37         106  
281 655         1817 my $order_by = $rule_template->order_by;
282 655         1766 my $group_by = $rule_template->group_by;
283 655         2517 my $aggregate = $rule_template->aggregate;
284 655         1736 my $recursion_desc = $rule_template->recursion_desc;
285              
286 655         2432 my ($first_table_name, @db_joins) = _resolve_db_joins_for_inheritance($class_meta);
287            
288 655         3317 $self->_db_joins(\@db_joins);
289 655         2609 $self->_obj_joins([]);
290              
291             # an array of arrays, containing $table_name, $column_name, $alias, $object_num
292             # as joins are done we extend this, and then condense it into object fabricators
293 655         863 my @db_property_data = @{ $class_data->{all_table_properties} };
  655         1762  
294              
295 655         936 my %group_by_property_names;
296 655 100       1778 if ($group_by) {
297             # we only pull back columns we're grouping by or aggregating if there is grouping happening
298 13         35 for my $name (@$group_by) {
299 3 50       23 unless ($class_name->can($name)) {
300 0         0 Carp::croak("Cannot group by '$name': Class $class_name has no property/method by that name");
301             }
302 3         38 $group_by_property_names{$name} = 1;
303             }
304 13         26 for my $data (@db_property_data) {
305 47         91 my $name = $data->[1]->property_name;
306 47 100       82 if ($group_by_property_names{$name}) {
307 1         2 $group_by_property_names{$name} = $data;
308             }
309             }
310 13         37 @db_property_data = grep { ref($_) } values %group_by_property_names;
  3         9  
311             }
312              
313 655         2773 my($order_by_columns, $order_by_non_column_data)
314             = $self->_determine_complete_order_by_list($rule_template, $class_data,\@db_property_data);
315              
316 655         2541 $self->_db_column_data(\@db_property_data);
317 655         2137 $self->_group_by_property_names(\%group_by_property_names);
318              
319             # Find out what delegated properties we'll be dealing with
320 655         962 my @sql_filters;
321             my @delegated_properties;
322 655         929 do {
323             my %filters =
324 1059         2864 map { $_ => $rule_template->operator_for($_) }
325 655         2833 grep { substr($_,0,1) ne '-' }
  1059         2551  
326             $rule_template->_property_names;
327              
328 655 100 66     2886 unless (@all_id_property_names == 1 && $all_id_property_names[0] eq "id") {
329 630         1025 delete $filters{'id'};
330             }
331              
332             # Remove the flag for descending/ascending sort
333 655 100       1973 my @order_by_properties = $order_by ? @$order_by : ();
334 655         1622 s/^-|\+// foreach @order_by_properties;
335              
336 655 50       3625 my %properties_involved = map { $_ => 1 }
  991 100       2309  
337             keys(%filters),
338             ($hints ? @$hints : ()),
339             @order_by_properties,
340             ($group_by ? @$group_by : ());
341              
342 655         2356 my @properties_involved = sort keys(%properties_involved);
343 655         995 my @errors;
344 655         2061 while (my $property_name = shift @properties_involved) {
345 985 100       2897 if (index($property_name,'.') != -1) {
346 18         31 push @delegated_properties, $property_name;
347 18         52 next;
348             }
349              
350 967         3093 my (@pmeta) = $class_meta->property_meta_for_name($property_name);
351 967 50       2292 unless (@pmeta) {
352 0 0       0 if ($class_name->can($property_name)) {
353             # method, not property
354 0         0 next;
355             }
356             else {
357 0         0 push @errors, "Class ".$class_meta->id." has no property or method named '$property_name'";
358 0         0 next;
359             }
360             }
361              
362             # For each property in this list, go up the inheritance and find the right property
363             # to query on. Give priority to properties that actually have columns
364             FIND_PROPERTY_WITH_COLUMN:
365 967         1781 foreach my $pmeta ( @pmeta ) {
366 967         33544 foreach my $candidate_class ( $class_meta->all_class_metas ) {
367 1276         4046 my $candidate_prop_meta = UR::Object::Property->get(class_name => $candidate_class->class_name,
368             property_name => $property_name);
369 1276 100       3286 next unless $candidate_prop_meta;
370 989 100       2684 if ($candidate_prop_meta->column_name) {
371 850         1178 $pmeta = $candidate_prop_meta;
372 850         2267 next FIND_PROPERTY_WITH_COLUMN;
373             }
374             }
375             }
376              
377 967         1299 my $property = $pmeta[0];
378 967         4101 my $table_name = $property->class_meta->first_table_name;
379 967         3011 my $operator = $rule_template->operator_for($property_name);
380 967         2645 my $value_position = $rule_template->value_position_for_property_name($property_name);
381              
382 967 50       4439 if ($property->can("expr_sql")) {
383 0 0       0 unless ($table_name) {
384 0         0 $ds->warning_message("Property '$property_name' of class '$class_name' can 'expr_sql' but has no table!");
385 0         0 next;
386             }
387 0         0 my $expr_sql = $property->expr_sql;
388 0 0       0 if (exists $filters{$property_name}) {
389 0         0 my @coercion = $self->data_source->cast_for_data_conversion(
390             $property->_data_type_as_class_name,
391             'UR::Value::String', # We can't know here what the type should be
392             $operator,
393             'where');
394 0         0 push @sql_filters,
395             $table_name => {
396             # cheap hack of prefixing with a whitespace differentiates
397             # from a regular column below
398             " " . $expr_sql => {
399             operator => $operator,
400             value_position => $value_position,
401             left_coercion => $coercion[0],
402             right_coercion => $coercion[1],
403             }
404             };
405             }
406 0         0 next;
407             }
408              
409             # If the property is calculate and has a calculate_from list, add the
410             # calculate_from things to the internal hints list, but not the template
411 967 100 66     28878 if ($property->is_calculated and $property->calculate_from) {
412 9         31 my $calculate_from = $property->calculate_from;
413 9         22 push @properties_involved, @$calculate_from;
414 9         17 push @$hints, @$calculate_from;
415 9         39 $hints{$_} = 1 foreach @$calculate_from;
416             }
417              
418 967 100 100     6421 if (exists($filters{$property_name}) and $filters{$property_name} eq 'isa') {
    100 66        
    100          
    100          
419             # RDBMS databases can't do 'isa'
420 2         10 $self->needs_further_boolexpr_evaluation_after_loading(1);
421 2         8 next;
422             }
423             elsif (my $column_name = $property->column_name) {
424             # normal column: filter on it
425 848 50       1953 unless ($table_name) {
426 0         0 $ds->warning_message("Property '$property_name' of class '$class_name' has column '$column_name' but has no table!");
427 0         0 next;
428             }
429 848 100       2464 if (exists $filters{$property_name}) {
430 809         2344 my @coercion = $self->data_source->cast_for_data_conversion(
431             $property->_data_type_as_class_name,
432             'UR::Value::String', # We can't know here what the type should be
433             $operator,
434             'where');
435 809         6596 push @sql_filters,
436             $table_name => {
437             $column_name => {
438             operator => $operator,
439             value_position => $value_position,
440             left_coercion => $coercion[0],
441             right_coercion => $coercion[1],
442             }
443             };
444             }
445             }
446             elsif ($property->is_delegated) {
447 76         244 push @delegated_properties, $property->property_name;
448             }
449             elsif ( ! exists($hints{$property_name}) or exists($filters{$property_name}) ) {
450 33         151 $self->needs_further_boolexpr_evaluation_after_loading(1);
451             }
452             else {
453 8         30 next;
454             }
455              
456             } # end of properties in the expression which control the query content
457              
458 655 50       2517 if (@errors) {
459 0         0 my $class_name = $class_meta->class_name;
460 0         0 $ds->error_message("ERRORS PROCESSING PARAMTERS: (" . join("\n", @errors) . ") used to generate SQL for $class_name!");
461             #print Data::Dumper::Dumper($rule_template);
462 0         0 Carp::croak("Can't continue");
463             }
464             };
465              
466 655         1138 my $object_num = 0;
467 655         2879 $self->_alias_count(0);
468              
469 655         937 my %hints_included;
470             my @select_hint;
471              
472             # FIXME - this needs to be broken out into delegated-property-join-resolver
473             # and inheritance-join-resolver methods that can be called recursively.
474             # It would better encapsulate what's going on and avoid bugs with complicated
475             # get()s
476              
477             # one iteration per target value involved in the query,
478             # including values needed for filtering, ordering, grouping, and hints (selecting more)
479             # these "properties" may be a single property name or an ad-hoc "chain"
480             DELEGATED_PROPERTY:
481 655         1897 for my $delegated_property (sort @delegated_properties) {
482 94         190 my $property_name = $delegated_property;
483 94   66     350 my $delegation_chain_data = $self->_delegation_chain_data || $self->_delegation_chain_data({});
484 94         365 $delegation_chain_data->{"__all__"}{table_alias} = {};
485 94         342 $delegation_chain_data->{"__all__"}{class_alias} = { $first_table_name => $class_meta };
486              
487 94         444 my ($final_accessor, $is_optional, @joins) = _resolve_object_join_data_for_property_chain($rule_template,$property_name,$property_name);
488              
489             # when there is no "final_accessor" it often means we have an object-accessor in a hint
490             # we want that to go through the join process, and only be left out at filter construction time
491             #unless ($final_accessor) {
492             #$self->needs_further_boolexpr_evaluation_after_loading(1);
493             #next;
494             #}
495              
496             # this is gathered here and used below, but previously was gathered internally to the methods which take it
497             # since it is no longer needed directly in this method it might be refactored into the places which use it
498 94         154 my %ds_for_class;
499 94         191 for my $join (@joins) {
500 223         767 my $source_class_object = $join->{'source_class'}->__meta__;
501 223         704 my ($source_data_source) = UR::Context->resolve_data_sources_for_class_meta_and_rule($source_class_object, $rule_template);
502 223         413 $ds_for_class{$join->{'source_class'}} = $source_data_source;
503            
504 223         898 my $foreign_class_object = $join->{'foreign_class'}->__meta__;
505 223         543 my ($foreign_data_source) = UR::Context->resolve_data_sources_for_class_meta_and_rule($foreign_class_object, $rule_template);
506 223         537 $ds_for_class{$join->{'foreign_class'}} = $foreign_data_source;
507             }
508              
509              
510             # Splice out joins that go through a UR::Value class and back out to the DB, since UR::Value-types
511             # don't get stored in the DB
512             # TODO: move this into the join creation logic
513 94         389 for (my $i = 0; $i < @joins; $i++) {
514 218 100 100     1429 if (
      66        
515             $i < $#joins
516             and
517             (
518             # db -> UR::Value -> db : shortcut
519             $joins[$i]->{'foreign_class'}->isa('UR::Value')
520             and $joins[$i+1]->{'source_class'}->isa('UR::Value')
521             #and $joins[$i]->{'foreign_class'}->isa($joins[$i+1]->{'source_class'}) ## remove this?
522             )
523             ) {
524             my $fixed_join = UR::Object::Join->_get_or_define(
525             source_class => $joins[$i]->{'source_class'},
526             source_property_names => $joins[$i]->{'source_property_names'},
527             foreign_class => $joins[$i+1]->{'foreign_class'},
528             foreign_property_names => $joins[$i+1]->{'foreign_property_names'},
529             is_optional => $joins[$i]->{'is_optional'},
530 5         54 id => $joins[$i]->{id} . "->" . $joins[$i+1]->{id});
531 5 100       27 if ($joins[$i+1]->{where}) {
532             # If there's a where involved, it will always be on the second thing,
533             # where the foreign_class is NOT a UR::Value
534 1         3 $fixed_join->{where} = $joins[$i+1]->{where};
535             }
536 5         22 splice(@joins, $i, 2, $fixed_join);
537             }
538             }
539              
540 94 100 66     771 if (@joins and $joins[-1]{foreign_class}->isa("UR::Value")) {
541             # the final join in a chain is often the link between a primitive value
542             # and the UR::Value subclass into which it falls ...irrelevent for db joins
543 86         359 $final_accessor = $joins[-1]->source_property_names->[0];
544 86         149 pop @joins;
545 86 50       234 next DELEGATED_PROPERTY unless @joins;
546             }
547              
548 94         155 my $last_class_object_excluding_inherited_joins;
549             my $alias_for_property_value;
550              
551             # one iteration per table between the start table and target
552 94         317 while (my $object_join = shift @joins) {
553 132         189 $object_num++;
554 132         226 my @joins_for_object = ($object_join);
555              
556             # one iteration per layer of inheritance for this object
557             # or per case of a join having additional filtering
558 132         164 my $current_inheritance_depth_for_this_target_join = 0;
559 132         336 while (my $join = shift @joins_for_object) {
560              
561 148         261 my $where = $join->{where};
562              
563 148         173 $current_inheritance_depth_for_this_target_join++;
564              
565 148         233 my $foreign_class_name = $join->{foreign_class};
566 148   33     719 my $foreign_class_object = $join->{'foreign_class_meta'} || $foreign_class_name->__meta__;
567              
568 148 100 66     1017 if ($foreign_class_object->join_hint and !($hints_included{$foreign_class_name}++)) {
569 1         4 push @select_hint, $foreign_class_object->join_hint;
570             }
571              
572 148 50       412 if (not exists $ds_for_class{$foreign_class_name}) {
573             # error: we should have at least a key with an empty value if we tried to find the ds
574 0         0 die "no data source key for $foreign_class_name when adding a join?"
575             }
576              
577 148         228 my $ds = $ds_for_class{$foreign_class_name};
578              
579 148 50       360 if (not $ds) {
580             # no ds for the next piece of data: we will have to resolve this on the client side
581             # this is where things may get slow if the query is insufficiently filtered
582 0         0 $self->needs_further_boolexpr_evaluation_after_loading(1);
583 0         0 next DELEGATED_PROPERTY;
584             }
585              
586             my $alias = $self->_add_join(
587             $delegated_property,
588             $join,
589             $object_num,
590             $is_optional,
591             $final_accessor,
592 148         617 $ds_for_class{$foreign_class_name},
593             );
594              
595 148 100       376 if (not $alias) {
596             # unable to add a join for another reason
597             # TODO: is the above the only valid case of a join being impossible?
598             # Can we remove this?
599 5         18 $self->needs_further_boolexpr_evaluation_after_loading(1);
600 5         26 next DELEGATED_PROPERTY;
601             }
602              
603             # set these for after all of the joins are done
604 143         205 my $last_class_name = $foreign_class_name;
605 143         173 my $last_class_object = $foreign_class_object;
606              
607             # on the first iteration, we figure out the remaining inherited iterations
608             # if there is inheritance to do, unshift those onto the stack ahead of other things
609 143 100       358 if ($current_inheritance_depth_for_this_target_join == 1) {
610 127 100 100     556 if ($final_accessor and $last_class_object->property_meta_for_name($final_accessor)) {
611 96         144 $last_class_object_excluding_inherited_joins = $last_class_object;
612             }
613 127         398 my @parents = grep { $_->table_name } $foreign_class_object->ancestry_class_metas;
  270         586  
614 127 100       392 if (@parents) {
615 16         53 my @last_id_property_names = $foreign_class_object->id_property_names;
616 16         35 for my $parent (@parents) {
617 16         60 my @parent_id_property_names = $parent->id_property_names;
618 16 50       45 die if @parent_id_property_names > 1;
619 16         45 my $parent_join_foreign_class_name = $parent->class_name;
620 16         147 my $inheritance_join = UR::Object::Join->_get_or_define(
621             source_class => $last_class_name,
622             source_property_names => [@last_id_property_names], # we change content below
623             foreign_class => $parent_join_foreign_class_name,
624             foreign_property_names => \@parent_id_property_names,
625             is_optional => $is_optional,
626             id => "${last_class_name}::" . join(',',@last_id_property_names),
627             );
628 16         33 unshift @joins_for_object, $inheritance_join;
629 16         35 @last_id_property_names = @parent_id_property_names;
630 16         23 $last_class_name = $foreign_class_name;
631              
632 16         66 my $foreign_class_object = $parent_join_foreign_class_name->__meta__;
633 16         61 my ($foreign_data_source) = UR::Context->resolve_data_sources_for_class_meta_and_rule($foreign_class_object, $rule_template);
634 16         48 $ds_for_class{$parent_join_foreign_class_name} = $foreign_data_source;
635             }
636 16         61 next;
637             }
638             }
639              
640 127 50 66     872 if (!@joins and not $alias_for_property_value) {
641             # we are out of joins for this delegated property
642             # setting $alias_for_property_value helps map to exactly where we do real filter/order/etc.
643 89         347 my $foreign_class_loading_data = $ds->_get_class_data_for_loading($foreign_class_object);
644 89 100 100     388 if ($final_accessor and
645 281         549 grep { $_->[1]->property_name eq $final_accessor } @{ $foreign_class_loading_data->{direct_table_properties} }
  88         228  
646             ) {
647 77         908 $alias_for_property_value = $alias;
648             #print "found alias for $property_name on $foreign_class_name: $alias\n";
649             }
650             else {
651             # The thing we're joining to isn't a database-backed column (maybe calculated?)
652 12         58 $self->needs_further_boolexpr_evaluation_after_loading(1);
653 12         129 next DELEGATED_PROPERTY;
654             }
655             }
656              
657             } # next join in the inheritance for this object
658              
659             } # next join across objects from the query subject to the delegated property target
660              
661             # done adding any new joins for this delegated property/property-chain
662              
663             # now see if anything in the where-clause needs to filter on the item joined-to
664 77         381 my $value_position = $rule_template->value_position_for_property_name($property_name);
665 77 100       279 if (defined $value_position) {
666             # this property _is_ used to filter results
667 62 50       162 if (not $final_accessor) {
668             # on the client side :(
669 0         0 $self->needs_further_boolexpr_evaluation_after_loading(1);
670 0         0 next;
671             }
672             else {
673             # at the database level :)
674 62         218 my $final_accessor_property_meta = $last_class_object_excluding_inherited_joins->property_meta_for_name($final_accessor);
675 62 50       189 unless ($final_accessor_property_meta) {
676 0         0 Carp::croak("No property metadata for property named '$final_accessor' in class "
677             . $last_class_object_excluding_inherited_joins->class_name
678             . " while resolving joins for property '" . $delegated_property->property_name . "' in class "
679             . $delegated_property->class_name);
680             }
681              
682 62         105 my $sql_lvalue;
683 62 50       194 if ($final_accessor_property_meta->is_calculated) {
684 0         0 $sql_lvalue = $final_accessor_property_meta->calculate_sql;
685 0 0       0 unless (defined($sql_lvalue)) {
686 0         0 $self->needs_further_boolexpr_evaluation_after_loading(1);
687 0         0 next;
688             }
689             }
690             else {
691 62         177 $sql_lvalue = $final_accessor_property_meta->column_name;
692 62 50       192 unless (defined($sql_lvalue)) {
693 0         0 Carp::confess("No column name set for non-delegated/calculated property $property_name of $class_name");
694             }
695             }
696              
697 62         232 my $operator = $rule_template->operator_for($property_name);
698              
699 62 50       194 unless ($alias_for_property_value) {
700 0         0 die "No alias found for $property_name?!";
701             }
702              
703 62         246 my @coercion = $self->data_source->cast_for_data_conversion(
704             $final_accessor_property_meta->_data_type_as_class_name,
705             'UR::Value::String', # We can't know here what the type should be
706             $operator,
707             'where');
708              
709 62         562 push @sql_filters,
710             $alias_for_property_value => {
711             $sql_lvalue => {
712             operator => $operator,
713             value_position => $value_position,
714             left_coercion => $coercion[0],
715             right_coercion => $coercion[1],
716             }
717             };
718             }
719             }
720            
721             } # next delegated property
722              
723             # the columns to query
724 655         1985 my $db_property_data = $self->_db_column_data;
725              
726             # the following two sets of variables hold the net result of the logic
727 655         942 my $select_clause;
728             my $from_clause;
729 0         0 my $connect_by_clause;
730 0         0 my $group_by_clause;
731              
732             # Build the SELECT clause explicitly.
733 655         3442 $select_clause = $ds->_select_clause_for_table_property_data(@$db_property_data);
734              
735             # Oracle places group_by in a comment in the select
736 655 100       4834 unshift(@select_hint, $class_meta->select_hint) if $class_meta->select_hint;
737              
738             # Build the FROM clause base.
739             # Add joins to the from clause as necessary, then
740 655 50       2244 $from_clause = (defined $first_table_name ? "$first_table_name" : '');
741              
742 655         884 my $cnt = 0;
743 655         890 my @sql_params;
744 655         862 my @sql_joins = @{ $self->_db_joins };
  655         1805  
745 655         1955 while (@sql_joins) {
746 185         281 my $table_name = shift (@sql_joins);
747 185         251 my $condition = shift (@sql_joins);
748 185         815 my ($table_alias) = ($table_name =~ /(\S+)\s*$/s);
749              
750 185         218 my $join_type;
751 185 100       882 if ($condition->{-is_required}) {
752 48         77 $join_type = 'INNER';
753             }
754             else {
755 137         172 $join_type = 'LEFT';
756             }
757              
758 185         503 $from_clause .= "\n$join_type join " . $table_name . " on ";
759             # Restart the counter on each join for the from clause,
760             # but for the where clause keep counting w/o reset.
761 185         205 $cnt = 0;
762              
763 185         458 for my $column_name (keys %$condition) {
764 272 100       742 next if substr($column_name,0,1) eq '-';
765              
766 224         312 my $linkage_data = $condition->{$column_name};
767 224 50       645 my $expr_sql = (substr($column_name,0,1) eq " " ? $column_name : "${table_alias}.${column_name}");
768             my ($operator, $value_position, $value, $link_table_name, $link_column_name, $left_coercion, $right_coercion)
769 224         632 = @$linkage_data{qw/operator value_position value link_table_name link_column_name left_coercion right_coercion/};
770              
771 224 100       753 $expr_sql = sprintf($right_coercion, $expr_sql) if ($right_coercion);
772              
773 224 100       482 $from_clause .= "\n and " if ($cnt++);
774              
775 224 100 66     923 if ($link_table_name and $link_column_name) {
    50          
776             # the linkage data is a join specifier
777 185         333 my $link_sql = "${link_table_name}.${link_column_name}";
778 185 50       518 $link_sql = sprintf($left_coercion, $link_sql) if ($left_coercion);
779 185         709 $from_clause .= "$link_sql = $expr_sql";
780             }
781             elsif (defined $value_position) {
782 0         0 Carp::croak("Joins cannot use variable values currently!");
783             }
784             else {
785 39         240 my ($more_sql, @more_params) = $ds->_extend_sql_for_column_operator_and_value($expr_sql, $operator, $value);
786 39 50       87 if ($more_sql) {
787 39         61 $from_clause .= $more_sql;
788 39         123 push @sql_params, @more_params;
789             }
790             else {
791             # error
792 0         0 return;
793             }
794             }
795             } # next column
796             } # next db join
797              
798             # build the WHERE clause by making a data structure which will be parsed outside of this module
799             # special handling of different size lists, and NULLs, make a completely reusable SQL template very hard.
800 655         1109 my @filter_specs;
801 655         1752 while (@sql_filters) {
802 871         1222 my $table_name = shift (@sql_filters);
803 871         1168 my $condition = shift (@sql_filters);
804 871         3155 my ($table_alias) = ($table_name =~ /(\S+)\s*$/s);
805              
806 871         2349 for my $column_name (keys %$condition) {
807 871         1323 my $linkage_data = $condition->{$column_name};
808 871 50       2981 my $expr_sql = (substr($column_name,0,1) eq " " ? $column_name : "${table_alias}.${column_name}");
809             my ($operator, $value_position, $value, $link_table_name, $link_column_name, $left_coercion, $right_coercion)
810 871         2522 = @$linkage_data{qw/operator value_position value link_table_name
811             link_column_name left_coercion right_coercion/};
812              
813 871 50 33     2510 if ($link_table_name and $link_column_name) {
814             # the linkage data is a join specifier
815 0         0 Carp::confess("explicit column linkage in where clause?");
816             #$sql .= "${link_table_name}.${link_column_name} = $expr_sql";
817             }
818             else {
819             # the linkage data is a value position from the @values list
820 871 50       1853 unless (defined $value_position) {
821 0         0 Carp::confess("No value position for $column_name in query!");
822             }
823              
824 871         3053 $expr_sql = sprintf($left_coercion, $expr_sql);
825              
826 871         4384 push @filter_specs, [$expr_sql, $operator, $value_position];
827             }
828             } # next column
829             } # next db filter
830              
831 655         1125 $connect_by_clause = '';
832 655         897 my $recurse_resolution_by_iteration = 0;
833 655 100       1709 if ($recursion_desc) {
834 5 50       16 unless (ref($recursion_desc) eq 'ARRAY') {
835 0         0 Carp::croak("Recursion description must be an arrayref with exactly 2 items");
836             }
837 5 50       14 if (@$recursion_desc != 2) {
838 0         0 Carp::croak("Recursion description must contain exactly 2 items; got ".scalar(@$recursion_desc)
839             . ': ' . join(', ',@$recursion_desc));
840             }
841              
842             # Oracle supports "connect by" queries.
843 5 50       18 if ($ds->does_support_recursive_queries eq 'connect by') {
844 0         0 my ($this,$prior) = @{ $recursion_desc };
  0         0  
845              
846 0         0 my $this_property_meta = $class_meta->property_meta_for_name($this);
847 0 0       0 unless ($this_property_meta) {
848 0         0 Carp::croak("Class ".$class_meta->class_name." has no property named '$this', named in the recursion description");
849             }
850 0         0 my $prior_property_meta = $class_meta->property_meta_for_name($prior);
851 0 0       0 unless ($prior_property_meta) {
852 0         0 Carp::croak("Class ".$class_meta->class_name." has no property named '$prior', named in the recursion description");
853             }
854              
855 0         0 my $this_class_meta = $this_property_meta->class_meta;
856 0         0 my $prior_class_meta = $prior_property_meta->class_meta;
857              
858 0         0 my $this_table_name = $this_class_meta->table_name;
859 0 0       0 unless ($this_table_name) {
860 0         0 Carp::croak("Cannot resolve table name from class ".$class_meta->class_name." and property '$this', named in the recursion description");
861             }
862 0         0 my $prior_table_name = $prior_class_meta->table_name;
863 0 0       0 unless ($prior_table_name) {
864 0         0 Carp::croak("Cannot resolve table name from class ".$class_meta->class_name." and property '$prior', named in the recursion description");
865             }
866              
867 0   0     0 my $this_column_name = $this_property_meta->column_name || $this;
868 0   0     0 my $prior_column_name = $prior_property_meta->column_name || $prior;
869              
870 0         0 $connect_by_clause = "connect by $this_table_name.$this_column_name = prior $prior_table_name.$prior_column_name\n";
871             } else {
872 5         11 $recurse_resolution_by_iteration = 1;
873             }
874             }
875              
876 655         857 my @property_names_in_resultset_order;
877 655         1368 for my $property_meta_array (@$db_property_data) {
878 3164         6144 push @property_names_in_resultset_order, $property_meta_array->[1]->property_name;
879             }
880              
881             # this is only used when making a real instance object instead of a "set"
882 655         904 my $per_object_in_resultset_loading_detail;
883 655 100       1620 unless ($group_by) {
884 642         2084 $per_object_in_resultset_loading_detail = $ds->_generate_loading_templates_arrayref(\@$db_property_data, $self->_obj_joins);
885             }
886              
887 655 100       1864 if ($group_by) {
888             # when grouping, we're making set objects instead of regular objects
889             # this means that we re-constitute the select clause and add a group_by clause
890 13 100       36 $group_by_clause = 'group by ' . $select_clause if (scalar(@$group_by));
891              
892             # Q: - does it even make sense for the user to specify an order_by in the
893             # get() request for Set objects? If so, then we need to concatonate these order_by_columns
894             # with the ones that already exist in $order_by_columns from the class data
895             # A: - yes, because group by means "return a list of subsets", and this lets you sort the subsets
896 13         35 $order_by_columns = $ds->_select_clause_columns_for_table_property_data(@$db_property_data);
897              
898 13 100       35 $select_clause .= ', ' if $select_clause;
899 13         24 $select_clause .= 'count(*) count';
900 13         24 for my $ag (@$aggregate) {
901 10 100       29 next if $ag eq 'count';
902             # TODO: translate property names to column names, and skip non-column properties
903 4         7 $select_clause .= ', ' . $ag;
904             }
905 13 50       36 unless (@$group_by == @$db_property_data) {
906 0         0 print "mismatch table properties vs group by!\n";
907             }
908             }
909              
910 655 100       33391 %$self = (
911             %$self,
912              
913             # custom for RDBMS
914             select_clause => $select_clause,
915             select_hint => scalar(@select_hint) ? \@select_hint : undef,
916             from_clause => $from_clause,
917             connect_by_clause => $connect_by_clause,
918             group_by_clause => $group_by_clause,
919             order_by_columns => $order_by_columns,
920             order_by_non_column_data => $order_by_non_column_data,
921             filter_specs => \@filter_specs,
922             sql_params => \@sql_params,
923             recurse_resolution_by_iteration => $recurse_resolution_by_iteration,
924              
925             # override defaults in the regular datasource $parent_template_data
926             property_names_in_resultset_order => \@property_names_in_resultset_order,
927             properties_meta_in_resultset_order => $db_property_data, # duplicate?!
928             loading_templates => $per_object_in_resultset_loading_detail,
929             );
930              
931 655         5522 my $template_data = $rule_template->{loading_data_cache} = $self;
932 655         8488 return $self;
933             }
934              
935             sub _init_filesystem {
936 33     33   50 my $self = shift;
937 33         106 my $rule_template = $self->rule_template;
938 33         106 my $ds = $self->data_source;
939              
940             # class-based values
941 33         100 my $class_name = $rule_template->subject_class_name;
942 33         114 my $class_meta = $class_name->__meta__;
943 33         116 my $class_data = $ds->_get_class_data_for_loading($class_meta);
944              
945 33         58 my @db_property_data = @{ $class_data->{all_table_properties} };
  33         94  
946              
947 33         142 my($order_by_columns, $order_by_non_column_data)
948             = $self->_determine_complete_order_by_list($rule_template, $class_data, \@db_property_data);
949              
950 33         803 %$self = (
951             %$self,
952              
953             order_by_columns => $order_by_columns,
954             order_by_non_column_data => $order_by_non_column_data,
955             );
956              
957 33         211 my $template_data = $rule_template->{loading_data_cache} = $self;
958 33         205 return $self;
959             }
960              
961             sub _add_join {
962 148     148   274 my ($self,
963             $property_name,
964             $join,
965             $object_num,
966             $is_optional,
967             $final_accessor,
968             $foreign_data_source,
969             ) = @_;
970              
971 148   33     401 my $delegation_chain_data = $self->_delegation_chain_data || $self->_delegation_chain_data({});
972 148   50     509 my $table_alias = $delegation_chain_data->{"__all__"}{table_alias} ||= {};
973 148   100     704 my $source_table_and_column_names = $delegation_chain_data->{$property_name}{latest_source_table_and_column_names} ||= [];
974              
975 148         240 my $source_class_name = $join->{source_class};
976 148   33     661 my $source_class_object = $join->{'source_class_meta'} || $source_class_name->__meta__;
977              
978 148   50     415 my $class_alias = $delegation_chain_data->{"__all__"}{class_alias} ||= {};
979 148 50 33     489 if (! %$class_alias and $source_class_object->table_name) {
980 0         0 $class_alias->{$source_class_object->table_name} = $source_class_object;
981             }
982              
983 148         209 my $foreign_class_name = $join->{foreign_class};
984 148   33     920 my $foreign_class_object = $join->{'foreign_class_meta'} || $foreign_class_name->__meta__;
985              
986 148         507 my $rule_template = $self->rule_template;
987 148         406 my $ds = $self->data_source;
988              
989 148         431 my $group_by = $rule_template->group_by;
990            
991             #my($foreign_data_source) = UR::Context->resolve_data_sources_for_class_meta_and_rule($foreign_class_object, $rule_template);
992 148 100 66     921 if (!$foreign_data_source or ($foreign_data_source ne $ds)) {
993             # FIXME - do something smarter in the future where it can do a join-y thing in memory
994 4         16 $self->needs_further_boolexpr_evaluation_after_loading(1);
995 4         11 return;
996             }
997              
998 144         581 my $foreign_class_loading_data = $ds->_get_class_data_for_loading($foreign_class_object);
999              
1000             # This will get filled in during the first pass, and every time after we've successfully
1001             # performed a join - ie. that the delegated property points directly to a class/property
1002             # that is a real table/column, and not a tableless class or another delegated property
1003 144         221 my @source_property_names;
1004 144 100       419 unless (@$source_table_and_column_names) {
1005 138         170 @source_property_names = @{ $join->{source_property_names} };
  138         450  
1006              
1007             @$source_table_and_column_names =
1008             map {
1009 137 100       856 if (my($view, $alias) = $ds->parse_view_and_alias_from_inline_view($_->[0])) {
1010             # This "table_name" was actually a bit of SQL with an inline view and an alias
1011 8         11 $_->[0] = $view;
1012 8         13 $_->[2] = $alias;
1013             }
1014 137         340 $_;
1015             }
1016             map {
1017 138         273 my($p) = $source_class_object->_concrete_property_meta_for_class_and_name($_);
  138         602  
1018 138 50       400 unless ($p) {
1019 0         0 Carp::croak("No property $_ for class ".$source_class_object->class_name);
1020             }
1021 138         603 my($table_name,$column_name) = $p->table_and_column_name_for_property();
1022 138 100 66     693 if ($table_name && $column_name) {
1023 137         417 [$table_name, $column_name];
1024             } else {
1025             #Carp::confess("Can't determine table and column for property $_ in class " .
1026             # $source_class_object->class_name);
1027 1         4 ();
1028             }
1029             }
1030             @source_property_names;
1031             }
1032 144 100       380 return unless @$source_table_and_column_names;
1033              
1034             #my @source_property_names = @{ $join->{source_property_names} };
1035             #my ($source_table_name, $fcols, $fprops) = $self->_resolve_table_and_column_data($source_class_object, @source_property_names);
1036             #my @source_column_names = @$fcols;
1037             #my @source_property_meta = @$fprops;
1038              
1039 143         169 my @foreign_property_names = @{ $join->{foreign_property_names} };
  143         368  
1040 143         547 my ($foreign_table_name, $fcols, $fprops) = $self->_resolve_table_and_column_data($foreign_class_object, @foreign_property_names);
1041 143         266 my @foreign_column_names = @$fcols;
1042 143         217 my @foreign_property_meta = @$fprops;
1043            
1044 143 50       349 unless (@foreign_column_names) {
1045             # all calculated properties: don't try to join any further
1046 0         0 return;
1047             }
1048              
1049 143 50       321 unless ($foreign_table_name) {
1050             # If we can't make the join because there is no datasource representation
1051             # for this class, we're done following the joins for this property
1052             # and will NOT try to filter on it at the datasource level
1053 0         0 $self->needs_further_boolexpr_evaluation_after_loading(1);
1054 0         0 return;
1055             }
1056            
1057 143 50       375 unless (@foreign_column_names == @foreign_property_meta) {
1058             # some calculated properties, be sure to re-check for a match after loading the object
1059 0         0 $self->needs_further_boolexpr_evaluation_after_loading(1);
1060             }
1061              
1062 143         492 my $alias = $self->_get_join_alias($join, $property_name);
1063              
1064 143 50       370 unless ($alias) {
1065 143         459 my $alias_num = $self->_alias_count($self->_alias_count+1);
1066            
1067 143   66     474 my $alias_name = $join->sub_group_label || $property_name;
1068 143 50       462 if (substr($alias_name,-1) eq '?') {
1069 0 0       0 chop($alias_name) if substr($alias_name,-1) eq '?';
1070             }
1071              
1072 143         283 my $alias_length = length($alias_name)+length($alias_num)+1;
1073 143         160 my $alias_max_length = 29;
1074 143 50       326 if ($alias_length > $alias_max_length) {
1075 0         0 $alias = substr($alias_name,0,$alias_max_length-length($alias_num)-1);
1076             }
1077             else {
1078 143         197 $alias = $alias_name;
1079             }
1080 143         316 $alias =~ s/\./_/g;
1081 143         310 $alias .= '_' . $alias_num;
1082              
1083 143         400 $self->_set_join_alias($join, $property_name, $alias);
1084              
1085 143 100       374 if ($foreign_class_object->table_name) {
1086 137         195 my @extra_db_filters;
1087             my @extra_obj_filters;
1088              
1089             # TODO: when "flatten" correctly feeds the "ON" clause we can remove this
1090             # This will crash if the "where" happens to use indirect things
1091 137         225 my $where = $join->{where};
1092 137 100       374 if ($where) {
1093 39         159 for (my $n = 0; $n < @$where; $n += 2) {
1094 39         83 my $key =$where->[$n];
1095 39         184 my ($name,$op) = ($key =~ /^(\S+)\s*(.*)/);
1096              
1097 39 50       137 if(index($name, '-') == 0) {
1098             #skip '-order_by', '-hint' and the like for joins
1099 0         0 next;
1100             }
1101              
1102             #my $meta = $foreign_class_object->property_meta_for_name($name);
1103             #my $column = $meta->is_calculated ? (defined($meta->calculate_sql) ? ($meta->calculate_sql) : () ) : ($meta->column_name);
1104 39         112 my ($table_name, $column_names, $property_metas) = $self->_resolve_table_and_column_data($foreign_class_object, $name);
1105 39         78 my $column = $column_names->[0];
1106              
1107 39 50       116 if (not $column) {
1108 0         0 Carp::confess("No column for $foreign_class_object->{id} $name? Indirect property flattening must be enabled to use indirect filters in where with via/to.");
1109             }
1110              
1111 39         97 my $value = $where->[$n+1];
1112 39 100       180 push @extra_db_filters, $column => { value => $value, ($op ? (operator => $op) : ()) };
1113 39 100       285 push @extra_obj_filters, $name => { value => $value, ($op ? (operator => $op) : ()) };
1114             }
1115             }
1116              
1117 137         192 my @db_join_data;
1118 137         438 for (my $n = 0; $n < @foreign_column_names; $n++) {
1119              
1120 137   66     735 my $link_table_name = $table_alias->{$source_table_and_column_names->[$n][0]}
1121             || $source_table_and_column_names->[$n][2]
1122             || $source_table_and_column_names->[$n][0];
1123              
1124 137         206 my $link_column_name = $source_table_and_column_names->[$n][1];
1125            
1126 137         186 my $foreign_column_name = $foreign_column_names[$n];
1127              
1128 137   66     380 my $link_class_meta = $class_alias->{$link_table_name} || $source_class_object;
1129 137         902 my $link_property_name = $link_class_meta->property_for_column($link_column_name);
1130              
1131             # _concrete_property_meta_for_class_and_name returns a list :(
1132             # since we're inspecting the joins by their "real" names and not the generic
1133             # "id", it will only ever return a 1-element list
1134 137         396 my($link_prop) = $link_class_meta->_concrete_property_meta_for_class_and_name($link_property_name);
1135 137         474 my $left_type = $link_prop->_data_type_as_class_name;
1136 137         387 my $right_type = $foreign_property_meta[$n]->_data_type_as_class_name;
1137 137         516 my @coercion = $self->data_source->cast_for_data_conversion($left_type, $right_type, '=', 'join');
1138              
1139 137         1073 push @db_join_data,
1140             $foreign_column_name => {
1141             link_table_name => $link_table_name,
1142             link_column_name => $link_column_name,
1143             left_coercion => $coercion[0],
1144             right_coercion => $coercion[1],
1145             };
1146             }
1147              
1148 137         842 $self->_add_db_join(
1149             "$foreign_table_name $alias" => {
1150             @db_join_data,
1151             @extra_db_filters,
1152             }
1153             );
1154            
1155             $self->_add_obj_join(
1156             "$alias" => {
1157             (
1158             map {
1159 137         471 $foreign_property_names[$_] => {
1160             link_class_name => $source_class_name,
1161 137   66     1592 link_alias => $table_alias->{$source_table_and_column_names->[$_][0]} # join alias
1162             || $source_table_and_column_names->[$_][2] # SQL inline view alias
1163             || $source_table_and_column_names->[$_][0], # table_name
1164             link_property_name => $source_property_names[$_]
1165             }
1166             }
1167             (0..$#foreign_property_names)
1168             ),
1169             @extra_obj_filters,
1170             }
1171             );
1172              
1173             # Add all of the columns in the join table to the return list
1174             # Note that we increment the object numbers.
1175             # Note: we add grouping columns individually instead of in chunks
1176 137 100       353 unless ($group_by) {
1177             $self->_add_columns(
1178             map {
1179 402         594 my $new = [@$_];
1180 402         426 $new->[2] = $alias;
1181 402         293 $new->[3] = $object_num;
1182 402         639 $new
1183             }
1184 131         187 @{ $foreign_class_loading_data->{direct_table_properties} }
  131         245  
1185             );
1186             }
1187             }
1188              
1189              
1190 143 100       366 if ($group_by) {
1191 6 100       16 if ($self->_groups_by_property($property_name)) {
1192             my ($p) =
1193             map {
1194 2         7 my $new = [@$_];
1195 2         7 $new->[2] = $alias;
1196 2         2 $new->[3] = 0;
1197 2         4 $new
1198             }
1199 8         14 grep { $_->[1]->property_name eq $final_accessor }
1200 2         5 @{ $foreign_class_loading_data->{direct_table_properties} };
  2         4  
1201 2         8 $self->_add_columns($p);
1202             }
1203             }
1204              
1205              
1206 143 50       428 if ($self->_orders_by_property($property_name)) {
1207             my ($p) =
1208             map {
1209 0         0 my $new = [@$_];
1210 0         0 $new->[2] = $alias;
1211 0         0 $new->[3] = 0;
1212 0         0 $new
1213             }
1214 0         0 grep { $_->[1]->property_name eq $final_accessor }
1215 0         0 @{ $foreign_class_loading_data->{direct_table_properties} };
  0         0  
1216             # ??? what do we do here now with $p?
1217             }
1218              
1219 143 100       367 unless ($is_optional) {
1220             # if _any_ part requires this, mark it required
1221 76         238 $self->_set_alias_required($alias);
1222             }
1223              
1224             } # done adding a new join alias for a join which has not yet been done
1225              
1226 143 100       390 if ($foreign_class_object->table_name) {
1227 137         317 $table_alias->{$foreign_table_name} = $alias;
1228 137         233 $class_alias->{$alias} = $foreign_class_object;
1229 137         299 @$source_table_and_column_names = (); # Flag that we need to re-derive this at the top of the loop
1230             }
1231              
1232 143         1326 return $alias;
1233             }
1234              
1235             sub _resolve_table_and_column_data {
1236 182     182   303 my ($class, $class_meta, @property_names) = @_;
1237             my @property_meta =
1238 182         267 map { $class_meta->_concrete_property_meta_for_class_and_name($_) }
  182         559  
1239             @property_names;
1240 182         244 my $table_name;
1241             my @column_names =
1242             map {
1243             # TODO: encapsulate
1244 182 50       240 if ($_->is_calculated) {
  182         517  
1245 0 0       0 if ($_->calculate_sql) {
1246 0         0 $_->calculate_sql;
1247             } else {
1248 0         0 ();
1249             }
1250             } else {
1251 182         195 my $column_name;
1252 182         461 ($table_name, $column_name) = $_->table_and_column_name_for_property();
1253 182         453 $column_name;
1254             }
1255             }
1256             @property_meta;
1257              
1258 182 100 66     1257 if ($table_name and $table_name =~ /^(.*)\s+(\w+)\s*$/s) {
1259 8         14 $table_name = $1;
1260             }
1261              
1262 182         551 return ($table_name, \@column_names, \@property_meta);
1263             }
1264              
1265             sub _set_join_alias {
1266 143     143   240 my ($self, $join, $property_name, $alias) = @_;
1267 143         333 $self->_join_data->{$join->id}{$property_name}{alias} = $alias;
1268 143 100       454 $self->_alias_data({}) unless $self->_alias_data();
1269 143         324 $self->_alias_data->{$alias}{join_id} = $join->id;
1270             }
1271              
1272             sub _get_join_alias {
1273 143     143   221 my ($self,$join,$property_name) = @_;
1274 143 100       505 $self->_join_data({}) unless $self->_join_data();
1275 143         333 return $self->_join_data->{$join->id}{$property_name}{alias};
1276             }
1277              
1278             sub _get_alias_join {
1279 2348     2348   2978 my ($self,$alias) = @_;
1280 2348         6709 my $alias_data = $self->_alias_data;
1281 2348 100 66     9279 return if (! $alias_data or ! exists($alias_data->{$alias}));
1282 162         417 my $join_id = $self->_alias_data->{$alias}{join_id};
1283 162         763 UR::Object::Join->get($join_id);
1284             }
1285              
1286             sub _add_db_join {
1287 137     137   218 my ($self, $key, $data) = @_;
1288            
1289 137         684 my ($alias) = ($key =~/\w+$/);
1290 137   33     420 my $alias_data = $self->_alias_data || $self->_alias_data({});
1291 137         353 $alias_data->{$alias}{db_join} = $data;
1292            
1293 137   33     421 my $db_joins = $self->_db_joins || $self->_db_joins([]);
1294 137         366 push @$db_joins, $key, $data;
1295             }
1296              
1297             sub _add_obj_join {
1298 137     137   231 my ($self, $key, $data) = @_;
1299            
1300 137 50       359 Carp::confess() unless ref $data;
1301 137   33     333 my $alias_data = $self->_alias_data || $self->_alias_data({});
1302 137         296 $alias_data->{$key}{obj_join} = $data; # the key is the alias here
1303            
1304 137   33     384 my $obj_joins = $self->_obj_joins || $self->_obj_joins([]);
1305 137         337 push @$obj_joins, $key, $data;
1306             }
1307              
1308             sub _set_alias_required {
1309 76     76   110 my ($self, $alias) = @_;
1310 76   33     179 my $alias_data = $self->_alias_data || $self->_alias_data({});
1311 76         150 $alias_data->{$alias}{is_required} = 1;
1312 76         278 $alias_data->{$alias}{db_join}{-is_required} = 1;
1313 76         212 $alias_data->{$alias}{obj_join}{-is_required} = 1;
1314             }
1315              
1316             sub _add_columns {
1317 133     133   178 my $self = shift;
1318 133         267 my @new = @_;
1319 133         396 my $old = $self->_db_column_data;
1320 133         222 my $pos = @$old;
1321 133         208 my $lob_column_positions = $self->{lob_column_positions};
1322 133         193 my $lob_column_names = $self->{lob_column_names};
1323 133         234 for my $class_property (@new) {
1324 404         442 my ($sql_class,$sql_property,$sql_table_name) = @$class_property;
1325 404   100     683 my $data_type = $sql_property->data_type || '';
1326 404 50       717 if ($data_type =~ /LOB$/) {
1327 0         0 push @$lob_column_names, $sql_property->column_name;
1328 0         0 push @$lob_column_positions, $pos;
1329             }
1330 404         449 $pos++;
1331             }
1332 133         437 push @$old, @new;
1333             }
1334              
1335             # Used by the object fabricator to find out which resultset column a
1336             # property's data is stored
1337             sub column_index_for_class_property_and_object_num {
1338 152     152 0 247 my($self, $class_name, $property_name, $object_num) = @_;
1339              
1340 152   50     300 $object_num ||= 0;
1341              
1342 152         458 my $db_column_data = $self->_db_column_data;
1343 152         454 for (my $resultset_col = 0; $resultset_col < @$db_column_data; $resultset_col++) {
1344 964 100 100     1647 if ($db_column_data->[$resultset_col]->[1]->class_name eq $class_name
      100        
1345             and $db_column_data->[$resultset_col]->[1]->property_name eq $property_name
1346             and $db_column_data->[$resultset_col]->[3] == $object_num
1347             ) {
1348 150         380 return $resultset_col;
1349             }
1350             }
1351 2         5 return undef;
1352             }
1353              
1354             # used by the object fabricator to determine the resultset column
1355             # the source property of a join is stored.
1356             sub column_index_for_class_and_property_before_object_num {
1357 93     93 0 155 my($self, $class_name, $property_name, $object_num) = @_;
1358 93 50       242 return unless $object_num;
1359              
1360 93         239 my $db_column_data = $self->_db_column_data;
1361 93         111 my $index;
1362 93         324 for (my $resultset_col = 0; $resultset_col < @$db_column_data; $resultset_col++) {
1363 472 100       794 last if ($db_column_data->[$resultset_col]->[3] >= $object_num);
1364 379 100 100     591 if ($db_column_data->[$resultset_col]->[1]->class_name eq $class_name
1365             and
1366             $db_column_data->[$resultset_col]->[1]->property_name eq $property_name
1367             ) {
1368 102         202 $index = $resultset_col;
1369             }
1370             }
1371 93         192 return $index;
1372             }
1373              
1374              
1375             sub _groups_by_property {
1376 6     6   10 my ($self, $property_name) = @_;
1377 6         16 return $self->_group_by_property_names->{$property_name};
1378             }
1379              
1380             sub _orders_by_property {
1381 143     143   190 my ($self, $property_name) = @_;
1382 143         393 return $self->_order_by_property_names->{$property_name};
1383             }
1384              
1385             sub _resolve_db_joins_for_inheritance {
1386 655     655   1045 my $class_meta = $_[0];
1387              
1388 655         959 my $first_table_name;
1389             my @sql_joins;
1390              
1391 0         0 my $prev_table_name;
1392 0         0 my $prev_id_column_name;
1393 0         0 my $prev_property_meta;
1394              
1395 655         2174 my @parent_class_objects = $class_meta->ancestry_class_metas;
1396              
1397 655         1154 my %seen;
1398 655         1447 for my $co ( $class_meta, @parent_class_objects ) {
1399 2222         5132 my $class_name = $co->class_name;
1400 2222 100       5530 next if $seen{$class_name}++;
1401              
1402 2221         4956 my @id_property_objects = $co->direct_id_property_metas;
1403 2221         3021 my %id_properties = map { $_->property_name => 1 } @id_property_objects;
  2654         5245  
1404             my @id_column_names =
1405 2221         2967 map { $_->column_name }
  2654         4907  
1406             @id_property_objects;
1407              
1408 2221         5003 my $table_name = $co->table_name;
1409 2221 100       5206 if ($table_name) {
1410 703   66     2917 $first_table_name ||= $table_name;
1411 703 100       1683 if ($prev_table_name) {
1412 48 50       158 die "Database-level inheritance cannot be used with multi-value-id classes ($class_name)!" if @id_property_objects > 1;
1413 48         75 my $prev_table_alias;
1414 48 100       300 if ($prev_table_name =~ /.*\s+(\w+)\s*$/) {
1415 2         9 $prev_table_alias = $1;
1416             }
1417             else {
1418 46         74 $prev_table_alias = $prev_table_name;
1419             }
1420              
1421 48         270 my @coercion = $co->data_source->cast_for_data_conversion(
1422             $prev_property_meta->_data_type_as_class_name,
1423             $id_property_objects[0]->_data_type_as_class_name,
1424             '=',
1425             'join');
1426 48         202 push @sql_joins,
1427             $table_name =>
1428             {
1429             $id_property_objects[0]->column_name => {
1430             link_table_name => $prev_table_alias,
1431             link_column_name => $prev_id_column_name,
1432             left_coercion => $coercion[0],
1433             right_coercion => $coercion[1],
1434             },
1435             -is_required => 1,
1436             };
1437             }
1438 703         1071 $prev_table_name = $table_name;
1439 703         2006 $prev_id_column_name = $id_property_objects[0]->column_name;
1440 703         1812 $prev_property_meta = $id_property_objects[0];
1441             }
1442             }
1443              
1444 655         2460 return ($first_table_name, @sql_joins);
1445             }
1446              
1447             sub _resolve_object_join_data_for_property_chain {
1448 94     94   176 my ($rule_template, $property_name) = @_;
1449 94         298 my $class_meta = $rule_template->subject_class_name->__meta__;
1450            
1451 94         153 my @joins;
1452             my $is_optional;
1453 0         0 my $final_accessor;
1454              
1455 94         505 my @pmeta = $class_meta->_concrete_property_meta_for_class_and_name($property_name);
1456              
1457 94         165 my $last_class_meta = $class_meta;
1458 94         211 for my $meta (@pmeta) {
1459 114 50       291 if (!$meta) {
1460 0         0 Carp::croak "Can't resolve joins for ".$rule_template->subject_class_name . " property '$property_name': No property metadata found for that class and property_name";
1461             }
1462             #id is a special property that we want to look up, but isn't necessarily on a table
1463             #so if it aliases another property, we look at that instead
1464 114 100 100     318 if($meta->property_name eq 'id' and $meta->class_name eq 'UR::Object') {
1465 2         93 my @id_properties = grep {$_->class_name ne 'UR::Object'} $last_class_meta->id_properties;
  4         8  
1466 2 50       7 if(@id_properties == 1) {
    0          
1467 2         4 $meta = $id_properties[0];
1468 2         7 $last_class_meta = $meta->class_name->__meta__;
1469 2         5 next;
1470             }
1471             elsif (@id_properties > 1) {
1472 0         0 Carp::confess "can't join to class " . $last_class_meta->class_name . " with multiple id properties: @id_properties";
1473             }
1474             }
1475 112 100 100     334 if($meta->data_type and $meta->data_type =~ /::/) {
1476 25         84 $last_class_meta = UR::Object::Type->get($meta->data_type);
1477             } else {
1478 87         288 $last_class_meta = UR::Object::Type->get($meta->class_name);
1479             }
1480 112 50       418 last unless $last_class_meta;
1481             }
1482              
1483             # we can't actually get this from the joins because
1484             # a bunch of optional things can be chained together to form
1485             # something non-optional
1486 94         188 $is_optional = 0;
1487 94         196 for my $pmeta (@pmeta) {
1488 114         470 push @joins, $pmeta->_resolve_join_chain();
1489 114 100 100     413 $is_optional = 1 if $pmeta->is_optional or $pmeta->is_many;
1490             }
1491              
1492 94 50       301 return unless @joins;
1493 94         420 return ($joins[-1]->{source_name_for_foreign}, $is_optional, @joins)
1494             };
1495              
1496             sub _init_light {
1497 655     655   989 my $self = shift;
1498 655         2487 my $rule_template = $self->rule_template;
1499 655         1738 my $ds = $self->data_source;
1500              
1501 655         1968 my $class_name = $rule_template->subject_class_name;
1502 655         3077 my $class_meta = $class_name->__meta__;
1503 655         4013 my $class_data = $ds->_get_class_data_for_loading($class_meta);
1504              
1505 655         926 my @parent_class_objects = @{ $class_data->{parent_class_objects} };
  655         1951  
1506 655         948 my @all_properties = @{ $class_data->{all_properties} };
  655         1871  
1507 655         1125 my $sub_classification_meta_class_name = $class_data->{sub_classification_meta_class_name};
1508 655         1046 my $subclassify_by = $class_data->{subclassify_by};
1509            
1510 655         843 my @all_id_property_names = @{ $class_data->{all_id_property_names} };
  655         1630  
1511 655         915 my @id_properties = @{ $class_data->{id_properties} };
  655         1515  
1512 655         1097 my $id_property_sorter = $class_data->{id_property_sorter};
1513 655         1054 my $sub_typing_property = $class_data->{sub_typing_property};
1514 655         977 my $class_table_name = $class_data->{class_table_name};
1515            
1516 655         2002 my $recursion_desc = $rule_template->recursion_desc;
1517 655         957 my $recurse_property_on_this_row;
1518             my $recurse_property_referencing_other_rows;
1519 0         0 my $recurse_resolution_by_iteration;
1520 655 100       1677 if ($recursion_desc) {
1521 5         10 ($recurse_property_on_this_row,$recurse_property_referencing_other_rows) = @$recursion_desc;
1522 5         28 $recurse_resolution_by_iteration = ! $ds->does_support_recursive_queries;
1523             }
1524            
1525 655         869 my $needs_further_boolexpr_evaluation_after_loading;
1526            
1527             my $is_join_across_data_source;
1528              
1529 0         0 my @sql_params;
1530 0         0 my @filter_specs;
1531 0         0 my @property_names_in_resultset_order;
1532 655         910 my $object_num = 0; # 0-based, usually zero unless there are joins
1533            
1534 655         2456 my @filters = $rule_template->_property_names;
1535             my %filters =
1536 1059         2601 map { $_ => 0 }
1537 655         1478 grep { substr($_,0,1) ne '-' }
  1059         2624  
1538             @filters;
1539            
1540 655 100 66     2758 unless (@all_id_property_names == 1 && $all_id_property_names[0] eq "id") {
1541 630         1012 delete $filters{'id'};
1542             }
1543            
1544             my (
1545 655         985 @sql_joins,
1546             @sql_filters,
1547             $prev_table_name,
1548             $prev_id_column_name,
1549             $pk_used,
1550             @delegated_properties,
1551             %chain_delegates,
1552             );
1553              
1554 655         1730 for my $key (keys %filters) {
1555 911 100       2832 if (index($key,'.') != -1) {
1556 18         62 $chain_delegates{$key} = delete $filters{$key};
1557             }
1558             }
1559              
1560 655         1368 for my $co ( $class_meta, @parent_class_objects ) {
1561 2221         5303 my $class_name = $co->class_name;
1562 2221 100 66     13264 last if ( ($class_name eq 'UR::Object') or (not $class_name->isa("UR::Object")) );
1563 1566         5712 my @id_property_objects = $co->direct_id_property_metas;
1564 1566 50       3643 if (@id_property_objects == 0) {
1565 0         0 @id_property_objects = $co->property_meta_for_name("id");
1566 0 0       0 if (@id_property_objects == 0) {
1567 0         0 Carp::confess("Couldn't determine ID properties for $class_name\n");
1568             }
1569             }
1570 1566         2355 my %id_properties = map { $_->property_name => 1 } @id_property_objects;
  1999         4111  
1571             my @id_column_names =
1572 1566         2198 map { $_->column_name }
  1999         3882  
1573             @id_property_objects;
1574 1566         3813 for my $property_name (sort keys %filters) {
1575 934         3433 my $property = UR::Object::Property->get(class_name => $class_name, property_name => $property_name);
1576 934 100       2515 next unless $property;
1577 893         3182 my $operator = $rule_template->operator_for($property_name);
1578 893         3223 my $value_position = $rule_template->value_position_for_property_name($property_name);
1579 893         1568 delete $filters{$property_name};
1580 893 100       2284 $pk_used = 1 if $id_properties{ $property_name };
1581 893 50 100     2825 if ($property->is_legacy_eav) {
    100          
    100          
1582 0         0 die "Old GSC EAV can be handled with a via/to/where/is_mutable=1";
1583             }
1584             elsif ($property->is_delegated) {
1585 51         203 push @delegated_properties, $property;
1586             }
1587             elsif ($property->is_calculated || $property->is_transient) {
1588 7         19 $needs_further_boolexpr_evaluation_after_loading = 1;
1589             }
1590             else {
1591 835         4288 push @sql_filters,
1592             $class_name =>
1593             {
1594             $property_name => { operator => $operator, value_position => $value_position }
1595             };
1596             }
1597             }
1598 1566         3633 $prev_id_column_name = $id_property_objects[0]->column_name;
1599             } # end of inheritance loop
1600            
1601 655 50       2315 if ( my @errors = keys(%filters) ) {
1602 0         0 my $class_name = $class_meta->class_name;
1603 0         0 $ds->error_message('Unknown param(s) (' . join(',',@errors) . ") used to generate SQL for $class_name!");
1604 0         0 Carp::confess();
1605             }
1606              
1607 655         1174 my $last_class_name = $class_name;
1608 655         929 my $last_class_object = $class_meta;
1609 655         951 my $alias_num = 1;
1610 655         924 my %joins_done;
1611             my $joins_across_data_sources;
1612              
1613             DELEGATED_PROPERTY:
1614 655         1436 for my $delegated_property (@delegated_properties) {
1615 51         79 my $last_alias_for_this_chain;
1616 51         158 my $property_name = $delegated_property->property_name;
1617 51         302 my @joins = $delegated_property->_resolve_join_chain($property_name);
1618 51         234 my $relationship_name = $delegated_property->via;
1619 51 50       165 unless ($relationship_name) {
1620 0         0 $relationship_name = $property_name;
1621 0         0 $needs_further_boolexpr_evaluation_after_loading = 1;
1622             }
1623              
1624 51         245 my $delegate_class_meta = $delegated_property->class_meta;
1625 51         383 my($via_accessor_meta) = $delegate_class_meta->_concrete_property_meta_for_class_and_name($relationship_name);
1626 51 50       168 next unless $via_accessor_meta;
1627 51         189 my $final_accessor = $delegated_property->to;
1628              
1629 51         173 my $data_type = $via_accessor_meta->data_type;
1630 51 50       167 unless ($data_type) {
1631 0         0 Carp::croak "Can't resolve delegation for $property_name on class $class_name: via property $relationship_name has no data type";
1632             }
1633              
1634 51         162 my $data_type_meta = UR::Object::Type->get($via_accessor_meta->data_type);
1635 51 50       192 unless ($data_type_meta) {
1636 0         0 Carp::croak "No class meta data for " . $via_accessor_meta->data_type .
1637             " while resolving property $property_name on class $class_name";
1638             }
1639 51         360 my($final_accessor_meta) = $data_type_meta->_concrete_property_meta_for_class_and_name(
1640             $final_accessor
1641             );
1642 51 50       166 unless ($final_accessor_meta) {
1643 0         0 Carp::croak("No property '$final_accessor' on class " . $via_accessor_meta->data_type .
1644             " while resolving property $property_name on class $class_name");
1645             }
1646              
1647             # Follow the chain of via/to delegation down to where the data ultimately lives
1648 51         201 while($final_accessor_meta->is_delegated) {
1649             # May have been 'to' an id_by/id_class_by property. Stop chaining and do two queries
1650             # If we had access to the value at this point, we could continue joining through that
1651             # value's class and id
1652 10 50 33     38 next DELEGATED_PROPERTY if ($final_accessor_meta->id_by or $final_accessor_meta->id_class_by);
1653              
1654 10         18 my $prev_accessor_meta = $final_accessor_meta;
1655 10         48 $final_accessor_meta = $final_accessor_meta->to_property_meta();
1656 10 50       46 unless ($final_accessor_meta) {
1657 0         0 Carp::croak("Can't resolve property '$final_accessor' of class " . $via_accessor_meta->data_type
1658             . ": Resolution involved property '" . $prev_accessor_meta->property_name . "' of class "
1659             . $prev_accessor_meta->class_name
1660             . " which is delegated, but its via/to metadata does not resolve to a known class and property");
1661             }
1662             }
1663 51         169 $final_accessor = $final_accessor_meta->property_name;
1664 51         126 for my $join (@joins) {
1665 114         232 my $source_class_name = $join->{source_class};
1666 114   33     575 my $source_class_object = $join->{'source_class_meta'} || $source_class_name->__meta__;
1667              
1668 114         174 my $foreign_class_name = $join->{foreign_class};
1669 114 100       765 next DELEGATED_PROPERTY if ($foreign_class_name->isa('UR::Value'));
1670 66   33     353 my $foreign_class_object = $join->{'foreign_class_meta'} || $foreign_class_name->__meta__;
1671 66         228 my($foreign_data_source) = $UR::Context::current->resolve_data_sources_for_class_meta_and_rule($foreign_class_object, $rule_template);
1672 66 50 66     923 if (! $foreign_data_source) {
    100 66        
1673 0         0 $needs_further_boolexpr_evaluation_after_loading = 1;
1674 0         0 next DELEGATED_PROPERTY;
1675              
1676             } elsif ($foreign_data_source ne $ds or
1677             ! $ds->does_support_joins or
1678             ! $foreign_data_source->does_support_joins
1679             )
1680             {
1681 3         4 push(@{$joins_across_data_sources->{$foreign_data_source->id}}, $delegated_property);
  3         14  
1682 3         13 next DELEGATED_PROPERTY;
1683             }
1684 63         103 my @source_property_names = @{ $join->{source_property_names} };
  63         213  
1685             my @source_table_and_column_names =
1686             map {
1687 63         132 my($p) = $source_class_object->_concrete_property_meta_for_class_and_name($_);
  63         197  
1688 63 50       169 unless ($p) {
1689 0         0 Carp::confess("No property $_ for class $source_class_object->{class_name}\n");
1690             }
1691 63 50       252 unless ($p->class_name->__meta__) {
1692 0         0 Carp::croak("Can't get class metadata for " . $p->class_name);
1693             }
1694 63         194 [$p->class_name->__meta__->class_name, $p->property_name];
1695             }
1696             @source_property_names;
1697 63         99 my $foreign_table_name = $foreign_class_name;
1698 63 50       162 unless ($foreign_table_name) {
1699             # If we can't make the join because there is no datasource representation
1700             # for this class, we're done following the joins for this property
1701             # and will NOT try to filter on it at the datasource level
1702 0         0 $needs_further_boolexpr_evaluation_after_loading = 1;
1703 0         0 next DELEGATED_PROPERTY;
1704             }
1705 63         90 my @foreign_property_names = @{ $join->{foreign_property_names} };
  63         159  
1706             my @foreign_property_meta =
1707             map {
1708 63         110 $foreign_class_object->_concrete_property_meta_for_class_and_name($_)
  63         252  
1709             }
1710             @foreign_property_names;
1711            
1712             my @foreign_column_names =
1713             map {
1714             # TODO: encapsulate
1715 63 0       102 $_->is_calculated ? (defined($_->calculate_sql) ? ($_->calculate_sql) : () ) : ($_->property_name)
  63 50       239  
1716             }
1717             @foreign_property_meta;
1718            
1719 63 50       166 unless (@foreign_column_names) {
1720             # all calculated properties: don't try to join any further
1721 0         0 last;
1722             }
1723 63 50       183 unless (@foreign_column_names == @foreign_property_meta) {
1724             # some calculated properties, be sure to re-check for a match after loading the object
1725 0         0 $needs_further_boolexpr_evaluation_after_loading = 1;
1726             }
1727 63         146 my $alias = $joins_done{$join->{id}};
1728 63 100       171 unless ($alias) {
1729 60         173 $alias = "${relationship_name}_${alias_num}";
1730 60         121 $alias_num++;
1731 60         79 $object_num++;
1732            
1733             push @sql_joins,
1734             "$foreign_table_name $alias" =>
1735             {
1736             map {
1737 60   66     220 $foreign_property_names[$_] => {
  60         471  
1738             link_table_name => $last_alias_for_this_chain || $source_table_and_column_names[$_][0],
1739             link_column_name => $source_table_and_column_names[$_][1]
1740             }
1741             }
1742             (0..$#foreign_property_names)
1743             };
1744            
1745             # Add all of the columns in the join table to the return list.
1746             push @all_properties,
1747 173         407 map { [$foreign_class_object, $_, $alias, $object_num] }
1748 173         242 map { $_->[1] } # These three lines are to get around a bug in perl
1749 152         271 sort { $a->[0] cmp $b->[0] } # 5.8's sort involving method calls within the sort
1750 173         301 map { [ $_->property_name, $_ ] } # sub that do sorts of their own
1751 60 100       292 grep { defined($_->column_name) && $_->column_name ne '' }
  281         506  
1752             UR::Object::Property->get( class_name => $foreign_class_name );
1753            
1754 60         257 $joins_done{$join->{id}} = $alias;
1755            
1756             }
1757             # Set these for after all of the joins are done
1758 63         106 $last_class_name = $foreign_class_name;
1759 63         94 $last_class_object = $foreign_class_object;
1760 63         209 $last_alias_for_this_chain = $alias;
1761             } # next join
1762 0 0       0 unless ($delegated_property->via) {
1763 0         0 next;
1764             }
1765 0         0 my($final_accessor_property_meta) = $last_class_object->_concrete_property_meta_for_class_and_name($final_accessor);
1766 0 0       0 unless ($final_accessor_property_meta) {
1767 0         0 Carp::croak("No property metadata for property named '$final_accessor' in class " . $last_class_object->class_name
1768             . " while resolving joins for property '" .$delegated_property->property_name . "' in class "
1769             . $delegated_property->class_name);
1770             }
1771 0         0 my $sql_lvalue;
1772 0 0       0 if ($final_accessor_property_meta->is_calculated) {
1773 0         0 $sql_lvalue = $final_accessor_property_meta->calculate_sql;
1774 0 0       0 unless (defined($sql_lvalue)) {
1775 0         0 $needs_further_boolexpr_evaluation_after_loading = 1;
1776 0         0 next;
1777             }
1778             }
1779             else {
1780 0         0 $sql_lvalue = $final_accessor_property_meta->column_name;
1781 0 0       0 unless (defined($sql_lvalue)) {
1782 0         0 Carp::confess("No column name set for non-delegated/calculated property $property_name of $class_name");
1783             }
1784             }
1785 0         0 my $operator = $rule_template->operator_for($property_name);
1786 0         0 my $value_position = $rule_template->value_position_for_property_name($property_name);
1787             } # next delegated property
1788 655         1265 for my $property_meta_array (@all_properties) {
1789 5071         8218 push @property_names_in_resultset_order, $property_meta_array->[1]->property_name;
1790             }
1791 655 100       1790 my $rule_template_without_recursion_desc = ($recursion_desc ? $rule_template->remove_filter('-recurse') : $rule_template);
1792 655         879 my $rule_template_specifies_value_for_subtype;
1793 655 100       1713 if ($sub_typing_property) {
1794 61         282 $rule_template_specifies_value_for_subtype = $rule_template->specifies_value_for($sub_typing_property)
1795             }
1796             #my $per_object_in_resultset_loading_detail = $ds->_generate_loading_templates_arrayref(\@all_properties);
1797 655   100     6996 %$self = (
1798             %$self,
1799             %$class_data,
1800             properties_for_params => \@all_properties,
1801             property_names_in_resultset_order => \@property_names_in_resultset_order,
1802             joins => \@sql_joins,
1803             rule_template_id => $rule_template->id,
1804             rule_template_without_recursion_desc => $rule_template_without_recursion_desc,
1805             rule_template_id_without_recursion_desc => $rule_template_without_recursion_desc->id,
1806             rule_matches_all => $rule_template->matches_all,
1807             rule_specifies_id => ($rule_template->specifies_value_for('id') || undef),
1808             rule_template_is_id_only => $rule_template->is_id_only,
1809             rule_template_specifies_value_for_subtype => $rule_template_specifies_value_for_subtype,
1810             recursion_desc => $rule_template->recursion_desc,
1811             recurse_property_on_this_row => $recurse_property_on_this_row,
1812             recurse_property_referencing_other_rows => $recurse_property_referencing_other_rows,
1813             recurse_resolution_by_iteration => $recurse_resolution_by_iteration,
1814             #loading_templates => $per_object_in_resultset_loading_detail,
1815             joins_across_data_sources => $joins_across_data_sources,
1816             );
1817 655         7067 return $self;
1818             }
1819              
1820             sub _init_core {
1821 143     143   239 my $self = shift;
1822 143         550 my $rule_template = $self->rule_template;
1823 143         418 my $ds = $self->data_source;
1824              
1825             # TODO: most of this only applies to the RDBMS subclass,
1826             # but some applies to any datasource. It doesn't hurt to have the RDBMS stuff
1827             # here and ignored, but it's not placed correctly.
1828            
1829             # class-based values
1830            
1831 143         446 my $class_name = $rule_template->subject_class_name;
1832 143         660 my $class_meta = $class_name->__meta__;
1833 143         743 my $class_data = $ds->_get_class_data_for_loading($class_meta);
1834              
1835 143         207 my @parent_class_objects = @{ $class_data->{parent_class_objects} };
  143         400  
1836 143         190 my @all_properties = @{ $class_data->{all_properties} };
  143         339  
1837 143         220 my $sub_classification_meta_class_name = $class_data->{sub_classification_meta_class_name};
1838 143         205 my $subclassify_by = $class_data->{subclassify_by};
1839            
1840 143         202 my @all_id_property_names = @{ $class_data->{all_id_property_names} };
  143         300  
1841 143         189 my @id_properties = @{ $class_data->{id_properties} };
  143         277  
1842 143         226 my $id_property_sorter = $class_data->{id_property_sorter};
1843            
1844 143         196 my $sub_typing_property = $class_data->{sub_typing_property};
1845 143         184 my $class_table_name = $class_data->{class_table_name};
1846            
1847             # individual query/boolexpr based
1848            
1849 143         422 my $recursion_desc = $rule_template->recursion_desc;
1850 143         197 my $recurse_property_on_this_row;
1851             my $recurse_property_referencing_other_rows;
1852 143 50       360 if ($recursion_desc) {
1853 0         0 ($recurse_property_on_this_row,$recurse_property_referencing_other_rows) = @$recursion_desc;
1854             }
1855            
1856             # _usually_ items freshly loaded from the DB don't need to be evaluated through the rule
1857             # because the SQL gets constructed in such a way that all the items returned would pass anyway.
1858             # But in certain cases (a delegated property trying to match a non-object value (which is a bug
1859             # in the caller's code from one point of view) or with calculated non-sql properties, then the
1860             # sql will return a superset of the items we're actually asking for, and the loader needs to
1861             # validate them through the rule
1862 143         176 my $needs_further_boolexpr_evaluation_after_loading;
1863            
1864             # Does fulfilling this request involve querying more than one data source?
1865             my $is_join_across_data_source;
1866              
1867 0         0 my @sql_params;
1868 0         0 my @filter_specs;
1869 0         0 my @property_names_in_resultset_order;
1870 143         191 my $object_num = 0; # 0-based, usually zero unless there are joins
1871            
1872 143         576 my @filters = $rule_template->_property_names;
1873             my %filters =
1874 189         458 map { $_ => 0 }
1875 143         328 grep { substr($_,0,1) ne '-' }
  189         468  
1876             @filters;
1877            
1878 143 100 66     693 unless (@all_id_property_names == 1 && $all_id_property_names[0] eq "id") {
1879 99         166 delete $filters{'id'};
1880             }
1881            
1882             my (
1883 143         194 @sql_joins,
1884             @sql_filters,
1885             $prev_table_name,
1886             $prev_id_column_name,
1887             $pk_used,
1888             @delegated_properties,
1889             %chain_delegates,
1890             );
1891              
1892 143         406 for my $key (keys %filters) {
1893 154 100       560 if (index($key,'.') != -1) {
1894 2         7 $chain_delegates{$key} = delete $filters{$key};
1895             }
1896             }
1897 143         282 for my $co ( $class_meta, @parent_class_objects ) {
1898 398         940 my $class_name = $co->class_name;
1899            
1900 398 100 66     2188 last if ( ($class_name eq 'UR::Object') or (not $class_name->isa("UR::Object")) );
1901            
1902 255         1141 my @id_property_objects = $co->direct_id_property_metas;
1903            
1904 255 50       600 if (@id_property_objects == 0) {
1905 0         0 @id_property_objects = $co->property_meta_for_name("id");
1906 0 0       0 if (@id_property_objects == 0) {
1907 0         0 Carp::confess("Couldn't determine ID properties for $class_name\n");
1908             }
1909             }
1910            
1911 255         420 my %id_properties = map { $_->property_name => 1 } @id_property_objects;
  270         688  
1912             my @id_column_names =
1913 255         395 map { $_->column_name }
  270         642  
1914             @id_property_objects;
1915            
1916 255         695 for my $property_name (sort keys %filters)
1917             {
1918 191         640 my $property = UR::Object::Property->get(class_name => $class_name, property_name => $property_name);
1919 191 100       510 next unless $property;
1920              
1921 152         516 my $operator = $rule_template->operator_for($property_name);
1922 152         542 my $value_position = $rule_template->value_position_for_property_name($property_name);
1923              
1924 152         246 delete $filters{$property_name};
1925 152 100       420 $pk_used = 1 if $id_properties{ $property_name };
1926              
1927 152 50       504 if ($property->is_legacy_eav) {
    50          
    100          
    50          
1928 0         0 die "Old GSC EAV can be handled with a via/to/where/is_mutable=1";
1929             }
1930             elsif ($property->is_transient) {
1931 0         0 die "Query by transient property $property_name on $class_name cannot be done!";
1932             }
1933             elsif ($property->is_delegated) {
1934 7         19 push @delegated_properties, $property;
1935             }
1936             elsif ($property->is_calculated) {
1937 0         0 $needs_further_boolexpr_evaluation_after_loading = 1;
1938             }
1939             else {
1940             # normal column: filter on it
1941 145         728 push @sql_filters,
1942             $class_name =>
1943             {
1944             $property_name => { operator => $operator, value_position => $value_position }
1945             };
1946             }
1947             }
1948            
1949 255         673 $prev_id_column_name = $id_property_objects[0]->column_name;
1950            
1951             } # end of inheritance loop
1952            
1953 143 50       528 if ( my @errors = keys(%filters) ) {
1954 0         0 my $class_name = $class_meta->class_name;
1955 0         0 $ds->error_message('Unknown param(s) (' . join(',',@errors) . ") used to generate SQL for $class_name!");
1956 0         0 Carp::confess();
1957             }
1958              
1959 143         241 my $last_class_name = $class_name;
1960 143         188 my $last_class_object = $class_meta;
1961 143         180 my $alias_num = 1;
1962              
1963 143         215 my %joins_done;
1964             my $joins_across_data_sources;
1965              
1966             DELEGATED_PROPERTY:
1967 143         277 for my $delegated_property (@delegated_properties) {
1968 7         13 my $last_alias_for_this_chain;
1969            
1970 7         20 my $property_name = $delegated_property->property_name;
1971 7         32 my @joins = $delegated_property->_resolve_join_chain($property_name);
1972             #pop @joins if $joins[-1]->{foreign_class}->isa("UR::Value");
1973 7         28 my $relationship_name = $delegated_property->via;
1974 7 50       21 unless ($relationship_name) {
1975 0         0 $relationship_name = $property_name;
1976 0         0 $needs_further_boolexpr_evaluation_after_loading = 1;
1977             }
1978              
1979 7         30 my $delegate_class_meta = $delegated_property->class_meta;
1980 7         39 my($via_accessor_meta) = $delegate_class_meta->_concrete_property_meta_for_class_and_name($relationship_name);
1981 7         19 my $final_accessor = $delegated_property->to;
1982 7         20 my($final_accessor_meta) = $via_accessor_meta->data_type->__meta__->_concrete_property_meta_for_class_and_name(
1983             $final_accessor
1984             );
1985 7 50       21 unless ($final_accessor_meta) {
1986 0         0 Carp::croak("No property '$final_accessor' on class " . $via_accessor_meta->data_type .
1987             " while resolving property $property_name on class $class_name");
1988             }
1989 7         21 while($final_accessor_meta->is_delegated) {
1990 0         0 $final_accessor_meta = $final_accessor_meta->to_property_meta();
1991 0 0       0 unless ($final_accessor_meta) {
1992 0         0 Carp::croak("No property '$final_accessor' on class " . $via_accessor_meta->data_type .
1993             " while resolving property $property_name on class $class_name");
1994             }
1995             }
1996 7         19 $final_accessor = $final_accessor_meta->property_name;
1997            
1998 7         16 for my $join (@joins) {
1999              
2000 7         16 my $source_class_name = $join->{source_class};
2001 7   33     39 my $source_class_object = $join->{'source_class_meta'} || $source_class_name->__meta__;
2002              
2003 7         12 my $foreign_class_name = $join->{foreign_class};
2004 7   33     39 my $foreign_class_object = $join->{'foreign_class_meta'} || $foreign_class_name->__meta__;
2005 7         27 my($foreign_data_source) = $UR::Context::current->resolve_data_sources_for_class_meta_and_rule($foreign_class_object, $rule_template);
2006 7 50 66     71 if (! $foreign_data_source) {
    50 33        
2007 0         0 $needs_further_boolexpr_evaluation_after_loading = 1;
2008 0         0 next DELEGATED_PROPERTY;
2009              
2010             } elsif ($foreign_data_source ne $ds or
2011             ! $ds->does_support_joins or
2012             ! $foreign_data_source->does_support_joins
2013             )
2014             {
2015 7         9 push(@{$joins_across_data_sources->{$foreign_data_source->id}}, $delegated_property);
  7         25  
2016 7         27 next DELEGATED_PROPERTY;
2017             }
2018              
2019 0         0 my @source_property_names = @{ $join->{source_property_names} };
  0         0  
2020              
2021             my @source_table_and_column_names =
2022             map {
2023 0         0 my($p) = $source_class_object->_concrete_property_meta_for_class_and_name($_);
  0         0  
2024 0 0       0 unless ($p) {
2025 0         0 Carp::confess("No property $_ for class $source_class_object->{class_name}\n");
2026             }
2027 0         0 [$p->class_name->__meta__->class_name, $p->property_name];
2028             }
2029             @source_property_names;
2030              
2031              
2032 0         0 my $foreign_table_name = $foreign_class_name;
2033              
2034 0 0       0 unless ($foreign_table_name) {
2035             # If we can't make the join because there is no datasource representation
2036             # for this class, we're done following the joins for this property
2037             # and will NOT try to filter on it at the datasource level
2038 0         0 $needs_further_boolexpr_evaluation_after_loading = 1;
2039 0         0 next DELEGATED_PROPERTY;
2040             }
2041              
2042 0         0 my @foreign_property_names = @{ $join->{foreign_property_names} };
  0         0  
2043             my @foreign_property_meta =
2044             map {
2045 0         0 $foreign_class_object->_concrete_property_meta_for_class_and_name($_);
  0         0  
2046             }
2047             @foreign_property_names;
2048            
2049             my @foreign_column_names =
2050             map {
2051             # TODO: encapsulate
2052 0 0       0 $_->is_calculated ? (defined($_->calculate_sql) ? ($_->calculate_sql) : () ) : ($_->property_name)
  0 0       0  
2053             }
2054             @foreign_property_meta;
2055            
2056 0 0       0 unless (@foreign_column_names) {
2057             # all calculated properties: don't try to join any further
2058 0         0 last;
2059             }
2060 0 0       0 unless (@foreign_column_names == @foreign_property_meta) {
2061             # some calculated properties, be sure to re-check for a match after loading the object
2062 0         0 $needs_further_boolexpr_evaluation_after_loading = 1;
2063             }
2064            
2065 0         0 my $alias = $joins_done{$join->{id}};
2066 0 0       0 unless ($alias) {
2067 0         0 $alias = "${relationship_name}_${alias_num}";
2068 0         0 $alias_num++;
2069 0         0 $object_num++;
2070              
2071 0         0 my @source_property_meta = map { $source_class_object->_concrete_property_meta_for_class_and_name($_) }
  0         0  
2072             @source_property_names;
2073             push @sql_joins,
2074             "$foreign_table_name $alias" =>
2075             {
2076             map {
2077 0         0 my @coercion = $ds->cast_for_data_conversion(
  0         0  
2078             $source_property_meta[$_]->_data_type_as_class_name,
2079             $foreign_property_meta[$_]->_data_type_as_class_name,
2080             '=',
2081             'join');
2082 0   0     0 $foreign_property_names[$_] => {
2083             link_table_name => $last_alias_for_this_chain || $source_table_and_column_names[$_][0],
2084             link_column_name => $source_table_and_column_names[$_][1],
2085             left_coercion => $coercion[0],
2086             right_coercion => $coercion[1],
2087             }
2088             }
2089             (0..$#foreign_property_names)
2090             };
2091            
2092             # Add all of the columns in the join table to the return list.
2093             push @all_properties,
2094 0         0 map { [$foreign_class_object, $_, $alias, $object_num] }
2095 0         0 map { $_->[1] } # These three lines are to get around a bug in perl
2096 0         0 sort { $a->[0] cmp $b->[0] } # 5.8's sort involving method calls within the sort
2097 0         0 map { [ $_->property_name, $_ ] } # sub that do sorts of their own
2098 0 0       0 grep { defined($_->column_name) && $_->column_name ne '' }
  0         0  
2099             UR::Object::Property->get( class_name => $foreign_class_name );
2100            
2101 0         0 $joins_done{$join->{id}} = $alias;
2102            
2103             }
2104            
2105             # Set these for after all of the joins are done
2106 0         0 $last_class_name = $foreign_class_name;
2107 0         0 $last_class_object = $foreign_class_object;
2108 0         0 $last_alias_for_this_chain = $alias;
2109            
2110             } # next join
2111              
2112 0 0       0 unless ($delegated_property->via) {
2113 0         0 next;
2114             }
2115              
2116 0         0 my($final_accessor_property_meta) = $last_class_object->_concrete_property_meta_for_class_and_name($id_properties[0]);
2117 0 0       0 unless ($final_accessor_property_meta) {
2118 0         0 Carp::croak("No property metadata for property named '$final_accessor' in class " . $last_class_object->class_name
2119             . " while resolving joins for property '" .$delegated_property->property_name . "' in class "
2120             . $delegated_property->class_name);
2121             }
2122            
2123 0         0 my $sql_lvalue;
2124 0 0       0 if ($final_accessor_property_meta->is_calculated) {
2125 0         0 $sql_lvalue = $final_accessor_property_meta->calculate_sql;
2126 0 0       0 unless (defined($sql_lvalue)) {
2127 0         0 $needs_further_boolexpr_evaluation_after_loading = 1;
2128 0         0 next;
2129             }
2130             }
2131             else {
2132 0         0 $sql_lvalue = $final_accessor_property_meta->column_name;
2133 0 0       0 unless (defined($sql_lvalue)) {
2134 0         0 Carp::confess("No column name set for non-delegated/calculated property $property_name of $class_name");
2135             }
2136             }
2137              
2138 0         0 my $operator = $rule_template->operator_for($property_name);
2139 0         0 my $value_position = $rule_template->value_position_for_property_name($property_name);
2140             } # next delegated property
2141            
2142 143         263 for my $property_meta_array (@all_properties) {
2143 636         1119 push @property_names_in_resultset_order, $property_meta_array->[1]->property_name;
2144             }
2145            
2146 143 50       369 my $rule_template_without_recursion_desc = ($recursion_desc ? $rule_template->remove_filter('-recurse') : $rule_template);
2147            
2148 143         208 my $rule_template_specifies_value_for_subtype;
2149 143 100       362 if ($sub_typing_property) {
2150 1         6 $rule_template_specifies_value_for_subtype = $rule_template->specifies_value_for($sub_typing_property)
2151             }
2152              
2153 143 100 66     264 my @this_ds_properties = grep { ! $_->[1]->is_delegated
  636         981  
2154             and (! $_->[1]->is_calculated or $_->[1]->calculate_sql)
2155             }
2156             @all_properties;
2157              
2158 143         863 my $per_object_in_resultset_loading_detail = $ds->_generate_loading_templates_arrayref(\@this_ds_properties);
2159              
2160 143   100     1291 %$self = (
2161             %$self,
2162              
2163             %$class_data,
2164            
2165             properties_for_params => \@all_properties,
2166             property_names_in_resultset_order => \@property_names_in_resultset_order,
2167             joins => \@sql_joins,
2168            
2169             rule_template_id => $rule_template->id,
2170             rule_template_without_recursion_desc => $rule_template_without_recursion_desc,
2171             rule_template_id_without_recursion_desc => $rule_template_without_recursion_desc->id,
2172             rule_matches_all => $rule_template->matches_all,
2173             rule_specifies_id => ($rule_template->specifies_value_for('id') || undef),
2174             rule_template_is_id_only => $rule_template->is_id_only,
2175             rule_template_specifies_value_for_subtype => $rule_template_specifies_value_for_subtype,
2176            
2177             recursion_desc => $rule_template->recursion_desc,
2178             recurse_property_on_this_row => $recurse_property_on_this_row,
2179             recurse_property_referencing_other_rows => $recurse_property_referencing_other_rows,
2180            
2181             loading_templates => $per_object_in_resultset_loading_detail,
2182              
2183             joins_across_data_sources => $joins_across_data_sources,
2184             );
2185            
2186 143         1433 return $self;
2187             }
2188              
2189             sub _init_default {
2190 61     61   87 my $self = shift;
2191 61         211 my $bx_template = $self->rule_template;
2192 61         136 $self->{needs_further_boolexpr_evaluation_after_loading} = 1;
2193 61         121 my $all_possible_headers = $self->{loading_templates}[0]{property_names};
2194 61         79 my $expected_headers;
2195 61         171 my $class_meta = $bx_template->subject_class_name->__meta__;
2196 61         139 for my $pname (@$all_possible_headers) {
2197 185         693 my $pmeta = $class_meta->property($pname);
2198 185 50       398 if ($pmeta->is_delegated) {
2199 0         0 next;
2200             }
2201 185         358 push @$expected_headers, $pname;
2202             }
2203 61         123 $self->{loading_templates}[0]{property_names} = $expected_headers;
2204              
2205 61 100       198 if ($bx_template->subject_class_name->isa('UR::Value')) {
2206             # Hack so the objects get blessed into the proper subclass in the Object Fabricator.
2207             # This is necessary so every possible UR::Value subclass doesn't need its
2208             # own "id" property defined. Without it, the data shows that these objects get
2209             # loaded as the base UR::Value class (since its "id" is defined on UR:Value)
2210             # and then would get automagically subclassed.
2211 45         116 $self->{'loading_templates'}->[0]->{'final_class_name'} = $bx_template->subject_class_name
2212             }
2213              
2214 61         146 return $self;
2215             }
2216              
2217              
2218             sub _init_remote_cache {
2219 0     0   0 my $self = shift;
2220 0         0 my $rule_template = $self->rule_template;
2221 0         0 my $ds = $self->data_source;
2222              
2223 0         0 my $class_name = $rule_template->subject_class_name;
2224 0         0 my $class_meta = $class_name->__meta__;
2225 0         0 my $class_data = $ds->_get_class_data_for_loading($class_meta);
2226              
2227 0         0 my $recursion_desc = $rule_template->recursion_desc;
2228 0 0       0 my $rule_template_without_recursion_desc = ($recursion_desc ? $rule_template->remove_filter('-recurse') : $rule_template);
2229 0         0 my $rule_template_specifies_value_for_subtype;
2230 0         0 my $sub_typing_property = $class_data->{'sub_typing_property'};
2231 0 0       0 if ($sub_typing_property) {
2232 0         0 $rule_template_specifies_value_for_subtype = $rule_template->specifies_value_for($sub_typing_property)
2233             }
2234              
2235 0         0 my @property_names = $class_name->__meta__->all_property_names;
2236              
2237 0   0     0 %$self = (
2238             %$self,
2239              
2240             select_clause => '',
2241             select_hint => undef,
2242             from_clause => '',
2243             where_clause => '',
2244             connect_by_clause => '',
2245             order_by_clause => '',
2246              
2247             needs_further_boolexpr_evaluation_after_loading => undef,
2248             loading_templates => [],
2249              
2250             sql_params => [],
2251             filter_specs => [],
2252             property_names_in_resultset_order => \@property_names,
2253             properties_for_params => [],
2254              
2255             rule_template_id => $rule_template->id,
2256             rule_template_without_recursion_desc => $rule_template_without_recursion_desc,
2257             rule_template_id_without_recursion_desc => $rule_template_without_recursion_desc->id,
2258             rule_matches_all => $rule_template->matches_all,
2259             rule_specifies_id => ($rule_template->specifies_value_for('id') || undef),
2260             rule_template_is_id_only => $rule_template->is_id_only,
2261             rule_template_specifies_value_for_subtype => $rule_template_specifies_value_for_subtype,
2262              
2263             recursion_desc => undef,
2264             recurse_property_on_this_row => undef,
2265             recurse_property_referencing_other_rows => undef,
2266              
2267             %$class_data,
2268             );
2269              
2270 0         0 return $self;
2271             }
2272              
2273             sub order_by_column_list {
2274 1447     1447 0 2040 my $self = shift;
2275              
2276 1447         3933 $self->_resolve_order_by_and_descending_data();
2277 1447         2739 return $self->{_order_by_column_list};
2278             }
2279              
2280             sub _resolve_order_by_and_descending_data {
2281 3621     3621   3898 my $self = shift;
2282              
2283 3621 100       8467 unless ($self->{_order_by_column_list}) {
2284 638         859 my %is_descending;
2285             my @order_by_columns =
2286             map {
2287             m/^-(.*)/
2288 1078 100       3776 ? $is_descending{$1} = $1
2289             : $_;
2290             }
2291 638 50       1047 @{ $self->order_by_columns || [] };
  638         2251  
2292              
2293 638         1634 $self->{_order_by_column_list} = \@order_by_columns;
2294 638         1660 $self->{_order_by_column_is_descending} = \%is_descending;
2295             }
2296             }
2297              
2298             sub order_by_column_is_descending {
2299 2174     2174 0 2725 my($self, $column_name) = @_;
2300              
2301 2174         3938 $self->_resolve_order_by_and_descending_data();
2302 2174         5841 return $self->{_order_by_column_is_descending}->{$column_name};
2303             }
2304              
2305             sub property_meta_for_column {
2306 2174     2174 0 2930 my($self, $table_and_column_name) = @_;
2307              
2308 2174         4081 $table_and_column_name = lc($table_and_column_name);
2309              
2310 2174         5539 my $data_source = $self->data_source();
2311 2174         7976 my ($table_name, $column_name) = $data_source->_resolve_table_and_column_from_column_name($table_and_column_name);
2312              
2313 2174 100       6336 if (my $join = $self->_get_alias_join($table_name)) {
2314             # The given $table_name was actually a join alias
2315 2         8 my $foreign_class_meta = $join->foreign_class->__meta__;
2316 2         13 my $prop_name = $foreign_class_meta->property_for_column($column_name);
2317 2 50       12 return $prop_name
2318             ? $foreign_class_meta->property_meta_for_name($prop_name)
2319             : undef;
2320              
2321             } else {
2322 2172         4864 my $class_meta = $self->class_name->__meta__;
2323 2172         10188 my $prop_name = $class_meta->property_for_column($table_and_column_name);
2324 2172         4687 return $class_meta->property_meta_for_name($prop_name);
2325             }
2326             }
2327              
2328             1;
2329