File Coverage

lib/UR/BoolExpr/Template.pm
Criterion Covered Total %
statement 246 306 80.3
branch 87 126 69.0
condition 54 72 75.0
subroutine 30 37 81.0
pod 1 20 5.0
total 418 561 74.5


line stmt bran cond sub pod time code
1              
2             =head1 NAME
3              
4             UR::BoolExpr::Template - a UR::BoolExpr minus specific values
5              
6             =head1 SYNOPSIS
7              
8             =head1 DESCRIPTION
9              
10             =cut
11              
12             package UR::BoolExpr::Template;
13              
14 266     266   1113 use warnings;
  266         320  
  266         9365  
15 266     266   935 use strict;
  266         294  
  266         5876  
16              
17 266     266   852 use Scalar::Util qw(blessed);
  266         285  
  266         11455  
18 266     266   923 use Data::Dumper;
  266         291  
  266         9857  
19 266     266   950 use UR;
  266         281  
  266         2753  
20              
21             our @CARP_NOT = qw(UR::BoolExpr);
22              
23             # readable stringification
24 266     266   10373 use overload ('""' => 'id');
  266         310  
  266         1725  
25 266     266   21749 use overload ('==' => sub { $_[0] . '' eq $_[1] . '' } );
  266     0   341  
  266         1261  
  0         0  
26 266     266   15495 use overload ('eq' => sub { $_[0] . '' eq $_[1] . '' } );
  266     2   319  
  266         1105  
  2         183  
