Filename | /var/www/foswiki11/lib/Foswiki/Query/HoistREs.pm |
Statements | Executed 1538 statements in 4.37ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
40 | 1 | 1 | 912µs | 1.69ms | _hoistEQ | Foswiki::Query::HoistREs::
41 | 1 | 1 | 709µs | 3.02ms | collatedHoist | Foswiki::Query::HoistREs::
40 | 1 | 1 | 532µs | 532µs | _hoistDOT | Foswiki::Query::HoistREs::
41 | 1 | 1 | 328µs | 2.31ms | hoist | Foswiki::Query::HoistREs::
40 | 1 | 1 | 288µs | 1.98ms | _hoistOR | Foswiki::Query::HoistREs::
80 | 2 | 1 | 250µs | 250µs | _hoistConstant | Foswiki::Query::HoistREs::
1 | 1 | 1 | 15µs | 27µs | BEGIN@17 | Foswiki::Query::HoistREs::
1 | 1 | 1 | 9µs | 14µs | BEGIN@18 | Foswiki::Query::HoistREs::
1 | 1 | 1 | 9µs | 37µs | BEGIN@33 | Foswiki::Query::HoistREs::
1 | 1 | 1 | 4µs | 4µs | BEGIN@20 | Foswiki::Query::HoistREs::
1 | 1 | 1 | 3µs | 3µs | BEGIN@21 | Foswiki::Query::HoistREs::
Line | State ments |
Time on line |
Calls | Time in subs |
Code |
---|---|---|---|---|---|
1 | # See bottom of file for license and copyright information | ||||
2 | |||||
3 | =begin TML | ||||
4 | |||||
5 | ---+ package Foswiki::Query::HoistREs | ||||
6 | |||||
7 | Static functions to extract regular expressions from queries. The REs can | ||||
8 | be used in caching stores that use the Foswiki standard inline meta-data | ||||
9 | representation to pre-filter topic lists for more efficient query matching. | ||||
10 | |||||
11 | See =Store/QueryAlgorithms/BruteForce.pm= for an example of usage. | ||||
12 | |||||
13 | =cut | ||||
14 | |||||
15 | package Foswiki::Query::HoistREs; | ||||
16 | |||||
17 | 2 | 26µs | 2 | 40µs | # spent 27µs (15+12) within Foswiki::Query::HoistREs::BEGIN@17 which was called:
# once (15µs+12µs) by Foswiki::Store::QueryAlgorithms::BruteForce::BEGIN@37 at line 17 # spent 27µs making 1 call to Foswiki::Query::HoistREs::BEGIN@17
# spent 12µs making 1 call to strict::import |
18 | 2 | 26µs | 2 | 19µs | # spent 14µs (9+5) within Foswiki::Query::HoistREs::BEGIN@18 which was called:
# once (9µs+5µs) by Foswiki::Store::QueryAlgorithms::BruteForce::BEGIN@37 at line 18 # spent 14µs making 1 call to Foswiki::Query::HoistREs::BEGIN@18
# spent 5µs making 1 call to warnings::import |
19 | |||||
20 | 2 | 18µs | 1 | 4µs | # spent 4µs within Foswiki::Query::HoistREs::BEGIN@20 which was called:
# once (4µs+0s) by Foswiki::Store::QueryAlgorithms::BruteForce::BEGIN@37 at line 20 # spent 4µs making 1 call to Foswiki::Query::HoistREs::BEGIN@20 |
21 | 2 | 31µs | 1 | 3µs | # spent 3µs within Foswiki::Query::HoistREs::BEGIN@21 which was called:
# once (3µs+0s) by Foswiki::Store::QueryAlgorithms::BruteForce::BEGIN@37 at line 21 # spent 3µs making 1 call to Foswiki::Query::HoistREs::BEGIN@21 |
22 | |||||
23 | # Try to optimise a query by hoisting regular expression searches | ||||
24 | # out of the query | ||||
25 | # | ||||
26 | # patterns we need to look for: | ||||
27 | # | ||||
28 | # top level is defined by a sequence of AND and OR conjunctions | ||||
29 | # second level, = and ~ and =~ | ||||
30 | # second level LHS is a field access | ||||
31 | # second level RHS is a static string or number | ||||
32 | |||||
33 | 2 | 1.30ms | 2 | 66µs | # spent 37µs (9+29) within Foswiki::Query::HoistREs::BEGIN@33 which was called:
# once (9µs+29µs) by Foswiki::Store::QueryAlgorithms::BruteForce::BEGIN@37 at line 33 # spent 37µs making 1 call to Foswiki::Query::HoistREs::BEGIN@33
# spent 29µs making 1 call to constant::import |
34 | |||||
35 | =begin TML | ||||
36 | |||||
37 | ---++ ObjectMethod collatedHoist($query) -> $hasRef | ||||
38 | |||||
39 | retuns a hashRef where the keys are the node's (web|name|text) | ||||
40 | for which we have hoisted regex's | ||||
41 | and who's values are a list of regex's | ||||
42 | |||||
43 | and also keys of "(web|name|text)_source" where the list contains | ||||
44 | the non-regex version (ie what the user entered) | ||||
45 | |||||
46 | =cut | ||||
47 | |||||
48 | # spent 3.02ms (709µs+2.31) within Foswiki::Query::HoistREs::collatedHoist which was called 41 times, avg 74µs/call:
# 41 times (709µs+2.31ms) by Foswiki::Store::QueryAlgorithms::BruteForce::_webQuery at line 140 of /var/www/foswiki11/lib/Foswiki/Store/QueryAlgorithms/BruteForce.pm, avg 74µs/call | ||||
49 | 41 | 17µs | my $node = shift; | ||
50 | |||||
51 | 41 | 14µs | my %collation; | ||
52 | |||||
53 | 41 | 152µs | 41 | 2.31ms | my @ops = hoist($node); # spent 2.31ms making 41 calls to Foswiki::Query::HoistREs::hoist, avg 56µs/call |
54 | 41 | 68µs | foreach my $op (@ops) { | ||
55 | 40 | 105µs | push( @{ $collation{ $op->{node} } }, $op->{regex} ); | ||
56 | 40 | 113µs | push( @{ $collation{ $op->{node} . '_source' } }, $op->{source} ); | ||
57 | } | ||||
58 | |||||
59 | #use Data::Dumper; | ||||
60 | #print STDERR "--- hoisted: ".Dumper(%collation)."\n" if MONITOR_HOIST; | ||||
61 | |||||
62 | 41 | 175µs | return \%collation; | ||
63 | } | ||||
64 | |||||
65 | =begin TML | ||||
66 | |||||
67 | ---++ ObjectMethod hoist($query) -> @hashRefs | ||||
68 | |||||
69 | Extract useful filter REs from the given query. The list returned is a list | ||||
70 | of filter expressions that can be used with a cache search to refine the | ||||
71 | list of topics. The full query should still be applied to topics that remain | ||||
72 | after the filter match has been applied; this is purely an optimisation. | ||||
73 | |||||
74 | each hash in the array contains the node the regex is for, and a regex string | ||||
75 | |||||
76 | { | ||||
77 | node => 'web|name|text', | ||||
78 | regex => 'Web.*' | ||||
79 | source => 'Web*' | ||||
80 | } | ||||
81 | |||||
82 | =cut | ||||
83 | |||||
84 | # spent 2.31ms (328µs+1.98) within Foswiki::Query::HoistREs::hoist which was called 41 times, avg 56µs/call:
# 41 times (328µs+1.98ms) by Foswiki::Query::HoistREs::collatedHoist at line 53, avg 56µs/call | ||||
85 | 41 | 16µs | my $node = shift; | ||
86 | |||||
87 | 41 | 48µs | return () unless ref( $node->{op} ); | ||
88 | |||||
89 | 40 | 36µs | if ( $node->{op}->{name} eq '(' ) { | ||
90 | return hoist( $node->{params}[0] ); | ||||
91 | } | ||||
92 | |||||
93 | print STDERR "hoist ", $node->stringify(), "\n" if MONITOR_HOIST; | ||||
94 | 40 | 43µs | if ( $node->{op}->{name} eq 'and' ) { | ||
95 | my @lhs = hoist( $node->{params}[0] ); | ||||
96 | my $rhs = _hoistOR( $node->{params}[1] ); | ||||
97 | if ( scalar(@lhs) && $rhs ) { | ||||
98 | return ( @lhs, $rhs ); | ||||
99 | } | ||||
100 | elsif ( scalar(@lhs) ) { | ||||
101 | return @lhs; | ||||
102 | } | ||||
103 | elsif ($rhs) { | ||||
104 | return ($rhs); | ||||
105 | } | ||||
106 | } | ||||
107 | else { | ||||
108 | 40 | 94µs | 40 | 1.98ms | my $or = _hoistOR($node); # spent 1.98ms making 40 calls to Foswiki::Query::HoistREs::_hoistOR, avg 50µs/call |
109 | 40 | 105µs | return ($or) if $or; | ||
110 | } | ||||
111 | |||||
112 | print STDERR "\tFAILED\n" if MONITOR_HOIST; | ||||
113 | return (); | ||||
114 | } | ||||
115 | |||||
116 | # depth 1; we can handle a sequence of ORs | ||||
117 | # spent 1.98ms (288µs+1.69) within Foswiki::Query::HoistREs::_hoistOR which was called 40 times, avg 50µs/call:
# 40 times (288µs+1.69ms) by Foswiki::Query::HoistREs::hoist at line 108, avg 50µs/call | ||||
118 | 40 | 10µs | my $node = shift; | ||
119 | |||||
120 | 40 | 33µs | return unless ref( $node->{op} ); | ||
121 | |||||
122 | 40 | 26µs | if ( $node->{op}->{name} eq '(' ) { | ||
123 | return _hoistOR( $node->{params}[0] ); | ||||
124 | } | ||||
125 | |||||
126 | 40 | 35µs | if ( $node->{op}->{name} eq 'or' ) { | ||
127 | print STDERR "hoistOR ", $node->stringify(), "\n" if MONITOR_HOIST; | ||||
128 | my $lhs = _hoistOR( $node->{params}[0] ); | ||||
129 | my $rhs = _hoistEQ( $node->{params}[1] ); | ||||
130 | if ( $lhs && $rhs ) { | ||||
131 | if ( $lhs->{node} eq $rhs->{node} ) { | ||||
132 | return { | ||||
133 | node => $lhs->{node}, | ||||
134 | regex => $lhs->{regex} . '|' . $rhs->{regex}, | ||||
135 | source => $lhs->{source} . ',' . $rhs->{source} | ||||
136 | }; | ||||
137 | } | ||||
138 | return ( $lhs, $rhs ); | ||||
139 | } | ||||
140 | } | ||||
141 | else { | ||||
142 | 40 | 183µs | 40 | 1.69ms | return _hoistEQ($node); # spent 1.69ms making 40 calls to Foswiki::Query::HoistREs::_hoistEQ, avg 42µs/call |
143 | } | ||||
144 | |||||
145 | print STDERR "\tFAILED\n" if MONITOR_HOIST; | ||||
146 | return; | ||||
147 | } | ||||
148 | |||||
149 | # depth 2: can handle = and ~ expressions | ||||
150 | # spent 1.69ms (912µs+781µs) within Foswiki::Query::HoistREs::_hoistEQ which was called 40 times, avg 42µs/call:
# 40 times (912µs+781µs) by Foswiki::Query::HoistREs::_hoistOR at line 142, avg 42µs/call | ||||
151 | 40 | 10µs | my $node = shift; | ||
152 | |||||
153 | 40 | 36µs | return unless ref( $node->{op} ); | ||
154 | |||||
155 | 40 | 28µs | if ( $node->{op}->{name} eq '(' ) { | ||
156 | return _hoistEQ( $node->{params}[0] ); | ||||
157 | } | ||||
158 | |||||
159 | print STDERR "hoistEQ ", $node->stringify(), "\n" if MONITOR_HOIST; | ||||
160 | |||||
161 | # \000RHS\001 is a placholder for the RHS term | ||||
162 | 40 | 36µs | if ( $node->{op}->{name} eq '=' ) { | ||
163 | 40 | 146µs | 40 | 532µs | my $lhs = _hoistDOT( $node->{params}[0] ); # spent 532µs making 40 calls to Foswiki::Query::HoistREs::_hoistDOT, avg 13µs/call |
164 | 40 | 121µs | 40 | 179µs | my $rhs = _hoistConstant( $node->{params}[1] ); # spent 179µs making 40 calls to Foswiki::Query::HoistREs::_hoistConstant, avg 4µs/call |
165 | 40 | 19µs | if ( $lhs && defined $rhs ) { | ||
166 | 40 | 36µs | $rhs = quotemeta($rhs); | ||
167 | 40 | 180µs | $lhs->{regex} =~ s/\000RHS\001/$rhs/g; | ||
168 | 40 | 112µs | 40 | 70µs | $lhs->{source} = _hoistConstant( $node->{params}[1] ); # spent 70µs making 40 calls to Foswiki::Query::HoistREs::_hoistConstant, avg 2µs/call |
169 | 40 | 110µs | return $lhs; | ||
170 | } | ||||
171 | |||||
172 | # = is symmetric, so try the other order | ||||
173 | $lhs = _hoistDOT( $node->{params}[1] ); | ||||
174 | $rhs = _hoistConstant( $node->{params}[0] ); | ||||
175 | if ( $lhs && defined $rhs ) { | ||||
176 | $rhs = quotemeta($rhs); | ||||
177 | $lhs->{regex} =~ s/\000RHS\001/$rhs/g; | ||||
178 | $lhs->{source} = _hoistConstant( $node->{params}[0] ); | ||||
179 | return $lhs; | ||||
180 | } | ||||
181 | } | ||||
182 | elsif ( $node->{op}->{name} eq '~' ) { | ||||
183 | my $lhs = _hoistDOT( $node->{params}[0] ); | ||||
184 | my $rhs = _hoistConstant( $node->{params}[1] ); | ||||
185 | if ( $lhs && defined $rhs ) { | ||||
186 | $rhs = quotemeta($rhs); | ||||
187 | $rhs =~ s/\\\?/./g; | ||||
188 | $rhs =~ s/\\\*/.*/g; | ||||
189 | $lhs->{regex} =~ s/\000RHS\001/$rhs/g; | ||||
190 | $lhs->{source} = _hoistConstant( $node->{params}[1] ); | ||||
191 | return $lhs; | ||||
192 | } | ||||
193 | } | ||||
194 | elsif ( $node->{op}->{name} eq '=~' ) { | ||||
195 | my $lhs = _hoistDOT( $node->{params}[0] ); | ||||
196 | my $rhs = _hoistConstant( $node->{params}[1] ); | ||||
197 | if ( $lhs && defined $rhs ) { | ||||
198 | |||||
199 | #need to detect if its a field, or in a text, and if its a field, remove the ^$ chars... | ||||
200 | #or if there are no ^$, add .*'s if they are not present | ||||
201 | if ( $lhs->{regex} ne "\000RHS\001" ) { | ||||
202 | if ( ( not( $rhs =~ /^\^/ ) ) | ||||
203 | and ( not( $rhs =~ /^\.\*/ ) ) ) | ||||
204 | { | ||||
205 | $rhs = '.*' . $rhs; | ||||
206 | } | ||||
207 | |||||
208 | if ( ( not( $rhs =~ /\$$/ ) ) | ||||
209 | and ( not( $rhs =~ /\.\*$/ ) ) ) | ||||
210 | { | ||||
211 | $rhs = $rhs . '.*'; | ||||
212 | } | ||||
213 | |||||
214 | #if we're embedding the regex into another, then remove the ^'s | ||||
215 | $rhs =~ s/^\^//; | ||||
216 | $rhs =~ s/\$$//; | ||||
217 | } | ||||
218 | $lhs->{regex} =~ s/\000RHS\001/$rhs/g; | ||||
219 | $lhs->{source} = _hoistConstant( $node->{params}[1] ); | ||||
220 | return $lhs; | ||||
221 | } | ||||
222 | } | ||||
223 | |||||
224 | print STDERR "\tFAILED\n" if MONITOR_HOIST; | ||||
225 | return; | ||||
226 | } | ||||
227 | |||||
228 | # Expecting a (root level) field access expression. This must be of the form | ||||
229 | # <name> | ||||
230 | # or | ||||
231 | # <rootfield>.<name> | ||||
232 | # <rootfield> may be aliased | ||||
233 | # spent 532µs within Foswiki::Query::HoistREs::_hoistDOT which was called 40 times, avg 13µs/call:
# 40 times (532µs+0s) by Foswiki::Query::HoistREs::_hoistEQ at line 163, avg 13µs/call | ||||
234 | 40 | 19µs | my $node = shift; | ||
235 | |||||
236 | 40 | 39µs | if ( ref( $node->{op} ) && $node->{op}->{name} eq '(' ) { | ||
237 | return _hoistDOT( $node->{params}[0] ); | ||||
238 | } | ||||
239 | |||||
240 | print STDERR "hoistDOT ", $node->stringify(), "\n" if MONITOR_HOIST; | ||||
241 | 40 | 101µs | if ( ref( $node->{op} ) && $node->{op}->{name} eq '.' ) { | ||
242 | my $lhs = $node->{params}[0]; | ||||
243 | my $rhs = $node->{params}[1]; | ||||
244 | if ( !ref( $lhs->{op} ) | ||||
245 | && !ref( $rhs->{op} ) | ||||
246 | && $lhs->{op} eq $Foswiki::Infix::Node::NAME | ||||
247 | && $rhs->{op} eq $Foswiki::Infix::Node::NAME ) | ||||
248 | { | ||||
249 | $lhs = $lhs->{params}[0]; | ||||
250 | $rhs = $rhs->{params}[0]; | ||||
251 | if ( $Foswiki::Query::Node::aliases{$lhs} ) { | ||||
252 | $lhs = $Foswiki::Query::Node::aliases{$lhs}; | ||||
253 | } | ||||
254 | if ( $lhs =~ /^META:/ ) { | ||||
255 | |||||
256 | # \000RHS\001 is a placholder for the RHS term | ||||
257 | return { | ||||
258 | node => 'text', | ||||
259 | regex => '^%' | ||||
260 | . $lhs | ||||
261 | . '{.*\\b' | ||||
262 | . $rhs | ||||
263 | . "=\\\"\000RHS\001\\\"" | ||||
264 | }; | ||||
265 | } | ||||
266 | |||||
267 | # Otherwise assume the term before the dot is the form name | ||||
268 | if ( $rhs eq 'text' ) { | ||||
269 | |||||
270 | # Special case for the text body | ||||
271 | return { node => 'text', regex => "\000RHS\001" }; | ||||
272 | } | ||||
273 | else { | ||||
274 | return { | ||||
275 | node => 'text', | ||||
276 | regex => | ||||
277 | "^%META:FIELD{name=\\\"$rhs\\\".*\\bvalue=\\\"\000RHS\001\\\"" | ||||
278 | }; | ||||
279 | } | ||||
280 | |||||
281 | } | ||||
282 | } | ||||
283 | elsif ( !ref( $node->{op} ) && $node->{op} eq $Foswiki::Infix::Node::NAME ) | ||||
284 | { | ||||
285 | 40 | 62µs | if ( $node->{params}[0] eq 'name' ) { | ||
286 | |||||
287 | # Special case for the topic name | ||||
288 | return { node => 'name', regex => "\000RHS\001" }; | ||||
289 | return; | ||||
290 | } | ||||
291 | elsif ( $node->{params}[0] eq 'web' ) { | ||||
292 | |||||
293 | # Special case for the web name | ||||
294 | return { node => 'web', regex => "\000RHS\001" }; | ||||
295 | return; | ||||
296 | } | ||||
297 | elsif ( $node->{params}[0] eq 'text' ) { | ||||
298 | |||||
299 | # Special case for the text body | ||||
300 | return { node => 'text', regex => "\000RHS\001" }; | ||||
301 | } | ||||
302 | else { | ||||
303 | return { | ||||
304 | 40 | 324µs | node => 'text', | ||
305 | regex => | ||||
306 | "^%META:FIELD{name=\\\"$node->{params}[0]\\\".*\\bvalue=\\\"\0RHS\1\\\"" | ||||
307 | }; | ||||
308 | } | ||||
309 | } | ||||
310 | |||||
311 | print STDERR "\tFAILED\n" if MONITOR_HOIST; | ||||
312 | return; | ||||
313 | } | ||||
314 | |||||
315 | # Expecting a constant | ||||
316 | sub _hoistConstant { | ||||
317 | 80 | 23µs | my $node = shift; | ||
318 | |||||
319 | print STDERR "hoistCONST ", $node->stringify(), "\n" if MONITOR_HOIST; | ||||
320 | 80 | 293µs | if ( | ||
321 | !ref( $node->{op} ) | ||||
322 | && ( $node->{op} eq $Foswiki::Infix::Node::STRING | ||||
323 | || $node->{op} eq $Foswiki::Infix::Node::NUMBER ) | ||||
324 | ) | ||||
325 | { | ||||
326 | return $node->{params}[0]; | ||||
327 | } | ||||
328 | return; | ||||
329 | } | ||||
330 | |||||
331 | 1 | 2µs | 1; | ||
332 | __END__ |