File Coverage

blib/lib/RDF/Query/Plan/Path.pm
Criterion Covered Total %
statement 175 321 54.5
branch 26 94 27.6
condition n/a
subroutine 27 32 84.3
pod 15 15 100.0
total 243 462 52.6


line stmt bran cond sub pod time code
1             # RDF::Query::Plan::Path
2             # -----------------------------------------------------------------------------
3              
4             =head1 NAME
5              
6             RDF::Query::Plan::Path - Executable query plan for Paths.
7              
8             =head1 VERSION
9              
10             This document describes RDF::Query::Plan::Path version 2.915_01.
11              
12             =head1 METHODS
13              
14             Beyond the methods documented below, this class inherits methods from the
15             L<RDF::Query::Plan> class.
16              
17             =over 4
18              
19             =cut
20              
21             package RDF::Query::Plan::Path;
22              
23 35     35   192 use strict;
  35         65  
  35         880  
24 35     35   180 use warnings;
  35         76  
  35         925  
25 35     35   199 use base qw(RDF::Query::Plan);
  35         71  
  35         2389  
26              
27 35     35   204 use Log::Log4perl;
  35         72  
  35         256  
28 35     35   1720 use Scalar::Util qw(blessed refaddr);
  35         79  
  35         2062  
29 35     35   187 use Time::HiRes qw(gettimeofday tv_interval);
  35         78  
  35         243  
30              
31 35     35   3714 use RDF::Query::ExecutionContext;
  35         79  
  35         878  
32 35     35   188 use RDF::Query::VariableBindings;
  35         80  
  35         1702  
