line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package UR::BoolExpr; |
2
|
266
|
|
|
266
|
|
1024
|
use warnings; |
|
266
|
|
|
|
|
1990
|
|
|
266
|
|
|
|
|
7938
|
|
3
|
266
|
|
|
266
|
|
924
|
use strict; |
|
266
|
|
|
|
|
338
|
|
|
266
|
|
|
|
|
9308
|
|
4
|
|
|
|
|
|
|
|
5
|
266
|
|
|
266
|
|
3063
|
use List::MoreUtils qw(uniq); |
|
266
|
|
|
|
|
297
|
|
|
266
|
|
|
|
|
5909
|
|
6
|
266
|
|
|
266
|
|
110841
|
use List::Util qw(first); |
|
266
|
|
|
|
|
348
|
|
|
266
|
|
|
|
|
19582
|
|
7
|
266
|
|
|
266
|
|
1031
|
use Scalar::Util qw(blessed); |
|
266
|
|
|
|
|
333
|
|
|
266
|
|
|
|
|
12700
|
|
8
|
|
|
|
|
|
|
require UR; |
9
|
|
|
|
|
|
|
|
10
|
266
|
|
|
266
|
|
1005
|
use Carp; |
|
266
|
|
|
|
|
357
|
|
|
266
|
|
|
|
|
23271
|
|
11
|
|
|
|
|
|
|
our @CARP_NOT = ('UR::Context'); |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
our $VERSION = "0.46"; # UR $VERSION;; |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
# readable stringification |
16
|
266
|
|
|
266
|
|
4431
|
use overload ('""' => '__display_name__'); |
|
266
|
|
|
|
|
4016
|
|
|
266
|
|
|
|
|
6140
|
|
17
|
266
|
|
|
266
|
|
28163
|
use overload ('==' => sub { $_[0] . '' eq $_[1] . '' } ); |
|
266
|
|
|
33
|
|
353
|
|
|
266
|
|
|
|
|
2971
|
|
|
33
|
|
|
|
|
77
|
|
18
|
266
|
|
|
266
|
|
20817
|
use overload ('eq' => sub { $_[0] . '' eq $_[1] . '' } ); |
|
266
|
|
|
1
|
|
371
|
|
|
266
|
|
|
|
|
2992
|
|
|
1
|
|
|
|
|
2065
|
|
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
UR::Object::Type->define( |
21
|
|
|
|
|
|
|
class_name => 'UR::BoolExpr', |
22
|
|
|
|
|
|
|
composite_id_separator => $UR::BoolExpr::Util::id_sep, |
23
|
|
|
|
|
|
|
id_by => [ |
24
|
|
|
|
|
|
|
template_id => { type => 'Blob' }, |
25
|
|
|
|
|
|
|
value_id => { type => 'Blob' }, |
26
|
|
|
|
|
|
|
], |
27
|
|
|
|
|
|
|
has => [ |
28
|
|
|
|
|
|
|
template => { is => 'UR::BoolExpr::Template', id_by => 'template_id' }, |
29
|
|
|
|
|
|
|
subject_class_name => { via => 'template' }, |
30
|
|
|
|
|
|
|
logic_type => { via => 'template' }, |
31
|
|
|
|
|
|
|
logic_detail => { via => 'template' }, |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
num_values => { via => 'template' }, |
34
|
|
|
|
|
|
|
is_normalized => { via => 'template' }, |
35
|
|
|
|
|
|
|
is_id_only => { via => 'template' }, |
36
|
|
|
|
|
|
|
has_meta_options => { via => 'template' }, |
37
|
|
|
|
|
|
|
], |
38
|
|
|
|
|
|
|
is_transactional => 0, |
39
|
|
|
|
|
|
|
); |
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
# for performance |
43
|
|
|
|
|
|
|
sub UR::BoolExpr::Type::resolve_composite_id_from_ordered_values { |
44
|
557792
|
|
|
557792
|
|
418352
|
shift; |
45
|
557792
|
|
|
|
|
1156259
|
return join($UR::BoolExpr::Util::id_sep,@_); |
46
|
|
|
|
|
|
|
} |
47
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
# only respect the first delimiter instead of splitting |
49
|
|
|
|
|
|
|
sub UR::BoolExpr::Type::resolve_ordered_values_from_composite_id { |
50
|
347619
|
|
|
347619
|
|
316136
|
my ($self,$id) = @_; |
51
|
347619
|
|
|
|
|
396634
|
my $pos = index($id,$UR::BoolExpr::Util::id_sep); |
52
|
347619
|
|
|
|
|
839051
|
return (substr($id,0,$pos), substr($id,$pos+1)); |
53
|
|
|
|
|
|
|
} |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
sub template { |
56
|
2993309
|
|
|
2993309
|
0
|
2079730
|
my $self = $_[0]; |
57
|
2993309
|
|
66
|
|
|
5407864
|
return $self->{template} ||= $self->__template; |
58
|
|
|
|
|
|
|
} |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
sub flatten { |
61
|
13
|
|
|
13
|
1
|
421
|
my $self = shift; |
62
|
13
|
50
|
|
|
|
44
|
return $self->{flatten} if exists $self->{flatten}; |
63
|
13
|
|
|
|
|
21
|
my $flat = $self->template->_flatten_bx($self); |
64
|
13
|
|
|
|
|
26
|
$self->{flatten} = $flat; |
65
|
13
|
100
|
|
|
|
79
|
Scalar::Util::weaken($self->{flatten}) if $self == $flat; |
66
|
13
|
|
|
|
|
27
|
return $flat; |
67
|
|
|
|
|
|
|
} |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
sub reframe { |
70
|
20
|
|
|
20
|
1
|
53
|
my $self = shift; |
71
|
20
|
|
|
|
|
31
|
my $in_terms_of = shift; |
72
|
20
|
50
|
|
|
|
66
|
return $self->{reframe}{$in_terms_of} if $self->{reframe}{$in_terms_of}; |
73
|
20
|
|
|
|
|
41
|
my $reframe = $self->template->_reframe_bx($self, $in_terms_of); |
74
|
20
|
|
|
|
|
41
|
$self->{reframe}{$in_terms_of} = $reframe; |
75
|
20
|
50
|
|
|
|
156
|
Scalar::Util::weaken($self->{reframe}{$in_terms_of}) if $self == $reframe; |
76
|
20
|
|
|
|
|
55
|
return $reframe; |
77
|
|
|
|
|
|
|
} |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
# override the UR/system display name |
81
|
|
|
|
|
|
|
# this is used in stringification overload |
82
|
|
|
|
|
|
|
sub __display_name__ { |
83
|
522
|
|
|
522
|
|
5814
|
my $self = shift; |
84
|
522
|
|
|
|
|
872
|
my %b = $self->_params_list; |
85
|
522
|
|
|
|
|
2106
|
my $s = Data::Dumper->new([\%b])->Terse(1)->Indent(0)->Useqq(1)->Sortkeys(1)->Dump; |
86
|
522
|
|
|
|
|
32380
|
$s =~ s/\n/ /gs; |
87
|
522
|
|
|
|
|
1950
|
$s =~ s/^\s*{//; |
88
|
522
|
|
|
|
|
1363
|
$s =~ s/\}\s*$//; |
89
|
522
|
|
|
|
|
2323
|
$s =~ s/\"(\w+)\" \=\> / $1 => /g; |
90
|
522
|
|
|
|
|
1328
|
return __PACKAGE__ . '=(' . $self->subject_class_name . ':' . $s . ')'; |
91
|
|
|
|
|
|
|
} |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
# The primary function: evaluate a subject object as matching the rule or not. |
94
|
|
|
|
|
|
|
sub evaluate { |
95
|
103880
|
|
|
103880
|
1
|
158478
|
my $self = shift; |
96
|
103880
|
|
|
|
|
80854
|
my $subject = shift; |
97
|
103880
|
|
|
|
|
128426
|
my $template = $self->template; |
98
|
103880
|
|
|
|
|
133247
|
my @values = $self->values; |
99
|
103880
|
|
|
|
|
218320
|
return $template->evaluate_subject_and_values($subject,@values); |
100
|
|
|
|
|
|
|
} |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
# Behind the id properties: |
103
|
|
|
|
|
|
|
sub template_and_values { |
104
|
347619
|
|
|
347619
|
1
|
273046
|
my $self = shift; |
105
|
347619
|
|
|
|
|
522892
|
my ($template_id, $value_id) = UR::BoolExpr::Type->resolve_ordered_values_from_composite_id($self->id); |
106
|
347619
|
|
|
|
|
696846
|
return (UR::BoolExpr::Template->get($template_id), UR::BoolExpr::Util::value_id_to_values($value_id)); |
107
|
|
|
|
|
|
|
} |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
# Returns true if the rule represents a subset of the things the other |
110
|
|
|
|
|
|
|
# rule would match. It returns undef if the answer is not known, such as |
111
|
|
|
|
|
|
|
# when one of the values is a list and we didn't go to the trouble of |
112
|
|
|
|
|
|
|
# searching the list for a matching value |
113
|
|
|
|
|
|
|
sub is_subset_of { |
114
|
247
|
|
|
247
|
1
|
350
|
my($self, $other_rule) = @_; |
115
|
|
|
|
|
|
|
|
116
|
247
|
50
|
33
|
|
|
1717
|
return 0 unless (ref($other_rule) and $self->isa(ref $other_rule)); |
117
|
|
|
|
|
|
|
|
118
|
247
|
|
|
|
|
502
|
my $my_template = $self->template; |
119
|
247
|
|
|
|
|
402
|
my $other_template = $other_rule->template; |
120
|
|
|
|
|
|
|
|
121
|
247
|
50
|
33
|
|
|
1597
|
unless ($my_template->isa("UR::BoolExpr::Template::And") |
122
|
|
|
|
|
|
|
and $other_template->isa("UR::BoolExpr::Template::And")) { |
123
|
0
|
|
|
|
|
0
|
Carp::confess("This method currently works only on ::And expressions. Update to handle ::Or, ::PropertyComparison, and templates of mismatched class!"); |
124
|
|
|
|
|
|
|
} |
125
|
247
|
100
|
|
|
|
662
|
return unless ($my_template->is_subset_of($other_template)); |
126
|
|
|
|
|
|
|
|
127
|
226
|
|
|
|
|
285
|
my $values_match = 1; |
128
|
226
|
|
|
|
|
511
|
foreach my $prop ( $other_template->_property_names ) { |
129
|
292
|
|
50
|
|
|
676
|
my $my_operator = $my_template->operator_for($prop) || '='; |
130
|
292
|
|
50
|
|
|
581
|
my $other_operator = $other_template->operator_for($prop) || '='; |
131
|
|
|
|
|
|
|
|
132
|
292
|
|
|
|
|
694
|
my $my_value = $self->value_for($prop); |
133
|
292
|
|
|
|
|
481
|
my $other_value = $other_rule->value_for($prop); |
134
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
# If either is a list of values, return undef |
136
|
292
|
50
|
33
|
|
|
1233
|
return undef if (ref($my_value) || ref($other_value)); |
137
|
|
|
|
|
|
|
|
138
|
266
|
|
|
266
|
|
180318
|
no warnings 'uninitialized'; |
|
266
|
|
|
|
|
440
|
|
|
266
|
|
|
|
|
302530
|
|
139
|
292
|
100
|
|
|
|
716
|
$values_match = undef if ($my_value ne $other_value); |
140
|
|
|
|
|
|
|
} |
141
|
|
|
|
|
|
|
|
142
|
226
|
|
|
|
|
1065
|
return $values_match; |
143
|
|
|
|
|
|
|
} |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
sub values { |
146
|
856288
|
|
|
856288
|
1
|
689507
|
my $self = shift; |
147
|
856288
|
100
|
|
|
|
1249618
|
if ($self->{values}) { |
148
|
354875
|
|
|
|
|
257753
|
return @{ $self->{values}} |
|
354875
|
|
|
|
|
661585
|
|
149
|
|
|
|
|
|
|
} |
150
|
501413
|
|
|
|
|
822726
|
my $value_id = $self->value_id; |
151
|
501413
|
50
|
33
|
|
|
1577769
|
return unless defined($value_id) and length($value_id); |
152
|
501413
|
|
|
|
|
368525
|
my @values; |
153
|
501413
|
|
|
|
|
884960
|
@values = UR::BoolExpr::Util::value_id_to_values($value_id); |
154
|
501413
|
100
|
|
|
|
889291
|
if (my $hard_refs = $self->{hard_refs}) { |
155
|
64
|
|
|
|
|
182
|
for my $n (keys %$hard_refs) { |
156
|
80
|
|
|
|
|
247
|
$values[$n] = $hard_refs->{$n}; |
157
|
|
|
|
|
|
|
} |
158
|
|
|
|
|
|
|
} |
159
|
501413
|
|
|
|
|
581325
|
$self->{values} = \@values; |
160
|
501413
|
|
|
|
|
1044065
|
return @values; |
161
|
|
|
|
|
|
|
} |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
sub value_for_id { |
164
|
279936
|
|
|
279936
|
1
|
215597
|
my $self = shift; |
165
|
279936
|
|
|
|
|
314144
|
my $t = $self->template; |
166
|
279936
|
|
|
|
|
464865
|
my $position = $t->id_position; |
167
|
279936
|
100
|
|
|
|
405145
|
return unless defined $position; |
168
|
279934
|
|
|
|
|
400618
|
return $self->value_for_position($position); |
169
|
|
|
|
|
|
|
} |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
sub specifies_value_for { |
172
|
1058
|
|
|
1058
|
1
|
35446
|
my $self = shift; |
173
|
1058
|
|
|
|
|
1762
|
my $rule_template = $self->template; |
174
|
1058
|
|
|
|
|
2670
|
return $rule_template->specifies_value_for(@_); |
175
|
|
|
|
|
|
|
} |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
sub value_for { |
178
|
2226
|
|
|
2226
|
1
|
86551
|
my $self = shift; |
179
|
2226
|
|
|
|
|
2177
|
my $property_name = shift; |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
# TODO: refactor to be more efficient |
182
|
2226
|
|
|
|
|
3260
|
my $template = $self->template; |
183
|
2226
|
|
|
|
|
3652
|
my $h = $self->legacy_params_hash; |
184
|
2226
|
|
|
|
|
2100
|
my $v; |
185
|
2226
|
100
|
|
|
|
3527
|
if (exists $h->{$property_name}) { |
186
|
|
|
|
|
|
|
# normal case |
187
|
1745
|
|
|
|
|
1907
|
$v = $h->{$property_name}; |
188
|
1745
|
|
|
|
|
4013
|
my $tmpl_pos = $template->value_position_for_property_name($property_name); |
189
|
1745
|
100
|
|
|
|
5654
|
if (exists $self->{'hard_refs'}->{$tmpl_pos}) { |
|
|
100
|
|
|
|
|
|
190
|
47
|
|
|
|
|
85
|
$v = $self->{'hard_refs'}->{$tmpl_pos}; # It was stored during resolve() as a hard ref |
191
|
|
|
|
|
|
|
} |
192
|
|
|
|
|
|
|
elsif ($self->_value_is_old_style_operator_and_value($v)) { |
193
|
209
|
|
|
|
|
262
|
$v = $v->{'value'}; # It was old style operator/value hash |
194
|
|
|
|
|
|
|
} |
195
|
|
|
|
|
|
|
} else { |
196
|
|
|
|
|
|
|
# No value found under that name... try decomposing the id |
197
|
481
|
100
|
|
|
|
1180
|
return if $property_name eq 'id'; |
198
|
252
|
|
|
|
|
611
|
my $id_value = $self->value_for('id'); |
199
|
252
|
|
|
|
|
665
|
my $class_meta = $self->subject_class_name->__meta__(); |
200
|
252
|
|
|
|
|
1252
|
my @id_property_values = $class_meta->get_composite_id_decomposer->($id_value); |
201
|
|
|
|
|
|
|
|
202
|
252
|
|
|
|
|
739
|
my @id_property_names = $class_meta->id_property_names; |
203
|
252
|
|
|
|
|
620
|
for (my $i = 0; $i < @id_property_names; $i++) { |
204
|
450
|
100
|
|
|
|
1132
|
if ($id_property_names[$i] eq $property_name) { |
205
|
96
|
|
|
|
|
97
|
$v = $id_property_values[$i]; |
206
|
96
|
|
|
|
|
166
|
last; |
207
|
|
|
|
|
|
|
} |
208
|
|
|
|
|
|
|
} |
209
|
|
|
|
|
|
|
} |
210
|
1997
|
|
|
|
|
7039
|
return $v; |
211
|
|
|
|
|
|
|
} |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
sub value_for_position { |
214
|
279934
|
|
|
279934
|
0
|
258896
|
my ($self, $pos) = @_; |
215
|
279934
|
|
|
|
|
387786
|
return ($self->values)[$pos]; |
216
|
|
|
|
|
|
|
} |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
sub operator_for { |
219
|
272
|
|
|
272
|
1
|
6665
|
my $self = shift; |
220
|
272
|
|
|
|
|
470
|
my $t = $self->template; |
221
|
272
|
|
|
|
|
649
|
return $t->operator_for(@_); |
222
|
|
|
|
|
|
|
} |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
sub underlying_rules { |
225
|
52
|
|
|
52
|
0
|
422
|
my $self = shift; |
226
|
52
|
100
|
|
|
|
133
|
unless (exists $self->{'_underlying_rules'}) { |
227
|
1
|
|
|
|
|
3
|
my @values = $self->values; |
228
|
1
|
|
|
|
|
2
|
$self->{'_underlying_rules'} = [ $self->template->get_underlying_rules_for_values(@values) ]; |
229
|
|
|
|
|
|
|
} |
230
|
52
|
|
|
|
|
36
|
return @{ $self->{'_underlying_rules'} }; |
|
52
|
|
|
|
|
139
|
|
231
|
|
|
|
|
|
|
} |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
# De-compose the rule back into its original form. |
234
|
|
|
|
|
|
|
sub params_list { |
235
|
|
|
|
|
|
|
# This is the reverse of the bulk of resolve. |
236
|
|
|
|
|
|
|
# It returns the params in list form, directly coercable into a hash if necessary. |
237
|
|
|
|
|
|
|
# $r = UR::BoolExpr->resolve($c1,@p1); |
238
|
|
|
|
|
|
|
# ($c2, @p2) = ($r->subject_class_name, $r->params_list); |
239
|
131612
|
|
|
131612
|
0
|
112513
|
my $self = shift; |
240
|
131612
|
|
|
|
|
157831
|
my $template = $self->template; |
241
|
131612
|
|
|
|
|
201651
|
my @values_sorted = $self->values; |
242
|
131612
|
|
|
|
|
348262
|
return $template->params_list_for_values(@values_sorted); |
243
|
|
|
|
|
|
|
} |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
# TODO: replace these with the logical set operations |
246
|
|
|
|
|
|
|
# FIXME: the name is confusing b/c it doesn't mutate the object, it returns a different object |
247
|
|
|
|
|
|
|
sub add_filter { |
248
|
655
|
|
|
655
|
0
|
757
|
my $self = shift; |
249
|
655
|
|
|
|
|
1624
|
return __PACKAGE__->resolve($self->subject_class_name, $self->params_list, @_); |
250
|
|
|
|
|
|
|
} |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
# TODO: replace these with the logical set operations |
253
|
|
|
|
|
|
|
# FIXME: the name is confusing b/c it doesn't mutate the object, it returns a different object |
254
|
|
|
|
|
|
|
sub remove_filter { |
255
|
602
|
|
|
602
|
0
|
631
|
my $self = shift; |
256
|
602
|
|
|
|
|
593
|
my $property_name = shift; |
257
|
602
|
|
|
|
|
959
|
my @params_list = $self->params_list; |
258
|
602
|
|
|
|
|
679
|
my @new_params_list; |
259
|
602
|
|
|
|
|
1397
|
for (my $n=0; $n<=$#params_list; $n+=2) { |
260
|
945
|
|
|
|
|
939
|
my $key = $params_list[$n]; |
261
|
945
|
100
|
|
|
|
9615
|
if ($key =~ /^$property_name\b/) { |
262
|
571
|
|
|
|
|
1395
|
next; |
263
|
|
|
|
|
|
|
} |
264
|
374
|
|
|
|
|
566
|
my $value = $params_list[$n+1]; |
265
|
374
|
|
|
|
|
927
|
push @new_params_list, $key, $value; |
266
|
|
|
|
|
|
|
} |
267
|
602
|
|
|
|
|
1532
|
return __PACKAGE__->resolve($self->subject_class_name, @new_params_list); |
268
|
|
|
|
|
|
|
} |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
# as above, doesn't mutate, just returns a different bx |
271
|
|
|
|
|
|
|
sub sub_classify { |
272
|
0
|
|
|
0
|
0
|
0
|
my ($self,$subclass_name) = @_; |
273
|
0
|
|
|
|
|
0
|
my ($t,@v) = $self->template_and_values(); |
274
|
0
|
|
|
|
|
0
|
return $t->sub_classify($subclass_name)->get_rule_for_values(@v); |
275
|
|
|
|
|
|
|
} |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
# flyweight constructor |
278
|
|
|
|
|
|
|
# like regular UR::Value objects, but kept separate from the cache but kept |
279
|
|
|
|
|
|
|
# out of the regular transaction cache so they alwasy vaporize when derefed |
280
|
|
|
|
|
|
|
sub get { |
281
|
743715
|
|
|
743715
|
1
|
622626
|
my $rule_id = pop; |
282
|
743715
|
100
|
|
|
|
1399332
|
unless (exists $UR::Object::rules->{$rule_id}) { |
283
|
634825
|
|
|
|
|
634475
|
my $pos = index($rule_id,$UR::BoolExpr::Util::id_sep); |
284
|
634825
|
|
|
|
|
1119022
|
my ($template_id,$value_id) = (substr($rule_id,0,$pos), substr($rule_id,$pos+1)); |
285
|
634825
|
|
|
|
|
1424778
|
my $rule = { id => $rule_id, template_id => $template_id, value_id => $value_id }; |
286
|
634825
|
|
|
|
|
677325
|
bless ($rule, "UR::BoolExpr"); |
287
|
634825
|
|
|
|
|
992372
|
$UR::Object::rules->{$rule_id} = $rule; |
288
|
634825
|
|
|
|
|
1523613
|
Scalar::Util::weaken($UR::Object::rules->{$rule_id}); |
289
|
634825
|
|
|
|
|
1025951
|
return $rule; |
290
|
|
|
|
|
|
|
} |
291
|
108890
|
|
|
|
|
158737
|
return $UR::Object::rules->{$rule_id}; |
292
|
|
|
|
|
|
|
} |
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
# because these are weakened |
295
|
|
|
|
|
|
|
sub DESTROY { |
296
|
634787
|
|
|
634787
|
|
3027340
|
delete $UR::Object::rules->{$_[0]->{id}}; |
297
|
|
|
|
|
|
|
} |
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
sub flatten_hard_refs { |
300
|
344092
|
|
|
344092
|
0
|
270912
|
my $self = $_[0]; |
301
|
344092
|
100
|
|
|
|
713648
|
return $self if not $self->{hard_refs}; |
302
|
|
|
|
|
|
|
|
303
|
179
|
|
|
|
|
457
|
my $subject_class_name = $self->subject_class_name; |
304
|
179
|
|
|
|
|
497
|
my $meta = $subject_class_name->__meta__; |
305
|
179
|
|
|
|
|
457
|
my %params = $self->_params_list; |
306
|
179
|
|
|
|
|
256
|
my $changes = 0; |
307
|
179
|
|
|
|
|
422
|
for my $key (keys %params) { |
308
|
183
|
|
|
|
|
260
|
my $value = $params{$key}; |
309
|
183
|
100
|
100
|
|
|
1050
|
if (ref($value) and Scalar::Util::blessed($value) and $value->isa("UR::Object")) { |
|
|
|
66
|
|
|
|
|
310
|
8
|
|
|
|
|
46
|
my ($property_name, $op) = ($key =~ /^(\S+)\s*(.*)/); |
311
|
|
|
|
|
|
|
|
312
|
8
|
|
|
|
|
81
|
my $pmeta = $meta->property($property_name); |
313
|
8
|
|
|
|
|
36
|
my $final_pmeta = $pmeta->final_property_meta(); |
314
|
|
|
|
|
|
|
|
315
|
8
|
|
|
|
|
19
|
my @possible_data_types = uniq grep { $_ } map { $_->data_type } ($pmeta, $final_pmeta); |
|
16
|
|
|
|
|
59
|
|
|
16
|
|
|
|
|
30
|
|
316
|
8
|
50
|
|
|
|
30
|
unless (@possible_data_types) { |
317
|
|
|
|
|
|
|
# this might not be possible at runtime |
318
|
0
|
|
|
|
|
0
|
croak sprintf 'unable to determine data type for property: %s', $property_name; |
319
|
|
|
|
|
|
|
} |
320
|
|
|
|
|
|
|
|
321
|
8
|
|
|
8
|
|
58
|
my $data_type = first { $value->isa($_) } @possible_data_types; |
|
8
|
|
|
|
|
32
|
|
322
|
8
|
50
|
|
|
|
56
|
unless ($data_type) { |
323
|
0
|
|
|
|
|
0
|
croak sprintf 'value type, %s, is incompatible with: %s', $value->class, join(', ', @possible_data_types); |
324
|
|
|
|
|
|
|
} |
325
|
|
|
|
|
|
|
|
326
|
8
|
|
|
|
|
8
|
my $value2 = do { |
327
|
8
|
|
|
|
|
10
|
local $@; |
328
|
8
|
|
|
|
|
15
|
eval { |
329
|
8
|
|
|
|
|
24
|
$data_type->get($value->id) |
330
|
|
|
|
|
|
|
}; |
331
|
|
|
|
|
|
|
}; |
332
|
8
|
50
|
|
|
|
25
|
unless ($value2) { |
333
|
0
|
|
|
|
|
0
|
croak sprintf 'unable to retrieve a %s by value ID: %s', $data_type, $value->id; |
334
|
|
|
|
|
|
|
} |
335
|
|
|
|
|
|
|
|
336
|
8
|
100
|
|
|
|
31
|
unless ($value2 eq $value) { |
337
|
1
|
|
|
|
|
5
|
croak sprintf 'retrieved duplicate %s with ID, %s,', $data_type, $value->id; |
338
|
|
|
|
|
|
|
} |
339
|
|
|
|
|
|
|
|
340
|
|
|
|
|
|
|
# safe to re-represent as .id |
341
|
7
|
|
|
|
|
16
|
my $new_key = $property_name . '.id'; |
342
|
7
|
50
|
|
|
|
21
|
$new_key .= ' ' . $op if $op; |
343
|
7
|
|
|
|
|
12
|
delete $params{$key}; |
344
|
7
|
|
|
|
|
22
|
$params{$new_key} = $value->id; |
345
|
7
|
|
|
|
|
21
|
$changes++; |
346
|
|
|
|
|
|
|
} |
347
|
|
|
|
|
|
|
} |
348
|
178
|
100
|
|
|
|
369
|
if ($changes) { |
349
|
7
|
|
|
|
|
30
|
return $self->resolve_normalized($subject_class_name, %params); |
350
|
|
|
|
|
|
|
} else { |
351
|
171
|
|
|
|
|
443
|
return $self; |
352
|
|
|
|
|
|
|
} |
353
|
|
|
|
|
|
|
} |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
sub resolve_normalized { |
356
|
63633
|
|
|
63633
|
0
|
58101
|
my $class = shift; |
357
|
63633
|
|
|
|
|
116825
|
my ($unnormalized_rule, @extra) = $class->resolve(@_); |
358
|
63633
|
|
|
|
|
111051
|
my $normalized_rule = $unnormalized_rule->normalize(); |
359
|
63633
|
50
|
|
|
|
103222
|
return if !defined(wantarray); |
360
|
63633
|
100
|
|
|
|
96756
|
return ($normalized_rule,@extra) if wantarray; |
361
|
62665
|
50
|
|
|
|
96495
|
if (@extra) { |
362
|
266
|
|
|
266
|
|
1383
|
no warnings; |
|
266
|
|
|
|
|
380
|
|
|
266
|
|
|
|
|
280337
|
|
363
|
0
|
|
|
|
|
0
|
my $rule_class = $normalized_rule->subject_class_name; |
364
|
0
|
|
|
|
|
0
|
Carp::confess("Extra params for class $rule_class found: @extra\n"); |
365
|
|
|
|
|
|
|
} |
366
|
62665
|
|
|
|
|
119278
|
return $normalized_rule; |
367
|
|
|
|
|
|
|
} |
368
|
|
|
|
|
|
|
|
369
|
|
|
|
|
|
|
sub resolve_for_template_id_and_values { |
370
|
0
|
|
|
0
|
0
|
0
|
my ($class,$template_id, @values) = @_; |
371
|
0
|
|
|
|
|
0
|
my $value_id = UR::BoolExpr::Util::values_to_value_id(@values); |
372
|
0
|
|
|
|
|
0
|
my $rule_id = $class->__meta__->resolve_composite_id_from_ordered_values($template_id,$value_id); |
373
|
0
|
|
|
|
|
0
|
$class->get($rule_id); |
374
|
|
|
|
|
|
|
} |
375
|
|
|
|
|
|
|
|
376
|
|
|
|
|
|
|
|
377
|
|
|
|
|
|
|
# Return true if it's a hashref that specifies the old-style operator/value |
378
|
|
|
|
|
|
|
# like property => { operator => '=', value => 1 } |
379
|
|
|
|
|
|
|
# FYI, the new way to do this is: |
380
|
|
|
|
|
|
|
# 'property =' => 1 |
381
|
|
|
|
|
|
|
sub _value_is_old_style_operator_and_value { |
382
|
2852
|
|
|
2852
|
|
3468
|
my($class,$value) = @_; |
383
|
|
|
|
|
|
|
|
384
|
|
|
|
|
|
|
return (ref($value) eq 'HASH') |
385
|
|
|
|
|
|
|
&& |
386
|
|
|
|
|
|
|
(exists($value->{'operator'})) |
387
|
|
|
|
|
|
|
&& |
388
|
|
|
|
|
|
|
(exists($value->{'value'})) |
389
|
|
|
|
|
|
|
&& |
390
|
|
|
|
|
|
|
( (keys(%$value) == 2) |
391
|
|
|
|
|
|
|
|| |
392
|
|
|
|
|
|
|
((keys(%$value) == 3) |
393
|
2852
|
|
33
|
|
|
15215
|
&& exists($value->{'escape'})) |
394
|
|
|
|
|
|
|
); |
395
|
|
|
|
|
|
|
} |
396
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
my $resolve_depth; |
399
|
|
|
|
|
|
|
sub resolve { |
400
|
190580
|
|
|
190580
|
0
|
165643
|
$resolve_depth++; |
401
|
190580
|
50
|
|
|
|
278757
|
Carp::confess("Deep recursion in UR::BoolExpr::resolve()!") if $resolve_depth > 10; |
402
|
|
|
|
|
|
|
|
403
|
|
|
|
|
|
|
# handle the case in which we've already processed the params into a boolexpr |
404
|
190580
|
100
|
100
|
|
|
416761
|
if ( @_ == 3 and ref($_[2]) and ref($_[2])->isa("UR::BoolExpr") ) { |
|
|
|
100
|
|
|
|
|
405
|
5992
|
|
|
|
|
5976
|
$resolve_depth--; |
406
|
5992
|
|
|
|
|
11825
|
return $_[2]; |
407
|
|
|
|
|
|
|
} |
408
|
|
|
|
|
|
|
|
409
|
184588
|
|
|
|
|
158953
|
my $class = shift; |
410
|
184588
|
|
|
|
|
142039
|
my $subject_class = shift; |
411
|
184588
|
50
|
|
|
|
253199
|
Carp::confess("Can't resolve BoolExpr: expected subject class as arg 2, got '$subject_class'") if not $subject_class; |
412
|
|
|
|
|
|
|
# support for legacy passing of hashref instead of object or list |
413
|
|
|
|
|
|
|
# TODO: eliminate the need for this |
414
|
184588
|
|
|
|
|
143112
|
my @in_params; |
415
|
184588
|
100
|
100
|
|
|
1112377
|
if ($subject_class->isa('UR::Value::PerlReference') and $subject_class eq 'UR::Value::' . ref($_[0])) { |
|
|
100
|
|
|
|
|
|
416
|
1
|
|
|
|
|
2
|
@in_params = @_; |
417
|
|
|
|
|
|
|
} |
418
|
|
|
|
|
|
|
elsif (ref($_[0]) eq "HASH") { |
419
|
2
|
|
|
|
|
4
|
@in_params = %{$_[0]}; |
|
2
|
|
|
|
|
11
|
|
420
|
|
|
|
|
|
|
} |
421
|
|
|
|
|
|
|
else { |
422
|
184585
|
|
|
|
|
289267
|
@in_params = @_; |
423
|
|
|
|
|
|
|
} |
424
|
|
|
|
|
|
|
|
425
|
184588
|
100
|
100
|
|
|
628261
|
if (defined($in_params[0]) and $in_params[0] eq '-or') { |
426
|
31
|
|
|
|
|
49
|
shift @in_params; |
427
|
31
|
|
|
|
|
41
|
my @sub_queries = @{ shift @in_params }; |
|
31
|
|
|
|
|
59
|
|
428
|
|
|
|
|
|
|
|
429
|
31
|
|
|
|
|
33
|
my @meta_params; |
430
|
31
|
|
|
|
|
114
|
for (my $i = 0; $i < @in_params; $i += 2 ) { |
431
|
3
|
50
|
|
|
|
15
|
if ($in_params[$i] =~ m/^-/) { |
432
|
3
|
|
|
|
|
13
|
push @meta_params, $in_params[$i], $in_params[$i+1]; |
433
|
|
|
|
|
|
|
} |
434
|
|
|
|
|
|
|
} |
435
|
|
|
|
|
|
|
|
436
|
31
|
|
|
|
|
176
|
my $bx = UR::BoolExpr::Template::Or->_compose( |
437
|
|
|
|
|
|
|
$subject_class, |
438
|
|
|
|
|
|
|
\@sub_queries, |
439
|
|
|
|
|
|
|
\@meta_params, |
440
|
|
|
|
|
|
|
); |
441
|
|
|
|
|
|
|
|
442
|
31
|
|
|
|
|
40
|
$resolve_depth--; |
443
|
31
|
|
|
|
|
73
|
return $bx; |
444
|
|
|
|
|
|
|
} |
445
|
|
|
|
|
|
|
|
446
|
184557
|
100
|
|
|
|
436075
|
if (@in_params == 1) { |
|
|
50
|
|
|
|
|
|
447
|
6194
|
|
|
|
|
11614
|
unshift @in_params, "id"; |
448
|
|
|
|
|
|
|
} |
449
|
|
|
|
|
|
|
elsif (@in_params % 2 == 1) { |
450
|
0
|
|
|
|
|
0
|
Carp::carp("Odd number of params while creating $class: (",join(',',@in_params),")"); |
451
|
|
|
|
|
|
|
} |
452
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
# split the params into keys and values |
454
|
|
|
|
|
|
|
# where an operator is on the right-side, it is moved into the key |
455
|
184557
|
|
|
|
|
181909
|
my $count = @in_params; |
456
|
184557
|
|
|
|
|
160057
|
my (@keys,@values,@constant_values,$key,$value,$property_name,$operator,@hard_refs); |
457
|
184557
|
|
|
|
|
319248
|
for(my $n = 0; $n < $count;) { |
458
|
217869
|
|
|
|
|
221380
|
$key = $in_params[$n++]; |
459
|
217869
|
|
|
|
|
183820
|
$value = $in_params[$n++]; |
460
|
|
|
|
|
|
|
|
461
|
217869
|
50
|
|
|
|
312842
|
unless (defined $key) { |
462
|
0
|
|
|
|
|
0
|
Carp::croak("Can't resolve BoolExpr: undef is an invalid key/property name. Args were: ".join(', ',@in_params)); |
463
|
|
|
|
|
|
|
} |
464
|
|
|
|
|
|
|
|
465
|
217869
|
100
|
|
|
|
439658
|
if (UR::BoolExpr::Util::is_meta_param($key)) { |
466
|
|
|
|
|
|
|
# these are keys whose values live in the rule template |
467
|
650
|
|
|
|
|
810
|
push @keys, $key; |
468
|
650
|
|
|
|
|
791
|
push @constant_values, $value; |
469
|
650
|
|
|
|
|
1246
|
next; |
470
|
|
|
|
|
|
|
} |
471
|
|
|
|
|
|
|
|
472
|
217219
|
100
|
|
|
|
417723
|
if ($key =~ m/^(_id_only|_param_key|_unique|__get_serial|_change_count)$/) { |
473
|
|
|
|
|
|
|
# skip the pair: legacy/internal cruft |
474
|
93
|
|
|
|
|
142
|
next; |
475
|
|
|
|
|
|
|
} |
476
|
|
|
|
|
|
|
|
477
|
217126
|
|
|
|
|
263696
|
my $pos = index($key,' '); |
478
|
217126
|
100
|
|
|
|
260254
|
if ($pos != -1) { |
479
|
|
|
|
|
|
|
# the key is "propname op" |
480
|
790
|
|
|
|
|
1158
|
$property_name = substr($key,0,$pos); |
481
|
790
|
|
|
|
|
1199
|
$operator = substr($key,$pos+1); |
482
|
790
|
50
|
|
|
|
1721
|
if (substr($operator,0,1) eq ' ') { |
483
|
0
|
|
|
|
|
0
|
$operator =~ s/^\s+//; |
484
|
|
|
|
|
|
|
} |
485
|
|
|
|
|
|
|
} |
486
|
|
|
|
|
|
|
else { |
487
|
|
|
|
|
|
|
# the key is "propname" |
488
|
216336
|
|
|
|
|
171766
|
$property_name = $key; |
489
|
216336
|
|
|
|
|
175922
|
$operator = ''; |
490
|
|
|
|
|
|
|
} |
491
|
|
|
|
|
|
|
|
492
|
217126
|
100
|
|
|
|
335142
|
if (my $ref = ref($value)) { |
493
|
11080
|
100
|
100
|
|
|
41102
|
if ( (not $operator) and ($ref eq "HASH")) { |
494
|
1154
|
100
|
|
|
|
2816
|
if ( $class->_value_is_old_style_operator_and_value($value)) { |
495
|
|
|
|
|
|
|
# the key => { operator => $o, value => $v } syntax |
496
|
|
|
|
|
|
|
# cannot be used with a value type of HASH |
497
|
|
|
|
|
|
|
$operator = defined($value->{operator}) |
498
|
|
|
|
|
|
|
? lc($value->{operator}) |
499
|
871
|
100
|
|
|
|
2288
|
: ''; |
500
|
871
|
50
|
|
|
|
1699
|
if (exists $value->{escape}) { |
501
|
|
|
|
|
|
|
$operator .= "-" . $value->{escape} |
502
|
0
|
|
|
|
|
0
|
} |
503
|
871
|
|
|
|
|
1504
|
$key .= " " . $operator; |
504
|
871
|
|
|
|
|
1069
|
$value = $value->{value}; |
505
|
871
|
|
|
|
|
1008
|
$ref = ref($value); |
506
|
|
|
|
|
|
|
} |
507
|
|
|
|
|
|
|
else { |
508
|
|
|
|
|
|
|
# the HASH is a value for the specified param |
509
|
283
|
|
|
|
|
366
|
push @hard_refs, scalar(@values), $value; |
510
|
|
|
|
|
|
|
} |
511
|
|
|
|
|
|
|
} |
512
|
|
|
|
|
|
|
|
513
|
11080
|
100
|
|
|
|
18850
|
if ($ref eq "ARRAY") { |
514
|
1805
|
100
|
|
|
|
3511
|
if (not $operator) { |
|
|
50
|
|
|
|
|
|
515
|
|
|
|
|
|
|
# key => [] is the same as "key in" => [] |
516
|
1397
|
|
|
|
|
1467
|
$operator = 'in'; |
517
|
1397
|
|
|
|
|
1877
|
$key .= ' in'; |
518
|
|
|
|
|
|
|
} |
519
|
|
|
|
|
|
|
elsif ($operator eq 'not') { |
520
|
|
|
|
|
|
|
# "key not" => [] is the same as "key not in" |
521
|
0
|
|
|
|
|
0
|
$operator .= ' in'; |
522
|
0
|
|
|
|
|
0
|
$key .= ' in'; |
523
|
|
|
|
|
|
|
} |
524
|
|
|
|
|
|
|
|
525
|
1805
|
|
|
|
|
2643
|
foreach my $val (@$value) { |
526
|
2724
|
100
|
|
|
|
4584
|
if (ref($val)) { |
527
|
|
|
|
|
|
|
# when there are any refs in the arrayref |
528
|
|
|
|
|
|
|
# we must keep the arrayerf contents |
529
|
|
|
|
|
|
|
# to reconstruct effectively |
530
|
54
|
|
|
|
|
81
|
push @hard_refs, scalar(@values), $value; |
531
|
54
|
|
|
|
|
84
|
last; |
532
|
|
|
|
|
|
|
} |
533
|
|
|
|
|
|
|
} |
534
|
|
|
|
|
|
|
|
535
|
|
|
|
|
|
|
} # done handling ARRAY value |
536
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
} # done handling ref values |
538
|
|
|
|
|
|
|
|
539
|
217126
|
|
|
|
|
207227
|
push @keys, $key; |
540
|
217126
|
|
|
|
|
395482
|
push @values, $value; |
541
|
|
|
|
|
|
|
} |
542
|
|
|
|
|
|
|
|
543
|
|
|
|
|
|
|
# the above uses no class metadata |
544
|
|
|
|
|
|
|
|
545
|
|
|
|
|
|
|
# this next section uses class metadata |
546
|
|
|
|
|
|
|
# it should be moved into the normalization layer |
547
|
|
|
|
|
|
|
|
548
|
184557
|
|
|
|
|
141016
|
my $subject_class_meta; |
549
|
184557
|
|
|
|
|
129266
|
my $exception = do { |
550
|
184557
|
|
|
|
|
149269
|
local $@; |
551
|
184557
|
|
|
|
|
185221
|
$subject_class_meta = eval { $subject_class->__meta__ }; |
|
184557
|
|
|
|
|
459488
|
|
552
|
184557
|
|
|
|
|
262415
|
$@; |
553
|
|
|
|
|
|
|
}; |
554
|
184557
|
50
|
|
|
|
272698
|
if ($exception) { |
555
|
0
|
|
|
|
|
0
|
Carp::croak("Can't get class metadata for $subject_class. Is it a valid class name?\nErrors were: $exception"); |
556
|
|
|
|
|
|
|
} |
557
|
184557
|
50
|
|
|
|
322939
|
unless ($subject_class_meta) { |
558
|
0
|
|
|
|
|
0
|
Carp::croak("No class metadata for $subject_class?!"); |
559
|
|
|
|
|
|
|
} |
560
|
|
|
|
|
|
|
|
561
|
|
|
|
|
|
|
my $subject_class_props = |
562
|
|
|
|
|
|
|
$subject_class_meta->{'cache'}{'UR::BoolExpr::resolve'} ||= |
563
|
184557
|
|
100
|
|
|
416570
|
{ map {$_, 1} ( $subject_class_meta->all_property_type_names) }; |
|
53079
|
|
|
|
|
64298
|
|
564
|
|
|
|
|
|
|
|
565
|
184557
|
|
|
|
|
232630
|
my($kn, $vn, $cn, $complex_values) = (0,0,0,0); |
566
|
184557
|
|
|
|
|
149952
|
my ($op,@extra,@xadd_keys,@xadd_values,@xremove_keys,@xremove_values,@extra_key_pos,@extra_value_pos, |
567
|
|
|
|
|
|
|
@swap_key_pos,@swap_key_value); |
568
|
|
|
|
|
|
|
|
569
|
184557
|
|
|
|
|
201359
|
for my $value (@values) { |
570
|
217275
|
|
|
|
|
197299
|
$key = $keys[$kn++]; |
571
|
217275
|
100
|
|
|
|
319468
|
if (UR::BoolExpr::Util::is_meta_param($key)) { |
572
|
149
|
|
|
|
|
173
|
$cn++; |
573
|
149
|
|
|
|
|
169
|
redo; |
574
|
|
|
|
|
|
|
} |
575
|
|
|
|
|
|
|
else { |
576
|
217126
|
|
|
|
|
162365
|
$vn++; |
577
|
|
|
|
|
|
|
} |
578
|
|
|
|
|
|
|
|
579
|
217126
|
|
|
|
|
236832
|
my $pos = index($key,' '); |
580
|
217126
|
100
|
|
|
|
244525
|
if ($pos != -1) { |
581
|
|
|
|
|
|
|
# "propname op" |
582
|
3054
|
|
|
|
|
3788
|
$property_name = substr($key,0,$pos); |
583
|
3054
|
|
|
|
|
3876
|
$operator = substr($key,$pos+1); |
584
|
3054
|
100
|
|
|
|
6070
|
if (substr($operator,0,1) eq ' ') { |
585
|
4
|
|
|
|
|
14
|
$operator =~ s/^\s+//; |
586
|
|
|
|
|
|
|
} |
587
|
|
|
|
|
|
|
} |
588
|
|
|
|
|
|
|
else { |
589
|
|
|
|
|
|
|
# "propname" |
590
|
214072
|
|
|
|
|
164484
|
$property_name = $key; |
591
|
214072
|
|
|
|
|
172911
|
$operator = ''; |
592
|
|
|
|
|
|
|
} |
593
|
|
|
|
|
|
|
|
594
|
|
|
|
|
|
|
# account for the case where this parameter does |
595
|
|
|
|
|
|
|
# not match an actual property |
596
|
217126
|
|
|
|
|
162062
|
my $base_property_name = $property_name; |
597
|
217126
|
|
|
|
|
365031
|
$base_property_name =~ s/[.-].+//; |
598
|
217126
|
100
|
|
|
|
380057
|
if (!exists $subject_class_props->{$base_property_name}) { |
599
|
230
|
50
|
|
|
|
606
|
if (substr($property_name,0,1) eq '_') { |
600
|
0
|
|
|
|
|
0
|
warn "ignoring $property_name in $subject_class bx construction!" |
601
|
|
|
|
|
|
|
} |
602
|
|
|
|
|
|
|
else { |
603
|
230
|
|
|
|
|
373
|
push @extra_key_pos, $kn-1; |
604
|
230
|
|
|
|
|
283
|
push @extra_value_pos, $vn-1; |
605
|
230
|
|
|
|
|
381
|
next; |
606
|
|
|
|
|
|
|
} |
607
|
|
|
|
|
|
|
} |
608
|
|
|
|
|
|
|
|
609
|
216896
|
|
|
|
|
186366
|
my $ref = ref($value); |
610
|
216896
|
100
|
|
|
|
367086
|
if($ref) { |
611
|
10404
|
|
|
|
|
9800
|
$complex_values = 1; |
612
|
10404
|
100
|
100
|
|
|
53057
|
if ($ref eq "ARRAY" and $operator ne 'between' and $operator ne 'not between') { |
|
|
100
|
100
|
|
|
|
|
|
|
100
|
|
|
|
|
|
613
|
1558
|
|
|
|
|
1466
|
my $data_type; |
614
|
|
|
|
|
|
|
my $is_many; |
615
|
1558
|
50
|
|
|
|
2458
|
if ($UR::initialized) { |
616
|
1558
|
|
|
|
|
4609
|
my $property_meta = $subject_class_meta->property_meta_for_name($property_name); |
617
|
1558
|
50
|
|
|
|
2934
|
unless (defined $property_meta) { |
618
|
0
|
|
|
|
|
0
|
push @extra_key_pos, $kn-1; |
619
|
0
|
|
|
|
|
0
|
push @extra_value_pos, $vn-1; |
620
|
0
|
|
|
|
|
0
|
next; |
621
|
|
|
|
|
|
|
} |
622
|
1558
|
|
|
|
|
4255
|
$data_type = $property_meta->data_type; |
623
|
1558
|
|
|
|
|
3331
|
$is_many = $property_meta->is_many; |
624
|
|
|
|
|
|
|
} |
625
|
|
|
|
|
|
|
else { |
626
|
0
|
0
|
|
|
|
0
|
if (exists $subject_class_meta->{has}{$property_name}) { |
627
|
0
|
|
|
|
|
0
|
$data_type = $subject_class_meta->{has}{$property_name}{data_type}; |
628
|
0
|
|
|
|
|
0
|
$is_many = $subject_class_meta->{has}{$property_name}{is_many}; |
629
|
|
|
|
|
|
|
} |
630
|
|
|
|
|
|
|
} |
631
|
1558
|
|
100
|
|
|
3108
|
$data_type ||= ''; |
632
|
|
|
|
|
|
|
|
633
|
1558
|
100
|
|
|
|
3831
|
if ($data_type eq 'ARRAY') { |
|
|
100
|
|
|
|
|
|
634
|
|
|
|
|
|
|
# ensure we re-constitute the original array not a copy |
635
|
219
|
|
|
|
|
281
|
push @hard_refs, $vn-1, $value; |
636
|
219
|
|
|
|
|
232
|
push @swap_key_pos, $vn-1; |
637
|
219
|
|
|
|
|
314
|
push @swap_key_value, $property_name; |
638
|
|
|
|
|
|
|
} |
639
|
|
|
|
|
|
|
elsif (not $is_many) { |
640
|
266
|
|
|
266
|
|
1392
|
no warnings; |
|
266
|
|
|
|
|
353
|
|
|
266
|
|
|
|
|
607468
|
|
641
|
|
|
|
|
|
|
|
642
|
|
|
|
|
|
|
# sort and replace |
643
|
|
|
|
|
|
|
# note that in perl5.10 and above strings like "inf*" have a numeric value |
644
|
|
|
|
|
|
|
# causing this kind of sorting to do surprising things. Hopefully looks_like_number() |
645
|
|
|
|
|
|
|
# does the right thing with these. |
646
|
|
|
|
|
|
|
# |
647
|
|
|
|
|
|
|
# undef/null sorts at the end |
648
|
831
|
50
|
|
831
|
|
1368
|
my $sorter = sub { if (! defined($a)) { return 1 } |
|
0
|
|
|
|
|
0
|
|
649
|
831
|
100
|
|
|
|
1189
|
if (! defined($b)) { return -1} |
|
1
|
|
|
|
|
3
|
|
650
|
955
|
|
|
|
|
3521
|
return $a cmp $b; }; |
|
830
|
|
|
|
|
1653
|
|
651
|
955
|
|
|
|
|
2948
|
$value = [ sort $sorter @$value ]; |
652
|
|
|
|
|
|
|
|
653
|
|
|
|
|
|
|
# Remove duplicates from the list |
654
|
955
|
|
|
|
|
1073
|
my $last = $value; |
655
|
955
|
|
|
|
|
2408
|
for (my $i = 0; $i < @$value;) { |
656
|
1480
|
100
|
|
|
|
2508
|
if ($last eq $value->[$i]) { |
657
|
19
|
|
|
|
|
52
|
splice(@$value, $i, 1); |
658
|
|
|
|
|
|
|
} |
659
|
|
|
|
|
|
|
else { |
660
|
1461
|
|
|
|
|
5325
|
$last = $value->[$i++]; |
661
|
|
|
|
|
|
|
} |
662
|
|
|
|
|
|
|
} |
663
|
|
|
|
|
|
|
# push @swap_key_pos, $vn-1; |
664
|
|
|
|
|
|
|
# push @swap_key_value, $property_name; |
665
|
|
|
|
|
|
|
} |
666
|
|
|
|
|
|
|
else { |
667
|
|
|
|
|
|
|
# disable: break 47, enable: break 62 |
668
|
|
|
|
|
|
|
#push @swap_key_pos, $vn-1; |
669
|
|
|
|
|
|
|
#push @swap_key_value, $property_name; |
670
|
|
|
|
|
|
|
} |
671
|
|
|
|
|
|
|
} |
672
|
|
|
|
|
|
|
elsif (blessed($value)) { |
673
|
8289
|
|
|
|
|
22971
|
my $property_meta = $subject_class_meta->property_meta_for_name($property_name); |
674
|
8289
|
50
|
|
|
|
16208
|
unless ($property_meta) { |
675
|
0
|
|
|
|
|
0
|
for my $class_name ($subject_class_meta->ancestry_class_names) { |
676
|
0
|
|
|
|
|
0
|
my $class_object = $class_name->__meta__; |
677
|
0
|
|
|
|
|
0
|
$property_meta = $subject_class_meta->property_meta_for_name($property_name); |
678
|
0
|
0
|
|
|
|
0
|
last if $property_meta; |
679
|
|
|
|
|
|
|
} |
680
|
0
|
0
|
|
|
|
0
|
unless ($property_meta) { |
681
|
0
|
|
|
|
|
0
|
Carp::croak("No property metadata for $subject_class property '$property_name'"); |
682
|
|
|
|
|
|
|
} |
683
|
|
|
|
|
|
|
} |
684
|
|
|
|
|
|
|
|
685
|
8289
|
100
|
100
|
|
|
21805
|
if ($property_meta->id_by or $property_meta->reverse_as) { |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
686
|
8173
|
|
|
|
|
15088
|
my $property_meta = $subject_class_meta->property_meta_for_name($property_name); |
687
|
8173
|
50
|
|
|
|
14394
|
unless ($property_meta) { |
688
|
0
|
|
|
|
|
0
|
Carp::croak("No property metadata for $subject_class property '$property_name'"); |
689
|
|
|
|
|
|
|
} |
690
|
|
|
|
|
|
|
|
691
|
8173
|
|
|
|
|
23174
|
my @joins = $property_meta->get_property_name_pairs_for_join(); |
692
|
8173
|
|
|
|
|
10947
|
for my $join (@joins) { |
693
|
|
|
|
|
|
|
# does this really work for >1 joins? |
694
|
19690
|
|
|
|
|
22490
|
my ($my_method, $their_method) = @$join; |
695
|
19690
|
|
|
|
|
16854
|
push @xadd_keys, $my_method; |
696
|
19690
|
|
|
|
|
49710
|
push @xadd_values, $value->$their_method; |
697
|
|
|
|
|
|
|
} |
698
|
|
|
|
|
|
|
# TODO: this may need to be moved into the above get_property_name_pairs_for_join(), |
699
|
|
|
|
|
|
|
# but the exact syntax for expressing that this is part of the join is unclear. |
700
|
8173
|
100
|
|
|
|
18341
|
if (my $id_class_by = $property_meta->id_class_by) { |
701
|
11
|
|
|
|
|
12
|
push @xadd_keys, $id_class_by; |
702
|
11
|
|
|
|
|
13
|
push @xadd_values, ref($value); |
703
|
|
|
|
|
|
|
} |
704
|
8173
|
|
|
|
|
10272
|
push @xremove_keys, $kn-1; |
705
|
8173
|
|
|
|
|
16754
|
push @xremove_values, $vn-1; |
706
|
|
|
|
|
|
|
} |
707
|
|
|
|
|
|
|
elsif ($property_meta->is_valid_storage_for_value($value)) { |
708
|
84
|
|
|
|
|
873
|
push @hard_refs, $vn-1, $value; |
709
|
|
|
|
|
|
|
} |
710
|
|
|
|
|
|
|
elsif ($value->can($property_name)) { |
711
|
|
|
|
|
|
|
# TODO: stop suporting foo_id => $foo, since you can do foo=>$foo, and foo_id=>$foo->id |
712
|
|
|
|
|
|
|
# Carp::cluck("using $property_name => \$obj to get $property_name => \$obj->$property_name is deprecated..."); |
713
|
32
|
|
|
|
|
277
|
$value = $value->$property_name; |
714
|
|
|
|
|
|
|
} |
715
|
|
|
|
|
|
|
else { |
716
|
0
|
0
|
|
|
|
0
|
$operator = 'eq' unless $operator; |
717
|
0
|
|
|
|
|
0
|
$DB::single = 1; |
718
|
0
|
|
|
|
|
0
|
print $value->isa($property_meta->_data_type_as_class_name),"\n"; |
719
|
0
|
|
|
|
|
0
|
print $value->isa($property_meta->_data_type_as_class_name),"\n"; |
720
|
0
|
|
|
|
|
0
|
Carp::croak("Invalid data type in rule. A value of type " . ref($value) . " cannot be used in class $subject_class property '$property_name' with operator $operator!"); |
721
|
|
|
|
|
|
|
} |
722
|
|
|
|
|
|
|
# end of handling a value which is an arrayref |
723
|
|
|
|
|
|
|
} |
724
|
|
|
|
|
|
|
elsif ($ref ne 'HASH') { |
725
|
|
|
|
|
|
|
# other reference, code, etc. |
726
|
274
|
|
|
|
|
530
|
push @hard_refs, $vn-1, $value; |
727
|
|
|
|
|
|
|
} |
728
|
|
|
|
|
|
|
} |
729
|
|
|
|
|
|
|
} |
730
|
184556
|
|
|
|
|
164603
|
push @keys, @xadd_keys; |
731
|
184556
|
|
|
|
|
143873
|
push @values, @xadd_values; |
732
|
|
|
|
|
|
|
|
733
|
184556
|
100
|
|
|
|
268974
|
if (@swap_key_pos) { |
734
|
89
|
|
|
|
|
174
|
@keys[@swap_key_pos] = @swap_key_value; |
735
|
|
|
|
|
|
|
} |
736
|
|
|
|
|
|
|
|
737
|
184556
|
100
|
|
|
|
262142
|
if (@extra_key_pos) { |
738
|
184
|
|
|
|
|
246
|
push @xremove_keys, @extra_key_pos; |
739
|
184
|
|
|
|
|
272
|
push @xremove_values, @extra_value_pos; |
740
|
184
|
|
|
|
|
581
|
for (my $n = 0; $n < @extra_key_pos; $n++) { |
741
|
230
|
|
|
|
|
660
|
push @extra, $keys[$extra_key_pos[$n]], $values[$extra_value_pos[$n]]; |
742
|
|
|
|
|
|
|
} |
743
|
|
|
|
|
|
|
} |
744
|
|
|
|
|
|
|
|
745
|
184556
|
100
|
|
|
|
257690
|
if (@xremove_keys) { |
746
|
4518
|
|
|
|
|
5279
|
my $write_key_idx = 0; |
747
|
4518
|
|
|
|
|
13048
|
for (my($read_key_idx, $xremove_key_idx) = (0,0); |
748
|
|
|
|
|
|
|
$read_key_idx < @keys; |
749
|
|
|
|
|
|
|
$read_key_idx++ |
750
|
|
|
|
|
|
|
) { |
751
|
29823
|
100
|
100
|
|
|
60512
|
if ($xremove_key_idx < @xremove_keys |
752
|
|
|
|
|
|
|
and |
753
|
|
|
|
|
|
|
$read_key_idx == $xremove_keys[$xremove_key_idx] |
754
|
|
|
|
|
|
|
) { |
755
|
8403
|
|
|
|
|
6938
|
$xremove_key_idx++; |
756
|
8403
|
|
|
|
|
13707
|
next; |
757
|
|
|
|
|
|
|
} |
758
|
21420
|
|
|
|
|
31896
|
$keys[$write_key_idx++] = $keys[$read_key_idx]; |
759
|
|
|
|
|
|
|
} |
760
|
4518
|
|
|
|
|
14320
|
$#keys = $write_key_idx-1; |
761
|
|
|
|
|
|
|
} |
762
|
|
|
|
|
|
|
|
763
|
184556
|
100
|
|
|
|
252820
|
if (@xremove_values) { |
764
|
4518
|
100
|
|
|
|
9159
|
if (@hard_refs) { |
765
|
|
|
|
|
|
|
# shift the numbers down to account for positional removals |
766
|
5
|
|
|
|
|
16
|
for (my $n = 0; $n < @hard_refs; $n+=2) { |
767
|
5
|
|
|
|
|
6
|
my $ref_pos = $hard_refs[$n]; |
768
|
5
|
|
|
|
|
8
|
for my $rem_pos (@xremove_values) { |
769
|
5
|
50
|
|
|
|
13
|
if ($rem_pos < $ref_pos) { |
|
|
0
|
|
|
|
|
|
770
|
5
|
|
|
|
|
7
|
$hard_refs[$n] -= 1; |
771
|
|
|
|
|
|
|
#print "$n from $ref_pos to $hard_refs[$n]\n"; |
772
|
5
|
|
|
|
|
13
|
$ref_pos = $hard_refs[$n]; |
773
|
|
|
|
|
|
|
} |
774
|
|
|
|
|
|
|
elsif ($rem_pos == $ref_pos) { |
775
|
0
|
|
|
|
|
0
|
$hard_refs[$n] = ''; |
776
|
0
|
|
|
|
|
0
|
$hard_refs[$n+1] = undef; |
777
|
|
|
|
|
|
|
} |
778
|
|
|
|
|
|
|
} |
779
|
|
|
|
|
|
|
} |
780
|
|
|
|
|
|
|
} |
781
|
|
|
|
|
|
|
|
782
|
4518
|
|
|
|
|
5194
|
my $write_value_idx = 0; |
783
|
4518
|
|
|
|
|
11863
|
for(my($read_value_idx, $xremove_value_idx) = (0,0); |
784
|
|
|
|
|
|
|
$read_value_idx < @values; |
785
|
|
|
|
|
|
|
$read_value_idx++ |
786
|
|
|
|
|
|
|
) { |
787
|
29815
|
100
|
100
|
|
|
57403
|
if ($xremove_value_idx < @xremove_values |
788
|
|
|
|
|
|
|
and |
789
|
|
|
|
|
|
|
$read_value_idx == $xremove_values[$xremove_value_idx] |
790
|
|
|
|
|
|
|
) { |
791
|
8403
|
|
|
|
|
6870
|
$xremove_value_idx++; |
792
|
8403
|
|
|
|
|
12063
|
next; |
793
|
|
|
|
|
|
|
} |
794
|
21412
|
|
|
|
|
32805
|
$values[$write_value_idx++] = $values[$read_value_idx]; |
795
|
|
|
|
|
|
|
} |
796
|
4518
|
|
|
|
|
9357
|
$#values = $write_value_idx-1; |
797
|
|
|
|
|
|
|
} |
798
|
|
|
|
|
|
|
|
799
|
184556
|
|
|
|
|
149498
|
my $template; |
800
|
184556
|
100
|
|
|
|
229733
|
if (@constant_values) { |
801
|
589
|
|
|
|
|
2684
|
$template = UR::BoolExpr::Template::And->_fast_construct( |
802
|
|
|
|
|
|
|
$subject_class, |
803
|
|
|
|
|
|
|
\@keys, |
804
|
|
|
|
|
|
|
\@constant_values, |
805
|
|
|
|
|
|
|
); |
806
|
|
|
|
|
|
|
} |
807
|
|
|
|
|
|
|
else { |
808
|
183967
|
|
66
|
|
|
931088
|
$template = $subject_class_meta->{cache}{"UR::BoolExpr::resolve"}{"template for class and keys without constant values"}{"$subject_class @keys"} |
809
|
|
|
|
|
|
|
||= UR::BoolExpr::Template::And->_fast_construct( |
810
|
|
|
|
|
|
|
$subject_class, |
811
|
|
|
|
|
|
|
\@keys, |
812
|
|
|
|
|
|
|
\@constant_values, |
813
|
|
|
|
|
|
|
); |
814
|
|
|
|
|
|
|
} |
815
|
|
|
|
|
|
|
|
816
|
184555
|
|
|
|
|
361842
|
my $value_id = UR::BoolExpr::Util::values_to_value_id(@values); |
817
|
|
|
|
|
|
|
|
818
|
184555
|
|
|
|
|
342025
|
my $rule_id = join($UR::BoolExpr::Util::id_sep,$template->{id},$value_id); |
819
|
|
|
|
|
|
|
|
820
|
184555
|
|
|
|
|
337627
|
my $rule = __PACKAGE__->get($rule_id); # flyweight constructor |
821
|
|
|
|
|
|
|
|
822
|
184555
|
|
|
|
|
254013
|
$rule->{template} = $template; |
823
|
184555
|
|
|
|
|
223659
|
$rule->{values} = \@values; |
824
|
|
|
|
|
|
|
|
825
|
184555
|
|
|
|
|
174688
|
$vn = 0; |
826
|
184555
|
|
|
|
|
135489
|
$cn = 0; |
827
|
184555
|
|
|
|
|
135018
|
my @list; |
828
|
184555
|
|
|
|
|
182496
|
for my $key (@keys) { |
829
|
229072
|
|
|
|
|
239196
|
push @list, $key; |
830
|
229072
|
100
|
|
|
|
349428
|
if (UR::BoolExpr::Util::is_meta_param($key)) { |
831
|
650
|
|
|
|
|
1107
|
push @list, $constant_values[$cn++]; |
832
|
|
|
|
|
|
|
} |
833
|
|
|
|
|
|
|
else { |
834
|
228422
|
|
|
|
|
326931
|
push @list, $values[$vn++]; |
835
|
|
|
|
|
|
|
} |
836
|
|
|
|
|
|
|
} |
837
|
184555
|
|
|
|
|
211251
|
$rule->{_params_list} = \@list; |
838
|
|
|
|
|
|
|
|
839
|
184555
|
100
|
|
|
|
292579
|
if (@hard_refs) { |
840
|
570
|
|
|
|
|
1647
|
$rule->{hard_refs} = { @hard_refs }; |
841
|
570
|
|
|
|
|
898
|
delete $rule->{hard_refs}{''}; |
842
|
|
|
|
|
|
|
} |
843
|
|
|
|
|
|
|
|
844
|
184555
|
|
|
|
|
159428
|
$resolve_depth--; |
845
|
184555
|
100
|
66
|
|
|
246844
|
if (wantarray) { |
|
|
100
|
|
|
|
|
|
846
|
178404
|
|
|
|
|
641453
|
return ($rule, @extra); |
847
|
|
|
|
|
|
|
} |
848
|
|
|
|
|
|
|
elsif (@extra && defined wantarray) { |
849
|
1
|
50
|
|
|
|
4
|
Carp::confess("Unknown parameters in rule for $subject_class: " . join(",", map { defined($_) ? "'$_'" : "(undef)" } @extra)); |
|
2
|
|
|
|
|
229
|
|
850
|
|
|
|
|
|
|
} |
851
|
|
|
|
|
|
|
else { |
852
|
6150
|
|
|
|
|
26677
|
return $rule; |
853
|
|
|
|
|
|
|
} |
854
|
|
|
|
|
|
|
} |
855
|
|
|
|
|
|
|
|
856
|
|
|
|
|
|
|
sub _params_list { |
857
|
5443
|
|
66
|
5443
|
|
12131
|
my $list = $_[0]->{_params_list} ||= do { |
858
|
79
|
|
|
|
|
104
|
my $self = $_[0]; |
859
|
79
|
|
|
|
|
159
|
my $template = $self->template; |
860
|
79
|
100
|
|
|
|
423
|
$self->values unless $self->{values}; |
861
|
79
|
|
|
|
|
94
|
my @list; |
862
|
|
|
|
|
|
|
# are method calls really too expensive here? |
863
|
79
|
|
|
|
|
132
|
my $template_class = ref($template); |
864
|
79
|
100
|
|
|
|
207
|
if ($template_class eq 'UR::BoolExpr::Template::And') { |
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
865
|
54
|
|
|
|
|
132
|
my ($k,$v,$c) = ($template->{_keys}, $self->{values}, $template->{_constant_values}); |
866
|
54
|
|
|
|
|
71
|
my $vn = 0; |
867
|
54
|
|
|
|
|
77
|
my $cn = 0; |
868
|
54
|
|
|
|
|
114
|
for my $key (@$k) { |
869
|
123
|
|
|
|
|
151
|
push @list, $key; |
870
|
123
|
100
|
|
|
|
202
|
if (UR::BoolExpr::Util::is_meta_param($key)) { |
871
|
18
|
|
|
|
|
42
|
push @list, $c->[$cn++]; |
872
|
|
|
|
|
|
|
} |
873
|
|
|
|
|
|
|
else { |
874
|
105
|
|
|
|
|
198
|
push @list, $v->[$vn++]; |
875
|
|
|
|
|
|
|
} |
876
|
|
|
|
|
|
|
} |
877
|
|
|
|
|
|
|
} |
878
|
|
|
|
|
|
|
elsif ($template_class eq 'UR::BoolExpr::Template::Or') { |
879
|
25
|
|
|
|
|
28
|
my @sublist; |
880
|
25
|
|
|
|
|
76
|
my @u = $self->underlying_rules(); |
881
|
25
|
|
|
|
|
43
|
for my $u (@u) { |
882
|
62
|
|
|
|
|
121
|
my @p = $u->_params_list; |
883
|
62
|
|
|
|
|
98
|
push @sublist, \@p; |
884
|
|
|
|
|
|
|
} |
885
|
25
|
|
|
|
|
76
|
@list = (-or => \@sublist); |
886
|
|
|
|
|
|
|
} |
887
|
|
|
|
|
|
|
elsif ($template_class->isa("UR::BoolExpr::PropertyComparison")) { |
888
|
0
|
|
|
|
|
0
|
@list = ($template->logic_detail => [@{$self->{values}}]); |
|
0
|
|
|
|
|
0
|
|
889
|
|
|
|
|
|
|
} |
890
|
79
|
|
|
|
|
192
|
\@list; |
891
|
|
|
|
|
|
|
}; |
892
|
5443
|
|
|
|
|
14551
|
return @$list; |
893
|
|
|
|
|
|
|
} |
894
|
|
|
|
|
|
|
|
895
|
|
|
|
|
|
|
sub normalize { |
896
|
517777
|
|
|
517777
|
1
|
411049
|
my $self = shift; |
897
|
|
|
|
|
|
|
|
898
|
517777
|
|
|
|
|
628208
|
my $rule_template = $self->template; |
899
|
|
|
|
|
|
|
|
900
|
517777
|
100
|
|
|
|
827080
|
if ($rule_template->{is_normalized}) { |
901
|
179096
|
|
|
|
|
253317
|
return $self; |
902
|
|
|
|
|
|
|
} |
903
|
338681
|
|
|
|
|
481021
|
my @unnormalized_values = $self->values(); |
904
|
|
|
|
|
|
|
|
905
|
338681
|
|
|
|
|
771633
|
my $normalized = $rule_template->get_normalized_rule_for_values(@unnormalized_values); |
906
|
338681
|
50
|
|
|
|
512749
|
return unless defined $normalized; |
907
|
|
|
|
|
|
|
|
908
|
338681
|
100
|
|
|
|
544918
|
if (my $special = $self->{hard_refs}) { |
909
|
64
|
|
|
|
|
378
|
$normalized->{hard_refs} = $rule_template->_normalize_non_ur_values_hash($special); |
910
|
|
|
|
|
|
|
} |
911
|
338681
|
|
|
|
|
557185
|
return $normalized; |
912
|
|
|
|
|
|
|
} |
913
|
|
|
|
|
|
|
|
914
|
|
|
|
|
|
|
# a handful of places still use this |
915
|
|
|
|
|
|
|
sub legacy_params_hash { |
916
|
61139
|
|
|
61139
|
0
|
55425
|
my $self = shift; |
917
|
|
|
|
|
|
|
|
918
|
|
|
|
|
|
|
# See if we have one already. |
919
|
61139
|
|
|
|
|
63408
|
my $params_array = $self->{legacy_params_array}; |
920
|
61139
|
100
|
|
|
|
99311
|
return { @$params_array } if $params_array; |
921
|
|
|
|
|
|
|
|
922
|
|
|
|
|
|
|
# Make one by starting with the one on the rule template |
923
|
58422
|
|
|
|
|
74273
|
my $rule_template = $self->template; |
924
|
58422
|
|
|
|
|
49721
|
my $params = { %{$rule_template->legacy_params_hash}, $self->params_list }; |
|
58422
|
|
|
|
|
118409
|
|
925
|
|
|
|
|
|
|
|
926
|
|
|
|
|
|
|
# If the template has a _param_key, fill it in. |
927
|
58422
|
100
|
|
|
|
137881
|
if (exists $params->{_param_key}) { |
928
|
3357
|
|
|
|
|
8296
|
$params->{_param_key} = $self->id; |
929
|
|
|
|
|
|
|
} |
930
|
|
|
|
|
|
|
|
931
|
|
|
|
|
|
|
# This was cached above and will return immediately on the next call. |
932
|
|
|
|
|
|
|
# Note: the caller should copy this reference before making changes. |
933
|
58422
|
|
|
|
|
201785
|
$self->{legacy_params_array} = [ %$params ]; |
934
|
58422
|
|
|
|
|
121619
|
return $params; |
935
|
|
|
|
|
|
|
} |
936
|
|
|
|
|
|
|
|
937
|
|
|
|
|
|
|
|
938
|
|
|
|
|
|
|
my $LOADED_BXPARSE = 0; |
939
|
|
|
|
|
|
|
sub resolve_for_string { |
940
|
125
|
|
|
125
|
0
|
5803
|
my ($class, $subject_class_name, $filter_string, $usage_hints_string, $order_string, $page_string) = @_; |
941
|
|
|
|
|
|
|
|
942
|
125
|
100
|
|
|
|
272
|
unless ($LOADED_BXPARSE) { |
943
|
4
|
|
|
|
|
7
|
my $exception = do { |
944
|
4
|
|
|
|
|
4
|
local $@; |
945
|
4
|
|
|
|
|
6
|
eval { require UR::BoolExpr::BxParser }; |
|
4
|
|
|
|
|
2560
|
|
946
|
4
|
|
|
|
|
288
|
$@; |
947
|
|
|
|
|
|
|
}; |
948
|
4
|
50
|
|
|
|
19
|
if ($exception) { |
949
|
0
|
|
|
|
|
0
|
Carp::croak("resolve_for_string() can't load UR::BoolExpr::BxParser: $exception"); |
950
|
|
|
|
|
|
|
} |
951
|
4
|
|
|
|
|
8
|
$LOADED_BXPARSE=1; |
952
|
|
|
|
|
|
|
} |
953
|
|
|
|
|
|
|
|
954
|
|
|
|
|
|
|
#$DB::single=1; |
955
|
|
|
|
|
|
|
#my $tree = UR::BoolExpr::BxParser::parse($filter_string, tokdebug => 1, yydebug => 7); |
956
|
125
|
|
|
|
|
321
|
my($tree, $remaining_strref) = UR::BoolExpr::BxParser::parse($filter_string); |
957
|
114
|
50
|
|
|
|
236
|
unless ($tree) { |
958
|
0
|
|
|
|
|
0
|
Carp::croak("resolve_for_string() couldn't parse string \"$filter_string\""); |
959
|
|
|
|
|
|
|
} |
960
|
|
|
|
|
|
|
|
961
|
114
|
100
|
|
|
|
218
|
push @$tree, '-hints', [split(',',$usage_hints_string) ] if ($usage_hints_string); |
962
|
114
|
100
|
|
|
|
216
|
push @$tree, '-order_by', [split(',',$order_string) ] if ($order_string); |
963
|
114
|
50
|
|
|
|
162
|
push @$tree, '-page', [split(',',$page_string) ] if ($page_string); |
964
|
|
|
|
|
|
|
|
965
|
114
|
|
|
|
|
98
|
my ($bx, @extra); |
966
|
114
|
100
|
|
|
|
157
|
if(wantarray) { |
967
|
6
|
|
|
|
|
26
|
($bx, @extra) = UR::BoolExpr->resolve($subject_class_name, @$tree); |
968
|
|
|
|
|
|
|
} else { |
969
|
108
|
|
|
|
|
325
|
$bx = UR::BoolExpr->resolve($subject_class_name, @$tree); |
970
|
|
|
|
|
|
|
} |
971
|
114
|
50
|
|
|
|
299
|
unless ($bx) { |
972
|
0
|
|
|
|
|
0
|
Carp::croak("Can't create BoolExpr on $subject_class_name from params generated from string " |
973
|
|
|
|
|
|
|
. $filter_string . " which parsed as:\n" |
974
|
|
|
|
|
|
|
. Data::Dumper::Dumper($tree)); |
975
|
|
|
|
|
|
|
} |
976
|
114
|
50
|
|
|
|
247
|
if ($$remaining_strref) { |
977
|
0
|
|
|
|
|
0
|
Carp::croak("Trailing input after the parsable end of the filter string: '". $$remaining_strref."'"); |
978
|
|
|
|
|
|
|
} |
979
|
114
|
100
|
|
|
|
181
|
if(wantarray) { |
980
|
6
|
|
|
|
|
32
|
return ($bx, @extra); |
981
|
|
|
|
|
|
|
} else { |
982
|
108
|
|
|
|
|
360
|
return $bx; |
983
|
|
|
|
|
|
|
} |
984
|
|
|
|
|
|
|
} |
985
|
|
|
|
|
|
|
|
986
|
|
|
|
|
|
|
sub _resolve_from_filter_array { |
987
|
0
|
|
|
0
|
|
|
my $class = shift; |
988
|
|
|
|
|
|
|
|
989
|
0
|
|
|
|
|
|
my $subject_class_name = shift; |
990
|
0
|
|
|
|
|
|
my $filters = shift; |
991
|
0
|
|
|
|
|
|
my $usage_hints = shift; |
992
|
0
|
|
|
|
|
|
my $order = shift; |
993
|
0
|
|
|
|
|
|
my $page = shift; |
994
|
|
|
|
|
|
|
|
995
|
0
|
|
|
|
|
|
my @rule_filters; |
996
|
|
|
|
|
|
|
|
997
|
|
|
|
|
|
|
my @keys; |
998
|
0
|
|
|
|
|
|
my @values; |
999
|
|
|
|
|
|
|
|
1000
|
0
|
|
|
|
|
|
for my $fdata (@$filters) { |
1001
|
0
|
|
|
|
|
|
my $rule_filter; |
1002
|
|
|
|
|
|
|
|
1003
|
|
|
|
|
|
|
# rule component |
1004
|
0
|
|
|
|
|
|
my $key = $fdata->[0]; |
1005
|
0
|
|
|
|
|
|
my $value; |
1006
|
|
|
|
|
|
|
|
1007
|
|
|
|
|
|
|
# process the operator |
1008
|
0
|
0
|
|
|
|
|
if ($fdata->[1] =~ /^!?(:|@|between|in)$/i) { |
|
|
0
|
|
|
|
|
|
1009
|
|
|
|
|
|
|
|
1010
|
0
|
|
|
|
|
|
my @list_parts; |
1011
|
|
|
|
|
|
|
my @range_parts; |
1012
|
|
|
|
|
|
|
|
1013
|
0
|
0
|
|
|
|
|
if ($fdata->[1] eq "@") { |
1014
|
|
|
|
|
|
|
# file path |
1015
|
0
|
|
|
|
|
|
my $fh = IO::File->new($fdata->[2]); |
1016
|
0
|
0
|
|
|
|
|
unless ($fh) { |
1017
|
0
|
|
|
|
|
|
die "Failed to open file $fdata->[2]: $!\n"; |
1018
|
|
|
|
|
|
|
} |
1019
|
0
|
|
|
|
|
|
@list_parts = $fh->getlines; |
1020
|
0
|
|
|
|
|
|
chomp @list_parts; |
1021
|
0
|
|
|
|
|
|
$fh->close; |
1022
|
|
|
|
|
|
|
} |
1023
|
|
|
|
|
|
|
else { |
1024
|
0
|
|
|
|
|
|
@list_parts = split(/\//,$fdata->[2]); |
1025
|
0
|
|
|
|
|
|
@range_parts = split(/-/,$fdata->[2]); |
1026
|
|
|
|
|
|
|
} |
1027
|
|
|
|
|
|
|
|
1028
|
0
|
0
|
|
|
|
|
if (@list_parts > 1) { |
|
|
0
|
|
|
|
|
|
1029
|
0
|
0
|
|
|
|
|
my $op = ($fdata->[1] =~ /^!/ ? 'not in' : 'in'); |
1030
|
|
|
|
|
|
|
# rule component |
1031
|
0
|
0
|
|
|
|
|
if (substr($key, -3, 3) ne ' in') { |
1032
|
0
|
|
|
|
|
|
$key = join(' ', $key, $op); |
1033
|
|
|
|
|
|
|
} |
1034
|
0
|
|
|
|
|
|
$value = \@list_parts; |
1035
|
0
|
|
|
|
|
|
$rule_filter = [$fdata->[0],$op,\@list_parts]; |
1036
|
|
|
|
|
|
|
} |
1037
|
|
|
|
|
|
|
elsif (@range_parts >= 2) { |
1038
|
0
|
0
|
|
|
|
|
if (@range_parts > 2) { |
|
|
0
|
|
|
|
|
|
1039
|
0
|
0
|
|
|
|
|
if (@range_parts % 2) { |
1040
|
0
|
|
|
|
|
|
die "The \":\" operator expects a range sparated by a single dash: @range_parts ." . "\n"; |
1041
|
|
|
|
|
|
|
} |
1042
|
|
|
|
|
|
|
else { |
1043
|
0
|
|
|
|
|
|
my $half = (@range_parts)/2; |
1044
|
0
|
|
|
|
|
|
$a = join("-",@range_parts[0..($half-1)]); |
1045
|
0
|
|
|
|
|
|
$b = join("-",@range_parts[$half..$#range_parts]); |
1046
|
|
|
|
|
|
|
} |
1047
|
|
|
|
|
|
|
} |
1048
|
|
|
|
|
|
|
elsif (@range_parts == 2) { |
1049
|
0
|
|
|
|
|
|
($a,$b) = @range_parts; |
1050
|
|
|
|
|
|
|
} |
1051
|
|
|
|
|
|
|
else { |
1052
|
0
|
|
|
|
|
|
die 'The ":" operator expects a range sparated by a dash.' . "\n"; |
1053
|
|
|
|
|
|
|
} |
1054
|
|
|
|
|
|
|
|
1055
|
0
|
|
|
|
|
|
$key = $fdata->[0] . " between"; |
1056
|
0
|
|
|
|
|
|
$value = [$a, $b]; |
1057
|
0
|
|
|
|
|
|
$rule_filter = [$fdata->[0], "between", [$a, $b] ]; |
1058
|
|
|
|
|
|
|
} |
1059
|
|
|
|
|
|
|
else { |
1060
|
0
|
|
|
|
|
|
die 'The ":" operator expects a range sparated by a dash, or a slash-separated list.' . "\n"; |
1061
|
|
|
|
|
|
|
} |
1062
|
|
|
|
|
|
|
|
1063
|
|
|
|
|
|
|
} |
1064
|
|
|
|
|
|
|
# this accounts for cases where value is null |
1065
|
|
|
|
|
|
|
elsif (length($fdata->[2])==0) { |
1066
|
0
|
0
|
|
|
|
|
if ($fdata->[1] eq "=") { |
1067
|
0
|
|
|
|
|
|
$key = $fdata->[0]; |
1068
|
0
|
|
|
|
|
|
$value = undef; |
1069
|
0
|
|
|
|
|
|
$rule_filter = [ $fdata->[0], "=", undef ]; |
1070
|
|
|
|
|
|
|
} |
1071
|
|
|
|
|
|
|
else { |
1072
|
0
|
|
|
|
|
|
$key = $fdata->[0] . " !="; |
1073
|
0
|
|
|
|
|
|
$value = undef; |
1074
|
0
|
|
|
|
|
|
$rule_filter = [ $fdata->[0], "!=", undef ]; |
1075
|
|
|
|
|
|
|
} |
1076
|
|
|
|
|
|
|
} |
1077
|
|
|
|
|
|
|
else { |
1078
|
0
|
|
0
|
|
|
|
$key = $fdata->[0] . ($fdata->[1] and $fdata->[1] ne '='? ' ' . $fdata->[1] : ''); |
1079
|
0
|
|
|
|
|
|
$value = $fdata->[2]; |
1080
|
0
|
|
|
|
|
|
$rule_filter = [ @$fdata ]; |
1081
|
|
|
|
|
|
|
} |
1082
|
|
|
|
|
|
|
|
1083
|
0
|
|
|
|
|
|
push @keys, $key; |
1084
|
0
|
|
|
|
|
|
push @values, $value; |
1085
|
|
|
|
|
|
|
} |
1086
|
0
|
0
|
0
|
|
|
|
if ($usage_hints or $order or $page) { |
|
|
|
0
|
|
|
|
|
1087
|
|
|
|
|
|
|
# todo: incorporate hints in a smarter way |
1088
|
0
|
|
|
|
|
|
my %p; |
1089
|
0
|
|
|
|
|
|
for my $key (@keys) { |
1090
|
0
|
|
|
|
|
|
$p{$key} = shift @values; |
1091
|
|
|
|
|
|
|
} |
1092
|
0
|
0
|
|
|
|
|
return $class->resolve( |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1093
|
|
|
|
|
|
|
$subject_class_name, |
1094
|
|
|
|
|
|
|
%p, |
1095
|
|
|
|
|
|
|
($usage_hints ? (-hints => $usage_hints) : () ), |
1096
|
|
|
|
|
|
|
($order ? (-order => $order) : () ), |
1097
|
|
|
|
|
|
|
($page ? (-page => $page) : () ), |
1098
|
|
|
|
|
|
|
); |
1099
|
|
|
|
|
|
|
} |
1100
|
|
|
|
|
|
|
else { |
1101
|
0
|
|
|
|
|
|
return UR::BoolExpr->_resolve_from_subject_class_name_keys_and_values( |
1102
|
|
|
|
|
|
|
subject_class_name => $subject_class_name, |
1103
|
|
|
|
|
|
|
keys => \@keys, |
1104
|
|
|
|
|
|
|
values=> \@values, |
1105
|
|
|
|
|
|
|
); |
1106
|
|
|
|
|
|
|
} |
1107
|
|
|
|
|
|
|
|
1108
|
|
|
|
|
|
|
} |
1109
|
|
|
|
|
|
|
|
1110
|
|
|
|
|
|
|
sub _resolve_from_subject_class_name_keys_and_values { |
1111
|
0
|
|
|
0
|
|
|
my $class = shift; |
1112
|
|
|
|
|
|
|
|
1113
|
0
|
|
|
|
|
|
my %params = @_; |
1114
|
0
|
|
|
|
|
|
my $subject_class_name = $params{subject_class_name}; |
1115
|
0
|
0
|
|
|
|
|
my @values = @{ $params{values} || [] }; |
|
0
|
|
|
|
|
|
|
1116
|
0
|
0
|
|
|
|
|
my @constant_values = @{ $params{constant_values} || [] }; |
|
0
|
|
|
|
|
|
|
1117
|
0
|
0
|
|
|
|
|
my @keys = @{ $params{keys} || [] }; |
|
0
|
|
|
|
|
|
|
1118
|
0
|
0
|
|
|
|
|
die "unexpected params: " . Data::Dumper::Dumper(\%params) if %params; |
1119
|
|
|
|
|
|
|
|
1120
|
0
|
|
|
|
|
|
my $value_id = UR::BoolExpr::Util::values_to_value_id(@values); |
1121
|
0
|
|
|
|
|
|
my $constant_value_id = UR::BoolExpr::Util::values_to_value_id(@constant_values); |
1122
|
|
|
|
|
|
|
|
1123
|
0
|
|
|
|
|
|
my $template_id = $subject_class_name . '/And/' . join(",",@keys) . "/" . $constant_value_id; |
1124
|
0
|
|
|
|
|
|
my $rule_id = join($UR::BoolExpr::Util::id_sep,$template_id,$value_id); |
1125
|
|
|
|
|
|
|
|
1126
|
0
|
|
|
|
|
|
my $rule = __PACKAGE__->get($rule_id); |
1127
|
|
|
|
|
|
|
|
1128
|
0
|
|
|
|
|
|
$rule->{values} = \@values; |
1129
|
|
|
|
|
|
|
|
1130
|
0
|
|
|
|
|
|
return $rule; |
1131
|
|
|
|
|
|
|
} |
1132
|
|
|
|
|
|
|
|
1133
|
|
|
|
|
|
|
1; |
1134
|
|
|
|
|
|
|
|
1135
|
|
|
|
|
|
|
=pod |
1136
|
|
|
|
|
|
|
|
1137
|
|
|
|
|
|
|
=head1 NAME |
1138
|
|
|
|
|
|
|
|
1139
|
|
|
|
|
|
|
UR::BoolExpr - a "where clause" for objects |
1140
|
|
|
|
|
|
|
|
1141
|
|
|
|
|
|
|
=head1 SYNOPSIS |
1142
|
|
|
|
|
|
|
|
1143
|
|
|
|
|
|
|
my $o = Acme::Employee->create( |
1144
|
|
|
|
|
|
|
ssn => '123-45-6789', |
1145
|
|
|
|
|
|
|
name => 'Pat Jones', |
1146
|
|
|
|
|
|
|
status => 'active', |
1147
|
|
|
|
|
|
|
start_date => UR::Context->current->now, |
1148
|
|
|
|
|
|
|
payroll_category => 'hourly', |
1149
|
|
|
|
|
|
|
boss => $other_employee, |
1150
|
|
|
|
|
|
|
); |
1151
|
|
|
|
|
|
|
|
1152
|
|
|
|
|
|
|
my $bx = Acme::Employee->define_boolexpr( |
1153
|
|
|
|
|
|
|
'payroll_category' => 'hourly', |
1154
|
|
|
|
|
|
|
'status' => ['active','terminated'], |
1155
|
|
|
|
|
|
|
'name like' => '%Jones', |
1156
|
|
|
|
|
|
|
'ssn matches' => '\d{3}-\d{2}-\d{4}', |
1157
|
|
|
|
|
|
|
'start_date between' => ['2009-01-01','2009-02-01'], |
1158
|
|
|
|
|
|
|
'boss.name in' => ['Cletus Titus', 'Mitzy Mayhem'], |
1159
|
|
|
|
|
|
|
); |
1160
|
|
|
|
|
|
|
|
1161
|
|
|
|
|
|
|
$bx->evaluate($o); # true |
1162
|
|
|
|
|
|
|
|
1163
|
|
|
|
|
|
|
$bx->specifies_value_for('payroll_category') # true |
1164
|
|
|
|
|
|
|
|
1165
|
|
|
|
|
|
|
$bx->value_for('payroll_cagtegory') # 'hourly' |
1166
|
|
|
|
|
|
|
|
1167
|
|
|
|
|
|
|
$o->payroll_category('salary'); |
1168
|
|
|
|
|
|
|
$bx->evaluate($o); # false |
1169
|
|
|
|
|
|
|
|
1170
|
|
|
|
|
|
|
# these could take either a boolean expression, or a list of params |
1171
|
|
|
|
|
|
|
# from which it will generate one on-the-fly |
1172
|
|
|
|
|
|
|
my $set = Acme::Employee->define_set($bx); # same as listing all of the params |
1173
|
|
|
|
|
|
|
my @matches = Acme::Employee->get($bx); # same as above, but returns the members |
1174
|
|
|
|
|
|
|
|
1175
|
|
|
|
|
|
|
my $bx2 = $bx->reframe('boss'); |
1176
|
|
|
|
|
|
|
#'employees.payroll_category' => 'hourly', |
1177
|
|
|
|
|
|
|
#'employees.status' => ['active','terminated'], |
1178
|
|
|
|
|
|
|
#'employees.name like' => '%Jones', |
1179
|
|
|
|
|
|
|
#'employees.ssn matches' => '\d{3}-\d{2}-\d{4}', |
1180
|
|
|
|
|
|
|
#'employees.start_date between' => ['2009-01-01','2009-02-01'], |
1181
|
|
|
|
|
|
|
#'name in' => ['Cletus Titus', 'Mitzy Mayhem'], |
1182
|
|
|
|
|
|
|
|
1183
|
|
|
|
|
|
|
my $bx3 = $bx->flatten(); |
1184
|
|
|
|
|
|
|
# any indirection in the params takes the form a.b.c at the lowest level |
1185
|
|
|
|
|
|
|
# also 'payroll_category' might become 'pay_history.category', and 'pay_history.is_current' => 1 is added to the list |
1186
|
|
|
|
|
|
|
# if this parameter has that as a custom filter |
1187
|
|
|
|
|
|
|
|
1188
|
|
|
|
|
|
|
|
1189
|
|
|
|
|
|
|
=head1 DESCRIPTION |
1190
|
|
|
|
|
|
|
|
1191
|
|
|
|
|
|
|
A UR::BoolExpr object captures a set of match criteria for some class of object. |
1192
|
|
|
|
|
|
|
|
1193
|
|
|
|
|
|
|
Calls to get(), create(), and define_set() all use this internally to objectify |
1194
|
|
|
|
|
|
|
their parameters. If given a boolean expression object directly they will use it. |
1195
|
|
|
|
|
|
|
Otherwise they will construct one from the parameters given. |
1196
|
|
|
|
|
|
|
|
1197
|
|
|
|
|
|
|
They have a 1:1 correspondence within the WHERE clause in an SQL statement where |
1198
|
|
|
|
|
|
|
RDBMS persistence is used. They also imply the FROM clause in these cases, |
1199
|
|
|
|
|
|
|
since the query properties control which joins must be included to return |
1200
|
|
|
|
|
|
|
the matching object set. |
1201
|
|
|
|
|
|
|
|
1202
|
|
|
|
|
|
|
=head1 REFLECTION |
1203
|
|
|
|
|
|
|
|
1204
|
|
|
|
|
|
|
The data used to create the boolean expression can be re-extracted: |
1205
|
|
|
|
|
|
|
|
1206
|
|
|
|
|
|
|
my $c = $r->subject_class_name; |
1207
|
|
|
|
|
|
|
# $c eq "GSC::Clone" |
1208
|
|
|
|
|
|
|
|
1209
|
|
|
|
|
|
|
my @p = $r->params_list; |
1210
|
|
|
|
|
|
|
# @p = four items |
1211
|
|
|
|
|
|
|
|
1212
|
|
|
|
|
|
|
my %p = $r->params_list; |
1213
|
|
|
|
|
|
|
# %p = two key value pairs |
1214
|
|
|
|
|
|
|
|
1215
|
|
|
|
|
|
|
=head1 TEMPLATE SUBCLASSES |
1216
|
|
|
|
|
|
|
|
1217
|
|
|
|
|
|
|
The template behind the expression can be of type ::Or, ::And or ::PropertyComparison. |
1218
|
|
|
|
|
|
|
These classes handle all of the operating logic for the expressions. |
1219
|
|
|
|
|
|
|
|
1220
|
|
|
|
|
|
|
Each of those classes incapsulates 0..n of the next type in the list. All templates |
1221
|
|
|
|
|
|
|
simplify to this level. See L for details. |
1222
|
|
|
|
|
|
|
|
1223
|
|
|
|
|
|
|
=head1 CONSTRUCTOR |
1224
|
|
|
|
|
|
|
|
1225
|
|
|
|
|
|
|
=over 4 |
1226
|
|
|
|
|
|
|
|
1227
|
|
|
|
|
|
|
my $bx = UR::BoolExpr->resolve('Some::Class', property_1 => 'value_1', ... property_n => 'value_n'); |
1228
|
|
|
|
|
|
|
my $bx1 = Some::Class->define_boolexpr(property_1 => value_1, ... property_n => 'value_n'); |
1229
|
|
|
|
|
|
|
my $bx2 = Some::Class->define_boolexpr('property_1 >' => 12345); |
1230
|
|
|
|
|
|
|
my $bx3 = UR::BoolExpr->resolve_for_string( |
1231
|
|
|
|
|
|
|
'Some::Class', |
1232
|
|
|
|
|
|
|
'property_1 = value_1 and ( property_2 < value_2 or property_3 = value_3 )', |
1233
|
|
|
|
|
|
|
); |
1234
|
|
|
|
|
|
|
|
1235
|
|
|
|
|
|
|
Returns a UR::BoolExpr object that can be used to perform tests on the given class and |
1236
|
|
|
|
|
|
|
properties. The default comparison for each property is equality. The third example shows |
1237
|
|
|
|
|
|
|
using greater-than operator for property_1. The last example shows constructing a |
1238
|
|
|
|
|
|
|
UR::BoolExpr from a string containing properties, operators and values joined with |
1239
|
|
|
|
|
|
|
'and' and 'or', with parentheses indicating precedence. |
1240
|
|
|
|
|
|
|
|
1241
|
|
|
|
|
|
|
=back |
1242
|
|
|
|
|
|
|
|
1243
|
|
|
|
|
|
|
C can parse simple and complicated expressions. A simple expression |
1244
|
|
|
|
|
|
|
is a property name followed by an operator followed by a value. The property name can be |
1245
|
|
|
|
|
|
|
a series of properties joined by dots (.) to indicate traversal of multiple layers of |
1246
|
|
|
|
|
|
|
indirect properties. Values that include spaces, characters that look like operators, |
1247
|
|
|
|
|
|
|
commas, or other special characters should be enclosed in quotes. |
1248
|
|
|
|
|
|
|
|
1249
|
|
|
|
|
|
|
The parser understands all the same operators the underlying C method understands: |
1250
|
|
|
|
|
|
|
=, <, >, <=, >=, "like", "between" and "in". Operators may be prefixed by a bang (!) or the |
1251
|
|
|
|
|
|
|
word "not" to negate the operator. The "like" operator understands the SQL wildcards % and _. |
1252
|
|
|
|
|
|
|
Values for the "between" operator should be separated by a minus (-). Values for the "in" |
1253
|
|
|
|
|
|
|
operator should begin with a left bracket, end with a right bracket, and have commas between |
1254
|
|
|
|
|
|
|
them. For example: |
1255
|
|
|
|
|
|
|
name_property in [Bob,Fred,Joe] |
1256
|
|
|
|
|
|
|
|
1257
|
|
|
|
|
|
|
Simple expressions may be joined together with the words "and" and "or" to form a more |
1258
|
|
|
|
|
|
|
complicated expression. "and" has higher precedence than "or", and parentheses can |
1259
|
|
|
|
|
|
|
surround sub-expressions to indicate the requested precedence. For example: |
1260
|
|
|
|
|
|
|
((prop1 = foo or prop2 = 1) and (prop2 > 10 or prop3 like 'Yo%')) or prop4 in [1,2,3] |
1261
|
|
|
|
|
|
|
|
1262
|
|
|
|
|
|
|
In general, whitespace is insignificant. The strings "prop1 = 1" is parsed the same as |
1263
|
|
|
|
|
|
|
"prop1=1". Spaces inside quoted value strings are preserved. For backward compatibility |
1264
|
|
|
|
|
|
|
with the deprecated string parser, bare words that appear after the operators =,<,>,<= |
1265
|
|
|
|
|
|
|
and >= which are separated by one or more spaces is treated as if it had quotes around |
1266
|
|
|
|
|
|
|
the list of words starting with the first character of the first word and ending with |
1267
|
|
|
|
|
|
|
the last character of the last word, meaning that spaces at the start and end of the |
1268
|
|
|
|
|
|
|
list are trimmed. |
1269
|
|
|
|
|
|
|
|
1270
|
|
|
|
|
|
|
Specific ordering may be requested by putting an "order by" clause at the end, and is the |
1271
|
|
|
|
|
|
|
same as using a -order argument to resolve(): |
1272
|
|
|
|
|
|
|
score > 10 order by name,score. |
1273
|
|
|
|
|
|
|
|
1274
|
|
|
|
|
|
|
Likewise, grouping and Set construction is indicated with a "group by" clause: |
1275
|
|
|
|
|
|
|
score > 10 group by color |
1276
|
|
|
|
|
|
|
|
1277
|
|
|
|
|
|
|
=head1 METHODS |
1278
|
|
|
|
|
|
|
|
1279
|
|
|
|
|
|
|
=over 4 |
1280
|
|
|
|
|
|
|
|
1281
|
|
|
|
|
|
|
=item evaluate |
1282
|
|
|
|
|
|
|
|
1283
|
|
|
|
|
|
|
$bx->evaluate($object) |
1284
|
|
|
|
|
|
|
|
1285
|
|
|
|
|
|
|
Returns true if the given object satisfies the BoolExpr |
1286
|
|
|
|
|
|
|
|
1287
|
|
|
|
|
|
|
|
1288
|
|
|
|
|
|
|
=item template_and_values |
1289
|
|
|
|
|
|
|
|
1290
|
|
|
|
|
|
|
($template, @values) = $bx->template_and_values(); |
1291
|
|
|
|
|
|
|
|
1292
|
|
|
|
|
|
|
Returns the UR::BoolExpr::Template and list of the values for the given BoolExpr |
1293
|
|
|
|
|
|
|
|
1294
|
|
|
|
|
|
|
=item is_subset_of |
1295
|
|
|
|
|
|
|
|
1296
|
|
|
|
|
|
|
$bx->is_subset_of($other_bx) |
1297
|
|
|
|
|
|
|
|
1298
|
|
|
|
|
|
|
Returns true if the set of objects that matches this BoolExpr is a subset of |
1299
|
|
|
|
|
|
|
the set of objects that matches $other_bx. In practice this means: |
1300
|
|
|
|
|
|
|
|
1301
|
|
|
|
|
|
|
* The subject class of $bx isa the subject class of $other_bx |
1302
|
|
|
|
|
|
|
* all the properties from $bx also appear in $other_bx |
1303
|
|
|
|
|
|
|
* the operators and values for $bx's properties match $other_bx |
1304
|
|
|
|
|
|
|
|
1305
|
|
|
|
|
|
|
=item values |
1306
|
|
|
|
|
|
|
|
1307
|
|
|
|
|
|
|
@values = $bx->values |
1308
|
|
|
|
|
|
|
|
1309
|
|
|
|
|
|
|
Return a list of the values from $bx. The values will be in the same order |
1310
|
|
|
|
|
|
|
the BoolExpr was created from |
1311
|
|
|
|
|
|
|
|
1312
|
|
|
|
|
|
|
=item value_for_id |
1313
|
|
|
|
|
|
|
|
1314
|
|
|
|
|
|
|
$id = $bx->value_for_id |
1315
|
|
|
|
|
|
|
|
1316
|
|
|
|
|
|
|
If $bx's properties include all the ID properties of its subject class, |
1317
|
|
|
|
|
|
|
C returns that value. Otherwise, it returns the empty list. |
1318
|
|
|
|
|
|
|
If the subject class has more than one ID property, this returns the value |
1319
|
|
|
|
|
|
|
of the composite ID. |
1320
|
|
|
|
|
|
|
|
1321
|
|
|
|
|
|
|
=item specifies_value_for |
1322
|
|
|
|
|
|
|
|
1323
|
|
|
|
|
|
|
$bx->specifies_value_for('property_name'); |
1324
|
|
|
|
|
|
|
|
1325
|
|
|
|
|
|
|
Returns true if the filter list of $bx includes the given property name |
1326
|
|
|
|
|
|
|
|
1327
|
|
|
|
|
|
|
=item value_for |
1328
|
|
|
|
|
|
|
|
1329
|
|
|
|
|
|
|
my $value = $bx->value_for('property_name'); |
1330
|
|
|
|
|
|
|
|
1331
|
|
|
|
|
|
|
Return the value for the given property |
1332
|
|
|
|
|
|
|
|
1333
|
|
|
|
|
|
|
=item operator_for |
1334
|
|
|
|
|
|
|
|
1335
|
|
|
|
|
|
|
my $operator = $bx->operator_for('property_name'); |
1336
|
|
|
|
|
|
|
|
1337
|
|
|
|
|
|
|
Return a string for the operator of the given property. A value of '' (the |
1338
|
|
|
|
|
|
|
empty string) means equality ("="). Other possible values include '<', '>', |
1339
|
|
|
|
|
|
|
'<=', '>=', 'between', 'true', 'false', 'in', 'not <', 'not >', etc. |
1340
|
|
|
|
|
|
|
|
1341
|
|
|
|
|
|
|
=item normalize |
1342
|
|
|
|
|
|
|
|
1343
|
|
|
|
|
|
|
$bx2 = $bx->normalize; |
1344
|
|
|
|
|
|
|
|
1345
|
|
|
|
|
|
|
A boolean expression can be changed in incidental ways and still be equivalent. |
1346
|
|
|
|
|
|
|
This method converts the expression into a normalized form so that it can be |
1347
|
|
|
|
|
|
|
compared to other normalized expressions without incidental differences |
1348
|
|
|
|
|
|
|
affecting the comparison. |
1349
|
|
|
|
|
|
|
|
1350
|
|
|
|
|
|
|
=item flatten |
1351
|
|
|
|
|
|
|
|
1352
|
|
|
|
|
|
|
$bx2 = $bx->flatten(); |
1353
|
|
|
|
|
|
|
|
1354
|
|
|
|
|
|
|
Transforms a boolean expression into a functional equivalent where |
1355
|
|
|
|
|
|
|
indirect properties are turned into property chains. |
1356
|
|
|
|
|
|
|
|
1357
|
|
|
|
|
|
|
For instance, in a class with |
1358
|
|
|
|
|
|
|
|
1359
|
|
|
|
|
|
|
a => { is => "A", id_by => "a_id" }, |
1360
|
|
|
|
|
|
|
b => { via => "a", to => "bb" }, |
1361
|
|
|
|
|
|
|
c => { via => "b", to => "cc" }, |
1362
|
|
|
|
|
|
|
|
1363
|
|
|
|
|
|
|
An expression of: |
1364
|
|
|
|
|
|
|
|
1365
|
|
|
|
|
|
|
c => 1234 |
1366
|
|
|
|
|
|
|
|
1367
|
|
|
|
|
|
|
Becomes: |
1368
|
|
|
|
|
|
|
|
1369
|
|
|
|
|
|
|
a.bb.cc => 1234 |
1370
|
|
|
|
|
|
|
|
1371
|
|
|
|
|
|
|
In cases where one of the indirect properties includes a "where" clause, |
1372
|
|
|
|
|
|
|
the flattened expression would have an additional value for each element: |
1373
|
|
|
|
|
|
|
|
1374
|
|
|
|
|
|
|
a => { is => "A", id_by => "a_id" }, |
1375
|
|
|
|
|
|
|
b => { via => "a", to => "bb" }, |
1376
|
|
|
|
|
|
|
c => { via => "b", where ["xx" => 5678], to => "cc" }, |
1377
|
|
|
|
|
|
|
|
1378
|
|
|
|
|
|
|
An expression of: |
1379
|
|
|
|
|
|
|
|
1380
|
|
|
|
|
|
|
c => 1234 |
1381
|
|
|
|
|
|
|
|
1382
|
|
|
|
|
|
|
Becomes: |
1383
|
|
|
|
|
|
|
|
1384
|
|
|
|
|
|
|
a.bb.cc => 1234 |
1385
|
|
|
|
|
|
|
a.bb.xx => 5678 |
1386
|
|
|
|
|
|
|
|
1387
|
|
|
|
|
|
|
|
1388
|
|
|
|
|
|
|
|
1389
|
|
|
|
|
|
|
=item reframe |
1390
|
|
|
|
|
|
|
|
1391
|
|
|
|
|
|
|
$bx = Acme::Order->define_boolexpr(status => 'active'); |
1392
|
|
|
|
|
|
|
$bx2 = $bx->reframe('customer'); |
1393
|
|
|
|
|
|
|
|
1394
|
|
|
|
|
|
|
The above will turn a query for orders which are active into a query for |
1395
|
|
|
|
|
|
|
customers with active orders, presuming an Acme::Order has a property called |
1396
|
|
|
|
|
|
|
"customer" with a defined relationship to another class. |
1397
|
|
|
|
|
|
|
|
1398
|
|
|
|
|
|
|
=back |
1399
|
|
|
|
|
|
|
|
1400
|
|
|
|
|
|
|
=head1 INTERNAL STRUCTURE |
1401
|
|
|
|
|
|
|
|
1402
|
|
|
|
|
|
|
A boolean expression (or "rule") has an "id", which completely describes the rule in stringified form, |
1403
|
|
|
|
|
|
|
and a method called evaluate($o) which tests the rule on a given object. |
1404
|
|
|
|
|
|
|
|
1405
|
|
|
|
|
|
|
The id is composed of two parts: |
1406
|
|
|
|
|
|
|
- A template_id. |
1407
|
|
|
|
|
|
|
- A value_id. |
1408
|
|
|
|
|
|
|
|
1409
|
|
|
|
|
|
|
Nearly all real work delegates to the template to avoid duplication of cached details. |
1410
|
|
|
|
|
|
|
|
1411
|
|
|
|
|
|
|
The template_id embeds several other properties, for which the rule delegates to it: |
1412
|
|
|
|
|
|
|
- subject_class_name, objects of which the rule can be applied-to |
1413
|
|
|
|
|
|
|
- subclass_name, the subclass of rule (property comparison, and, or "or") |
1414
|
|
|
|
|
|
|
- the body of the rule either key-op-val, or a list of other rules |
1415
|
|
|
|
|
|
|
|
1416
|
|
|
|
|
|
|
For example, the rule GSC::Clone name=x,chromosome>y: |
1417
|
|
|
|
|
|
|
- the template_id embeds: |
1418
|
|
|
|
|
|
|
subject_class_name = GSC::Clone |
1419
|
|
|
|
|
|
|
subclass_name = UR::BoolExpr::And |
1420
|
|
|
|
|
|
|
and the key-op pairs in sorted order: "chromosome>,name=" |
1421
|
|
|
|
|
|
|
- the value_id embeds the x,y values in a special format |
1422
|
|
|
|
|
|
|
|
1423
|
|
|
|
|
|
|
=head1 EXAMPLES |
1424
|
|
|
|
|
|
|
|
1425
|
|
|
|
|
|
|
|
1426
|
|
|
|
|
|
|
my $bool = $x->evaluate($obj); |
1427
|
|
|
|
|
|
|
|
1428
|
|
|
|
|
|
|
my $t = GSC::Clone->template_for_params( |
1429
|
|
|
|
|
|
|
"status =", |
1430
|
|
|
|
|
|
|
"chromosome []", |
1431
|
|
|
|
|
|
|
"clone_name like", |
1432
|
|
|
|
|
|
|
"clone_size between" |
1433
|
|
|
|
|
|
|
); |
1434
|
|
|
|
|
|
|
|
1435
|
|
|
|
|
|
|
my @results = $t->get_matching_objects( |
1436
|
|
|
|
|
|
|
"active", |
1437
|
|
|
|
|
|
|
[2,4,7], |
1438
|
|
|
|
|
|
|
"Foo%", |
1439
|
|
|
|
|
|
|
[100000,200000] |
1440
|
|
|
|
|
|
|
); |
1441
|
|
|
|
|
|
|
|
1442
|
|
|
|
|
|
|
my $r = $t->get_rule($v1,$v2,$v3); |
1443
|
|
|
|
|
|
|
|
1444
|
|
|
|
|
|
|
my $t = $r->template; |
1445
|
|
|
|
|
|
|
|
1446
|
|
|
|
|
|
|
my @results = $t->get_matching_objects($v1,$v2,$v3); |
1447
|
|
|
|
|
|
|
my @results = $r->get_matching_objects(); |
1448
|
|
|
|
|
|
|
|
1449
|
|
|
|
|
|
|
@r = $r->underlying_rules(); |
1450
|
|
|
|
|
|
|
for (@r) { |
1451
|
|
|
|
|
|
|
print $r->evaluate($c1); |
1452
|
|
|
|
|
|
|
} |
1453
|
|
|
|
|
|
|
|
1454
|
|
|
|
|
|
|
my $rt = $r->template(); |
1455
|
|
|
|
|
|
|
my @rt = $rt->get_underlying_rule_templates(); |
1456
|
|
|
|
|
|
|
|
1457
|
|
|
|
|
|
|
$r = $rt->get_rule_for_values(@v); |
1458
|
|
|
|
|
|
|
|
1459
|
|
|
|
|
|
|
$r = UR::BoolExpr->resolve_for_string( |
1460
|
|
|
|
|
|
|
'My::Class', |
1461
|
|
|
|
|
|
|
'name=Bob and (score=10 or score < 5)', |
1462
|
|
|
|
|
|
|
); |
1463
|
|
|
|
|
|
|
|
1464
|
|
|
|
|
|
|
=head1 SEE ALSO |
1465
|
|
|
|
|
|
|
|
1466
|
|
|
|
|
|
|
UR(3), UR::Object(3), UR::Object::Set(3), UR::BoolExpr::Template(3) |
1467
|
|
|
|
|
|
|
|
1468
|
|
|
|
|
|
|
=cut |