27              
28             UR::Object::Type->define(
29             class_name => __PACKAGE__,
30             is_transactional => 0,
31             composite_id_separator => '/',
32             id_by => [
33             subject_class_name => { is => 'Text' },
34             logic_type => { is => 'Text' },
35             logic_detail => { is => 'Text' },
36             constant_value_id => { is => 'Text' }
37             ],
38             has => [
39             is_normalized => { is => 'Boolean' },
40             is_id_only => { is => 'Boolean' },
41             is_partial_id => { is => 'Boolean' }, # True if at least 1, but not all the ID props are mentioned
42             is_unique => { is => 'Boolean' },
43            
44             matches_all => { is => 'Boolean' },
45             key_op_hash => { is => 'HASH' },
46             id_position => { is => 'Integer' },
47             normalized_id => { is => 'Text' },
48             normalized_positions_arrayref => { is => 'ARRAY' },
49             normalization_extender_arrayref => { is => 'ARRAY' },
50             _property_meta_hash => { is => 'HASH' },
51             _property_names_arrayref => { is => 'ARRAY' },
52             num_values => { is => 'Integer' },
53             _ambiguous_keys => { is => 'ARRAY' },
54            
55             _keys => { is => 'ARRAY' },
56             _constant_values => { is => 'ARRAY' },
57             ],
58             has_optional => [
59             hints => { is => 'ARRAY' },
60             recursion_desc => { is => 'ARRAY' },
61             order_by => { is => 'ARRAY' },
62             group_by => { is => 'ARRAY' },
63             aggregate => { is => 'ARRAY' },
64             limit => { is => 'Integer' },
65             offset => { is => 'Integer' },
66             ]
67             );
68              
69             our $VERSION = "0.46"; # UR $VERSION;;
70              
71             # Borrow from the util package.
72             # This will go away with refactoring.
73              
74             our $id_sep = $UR::BoolExpr::Util::id_sep;
75             our $record_sep = $UR::BoolExpr::Util::record_sep;
76             our $unit_sep = $UR::BoolExpr::Util::unit_sep;
77             our $null_value = $UR::BoolExpr::Util::null_value;
78             our $empty_string = $UR::BoolExpr::Util::empty_string;
79             our $empty_list = $UR::BoolExpr::Util::empty_list;
80              
81             # Names of the optional flags you can add to a rule
82             our @meta_param_names = qw(recursion_desc hints order_by group_by aggregate limit offset);
83              
84             # Wrappers for regular properties
85              
86             sub _property_names {
87 12382     12382   10376 return @{ $_[0]->{_property_names_arrayref} };
  12382         30016  
88             }
89              
90             # Indexability methods
91              
92             sub _indexable_property_names {
93 0 0   0   0 $_[0]->_resolve_indexing_params unless $_[0]->{_resolve_indexing_params};
94 0         0 @{ $_[0]->{_indexable_property_names} }
  0         0  
95             }
96              
97             sub _indexable_property_positions {
98 0 0   0   0 $_[0]->_resolve_indexing_params unless $_[0]->{_resolve_indexing_params};
99 0         0 @{ $_[0]->{_indexable_property_positions} }
  0         0  
100             }
101              
102             sub _is_fully_indexable {
103 0 0   0   0 $_[0]->_resolve_indexing_params unless $_[0]->{_resolve_indexing_params};
104 0         0 $_[0]->{_is_fully_indexable};
105             }
106              
107             sub _resolve_indexing_params {
108 0     0   0 my $self = $_[0];
109              
110 0         0 my $class_meta = UR::Object::Type->get($self->subject_class_name);
111              
112 0         0 my @all_names = $self->_property_names;
113              
114 0         0 for my $name (@all_names) {
115 0         0 my $m = $class_meta->property($name);
116 0 0       0 unless ($m) {
117             #$DB::single = 1;
118 0         0 $class_meta->property($name);
119             #$DB::single = 1;
120 0         0 $class_meta->property($name);
121             }
122             }
123            
124             my @indexable_names =
125             sort
126 0         0 map { $_->property_name }
127 0         0 grep { $_ } #and $_->is_indexable }
128 0         0 map { $class_meta->property_meta_for_name($_) }
  0         0  
129             @all_names;
130            
131             my @indexable_positions
132 0         0 = UR::Util::positions_of_values(\@all_names,\@indexable_names);
133            
134 0         0 $self->{_indexable_property_names} = \@indexable_names;
135 0         0 $self->{_indexable_property_positions} = \@indexable_positions;
136 0         0 $self->{_is_fully_indexable} = (@indexable_names == @all_names);
137            
138 0         0 return 1;
139             }
140              
141             # Return true if this rule template's parameters is a subset of the other's parameters
142             # Returns 0 if this rule specifies a parameter not in the other template
143             # Returns undef if all the properties match, but their operators do not, meaning that
144             # we do not know if an object evaluated as true under one rule's template would also be in the other
145             sub is_subset_of {
146 7598     7598 0 6420 my($self,$other_template) = @_;
147              
148 7598         10420 my $other_template_id = $other_template->id;
149 7598   100     16964 my $cached_subset_data = $self->{'__cache'}->{'is_subset_of'} ||= {};
150 7598 100       13206 if (exists $cached_subset_data->{$other_template_id}) {
151 3768         9490 return $cached_subset_data->{$other_template_id};
152             }
153              
154 3830 50 33     16857 unless (ref($other_template) and $self->isa(ref $other_template)) {
155 0         0 $cached_subset_data->{$other_template_id} = 0;
156 0         0 return 0;
157             }
158              
159 3830         7207 my $my_class = $self->subject_class_name;
160 3830         5894 my $other_class = $other_template->subject_class_name;
161 3830 100 100     16355 unless ($my_class eq $other_class or $my_class->isa($other_class)) {
162 2253         3671 $cached_subset_data->{$other_template_id} = undef;
163 2253         5830 return;
164             }
165              
166 1577         2552 my %operators = map { $_ => $self->operator_for($_) } $self->_property_names;
  2149         3080  
167 1577         1844 my $is_subset = 1;
168 1577         2203 foreach my $prop ( $other_template->_property_names ) {
169 1917 100       3306 unless (exists $operators{$prop}) {
170 799         723 $is_subset = 0;
171 799         813 last;
172             }
173 1118 100       1700 $is_subset = undef if ($operators{$prop} ne $other_template->operator_for($prop));
174             }
175              
176 1577 100       2663 if ($is_subset) {
177 413         1551 $is_subset = $self->_is_subset_of_limit_offset($other_template);
178             }
179              
180 1577         5756 return $cached_subset_data->{$other_template_id} = $is_subset;
181             }
182              
183             sub _is_subset_of_limit_offset {
184 413     413   543 my($self, $other_template) = @_;
185              
186 413 100 100     1122 return 1 unless ($self->offset or defined($self->limit)
      100        
      100        
187             or $other_template->offset or defined($other_template->limit));
188              
189             # need to do a more comprehensive filter match. If one or both templates
190             # has -limit and/or -offset, then the filters on both templates must match
191             # exactly. Otherwise, one result set could include objects that were
192             # skipped because of the other's offset or limit
193 68         138 my @my_filters = map { $_ . $self->operator_for($_) } $self->_property_names;
  90         123  
194 68         111 my @other_filters = map { $_ . $other_template->operator_for($_) } $other_template->_property_names;
  55         69  
195 68         261 my($both, $only_my, $only_other) = UR::Util::intersect_lists(\@my_filters, \@other_filters);
196 68 100 66     261 return undef if (@$only_my or @$only_other);
197              
198 46   100     86 my $my_offset = $self->offset || 0;
199 46         89 my $my_limit = $self->limit;
200 46   100     83 my $other_offset = $other_template->offset || 0;
201 46         78 my $other_limit = $other_template->limit;
202              
203 46         45 my $is_subset;
204 46 100 100     222 if (defined($my_limit) and defined($other_limit)) {
    100 100        
205 23         42 my $my_last = $my_offset + $my_limit;
206 23         33 my $other_last = $other_offset + $other_limit;
207              
208 23   100     76 $is_subset = ($my_offset >= $other_offset) && ($my_last <= $other_last);
209              
210             } elsif (!defined($my_limit) and defined($other_limit)) {
211 4         7 $is_subset = 0;
212              
213             } else {
214 19         55 $is_subset = $my_offset >= $other_offset;
215             }
216 46         111 return $is_subset;
217             }
218              
219              
220             # This is set lazily currently
221              
222             sub is_unique {
223 3501     3501 0 3565 my $self = $_[0];
224 3501 100       6836 if (defined $self->{is_unique}) {
225             return $self->{is_unique}
226 2049         9362 }
227              
228             # since this requires normalization, we don't set the value at construction time
229 1452         1715 my $normalized_self;
230 1452 100       3931 if ($self->is_normalized) {
231 1228         1860 $normalized_self = $self;
232             }
233             else {
234 224         710 $normalized_self = $self->get_normalized_template_equivalent($self);
235             }
236              
237 1452         5056 my $op = $normalized_self->operator_for('id');
238 1452 100 66     7528 if (defined($op) and ($op eq '' or $op eq '=')) {
      66        
239 595         2128 return $self->{is_unique} = 1;
240             }
241             else {
242 857         1366 $self->{is_unique} = 0;
243            
244             # if some combination of params can combine to
245             # satisfy at least one unique constraint,
246             # then we have uniqueness in the parameters.
247              
248 857 50       2425 if (my @ps = $self->subject_class_name->__meta__->unique_property_sets) {
249 0         0 my $property_meta_hash = $self->_property_meta_hash;
250 0         0 for my $property_set (@ps)
251             {
252 0 0       0 my $property_set = (ref($property_set) ? $property_set : [$property_set]);
253             my @properties_used_from_constraint =
254 0         0 grep { defined($_) }
255 0         0 @$property_meta_hash{@$property_set};
256            
257 0 0       0 if (@properties_used_from_constraint == @$property_set) {
258             # filter imprecise operators
259             @properties_used_from_constraint =
260             grep {
261 0         0 $_->{operator} !~ /^(not |)like(-.|)$/i
262             and
263 0   0     0 $_->{operator} !~ /^(not |)in/i
264             }
265             @properties_used_from_constraint;
266            
267 0 0       0 if (@properties_used_from_constraint == @$property_set) {
268 0         0 $self->{is_unique} = 1;
269 0         0 last;
270             }
271             else {
272             ## print "some properties use bad operators: @properties_used_from_constraint\n";
273             }
274             }
275             else {
276             ## print "too few properties in @properties_used_from_constraint\n";
277             }
278             }
279             }
280              
281 857         3262 return $self->{is_unique};
282             }
283             }
284              
285              
286             # Derivative of the ID.
287              
288             sub rule_template_subclass_name {
289 0     0 0 0 return "UR::BoolExpr::Template::" . shift->logic_type;
290             }
291              
292             sub get_normalized_template_equivalent {
293 339091     339091 0 634876 UR::BoolExpr::Template->get($_[0]->{normalized_id});
294             }
295              
296             sub get_rule_for_values {
297 557792     557792 0 428979 my $self = shift;
298 557792         1034652 my $value_id = UR::BoolExpr::Util::values_to_value_id(@_);
299 557792         1325543 my $rule_id = UR::BoolExpr->__meta__->resolve_composite_id_from_ordered_values($self->id,$value_id);
300 557792         1035775 my $r = UR::BoolExpr->get($rule_id);
301             #
302             # # FIXME - Don't do this part if the operator is 'in' or 'between'
303             # for (my $i = 0; $i < @_; $i++) {
304             # if (ref($_[$i]) and ! Scalar::Util::blessed($_[$i])) {
305             # $r->{'hard_refs'}->{$i} = $_[$i];
306             # }
307             # }
308 557792         690020 return $r;
309             }
310              
311             sub get_rule_for_value_id {
312 0     0 0 0 my $self = shift;
313 0         0 my $value_id = shift;
314              
315 0         0 my $rule_id = UR::BoolExpr->__meta__->resolve_composite_id_from_ordered_values($self->id,$value_id);
316 0         0 return UR::BoolExpr->get($rule_id);
317             }
318              
319             sub extend_params_list_for_values {
320 4049     4049 0 4023 my $self = shift;
321             #my @prev = @_;
322 4049         10390 my $extenders = $self->normalization_extender_arrayref;
323 4049 100       8108 if (@$extenders) {
324 530         595 my @result;
325 530         1322 my $subject_class = $self->subject_class_name->__meta__;
326 530         1640 for my $n (0 .. @$extenders-1) {
327 561         863 my $extender = $extenders->[$n];
328 561         1258 my ($input_positions_arrayref,$subref,@more_keys) = @$extender;
329 561         1288 my @more_values = @_[@$input_positions_arrayref];
330 561 100       1176 if ($subref) {
331             ## print "calling $subref on \n\t" . join("\n\t",@more_values) . "\n";
332 385         1752 @more_values = $subject_class->$subref(@more_values);
333             ## print "got: \n\t" . join("\n\t",@more_values) . "\n";
334             }
335 561         1365 while (@more_keys) {
336 564         752 my $k = shift @more_keys;
337 564         677 my $v = shift @more_values;
338 564         1826 push @result, $k => $v;
339             }
340             }
341 530         2643 return @result;
342             }
343 3519         12347 return ();
344             }
345              
346             sub get_normalized_rule_for_values {
347 339255     339255 0 270450 my $self = shift;
348 339255         388146 my @unnormalized_values = @_;
349              
350 339255 100       626441 if ($self->is_normalized) {
351 574         1059 return $self->get_rule_for_values(@unnormalized_values);
352             }
353              
354 338681         495429 my $normalized_rule_template = $self->get_normalized_template_equivalent;
355              
356             # The normalized rule set may have more values than were actually
357             # passed-in. These 'extenders' will add to the @values array
358             # before re-ordering it.
359 338681         605052 my $extenders = $self->normalization_extender_arrayref;
360 338681 100       529812 if (@$extenders) {
361 337625         561791 my $subject_class = $self->subject_class_name->__meta__;
362 337625         407848 for my $extender (@$extenders) {
363 337822         369985 my ($input_positions_arrayref,$subref) = @$extender;
364 337822         490220 my @more_values = @unnormalized_values[@$input_positions_arrayref];
365 337822 100       465798 if ($subref) {
366             ## print "calling $subref on \n\t" . join("\n\t",@more_values) . "\n";
367 222774         584482 @more_values = $subject_class->$subref(@more_values);
368             ## print "got: \n\t" . join("\n\t",@more_values) . "\n";
369             }
370 337822         531277 push @unnormalized_values, @more_values;
371             }
372             }
373            
374             # Normalize the values. Since the normalized template may have added properties,
375             # and a different order we may need to re-order and expand the values list.
376 338681         570538 my $normalized_positions_arrayref = $self->normalized_positions_arrayref;
377 338681         494913 my @normalized_values = @unnormalized_values[@$normalized_positions_arrayref];
378              
379 338681         471522 my $rule = $normalized_rule_template->get_rule_for_values(@normalized_values);
380 338681         592435 return $rule;
381             }
382              
383             sub _normalize_non_ur_values_hash {
384 64     64   111 my ($self,$unnormalized) = @_;
385 64         88 my %normalized;
386 64 50       204 if ($self->subject_class_name ne 'UR::Object::Property') {
387 64         167 my $normalized_positions_arrayref = $self->normalized_positions_arrayref;
388 64         271 my @reordered_values = @$unnormalized{@$normalized_positions_arrayref};
389 64         243 for (my $n = 0; $n < @reordered_values; $n++) {
390 341         266 my $value = $reordered_values[$n];
391 341 100       837 $normalized{$n} = $value if defined $value;
392             }
393             }
394 64         182 return \%normalized;
395             }
396              
397              
398             sub value_position_for_property_name {
399 3983 100   3983 0 8624 if (exists $_[0]{_property_meta_hash}{$_[1]}) {
400 3895         8478 return $_[0]{_property_meta_hash}{$_[1]}{value_position};
401             } else {
402 88         192 return undef;
403             }
404             }
405              
406             sub operator_for {
407 12164 100   12164 0 26542 if (exists $_[0]{_property_meta_hash}{$_[1]}) {
408 11335   100     52802 return $_[0]{_property_meta_hash}{$_[1]}{operator} || '=';
409             } else {
410 829         1415 return undef;
411             }
412             }
413              
414             sub operators_for_properties {
415 516   100     2460 my %properties = map { $_ => $_[0]->{'_property_meta_hash'}->{$_}->{'operator'} || '=' }
416 372     372 0 315 @{ $_[0]->{'_property_names_arrayref'} };
  372         750  
417 372         816 return \%properties;
418             }
419              
420             sub add_filter {
421 5     5 0 8 my $self = shift;
422 5         15 my $property_name = shift;
423 5         11 my $op = shift;
424 5         9 my $new_key = $property_name;
425 5 50       35 $new_key .= ' ' . $op if defined $op;
426 5         16 my ($subject_class_name, $logic_type, $logic_detail) = split("/",$self->id);
427 5 50       19 unless ($logic_type eq 'And') {
428 0         0 die "Attempt to add a filter to a rule besides an 'And' rule!";
429             }
430 5         11 my @keys = split(',',$logic_detail);
431 5         17 my $new_id = join('/',$subject_class_name,$logic_type,join(',',@keys,$new_key));
432 5         15 return $self->class->get($new_id);
433             }
434              
435             sub remove_filter {
436 34     34 0 49 my $self = shift;
437 34         54 my $filter = shift;
438 34         87 my ($subject_class_name, $logic_type, $logic_detail) = split("/",$self->id);
439 34         100 my @keys = grep { $_ !~ /^${filter}\b/ } split(',',$logic_detail);
  58         261  
440 34         114 my $new_id = join('/',$subject_class_name,$logic_type,join(',',@keys));
441             #print "$new_id\n";
442 34         155 return $self->class->get($new_id);
443             }
444              
445             sub sub_classify {
446 76     76 0 138 my ($self,$subclass_name) = @_;
447 76         228 my $new_id = $self->id;
448 76         698 $new_id =~ s/^.*?\//$subclass_name\//;
449 76         296 return $self->class->get($new_id);
450             }
451              
452              
453             # flyweight constructor
454             # NOTE: this caches outside of the regular system since these are stateless objects
455             sub get_by_subject_class_name_logic_type_and_logic_detail {
456 25576     25576 0 29287 my $class = shift;
457 25576         26254 my $subject_class_name = shift;
458 25576 0       45986 Carp::croak("Expected a subject class name as the first arg of UR::BoolExpr::Template constructor, got "
    50          
459             . ( defined($subject_class_name) ? "'$subject_class_name'" : "(undef)" ) ) unless ($subject_class_name);
460 25576         26798 my $logic_type = shift;
461 25576         25853 my $logic_detail = shift;
462 25576   66     45793 my $constant_value_id = shift || UR::BoolExpr::Util::values_to_value_id(); # default is an empty list of values
463              
464 25576         71320 return $class->get(join('/',$subject_class_name,$logic_type,$logic_detail,$constant_value_id));
465             }
466              
467             # The analogue of resolve in UR::BoolExpr. @params_list is a list if
468             # strings containing properties and operators separated by a space. For ex: "some_param ="
469             sub resolve {
470 25486     25486 0 49789 my($class,$subject_class_name, @params_list) = @_;
471              
472 25486         25438 my(@params, @constant_values);
473 25486         67417 for (my $i = 0; $i < @params_list; $i++) {
474 51122         57271 push @params, $params_list[$i];
475 51122 100       101935 if (UR::BoolExpr::Util::is_meta_param($params_list[$i])) {
476 14         49 push @constant_values, $params_list[++$i];
477             }
478             }
479              
480 25486         80037 return $class->get_by_subject_class_name_logic_type_and_logic_detail(
481             $subject_class_name,
482             "And",
483             join(',',@params),
484             UR::BoolExpr::Util::values_to_value_id(@constant_values));
485             }
486              
487             sub get {
488 1233408     1233408 1 1014469 my $class = shift;
489 1233408         919837 my $id = shift;
490 1233408 50       1605313 Carp::croak("Non-id params not supported for " . __PACKAGE__ . " yet!") if @_;
491              
492 1233408         1410851 my $self = $UR::Object::rule_templates->{$id};
493 1233408 100       2143785 return $self if $self;
494              
495 3735         13250 my ($subject_class_name,$logic_type,$logic_detail,$constant_value_id,@extra) = split('/',$id);
496 3735 50       7753 if (@extra) {
497             # account for a possible slash in the constant value id
498 0         0 $constant_value_id = join('/',$constant_value_id,@extra);
499             }
500              
501             # work on the base class or on subclasses
502 3735 100       8884 my $sub_class_name = (
503             $class eq __PACKAGE__
504             ? __PACKAGE__ . "::" . $logic_type
505             : $class
506             );
507              
508 3735 50       7086 unless ($logic_type) {
509 0         0 Carp::croak("Could not determine logic type from UR::BoolExpr::Template with id $id");
510             }
511              
512 3735 100       6504 if ($logic_type eq "And") {
513             # TODO: move into subclass
514 2431   100     9140 my @keys = split(/,/,$logic_detail || '');
515 2431         2457 my @constant_values;
516 2431 100       8761 @constant_values = UR::BoolExpr::Util::value_id_to_values($constant_value_id) if defined $constant_value_id;
517 2431         9204 return $sub_class_name->_fast_construct(
518             $subject_class_name,
519             \@keys,
520             \@constant_values,
521             $logic_detail,
522             $constant_value_id,
523             );
524             }
525             else {
526 1304         7332 $self = bless {
527             id => $id,
528             subject_class_name => $subject_class_name,
529             logic_type => $logic_type,
530             logic_detail => $logic_detail,
531             constant_value_id => $constant_value_id,
532             normalized_id => $id,
533             }, $sub_class_name;
534 1304         2971 $UR::Object::rule_templates->{$id} = $self;
535 1304         5577 return $self;
536             }
537             }
538              
539              
540             # Return true if the template has recursion_desc, hints, order or page set
541             sub has_meta_options {
542 117286     117286 0 99422 my $self = shift;
543 117286 100       424012 return 1 if @$self{@meta_param_names};
544 117270         297319 return 0;
545             }
546              
547              
548             # This is the basis for the hash used by the existing UR::Object system for each rule.
549             # this is created upon first request and cached in the object
550              
551             sub legacy_params_hash {
552 58422     58422 0 52893 my $self = shift;
553 58422         67310 my $legacy_params_hash = $self->{legacy_params_hash};
554 58422 100       232024 return $legacy_params_hash if $legacy_params_hash;
555            
556 1452         2311 $legacy_params_hash = {};
557            
558 1452         3464 my $template_id = $self->id;
559 1452         6217 my $key_op_hash = $self->key_op_hash;
560 1452         3684 my $id_only = $self->is_id_only;
561            
562 1452         3561 my $subject_class_name = $self->subject_class_name;
563 1452         4925 my $logic_type = $self->logic_type;
564 1452         4011 my $logic_detail = $self->logic_detail;
565 1452         4596 my @keys_sorted = $self->_underlying_keys;
566 1452         4739 my $subject_class_meta = $subject_class_name->__meta__;
567            
568 1452 50 66     13025 if (
      66        
      33        
569             (@keys_sorted and not $logic_detail)
570             or
571             ($logic_detail and not @keys_sorted)
572             ) {
573 0         0 Carp::confess();
574             }
575            
576 1452 100       3362 if (!$logic_detail) {
577 40         166 %$legacy_params_hash = (_unique => 0, _none => 1);
578             }
579             else {
580             # _id_only
581 1412 100       2916 if ($id_only) {
582 599         1607 $legacy_params_hash->{_id_only} = 1;
583             }
584             else {
585 813         1851 $legacy_params_hash->{_id_only} = 0;
586 813         1582 $legacy_params_hash->{_param_key} = undef;
587             }
588            
589             # _unique
590 1412 100       4050 if (my $id_op = $key_op_hash->{id}) {
591 696 100 66     2761 if ($id_op->{""} or $id_op->{"="}) {
592 596         1054 $legacy_params_hash->{_unique} = 1;
593 596 100       2421 unless ($self->is_unique) {
594 1         199 Carp::carp("The BoolExpr includes a filter on ID, but the is_unique flag is unexpectedly false for $self->{id}");
595             }
596             }
597             }
598              
599            
600              
601 1412 100       3580 unless ($legacy_params_hash->{_unique}) {
602 816 50 33     3192 if (defined $legacy_params_hash->{id} and not ref $legacy_params_hash->{id}) {
603             # if we have the id, then we have uniqueness
604             # NOT TRUE: we catch the truly unieq cses of having an id and an unambiguous operator above
605             #$legacy_params_hash->{_unique} = 1;
606             }
607             else {
608             # default to non-unique
609 816         1468 $legacy_params_hash->{_unique} = 0;
610            
611             # if some combination of params can combine to
612             # satisfy at least one unique constraint,
613             # then we have uniqueness in the parameters.
614            
615 816         5586 my @ps = $subject_class_meta->unique_property_sets;
616 816         1806 for my $property_set (@ps)
617             {
618 0 0       0 my $property_set = (ref($property_set) ? $property_set : [$property_set]);
619             my @properties_used_from_constraint =
620 0         0 grep { defined($_) }
621 0 0       0 (ref($property_set) ? @$key_op_hash{@$property_set} : $key_op_hash->{$property_set});
622            
623 0 0       0 if (@properties_used_from_constraint == @$property_set) {
624             # filter imprecise operators
625             @properties_used_from_constraint =
626             grep {
627 0         0 not (
628 0 0       0 grep { /^(not |)like(-.|)$/i or /^\[\]/}
  0         0  
629             keys %$_
630             )
631             }
632             @properties_used_from_constraint;
633            
634 0 0       0 if (@properties_used_from_constraint == @$property_set) {
635 0         0 $legacy_params_hash->{_unique} = 1;
636 0         0 last;
637             }
638             else {
639             ## print "some properties use bad operators: @properties_used_from_constraint\n";
640             }
641             }
642             else {
643             ## print "too few properties in @properties_used_from_constraint\n";
644             }
645             }
646             }
647            
648             # _param_key gets re-set as long as this has a true value
649 816 100       2577 $legacy_params_hash->{_param_key} = undef unless $id_only;
650             }
651             }
652              
653 1452 50 66     4164 if ($self->is_unique and not $legacy_params_hash->{_unique}) {
654 0         0 Carp::carp "is_unique IS set but legacy params hash is NO for $self->{id}";
655             #$DB::single = 1;
656 0         0 $self->is_unique;
657             }
658 1452 100 66     3197 if (!$self->is_unique and $legacy_params_hash->{_unique}) {
659 1         152 Carp::carp "is_unique NOT set but legacy params hash IS for $self->{id}";
660             #$DB::single = 1;
661 1         3 $self->is_unique;
662             }
663              
664 1452         2872 $self->{legacy_params_hash} = $legacy_params_hash;
665 1452         8135 return $legacy_params_hash;
666             }
667              
668             sub sorter {
669 20092     20092 0 22242 my $self = shift;
670              
671             # return a standard sorter for expressions using this template
672             # the template might contain a group_by or order_by clause which affects it...
673              
674 20092 50       36647 die "this method takes no paramters!" if @_;
675              
676 20092         40690 my $class = $self->subject_class_name;
677              
678 20092         19891 my $sort_meta;
679 20092 100       39350 if ($self->group_by) {
680 59         128 my $set_class = $class . "::Set";
681 59         351 $sort_meta = $set_class->__meta__;
682             }
683             else {
684 20033         50919 $sort_meta = $class->__meta__;
685             }
686              
687 20092         19786 my $sorter;
688 20092 100       43218 if (my $order_by = $self->order_by) {
689 233         773 $sorter = $sort_meta->sorter(@$order_by);
690             }
691             else {
692 19859         59506 $sorter = $sort_meta->sorter();
693             }
694              
695 20092         37626 return $sorter;
696             }
697              
698              
699             1;
700