33              
34             ######################################################################
35              
36             our ($VERSION);
37             BEGIN {
38 35     35   124148 $VERSION = '2.915_01';
39             }
40              
41             ######################################################################
42              
43             =item C<< new ( $path_operator, $path, $start, $end, $graph, $distinct, %args ) >>
44              
45             =cut
46              
47             sub new {
48 2     2 1 4 my $class = shift;
49 2         3 my $op = shift;
50 2         4 my $start = shift;
51 2         2 my $path = shift;
52 2         3 my $end = shift;
53 2         4 my $graph = shift;
54 2         3 my $distinct = shift;
55 2         6 my %args = @_;
56 2         13 my $self = $class->SUPER::new( $op, $path, $start, $end, $graph, $distinct, \%args );
57 2         5 my %vars;
58 2         5 for ($start, $end) {
59 4 50       31 $vars{ $_->name }++ if ($_->isa('RDF::Query::Node::Variable'));
60             }
61 2         21 $self->[0]{referenced_variables} = [keys %vars];
62 2         12 return $self;
63             }
64              
65             =item C<< execute ( $execution_context ) >>
66              
67             =cut
68              
69             sub execute ($) {
70 2     2 1 3 my $self = shift;
71 2         3 my $context = shift;
72 2         9 $self->[0]{delegate} = $context->delegate;
73 2 50       19 if ($self->state == $self->OPEN) {
74 0         0 throw RDF::Query::Error::ExecutionError -text => "PATH plan can't be executed while already open";
75             }
76            
77 2         9 my $l = Log::Log4perl->get_logger("rdf.query.plan.path");
78 2         45 $l->trace( "executing RDF::Query::Plan::Path " . $self->sse );
79            
80 2         21 my $start = $self->start;
81 2         6 my $end = $self->end;
82 2         6 my $graph = $self->graph;
83 2         10 my $bound = $context->bound;
84 2 50       8 if (%$bound) {
85 0         0 for ($start, $end, $graph) {
86 0 0       0 next unless (blessed($_));
87 0 0       0 next unless ($_->isa('RDF::Trine::Node::Variable'));
88 0 0       0 next unless (blessed($bound->{ $_->name }));
89 0         0 $_ = $bound->{ $_->name };
90             }
91             }
92            
93 2         7 $self->[0]{results} = [];
94 2 50       7 my @vars = grep { blessed($_) and $_->isa('RDF::Trine::Node::Variable') } ($self->start, $self->end);
  4         30  
95 2         8 my $model = $context->model;
96            
97 2         7 $self->[0]{bound} = $bound;
98 2         5 $self->[0]{graph} = $graph;
99 2         5 $self->[0]{count} = 0;
100 2         7 $self->[0]{context} = $context;
101 2         10 $self->state( $self->OPEN );
102              
103 2         4 my $op = $self->path_operator;
104 2 50       12 if ($op eq 'NegatedPropertySet') {
    100          
    50          
    0          
105 0         0 $self->_run_nps();
106             } elsif ($op eq 'ZeroOrMorePath') {
107 1         5 $self->_run_zeroormore();
108             } elsif ($op eq 'OneOrMorePath') {
109 1         5 $self->_run_oneormore();
110             } elsif ($op eq 'ZeroLengthPath') {
111 0         0 $self->_run_zerolength();
112             }
113            
114 2         10 $self;
115             }
116              
117              
118             sub _run_nps {
119 0     0   0 my $self = shift;
120 0         0 my $context = $self->[0]{context};
121 0         0 my $graph = $self->[0]{graph};
122 0 0       0 $graph = RDF::Trine::Node::Nil->new() unless (defined($graph));
123 0         0 my $model = $context->model;
124 0         0 my $path = $self->path;
125            
126 0         0 my $var = RDF::Query::Node::Variable->new();
127 0         0 my $st = RDF::Query::Algebra::Quad->new( $self->start, $var, $self->end, $graph );
128 0         0 my @nodes = $st->nodes;
129 0         0 my $plan = RDF::Query::Plan::Quad->new( @nodes[0..2], $graph );
130 0         0 my %not;
131 0         0 foreach my $n (@$path) {
132 0         0 $not{ $n->uri_value }++;
133             }
134            
135 0         0 $plan->execute( $context );
136 0         0 while (my $row = $plan->next) {
137 0 0       0 if (my $p = $row->{ $var->name }) {
138 0 0       0 next if (exists $not{ $p->uri_value });
139             }
140 0         0 push(@{ $self->[0]{results} }, $row);
  0         0  
141             }
142             }
143              
144             sub _run_zeroormore {
145 1     1   3 my $self = shift;
146 1         3 my $context = $self->[0]{context};
147 1         3 my $graph = $self->[0]{graph};
148 1 50       8 $graph = RDF::Trine::Node::Nil->new() unless (defined($graph));
149 1         10 my $model = $context->model;
150 1         4 my $path = $self->path;
151 1 50       4 my @vars = grep { blessed($_) and $_->isa('RDF::Trine::Node::Variable') } ($self->start, $self->end);
  2         17  
152 1 50       5 if (scalar(@vars) == 2) {
    0          
153             # var path+ var
154 1         2 my %nodes;
155 1         8 foreach my $n ($model->subjects(undef, undef, $graph), $model->objects(undef, undef, $graph)) {
156 18         1269 $nodes{ $n->as_string } = $n;
157             }
158 1         10 my $end = $self->end;
159 1         3 my $path = $self->path;
160 1         3 my @names = map { $_->name } @vars;
  2         11  
161 1         9 foreach my $start (values %nodes) {
162             # warn "starting var path* var path at $start";
163 13         25 my $r = [];
164 13         35 $self->_alp( $start, $path, $r, {} );
165 13         37 foreach my $term (@$r) {
166 19         56 my %data = ($names[0] => $start, $names[1] => $term);
167 19         95 my $vb = RDF::Query::VariableBindings->new(\%data);
168 19         25 push(@{ $self->[0]{results} }, $vb);
  19         80  
169             }
170             }
171             } elsif (scalar(@vars) == 1) {
172 0         0 my $start = $self->start;
173 0         0 my $end = $self->end;
174 0         0 my $path = $self->path;
175 0 0       0 if ($start->isa('RDF::Trine::Node::Variable')) {
176             # var path+ term
177 0         0 ($start, $end) = ($end, $start);
178 0         0 $path = ['^', $path];
179             }
180            
181             # term path+ var
182 0         0 my $r = [];
183 0         0 $self->_alp( $start, $path, $r, {} );
184            
185 0         0 my $name = $vars[0]->name;
186 0         0 foreach my $term (@$r) {
187 0         0 my $vb = RDF::Query::VariableBindings->new({ $name => $term });
188 0         0 push(@{ $self->[0]{results} }, $vb);
  0         0  
189             }
190             } else {
191             # term path+ term
192 0         0 my $var = RDF::Trine::Node::Variable->new();
193 0         0 my $start = $self->start;
194 0         0 my $end = $self->end;
195 0         0 my $path = $self->path;
196 0         0 my $r = [];
197 0         0 $self->_alp( $start, $path, $r, {} );
198 0         0 foreach my $term (@$r) {
199 0 0       0 if ($term->equal( $end )) {
200 0         0 my $vb = RDF::Query::VariableBindings->new({});
201 0         0 push(@{ $self->[0]{results} }, $vb);
  0         0  
202 0         0 return;
203             }
204             }
205             }
206             }
207              
208             sub _run_oneormore {
209 1     1   2 my $self = shift;
210 1         4 my $context = $self->[0]{context};
211 1         3 my $graph = $self->[0]{graph};
212 1 50       7 $graph = RDF::Trine::Node::Nil->new() unless (defined($graph));
213 1         10 my $model = $context->model;
214 1         7 my $path = $self->path;
215 1 50       9 my @vars = grep { blessed($_) and $_->isa('RDF::Trine::Node::Variable') } ($self->start, $self->end);
  2         18  
216 1 50       4 if (scalar(@vars) == 2) {
    0          
217             # var path+ var
218 1         2 my %nodes;
219 1         11 foreach my $n ($model->subjects(undef, undef, $graph), $model->objects(undef, undef, $graph)) {
220 18         1364 $nodes{ $n->as_string } = $n;
221             }
222 1         11 my $end = $self->end;
223 1         4 my $path = $self->path;
224 1         3 my @names = map { $_->name } @vars;
  2         11  
225 1         9 foreach my $start (values %nodes) {
226             # warn "starting var path+ var path at $start";
227 13         33 my $x = $self->_path_eval($start, $path);
228 13         265 my $r = [];
229 13         38 while (my $n = $x->next) {
230 3         32 $self->_alp( $n, $path, $r, {} );
231             }
232 13         191 foreach my $term (@$r) {
233 6         20 my %data = ($names[0] => $start, $names[1] => $term);
234 6         19 my $vb = RDF::Query::VariableBindings->new(\%data);
235 6         11 push(@{ $self->[0]{results} }, $vb);
  6         34  
236             }
237             }
238             } elsif (scalar(@vars) == 1) {
239 0         0 my $start = $self->start;
240 0         0 my $end = $self->end;
241 0         0 my $path = $self->path;
242 0 0       0 if ($start->isa('RDF::Trine::Node::Variable')) {
243             # var path+ term
244 0         0 ($start, $end) = ($end, $start);
245 0         0 $path = ['^', $path];
246             }
247            
248             # term path+ var
249 0         0 my $x = $self->_path_eval($start, $path);
250 0         0 my $r = [];
251 0         0 my $V = {};
252 0         0 while (my $n = $x->next) {
253 0         0 $self->_alp( $n, $path, $r, $V );
254             }
255            
256 0         0 my $name = $vars[0]->name;
257 0         0 foreach my $term (@$r) {
258 0         0 my $vb = RDF::Query::VariableBindings->new({ $name => $term });
259 0         0 push(@{ $self->[0]{results} }, $vb);
  0         0  
260             }
261             } else {
262             # term path+ term
263 0         0 my $var = RDF::Trine::Node::Variable->new();
264 0         0 my $start = $self->start;
265 0         0 my $end = $self->end;
266 0         0 my $path = $self->path;
267 0         0 my $x = $self->_path_eval($start, $path);
268 0         0 my $V = {};
269 0         0 while (my $n = $x->next) {
270 0         0 my $r = [];
271 0         0 $self->_alp( $n, $path, $r, $V );
272 0         0 foreach my $term (@$r) {
273 0 0       0 if ($term->equal( $end )) {
274 0         0 my $vb = RDF::Query::VariableBindings->new({});
275 0         0 push(@{ $self->[0]{results} }, $vb);
  0         0  
276 0         0 return;
277             }
278             }
279             }
280             }
281             }
282              
283             # returns an iterator of terms
284             sub _path_eval {
285 38     38   53 my $self = shift;
286 38         46 my $start = shift;
287 38         60 my $path = shift;
288 38         57 my $context = $self->[0]{context};
289 38         65 my $graph = $self->[0]{graph};
290 38 50       153 $graph = RDF::Trine::Node::Nil->new() unless (defined($graph));
291 38         340 my $var = RDF::Query::Node::Variable->new();
292 38         284 my $plan = RDF::Query::Plan->__path_plan( $start, $path, $var, $graph, $context, prevent_distinguishing_bnodes => 1, distinct => $self->distinct );
293 38         143 $plan->execute( $context );
294             my $iter = RDF::Trine::Iterator->new( sub {
295 50     50   532 my $r = $plan->next;
296 50 100       158 return unless ($r);
297 12         42 my $t = $r->{ $var->name };
298 12         81 return $t;
299 38         242 } );
300             }
301              
302             sub _alp {
303 25     25   37 my $self = shift;
304 25         32 my $term = shift;
305 25         29 my $path = shift;
306 25         31 my $r = shift;
307 25         33 my $v = shift;
308 25 50       82 return if (exists($v->{ $term->as_string }));
309 25         297 $v->{ $term->as_string } = $term;
310 25         220 push(@$r, $term);
311            
312 25         67 my $x = $self->_path_eval($term, $path);
313 25         646 while (my $n = $x->next) {
314 9         92 $self->_alp( $n, $path, $r, $v );
315             }
316            
317 25 50       239 unless ($self->distinct) {
318 0         0 delete $v->{ $term->as_string };
319             }
320             }
321              
322             sub _run_zerolength {
323 0     0   0 my $self = shift;
324 0         0 my $context = $self->[0]{context};
325 0         0 my $graph = $self->[0]{graph};
326 0 0       0 $graph = RDF::Trine::Node::Nil->new() unless (defined($graph));
327 0         0 my $model = $context->model;
328 0         0 my $path = $self->path;
329 0 0       0 my @vars = grep { blessed($_) and $_->isa('RDF::Trine::Node::Variable') } ($self->start, $self->end);
  0         0  
330 0 0       0 if (scalar(@vars) == 2) {
    0          
331             # -- bind VAR(s) to subjects and objects in the current active graph
332 0         0 my @names = map { $_->name } @vars;
  0         0  
333 0         0 my %nodes;
334 0         0 foreach my $n ($model->subjects(undef, undef, $graph), $model->objects(undef, undef, $graph)) {
335 0         0 $nodes{ $n->as_string } = $n;
336             }
337 0         0 foreach my $n (values %nodes) {
338 0         0 my %data;
339 0         0 @data{ @names } = ($n) x scalar(@names);
340 0         0 my $vb = RDF::Query::VariableBindings->new(\%data);
341 0         0 push(@{ $self->[0]{results} }, $vb);
  0         0  
342             }
343             } elsif (scalar(@vars) == 1) {
344 0 0       0 my ($term) = grep { blessed($_) and not($_->isa('RDF::Trine::Node::Variable')) } ($self->start, $self->end);
  0         0  
345 0         0 my $name = $vars[0]->name;
346 0         0 my $vb = RDF::Query::VariableBindings->new({ $name => $term });
347 0         0 push(@{ $self->[0]{results} }, $vb);
  0         0  
348             } else {
349 0 0       0 if ($self->start->equal( $self->end )) {
350 0         0 my $vb = RDF::Query::VariableBindings->new({});
351 0         0 push(@{ $self->[0]{results} }, $vb);
  0         0  
352             }
353             }
354             }
355              
356              
357              
358             =item C<< next >>
359              
360             =cut
361              
362             sub next {
363 27     27 1 31 my $self = shift;
364 27         78 my $l = Log::Log4perl->get_logger("rdf.query.plan.path");
365            
366 27 50       506 unless ($self->state == $self->OPEN) {
367 0         0 throw RDF::Query::Error::ExecutionError -text => "next() cannot be called on an un-open PATH";
368             }
369            
370 27 100       32 if (scalar(@{ $self->[0]{results} })) {
  27         76  
371 25         26 my $result = shift(@{ $self->[0]{results} });
  25         59  
372 25 50       131 $l->trace( 'returning path result: ' . $result ) if (defined($result));
373 25 50       797 if (my $d = $self->delegate) {
374 0         0 $d->log_result( $self, $result );
375             }
376 25         85 return $result;
377             }
378            
379 2         7 return;
380             }
381              
382             =item C<< close >>
383              
384             =cut
385              
386             sub close {
387 2     2 1 5 my $self = shift;
388 2 50       7 unless ($self->state == $self->OPEN) {
389 0         0 throw RDF::Query::Error::ExecutionError -text => "close() cannot be called on an un-open PATH";
390             }
391 2         5 delete $self->[0]{iter};
392 2         12 $self->SUPER::close();
393             }
394              
395             =item C<< path_operator >>
396              
397             Returns the path operation.
398              
399             =cut
400              
401             sub path_operator {
402 10     10 1 15 my $self = shift;
403 10         29 return $self->[1];
404             }
405              
406             =item C<< path >>
407              
408             Returns the path expression.
409              
410             =cut
411              
412             sub path {
413 14     14 1 17 my $self = shift;
414 14         23 return $self->[2];
415             }
416              
417             =item C<< start >>
418              
419             Returns the path start node.
420              
421             =cut
422              
423             sub start {
424 16     16 1 103 my $self = shift;
425 16         38 return $self->[3];
426             }
427              
428             =item C<< end >>
429              
430             Returns the path end node.
431              
432             =cut
433              
434             sub end {
435 18     18 1 21 my $self = shift;
436 18         40 return $self->[4];
437             }
438              
439             =item C<< graph >>
440              
441             Returns the named graph.
442              
443             =cut
444              
445             sub graph {
446 12     12 1 17 my $self = shift;
447 12         37 return $self->[5];
448             }
449              
450             =item C<< distinct >>
451              
452             Returns true if the pattern is guaranteed to return distinct results.
453              
454             =cut
455              
456             sub distinct {
457 63     63 1 78 my $self = shift;
458 63         328 return $self->[6];
459             }
460              
461             =item C<< ordered >>
462              
463             Returns true if the pattern is guaranteed to return ordered results.
464              
465             =cut
466              
467             sub ordered {
468 0     0 1 0 return [];
469             }
470              
471             =item C<< plan_node_name >>
472              
473             Returns the string name of this plan node, suitable for use in serialization.
474              
475             =cut
476              
477             sub plan_node_name {
478 8     8 1 11 my $self = shift;
479 8         17 return $self->path_operator;
480             }
481              
482             =item C<< plan_prototype >>
483              
484             Returns a list of scalar identifiers for the type of the content (children)
485             nodes of this plan node. See L<RDF::Query::Plan> for a list of the allowable
486             identifiers.
487              
488             =cut
489              
490             sub plan_prototype {
491 8     8 1 15 my $self = shift;
492 8         26 return qw(s N N N);
493             }
494              
495             =item C<< plan_node_data >>
496              
497             Returns the data for this plan node that corresponds to the values described by
498             the signature returned by C<< plan_prototype >>.
499              
500             =cut
501              
502             sub plan_node_data {
503 10     10 1 17 my $self = shift;
504 10         26 my $path = $self->path;
505 10 50       36 if (blessed($path)) {
506 10         32 return ($path->sse, $self->start, $self->end, $self->graph);
507             } else {
508 0           return ('(undefined path)', $self->start, $self->end, $self->graph);
509             }
510             }
511              
512              
513             =item C<< explain >>
514              
515             Returns a string serialization of the plan appropriate for display on the
516             command line.
517              
518             =cut
519              
520             sub explain {
521 0     0 1   my $self = shift;
522 0           my $s = shift;
523 0           my $count = shift;
524 0           my $indent = $s x $count;
525 0           my $type = $self->plan_node_name;
526 0           my $string = sprintf("%s%s (0x%x)\n", $indent, $type, refaddr($self));
527 0           $string .= $self->start->explain($s, $count+1);
528 0           my $path = $self->path;
529 0 0         if ($type eq 'NegatedPropertySet') {
    0          
530 0           $string .= "${indent}${s}(\n";
531 0           foreach my $iri (@$path) {
532 0           $string .= "${indent}${s}${s}" . $iri->as_string . "\n";
533             }
534 0           $string .= "${indent}${s})\n";
535             } elsif ($type =~ /^ZeroOrMorePath|OneOrMorePath|ZeroLengthPath$/) {
536 0           $string .= "${indent}${s}${s}" . $self->_path_as_string($path) . "\n";
537             } else {
538 0           throw RDF::Query::Error;
539             }
540 0           $string .= $self->end->explain($s, $count+1);
541             # $string .= $self->pattern->explain( $s, $count+1 );
542 0           return $string;
543             }
544              
545             sub _path_as_string {
546 0     0     my $self = shift;
547 0           my $path = shift;
548 0 0         if (blessed($path)) {
549 0           return $path->as_string;
550             }
551            
552 0           my ($op, @nodes) = @$path;
553 0 0         if ($op eq '/') {
    0          
554 0           return join('/', map { $self->_path_as_string($_) } @nodes);
  0            
555             } elsif ($op =~ /^[?+*]$/) {
556 0           return '(' . $self->_path_as_string($nodes[0]) . ')' . $op;
557             } else {
558 0           throw RDF::Query::Error -text => "Can't serialize path '$op' in plan explanation";
559             }
560             }
561              
562             1;
563              
564             __END__
565              
566             =back
567              
568             =head1 AUTHOR
569              
570             Gregory Todd Williams <gwilliams@cpan.org>
571              
572             =cut