File Coverage

blib/lib/RDF/Query/Plan.pm
Criterion Covered Total %
statement 593 846 70.0
branch 169 312 54.1
condition 48 88 54.5
subroutine 72 81 88.8
pod 16 16 100.0
total 898 1343 66.8


line stmt bran cond sub pod time code
1             # RDF::Query::Plan
2             # -----------------------------------------------------------------------------
3              
4             =head1 NAME
5              
6             RDF::Query::Plan - Executable query plan nodes.
7              
8             =head1 VERSION
9              
10             This document describes RDF::Query::Plan version 2.918.
11              
12             =head1 METHODS
13              
14             =over 4
15              
16             =cut
17              
18             package RDF::Query::Plan;
19              
20 35     35   149 use strict;
  35         52  
  35         924  
21 35     35   126 use warnings;
  35         55  
  35         722  
22 35     35   123 use Data::Dumper;
  35         53  
  35         1410  
23 35     35   135 use List::Util qw(reduce);
  35         60  
  35         1662  
24 35     35   145 use Scalar::Util qw(blessed reftype refaddr);
  35         48  
  35         1492  
25 35     35   132 use RDF::Query::Error qw(:try);
  35         56  
  35         189  
26 35     35   15215 use RDF::Query::BGPOptimizer;
  35         70  
  35         959  
27              
28 35     35   13927 use RDF::Query::Plan::Aggregate;
  35         84  
  35         963  
29 35     35   13591 use RDF::Query::Plan::BasicGraphPattern;
  35         77  
  35         825  
30 35     35   12434 use RDF::Query::Plan::Constant;
  35         70  
  35         896  
31 35     35   12613 use RDF::Query::Plan::Construct;
  35         73  
  35         904  
32 35     35   12274 use RDF::Query::Plan::Distinct;
  35         67  
  35         1049  
33 35     35   12546 use RDF::Query::Plan::Filter;
  35         69  
  35         935  
34 35     35   13951 use RDF::Query::Plan::Join::NestedLoop;
  35         72  
  35         1072  
35 35     35   14161 use RDF::Query::Plan::Join::PushDownNestedLoop;
  35         60  
  35         1192  
36 35     35   12442 use RDF::Query::Plan::Limit;
  35         64  
  35         1133  
37 35     35   12503 use RDF::Query::Plan::Offset;
  35         57  
  35         1153  
38 35     35   12647 use RDF::Query::Plan::Project;
  35         63  
  35         1246  
39 35     35   12572 use RDF::Query::Plan::Extend;
  35         67  
  35         1316  
40 35     35   12870 use RDF::Query::Plan::Quad;
  35         72  
  35         1400  
41 35     35   13459 use RDF::Query::Plan::Service;
  35         71  
  35         1451  
42 35     35   12557 use RDF::Query::Plan::Sort;
  35         62  
  35         1592  
43 35     35   12922 use RDF::Query::Plan::ComputedStatement;
  35         69  
  35         1488  
44 35     35   13074 use RDF::Query::Plan::ThresholdUnion;
  35         70  
  35         1492  
45 35     35   12366 use RDF::Query::Plan::Union;
  35         66  
  35         1528  
46 35     35   12682 use RDF::Query::Plan::SubSelect;
  35         68  
  35         1500  
47 35     35   12425 use RDF::Query::Plan::Iterator;
  35         60  
  35         1559  
48 35     35   12672 use RDF::Query::Plan::Load;
  35         62  
  35         1494  
49 35     35   12256 use RDF::Query::Plan::Clear;
  35         69  
  35         1612  
50 35     35   12861 use RDF::Query::Plan::Update;
  35         69  
  35         1670  
51 35     35   13422 use RDF::Query::Plan::Minus;
  35         69  
  35         1816  
52 35     35   12671 use RDF::Query::Plan::Sequence;
  35         62  
  35         1742  
53 35     35   13257 use RDF::Query::Plan::Path;
  35         70  
  35         2004  
54 35     35   12982 use RDF::Query::Plan::NamedGraph;
  35         73  
  35         1661  
55 35     35   12626 use RDF::Query::Plan::Copy;
  35         71  
  35         1775  
56 35     35   12314 use RDF::Query::Plan::Move;
  35         75  
  35         1840  
57              
58 35     35   191 use RDF::Trine::Statement;
  35         51  
  35         907  
59 35     35   150 use RDF::Trine::Statement::Quad;
  35         51  
  35         899  
60              
61 35     35   133 use constant READY => 0x01;
  35         44  
  35         2897  
62 35     35   153 use constant OPEN => 0x02;
  35         41  
  35         1797  
63 35     35   143 use constant CLOSED => 0x04;
  35         41  
  35         2777  
64              
65             ######################################################################
66              
67             our ($VERSION, %PLAN_CLASSES);
68             BEGIN {
69 35     35   67 $VERSION = '2.918';
70 35         14401 %PLAN_CLASSES = (
71             service => 'RDF::Query::Plan::Service',
72             );
73             }
74              
75             ######################################################################
76              
77             =item C<< new >>
78              
79             =cut
80              
81             sub new {
82 556     556 1 566 my $class = shift;
83 556         742 my @args = @_;
84 556         3509 return bless( [ { __state => $class->READY }, @args ], $class );
85             }
86              
87             =item C<< execute ( $execution_context ) >>
88              
89             =cut
90              
91             sub execute ($);
92              
93             =item C<< next >>
94              
95             =cut
96              
97             sub next;
98              
99             =item C<< get_all >>
100              
101             Returns all remaining rows.
102              
103             =cut
104              
105             sub get_all {
106 12     12 1 23 my $self = shift;
107 12 50       31 unless ($self->state == $self->OPEN) {
108 0         0 throw RDF::Query::Error::ExecutionError -text => "get_all can't be called on an unopen plan";
109             }
110 12         20 my @rows;
111 12         48 while (my $row = $self->next) {
112 34         102 push(@rows, $row);
113             }
114 12         48 return @rows;
115             }
116              
117             =item C<< close >>
118              
119             =cut
120              
121             sub close {
122 590     590 1 557 my $self = shift;
123 590         819 $self->state( CLOSED );
124             }
125              
126             =item C<< state ( [ $state ] ) >>
127              
128             Returns the current state of the plan (either READY, OPEN, or CLOSED).
129             If C<< $state >> is provided, updates the plan to a new state.
130              
131             =cut
132              
133             sub state {
134 5079     5079 1 3914 my $self = shift;
135 5079 100       7185 if (scalar(@_)) {
136 1190         1276 $self->[0]{__state} = shift;
137             }
138 5079         18928 return $self->[0]{__state};
139             }
140              
141             =item C<< logging_keys >>
142              
143             =cut
144              
145             sub logging_keys {
146 0     0 1 0 my $self = shift;
147 0   0     0 return $self->[0]{logging_keys} || {};
148             }
149              
150             =item C<< explain >>
151              
152             Returns a string serialization of the query plan appropriate for display
153             on the command line.
154              
155             =cut
156              
157             sub explain {
158 0     0 1 0 my $self = shift;
159             # warn 'Explaining query plan: ' . $self->serialize();
160 0         0 my ($s, $count) = (' ', 0);
161 0 0       0 if (@_) {
162 0         0 $s = shift;
163 0         0 $count = shift;
164             }
165 0         0 my $indent = '' . ($s x $count);
166 0         0 my $type = $self->plan_node_name;
167 0         0 my $string = sprintf("%s%s (0x%x)\n", $indent, $type, refaddr($self));
168 0         0 foreach my $p ($self->plan_node_data) {
169 0 0       0 if (blessed($p)) {
    0          
170 0 0       0 if ($p->isa('RDF::Trine::Statement::Quad')) {
    0          
171 0 0       0 $string .= "${indent}${s}" . join(' ', map { ($_->isa('RDF::Trine::Node::Nil')) ? "(nil)" : $_->as_sparql } $p->nodes) . "\n";
  0         0  
172             } elsif ($p->isa('RDF::Trine::Node::Nil')) {
173 0         0 $string .= "${indent}${s}(nil)\n";
174             } else {
175 0         0 $string .= $p->explain( $s, $count+1 );
176             }
177             } elsif (ref($p)) {
178 0         0 $string .= "${indent}${s}" . Dumper($p);
179 0         0 Carp::cluck 'unexpected non-blessed ref in RDF::Query::Plan->explain: ' . Dumper($p);
180             } else {
181 35     35   177 no warnings 'uninitialized';
  35         49  
  35         13899  
182 0         0 $string .= "${indent}${s}$p\n";
183             }
184             }
185 0         0 return $string;
186             }
187              
188             =item C<< sse >>
189              
190             =cut
191              
192             sub sse {
193 188     188 1 285 my $self = shift;
194 188   100     599 my $context = shift || {};
195 188   100     536 my $indent = shift || '';
196 188         168 my $more = ' ';
197 188         1140 my @proto = $self->plan_prototype;
198 188         437 my @data = $self->plan_node_data;
199 188         487 my $name = $self->plan_node_name;
200            
201 188         175 my @args;
202 188         222 my $list = \@data;
203 188         327 foreach my $i (0 .. $#proto) {
204 583         4493 my $p = $proto[ $i ];
205 583         946 push(@args, $self->_sse( $context, $indent, $more, $p, $list ));
206             }
207 188 100       744 return "(${name} " . join(' ', map { defined($_) ? $_ : '()' } @args) . ")";
  583         1758  
208             }
209              
210             sub _sse {
211 687     687   779 my $self = shift;
212 687         455 my $context = shift;
213 687         482 my $indent = shift;
214 687         584 my $more = shift;
215 687         489 my $p = shift;
216 687         480 my $list = shift;
217 687 100       1659 if ($p =~ m/^[PQTNWEJVqibswu]$/) {
    50          
    100          
    50          
    0          
218 630         543 my $v = shift(@$list);
219 630         987 return $self->_sse_atom($context, $indent, $more, $p, $v);
220             } elsif ($p eq 'A') {
221 0         0 my $v = shift(@$list);
222 0 0       0 if (blessed($v)) {
223 0         0 return $v->sse( $context, $indent );
224             } else {
225 0         0 return '()';
226             }
227             } elsif (substr($p, 0, 1) eq '\\') {
228 26         48 my $rest = substr($p, 1);
229 26         48 my $v = shift(@$list);
230 26         36 my @args;
231 26         88 while (@$v) {
232 30         165 push(@args, $self->_sse( $context, $indent, $more, $rest, $v ));
233             }
234 26         531 return '(' . join(' ', @args) . ')';
235             } elsif (substr($p, 0, 1) eq '*') {
236 31         56 my $rest = substr($p, 1);
237 31         37 my @args;
238 31         88 while (@$list) {
239 74         2132 push(@args, $self->_sse( $context, $indent, $more, $rest, $list ));
240             }
241 35     35   171 no warnings 'uninitialized';
  35         51  
  35         7546  
242 31         1096 return join("\n${indent}${more}", '', @args);
243             } elsif ($p =~ m/^[PQTNWEJVqibswu\\*]{2,}$/) {
244 0         0 my @args;
245 0         0 foreach my $p2 (split(//, $p)) {
246 0         0 my $v = shift(@$list);
247 0         0 push(@args, $self->_sse($context, $indent, $more, $p2, [$v]));
248             }
249 0         0 return '(' . join(' ', @args) . ')';
250             } else {
251 0         0 Carp::confess "unrecognized plan node prototype '$p'";
252             }
253             }
254              
255             sub _sse_atom {
256 630     630   479 my $self = shift;
257 630   50     852 my $context = shift || {};
258 630         497 my $indent = shift;
259 630         448 my $more = shift;
260 630         477 my $p = shift;
261 630         443 my $v = shift;
262 35     35   163 no warnings 'uninitialized';
  35         63  
  35         205963  
263            
264 630   100     1489 my $ns = $context->{ namespaces } || {};
265 630         940 my %ns = %$ns;
266            
267 630 100       2795 if ($p eq 's') {
    50          
    50          
    100          
    50          
    50          
    100          
    50          
268 8         10 for ($v) {
269 8         10 s/\\/\\\\/g;
270 8         9 s/"/\\"/g;
271 8         9 s/\n/\\n/g;
272 8         10 s/\t/\\t/g;
273             }
274 8         25 return qq["$v"];
275             } elsif ($p eq 'w') {
276 0         0 return $v;
277             } elsif ($p eq 'u') {
278 0         0 return qq[<$v>];
279             } elsif ($p eq 'i') {
280 3         9 return $v;
281             } elsif ($p eq 'b') {
282 0         0 return $v;
283             } elsif ($p eq 'W') {
284 0 0       0 if (blessed($v)) {
285 0         0 return $v->sse( { namespaces => \%ns }, "${indent}${more}" );
286             } else {
287 0         0 return $v;
288             }
289             } elsif ($p =~ m/^[PNETV]$/) {
290 589 100       1247 if (blessed($v)) {
291            
292 580 50       1705 Carp::cluck unless ($v->can('sse'));
293 580         1923 return $v->sse( { namespaces => \%ns }, "${indent}${more}" );
294             } else {
295 9         22 return '()';
296             }
297             } elsif ($p eq 'J') {
298 30 100       134 if ($v->isa('RDF::Query::Node::Variable')) {
299 2         6 return $v->name;
300             } else {
301 28         156 return $v->sse( { namespaces => \%ns }, "${indent}${more}" );
302             }
303             }
304             }
305              
306             =item C<< serialize >>
307              
308             Return a serialization of the query plan.
309              
310             =cut
311              
312             sub serialize {
313 0     0 1 0 my $self = shift;
314            
315             }
316              
317             =item C<< delegate >>
318              
319             Returns the delegate object if available.
320              
321             =cut
322              
323             sub delegate {
324 979     979 1 1123 my $self = shift;
325 979         3578 return $self->[0]{delegate};
326             }
327              
328             =item C<< referenced_variables >>
329              
330             Returns a list of variable names that are referenced by this plan.
331              
332             =cut
333              
334             sub referenced_variables {
335 366     366 1 348 my $self = shift;
336 366         441 my $refs = $self->[0]{referenced_variables};
337 366         281 return @{ $refs };
  366         1123  
338             }
339              
340             =item C<< as_iterator ( $context ) >>
341              
342             Returns an RDF::Trine::Iterator object for the current (already executed) plan.
343              
344             =cut
345              
346             sub as_iterator {
347 153     153 1 213 my $self = shift;
348 153         195 my $context = shift;
349 153         533 my $vars = $context->requested_variables;
350 153     321   1106 my $stream = RDF::Trine::Iterator::Bindings->new( sub { $self->next }, $vars, distinct => $self->distinct, sorted_by => $self->ordered );
  321         154528  
351 153         5814 return $stream;
352             }
353              
354             =item C<< is_update >>
355              
356             Returns true if the plan represents an update operation.
357              
358             =cut
359              
360             sub is_update {
361 0     0 1 0 return 0;
362             }
363              
364             =item C<< label ( $label => $value ) >>
365              
366             Sets the named C<< $label >> to C<< $value >> for this plan object.
367             If no C<< $value >> is given, returns the current label value, or undef if none
368             exists.
369              
370             =cut
371              
372             sub label {
373 779     779 1 655 my $self = shift;
374 779         650 my $label = shift;
375 779 50       1166 if (@_) {
376 779         605 my $value = shift;
377 779         1442 $self->[0]{labels}{ $label } = $value;
378             }
379 779         1063 return $self->[0]{labels}{ $label };
380             }
381              
382             =item C<< graph_labels >>
383              
384             =cut
385              
386             sub graph_labels {
387 0     0 1 0 my $self = shift;
388 0         0 my @labels;
389 0 0       0 foreach my $k (keys %{ $self->[0]{labels} || {} }) {
  0         0  
390 0 0       0 next if ($k eq 'algebra');
391 0         0 my $v = $self->label( $k );
392 0         0 local($Data::Dumper::Indent) = 0;
393 0         0 my $l = Data::Dumper->Dump([$v], [$k]);
394 0         0 push(@labels, $l);
395             }
396 0         0 my $label = join(", ", @labels);
397 0         0 return ' ' . $label;
398             }
399              
400             sub DESTROY {
401 546     546   77422 my $self = shift;
402 546 100       876 if ($self->state == OPEN) {
403 205         707 $self->close;
404             }
405             }
406              
407             ################################################################################
408              
409             =item C<< generate_plans ( $algebra, $execution_context, %args ) >>
410              
411             Returns a list of equivalent query plan objects for the given algebra object.
412              
413             =cut
414              
415             sub generate_plans {
416 773     773 1 854 my $self = shift;
417 773   33     2322 my $class = ref($self) || $self;
418 773         718 my $algebra = shift;
419 773         636 my $context = shift;
420 773   100     1849 my $config = $context->options || {};
421            
422 773         1329 my %args = @_;
423 773   66     3245 my $active_graph = $args{ active_graph } || RDF::Trine::Node::Nil->new();
424            
425 773         9777 my $l = Log::Log4perl->get_logger("rdf.query.plan");
426 773 50 33     21403 unless (blessed($algebra) and $algebra->isa('RDF::Query::Algebra')) {
427 0         0 throw RDF::Query::Error::MethodInvocationError (-text => "Cannot generate an execution plan with a non-algebra object $algebra");
428             }
429            
430 773         3135 $l->trace("generating query plan for " . $algebra->sse({ indent => ' ' }, ''));
431            
432             ############################################################################
433             ### Optimize simple COUNT(*) aggregates over BGPs
434 773 100       16473 if ($algebra->isa('RDF::Query::Algebra::Extend')) {
435 24         76 my $agg = $algebra->pattern;
436 24 100       109 if ($agg->isa('RDF::Query::Algebra::Aggregate')) {
437 15         43 my @group = $agg->groupby;
438 15 100       38 if (scalar(@group) == 0) {
439 9         27 my @ops = $agg->ops;
440 9 50 66     77 if (scalar(@ops) == 1 and $ops[0][0] eq 'COUNT(*)') {
441 0         0 my $ggp = $agg->pattern;
442 0 0       0 if ($ggp->isa('RDF::Query::Algebra::GroupGraphPattern')) {
443 0         0 my @bgp = $ggp->patterns;
444 0 0 0     0 if (scalar(@bgp) == 1 and ($bgp[0]->isa('RDF::Query::Algebra::BasicGraphPattern'))) {
445 0         0 my $bgp = $bgp[0];
446 0         0 my @triples = $bgp->triples;
447 0 0       0 if (scalar(@triples) == 1) {
448 0         0 $l->debug("Optimizing for COUNT(*) on 1-triple BGP: " . $bgp->sse({ indent => ' ' }, ''));
449 0         0 my $vars = $algebra->vars;
450 0         0 my $alias = $vars->[0];
451 0         0 my $name = $alias->name;
452 0         0 my $done = 0;
453 0         0 my $model = $context->model;
454             my $code = sub {
455 0 0   0   0 return if ($done);
456 0         0 $done = 1;
457             #warn Dumper(\@triples); # XXX
458 0         0 my $count = $model->count_statements( $triples[0]->nodes );
459 0         0 my $lit = RDF::Query::Node::Literal->new($count, undef, 'http://www.w3.org/2001/XMLSchema#integer');
460 0         0 my $vb = RDF::Query::VariableBindings->new( {
461             $name => $lit,
462             'COUNT(*)' => $lit, # this has to be kept around in case a HAVING clause needs it without the alias $name
463             } );
464 0         0 };
465 0         0 my $iter = RDF::Trine::Iterator::Bindings->new( $code, [] );
466 0         0 return RDF::Query::Plan::Iterator->new( $iter );
467             }
468             }
469             }
470             }
471             }
472             }
473             }
474             ############################################################################
475            
476 773         769 my ($project);
477 773         935 my $constant = delete $args{ constants };
478            
479 773 100 100     4462 if ($algebra->isa('RDF::Query::Algebra::Sort') or not($algebra->is_solution_modifier)) {
480             # projection has to happen *after* sorting, since a sort expr might reference a variable that we project away
481 594         690 $project = delete $args{ project };
482             }
483            
484 773         755 my @return_plans;
485 773         961 my $aclass = ref($algebra);
486 773         4117 my ($type) = ($aclass =~ m<::(\w+)$>);
487 773 100 100     6174 if ($type eq 'Aggregate') {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    100          
    50          
    100          
    100          
    50          
    100          
    100          
    100          
    100          
    100          
    50          
    50          
    50          
    0          
    0          
    0          
    0          
    0          
488 16         42 my @base = $self->generate_plans( $algebra->pattern, $context, %args );
489 16         52 my @groups = $algebra->groupby;
490 16         22 my @ops;
491 16         40 foreach my $o ($algebra->ops) {
492 18         43 my ($alias, $op, $opts, @cols) = @$o;
493 18         40 push(@ops, [ $alias, $op, $opts, @cols ]);
494             }
495 16         44 my @plans = map { RDF::Query::Plan::Aggregate->new( $_, \@groups, expressions => \@ops ) } @base;
  16         84  
496 16         33 push(@return_plans, @plans);
497             } elsif ($type eq 'Construct') {
498 4         11 my $triples = $algebra->triples;
499 4         11 my @base = $self->generate_plans( $algebra->pattern, $context, %args );
500 4         9 my @plans = map { RDF::Query::Plan::Construct->new( $_, $triples ) } @base;
  4         33  
501 4         8 push(@return_plans, @plans);
502             } elsif ($type eq 'Distinct') {
503 10         45 my @base = $self->generate_plans( $algebra->pattern, $context, %args );
504 10         19 my @plans = map { RDF::Query::Plan::Distinct->new( $_ ) } @base;
  10         76  
505 10         25 push(@return_plans, @plans);
506             } elsif ($type eq 'Filter') {
507 37         123 my @base = $self->generate_plans( $algebra->pattern, $context, %args, active_graph => $active_graph );
508 37         128 my $expr = $algebra->expr;
509 37         79 my @plans = map { RDF::Query::Plan::Filter->new( $expr, $_, $active_graph ) } @base;
  37         234  
510 37         60 push(@return_plans, @plans);
511             } elsif ($type eq 'BasicGraphPattern') {
512             my @triples = map {
513 146         420 ($args{ prevent_distinguishing_bnodes })
514 277 100       2324 ? $_
515             : $_->distinguish_bnode_variables
516             } $algebra->triples;
517 146         1782 my @normal_triples;
518             my @csg_triples;
519 146         272 foreach my $t (@triples) {
520 277 100       766 if (my @csg_plans = $self->_csg_plans( $context, $t )) {
521 1         5 push(@csg_triples, $t);
522             } else {
523 276         564 my @nodes = $t->nodes;
524 276         1729 $t = RDF::Query::Algebra::Quad->new( @nodes[ 0..2 ], $active_graph );
525             # if (my $g = $args{ named_graph }) {
526             # my @nodes = $t->nodes;
527             # $t = RDF::Query::Algebra::Quad->new( @nodes[0..2], $g );
528             # }
529 276         4149 push(@normal_triples, $t);
530             }
531             }
532            
533 146         190 my @plans;
534 146 50       446 if (scalar(@normal_triples) == 0) {
    100          
535 0         0 my $v = RDF::Query::VariableBindings->new( {} );
536 0         0 my $plan = RDF::Query::Plan::Constant->new( $v );
537 0         0 push(@plans, $plan);
538             } elsif (scalar(@normal_triples) == 1) {
539 70         1135 push(@plans, $self->generate_plans( @normal_triples, $context, %args ));
540             } else {
541 76         528 my $plan = RDF::Query::Plan::BasicGraphPattern->new( @normal_triples );
542 76         129 push(@plans, $plan);
543             }
544            
545 146 100       345 if (@csg_triples) {
546 1         2 my @csg_plans;
547 1         2 foreach my $t (@csg_triples) {
548 1         4 push(@csg_plans, [ $self->generate_plans( $t, $context, %args ) ]);
549             }
550 1         10 my @join_types = RDF::Query::Plan::Join->join_classes( $config );
551 1         3 while (my $cps = shift(@csg_plans)) {
552 1         2 my @temp_plans = @plans;
553 1         1 @plans = ();
554 1         2 foreach my $p (@temp_plans) {
555 1         3 foreach my $cp (@$cps) {
556 1         1 foreach my $join_type (@join_types) {
557 2         16 my $plan = $join_type->new( $p, $cp, 0, {} );
558 2         5 push(@plans, $plan);
559             }
560             }
561             }
562             }
563 1         3 push(@return_plans, @plans);
564             } else {
565 145         454 push(@return_plans, @plans);
566             }
567            
568             } elsif ($type eq 'GroupGraphPattern') {
569 205         572 my @input = $algebra->patterns();
570 205         276 my @patterns;
571 205         595 while (my $a = shift(@input)) {
572 201 50       963 if ($a->isa('RDF::Query::Algebra::Service')) {
573 0 0 0     0 if (scalar(@input) and $input[0]->isa('RDF::Query::Algebra::Service') and $a->endpoint->value eq $input[0]->endpoint->value) {
      0        
574 0         0 my $b = shift(@input);
575 0 0       0 if ($a->silent == $b->silent) {
576 0         0 my $p = RDF::Query::Algebra::GroupGraphPattern->new( map { $_->pattern } ($a, $b) );
  0         0  
577 0         0 my $s = RDF::Query::Algebra::Service->new( $a->endpoint, $p, $a->silent );
578 0         0 push(@patterns, $s);
579 0         0 next;
580             }
581             }
582             }
583 201         552 push(@patterns, $a);
584             }
585            
586 205         232 my @plans;
587 205 100       605 if (scalar(@patterns) == 0) {
    100          
588 18         115 my $v = RDF::Query::VariableBindings->new( {} );
589 18         120 my $plan = RDF::Query::Plan::Constant->new( $v );
590 18         36 push(@plans, $plan);
591             } elsif (scalar(@patterns) == 1) {
592 174         1673 push(@plans, $self->generate_plans( @patterns, $context, %args ));
593             } else {
594 13         77 push(@plans, map { $_->[0] } $self->_join_plans( $context, \@patterns, %args, method => 'patterns' ));
  13         39  
595             }
596            
597 205         376 push(@return_plans, @plans);
598             } elsif ($type eq 'Limit') {
599 8         28 my @base = $self->generate_plans( $algebra->pattern, $context, %args );
600 8         19 my @plans = map { RDF::Query::Plan::Limit->new( $algebra->limit, $_ ) } @base;
  8         31  
601 8         17 push(@return_plans, @plans);
602             } elsif ($type eq 'NamedGraph') {
603 14         24 my @plans;
604 14 100       38 if ($algebra->graph->isa('RDF::Query::Node::Resource')) {
605 4         15 @plans = $self->generate_plans( $algebra->pattern, $context, %args, active_graph => $algebra->graph );
606             } else {
607 10         33 @plans = map { RDF::Query::Plan::NamedGraph->new( $algebra->graph, $_ ) } $self->generate_plans( $algebra->pattern, $context, %args, active_graph => $algebra->graph );
  10         41  
608             }
609 14         31 push(@return_plans, @plans);
610             } elsif ($type eq 'Offset') {
611 3         12 my @base = $self->generate_plans( $algebra->pattern, $context, %args );
612 3         5 my @plans = map { RDF::Query::Plan::Offset->new( $algebra->offset, $_ ) } @base;
  3         10  
613 3         4 push(@return_plans, @plans);
614             } elsif ($type eq 'Optional') {
615             # just like a BGP or GGP, but we have to pass the optional flag to the join constructor
616 11         38 my @patterns = ($algebra->pattern, $algebra->optional);
617 11         26 my @base_plans = map { [ $self->generate_plans( $_, $context, %args ) ] } @patterns;
  22         132  
618 11         83 my @join_types = RDF::Query::Plan::Join->join_classes( $config );
619             # XXX this is currently only considering left-deep trees. maybe it should produce all trees?
620 11         17 my @plans;
621 11         13 my $base_a = shift(@base_plans);
622 11         14 my $base_b = shift(@base_plans);
623 11         16 foreach my $i (0 .. $#{ $base_a }) {
  11         27  
624 11         14 foreach my $j (0 .. $#{ $base_b }) {
  11         27  
625 11         19 my $a = $base_a->[ $i ];
626 11         18 my $b = $base_b->[ $j ];
627 11         15 foreach my $join_type (@join_types) {
628             try {
629 22     20   570 my $plan = $join_type->new( $a, $b, 1, {} );
630 11         31 push( @plans, $plan );
631       10     } catch RDF::Query::Error::MethodInvocationError with {
632             # my $e = shift;
633             # warn "caught MethodInvocationError: " . Dumper($e);
634 22         282 };
635             }
636             }
637             }
638 11         183 push(@return_plans, @plans);
639             } elsif ($type eq 'Minus') {
640 0         0 my @patterns = ($algebra->pattern, $algebra->minus);
641 0         0 my @base_plans = map { [ $self->generate_plans( $_, $context, %args ) ] } @patterns;
  0         0  
642 0         0 my @plans;
643 0         0 my $base_a = shift(@base_plans);
644 0         0 my $base_b = shift(@base_plans);
645 0         0 foreach my $i (0 .. $#{ $base_a }) {
  0         0  
646 0         0 foreach my $j (0 .. $#{ $base_b }) {
  0         0  
647 0         0 my $a = $base_a->[ $i ];
648 0         0 my $b = $base_b->[ $j ];
649 0         0 my $plan = RDF::Query::Plan::Minus->new( $a, $b );
650 0         0 push( @plans, $plan );
651             }
652             }
653 0         0 push(@return_plans, @plans);
654             } elsif ($type eq 'Project') {
655 134         369 my $pattern = $algebra->pattern;
656 134         321 my $vars = $algebra->vars;
657 134         1276 my @base = $self->generate_plans( $pattern, $context, %args );
658            
659 134 100       321 if ($constant) {
660             # if there's constant data to be joined, we better do it now in case
661             # the project gets rid of variables needed for the join
662 3         8 my @plans = splice( @base );
663 3         12 @base = $self->_add_constant_join( $context, $constant, @plans );
664 3         5 $constant = undef;
665             }
666            
667 134         160 my @plans;
668 134         202 foreach my $plan (@base) {
669 138         1388 push(@return_plans, RDF::Query::Plan::Project->new( $plan, $vars ));
670             }
671 134         227 push(@return_plans, @plans);
672             } elsif ($type eq 'Extend') {
673 24         65 my $pattern = $algebra->pattern;
674 24         65 my $vars = $algebra->vars;
675 24         220 my @base = $self->generate_plans( $pattern, $context, %args );
676 24         39 my @plans;
677 24         42 foreach my $plan (@base) {
678 24         156 push(@plans, RDF::Query::Plan::Extend->new( $plan, $vars ));
679             }
680 24         45 push(@return_plans, @plans);
681             } elsif ($type eq 'Service') {
682 0         0 my $pattern = $algebra->pattern;
683 0         0 my @base = $self->generate_plans( $pattern, $context, %args );
684 0         0 my @plans;
685 0         0 foreach my $plan (@base) {
686             my $sparqlcb = sub {
687 0     0   0 my $row = shift;
688 0         0 my $p = $pattern;
689 0 0       0 if ($row) {
690 0         0 $p = $p->bind_variables( $row );
691             }
692 0         0 my $ns = $context->ns;
693 0         0 my $pstr = $p->as_sparql({namespaces => $ns}, '');
694 0 0       0 unless (substr($pstr, 0, 1) eq '{') {
695 0         0 $pstr = "{ $pstr }";
696             }
697             my $sparql = join("\n",
698 0 0       0 (map { sprintf("PREFIX %s: <%s>", ($_ eq '__DEFAULT__' ? '' : $_), $ns->{$_}) } (keys %$ns)),
  0         0  
699             sprintf("SELECT * WHERE %s", $pstr)
700             );
701 0         0 return $sparql;
702 0         0 };
703            
704             # unless ($algebra->endpoint->can('uri_value')) {
705             # throw RDF::Query::Error::UnimplementedError (-text => "Support for variable-endpoint SERVICE blocks is not implemented");
706             # }
707            
708 0 0       0 if (my $ggp = $algebra->lhs) {
709 0         0 my @lhs_base = $self->generate_plans( $ggp, $context, %args );
710 0         0 foreach my $lhs_plan (@lhs_base) {
711 0         0 my $splan = RDF::Query::Plan::Service->new( $algebra->endpoint, $plan, $algebra->silent, $sparqlcb, $lhs_plan );
712 0         0 push(@plans, $splan);
713             }
714             } else {
715 0         0 push(@plans, $PLAN_CLASSES{'service'}->new( $algebra->endpoint, $plan, $algebra->silent, $sparqlcb ));
716             }
717             }
718 0         0 push(@return_plans, @plans);
719             } elsif ($type eq 'SubSelect') {
720 1         5 my $query = $algebra->query;
721 1         4 my $model = $context->model;
722 1         4 my %pargs = %args;
723 1         3 my $ag = $args{ active_graph };
724 1 50 33     6 if (blessed($ag) and $ag->isa('RDF::Query::Node::Variable')) {
725 0         0 my %vars = map { $_ => 1 } $query->pattern->referenced_variables;
  0         0  
726 0 0       0 if ($vars{ $ag->name }) {
727 0         0 my $new_ag = RDF::Query::Node::Variable->new();
728 0         0 my ($pattern) = $query->pattern;
729 0         0 my $new_pattern = $pattern->bind_variables( { $ag->name => $new_ag } );
730 0         0 my $apattern = RDF::Query::Algebra::Extend->new(
731             $new_pattern,
732             [
733             RDF::Query::Expression::Alias->new( 'alias', $ag, $new_ag )
734             ]
735             );
736 0         0 $query->{parsed}{triples} = [$apattern];
737             }
738 0         0 my ($plan) = $self->generate_plans( $query->pattern, $context, %args );
739 0         0 push(@return_plans, RDF::Query::Plan::SubSelect->new( $query, $plan ));
740             } else {
741 1         777 my ($plan) = $query->prepare( $context->model, planner_args => \%pargs );
742 1         26 push(@return_plans, RDF::Query::Plan::SubSelect->new( $query, $plan ));
743             }
744             } elsif ($type eq 'Sort') {
745 12         47 my @base = $self->generate_plans( $algebra->pattern, $context, %args );
746 12         49 my @order = $algebra->orderby;
747 12         16 my @neworder;
748 12         25 foreach my $o (@order) {
749 12         25 my ($dirname, $expr) = @$o;
750 12 100       35 my $dir = ($dirname eq 'ASC') ? 0 : 1;
751 12         33 push(@neworder, [$expr, $dir]);
752             }
753 12         18 my @plans = map { RDF::Query::Plan::Sort->new( $_, @neworder ) } @base;
  12         89  
754 12         22 push(@return_plans, @plans);
755             } elsif ($type eq 'Triple' or $type eq 'Quad') {
756 133         161 my $st;
757 133 100       257 if ($args{ prevent_distinguishing_bnodes }) {
758 40         42 $st = $algebra;
759             } else {
760 93         336 $st = $algebra->distinguish_bnode_variables;
761             }
762 133         1438 my $pred = $st->predicate;
763 133         1115 my @nodes = $st->nodes;
764            
765 133 100       606 if (my @csg_plans = $self->_csg_plans( $context, $st )) {
    100          
766 1         4 push(@return_plans, @csg_plans);
767             } elsif ($type eq 'Triple') {
768 24         78 my $plan = RDF::Query::Plan::Quad->new( @nodes[0..2], $active_graph, { sparql => $algebra->as_sparql, bf => $algebra->bf } );
769 24         80 push(@return_plans, $plan);
770             } else {
771 108 50       461 my $plan = (scalar(@nodes) == 4)
772             ? RDF::Query::Plan::Quad->new( @nodes, { sparql => $algebra->as_sparql } )
773             : RDF::Query::Plan::Quad->new( @nodes, RDF::Trine::Node::Nil->new(), { sparql => $algebra->as_sparql, bf => $algebra->bf } );
774 108         411 push(@return_plans, $plan);
775             }
776             } elsif ($type eq 'Path') {
777 5         21 my @plans = $self->_path_plans( $algebra, $context, %args );
778 5         12 push(@return_plans, @plans);
779             } elsif ($type eq 'Union') {
780 2         10 my @plans = map { [ $self->generate_plans( $_, $context, %args ) ] } $algebra->patterns;
  4         38  
781 2         3 my $plan = RDF::Query::Plan::Union->new( map { $_->[0] } @plans );
  4         14  
782 2         4 push(@return_plans, $plan);
783             } elsif ($type eq 'Sequence') {
784 0         0 my @pat = $algebra->patterns;
785 0 0       0 if (@pat) {
786 0         0 my @plans = map { [ $self->generate_plans( $_, $context, %args ) ] } @pat;
  0         0  
787 0         0 my $plan = RDF::Query::Plan::Sequence->new( map { $_->[0] } @plans );
  0         0  
788 0         0 push(@return_plans, $plan);
789             } else {
790 0     0   0 my $stream = RDF::Trine::Iterator::Bindings->new( sub {} );
791 0         0 push(@return_plans, $stream);
792             }
793             } elsif ($type eq 'Load') {
794 0         0 push(@return_plans, RDF::Query::Plan::Load->new( $algebra->url, $algebra->graph ));
795             } elsif ($type eq 'Update') {
796 8   100     24 my $ds = $algebra->dataset || {};
797 8   100     36 my $default = $ds->{'default'} || [];
798 8   50     35 my $named = $ds->{'named'} || {};
799 8         14 my $dcount = scalar(@$default);
800 8         7 my $ncount = scalar(@{[ keys %$named ]});
  8         20  
801             # warn 'Update dataset: ' . Dumper($algebra->dataset);
802 8         13 my @plans;
803            
804 8         14 my @dataset = ($ds);
805 8 100 66     53 if ($dcount == 1 and $ncount == 0) {
    50 33        
806             # if it's just a single named graph to be used as the default graph,
807             # then rewrite the pattern to use the named graph (and check to make
808             # sure there aren't any GRAPH blocks)
809 1         2 @dataset = ();
810 1         4 @plans = $self->generate_plans( $algebra->pattern, $context, %args, active_graph => $default->[0] );
811             } elsif ($dcount == 0 and $ncount == 0) {
812 7         11 @dataset = ();
813 7         19 @plans = $self->generate_plans( $algebra->pattern, $context, %args );
814             } else {
815 0         0 @plans = $self->generate_plans( $algebra->pattern, $context, %args );
816             }
817 8         14 foreach my $p (@plans) {
818 8         34 push(@return_plans, RDF::Query::Plan::Update->new( $algebra->delete_template, $algebra->insert_template, $p, @dataset ));
819             }
820             } elsif ($type eq 'Clear') {
821 0         0 push(@return_plans, RDF::Query::Plan::Clear->new( $algebra->graph ));
822             } elsif ($type eq 'Create') {
823 0         0 my $plan = RDF::Query::Plan::Constant->new();
824 0         0 push(@return_plans, $plan);
825             } elsif ($type eq 'Copy') {
826 0         0 my $plan = RDF::Query::Plan::Copy->new( $algebra->from, $algebra->to, $algebra->silent );
827 0         0 push(@return_plans, $plan);
828             } elsif ($type eq 'Move') {
829 0         0 my $plan = RDF::Query::Plan::Move->new( $algebra->from, $algebra->to, $algebra->silent );
830 0         0 push(@return_plans, $plan);
831             } elsif ($type eq 'Table') {
832 0         0 my $plan = RDF::Query::Plan::Constant->new( $algebra->rows );
833 0         0 push(@return_plans, $plan);
834             } else {
835 0         0 throw RDF::Query::Error::MethodInvocationError (-text => "Cannot generate an execution plan for unknown algebra class $aclass");
836             }
837            
838 773 0 50     1220 if ($constant and scalar(@$constant)) {
839 0         0 my @plans = splice( @return_plans );
840 0         0 @return_plans = $self->_add_constant_join( $context, $constant, @plans );
841             }
842            
843 773         906 foreach my $p (@return_plans) {
844 779 50       2139 Carp::confess 'not a plan: ' . Dumper($p) unless ($p->isa('RDF::Query::Plan'));
845 779         1418 $p->label( algebra => $algebra );
846             }
847            
848 773 50       1150 unless (scalar(@return_plans)) {
849 0         0 throw RDF::Query::Error::CompilationError (-text => "Cannot generate an execution plan for algebra of type $type", -object => $algebra);
850             }
851 773         1972 return @return_plans;
852             }
853              
854             sub _csg_plans {
855 410     410   437 my $self = shift;
856 410         387 my $context = shift;
857 410         378 my $st = shift;
858 410         764 my $pred = $st->predicate;
859 410 50       1977 return unless (blessed($context));
860 410         1009 my $query = $context->query;
861 410         649 my @return_plans;
862 410 100 66     2511 if (blessed($query) and $pred->isa('RDF::Trine::Node::Resource') and scalar(@{ $query->get_computed_statement_generators( $st->predicate->uri_value ) })) {
  406   100     784  
863 2         5 my $csg = $query->get_computed_statement_generators( $pred->uri_value );
864 2         6 my @nodes = $st->nodes;
865 2 50       8 my $quad = (scalar(@nodes) == 4) ? 1 : 0;
866 2         13 my $mp = RDF::Query::Plan::ComputedStatement->new( @nodes[0..3], $quad );
867 2         4 push(@return_plans, $mp);
868             }
869 410         1233 return @return_plans;
870             }
871              
872             sub _join_plans {
873 27     27   37 my $self = shift;
874 27         32 my $context = shift;
875 27         35 my $triples = shift;
876 27         59 my %args = @_;
877 27   50     80 my $config = $context->options || {};
878            
879 27         45 my $method = $args{ method };
880 27         138 my @join_types = RDF::Query::Plan::Join->join_classes( $config );
881            
882 27         32 my @plans;
883 27         68 my $opt = $context->optimize;
884 27 50       66 my @slice = ($opt) ? (0 .. $#{ $triples }) : (0);
  0         0  
885 27         52 foreach my $i (@slice) {
886 27         38 my @triples = @$triples;
887             # pick a triple to use as the LHS
888 27         49 my ($t) = splice( @triples, $i, 1 );
889            
890 27         138 my @_lhs = $self->generate_plans( $t, $context, %args );
891 27         47 my @lhs_plans = map { [ $_, [$t] ] } @_lhs;
  27         100  
892 27 100       57 if (@triples) {
893 14         103 my @rhs_plans = $self->_join_plans( $context, \@triples, %args );
894 14         36 foreach my $i (0 .. $#lhs_plans) {
895 14         27 foreach my $j (0 .. $#rhs_plans) {
896 14         21 my $a = $lhs_plans[ $i ][0];
897 14         18 my $b = $rhs_plans[ $j ][0];
898 14         22 my $algebra_a = $lhs_plans[ $i ][1];
899 14         19 my $algebra_b = $rhs_plans[ $j ][1];
900 14 50       70 Carp::confess 'no lhs for join: ' . Dumper(\@lhs_plans) unless (blessed($a));
901 14 50       55 Carp::confess 'no rhs for join: ' . Dumper(\@triples, \@rhs_plans) unless (blessed($b));
902 14         36 foreach ($algebra_a, $algebra_b) {
903 28 50 33     151 unless (ref($_) and reftype($_) eq 'ARRAY') {
904 0         0 Carp::cluck Dumper($_)
905             }
906             }
907 14         29 foreach my $join_type (@join_types) {
908 28 50 66     364 next if ($join_type eq 'RDF::Query::Plan::Join::PushDownNestedLoop' and $b->subplans_of_type('RDF::Query::Plan::Service'));
909             try {
910 28     28   587 my @algebras;
911 28         39 foreach ($algebra_a, $algebra_b) {
912 56 50       147 if (reftype($_) eq 'ARRAY') {
913 56         74 push(@algebras, @$_);
914             }
915             }
916 28         30 my %logging_keys;
917 28 50       53 if ($method eq 'triples') {
918 0         0 my $bgp = RDF::Query::Algebra::BasicGraphPattern->new( @algebras );
919 0         0 my $sparql = $bgp->as_sparql;
920 0         0 my $bf = $bgp->bf;
921 0         0 $logging_keys{ bf } = $bf;
922 0         0 $logging_keys{ sparql } = $sparql;
923             } else {
924 28         90 my $ggp = RDF::Query::Algebra::GroupGraphPattern->new( @algebras );
925 28         83 my $sparql = $ggp->as_sparql;
926 28         78 $logging_keys{ sparql } = $sparql;
927             }
928 28         188 my $plan = $join_type->new( $b, $a, 0, \%logging_keys );
929 27         86 push( @plans, [ $plan, [ @algebras ] ] );
930       1     } catch RDF::Query::Error::MethodInvocationError with {
931             # warn "caught MethodInvocationError.";
932 28         229 };
933             }
934             }
935             }
936             } else {
937 13         34 @plans = @lhs_plans;
938             }
939             }
940            
941 27 50       275 if ($opt) {
942 0         0 return @plans;
943             } else {
944 27 50       63 if (@plans) {
945 27         93 return $plans[0]; # XXX need to figure out what's the 'best' plan here
946             } else {
947 0         0 return;
948             }
949             }
950             }
951              
952             sub _add_constant_join {
953 3     3   4 my $self = shift;
954 3         4 my $context = shift;
955 3         5 my $constant = shift;
956 3         5 my @return_plans = @_;
957 3   50     12 my $config = $context->options || {};
958 3         18 my @join_types = RDF::Query::Plan::Join->join_classes( $config );
959 3         9 while (my $const = shift(@$constant)) {
960 3         6 my @plans = splice(@return_plans);
961 3         6 foreach my $p (@plans) {
962 3         5 foreach my $join_type (@join_types) {
963             try {
964 6     6   156 my $plan = $join_type->new( $p, $const );
965 6         11 push( @return_plans, $plan );
966       0     } catch RDF::Query::Error::MethodInvocationError with {
967             # warn "caught MethodInvocationError.";
968 6         76 };
969             }
970             }
971             }
972 3         36 return @return_plans;
973             }
974              
975             sub _path_plans {
976 5     5   6 my $self = shift;
977 5         4 my $algebra = shift;
978 5         5 my $context = shift;
979 5         9 my %args = @_;
980 5         12 my $path = $algebra->path;
981 5         10 my $start = $algebra->start;
982 5         10 my $end = $algebra->end;
983 5         9 for ($start, $end) {
984 10 50       38 if ($_->isa('RDF::Query::Node::Blank')) {
985 0         0 $_ = $_->make_distinguished_variable;
986             }
987             }
988            
989 5         13 my $npath = $self->_normalize_path( $path );
990 5         23 return $self->__path_plan( $start, $npath, $end, $args{ active_graph }, $context, %args );
991             }
992              
993             sub _normalize_path {
994 17     17   14 my $self = shift;
995 17         13 my $path = shift;
996 17 100       40 if (blessed($path)) {
997 10         21 return $path;
998             }
999            
1000 7         9 my $op = $path->[0];
1001 7         9 my @nodes = map { $self->_normalize_path($_) } @{ $path }[ 1 .. $#{ $path } ];
  12         30  
  7         11  
  7         13  
1002 7 50       32 if ($op eq '0-') {
    50          
    50          
    50          
1003 0         0 $op = '*';
1004             } elsif ($op eq '1-') {
1005 0         0 $op = '+';
1006             } elsif ($op eq '0-1') {
1007 0         0 $op = '?';
1008             } elsif ($op =~ /^-\d+$/) {
1009 0         0 $op = "0$op";
1010             }
1011            
1012 7 50       13 if ($op eq '!') {
1013             # re-order the nodes so that forward predicates come first, followed by backwards predicates
1014             # !(:fwd1|:fwd2|:fwd3|^:bkw1|^:bkw2|^:bkw3)
1015 0 0       0 @nodes = sort { blessed($a) ? -1 : (($a->[0] eq '^') ? 1 : -1) } @nodes;
  0 0       0  
1016             }
1017            
1018 7         14 return [$op, @nodes];
1019             }
1020              
1021             sub __path_plan {
1022 47     47   4098 my $self = shift;
1023 47         46 my $start = shift;
1024 47         29 my $path = shift;
1025 47         42 my $end = shift;
1026 47         50 my $graph = shift;
1027 47         30 my $context = shift;
1028 47         82 my %args = @_;
1029 47         36 my $distinct = 1; #$args{distinct} ? 1 : 0;
1030 47   50     103 my $config = $context->options || {};
1031 47         118 my $l = Log::Log4perl->get_logger("rdf.query.plan.path");
1032            
1033            
1034             # _simple_path will return an algebra object if the path can be expanded
1035             # into a simple basic graph pattern (for fixed-length paths)
1036 47 100       957 if (my $a = $self->_simple_path( $start, $path, $end, $graph )) {
1037 43         714 my ($plan) = $self->generate_plans( $a, $context, %args, prevent_distinguishing_bnodes => 1 );
1038 43         88 $l->trace('expanded path to pattern: ' . $plan->sse);
1039 43         298 return $plan;
1040             }
1041            
1042            
1043 4 50       9 if (blessed($path)) {
1044             ### X iri Y
1045             # $path is a resource object: this is a triple (a path of length 1)
1046 0         0 my $s = $start;
1047 0         0 my $e = $end;
1048 0 0       0 my $algebra = $graph
1049             ? RDF::Query::Algebra::Quad->new( $s, $path, $e, $graph )
1050             : RDF::Query::Algebra::Triple->new( $s, $path, $e );
1051 0         0 my ($plan) = $self->generate_plans( $algebra, $context, %args, prevent_distinguishing_bnodes => 1 );
1052 0         0 $l->trace('expanded path to pattern: ' . $plan->sse);
1053 0         0 return $plan;
1054             }
1055            
1056 4         9 my ($op, @nodes) = @$path;
1057            
1058             # if ($op eq 'DISTINCT') {
1059             # return $self->__path_plan( $start, $nodes[0], $end, $graph, $context, %args, distinct => 1 );
1060             # }
1061 4 50       24 if ($op eq '!') {
    50          
    100          
    50          
    50          
    100          
    50          
    0          
    0          
    0          
    0          
1062 0         0 my $total = scalar(@nodes);
1063 0 0       0 my $neg = scalar(@{ [ grep { not(blessed($_)) and $_->[0] eq '^' } @nodes ] });
  0         0  
  0         0  
1064 0         0 my $pos = $total - $neg;
1065 0 0       0 if ($pos == $total) {
    0          
1066             ### X !(:iri1|...|:irin) Y
1067 0         0 return RDF::Query::Plan::Path->new( 'NegatedPropertySet', $start, [@nodes], $end, $graph, $distinct, %args );
1068             } elsif ($neg == $total) {
1069             ### X !(^:iri1|...|^:irin)Y
1070 0         0 my @preds = map { $_->[1] } @nodes;
  0         0  
1071 0         0 return $self->__path_plan($start, ['^', ['!', @preds]], $end, $graph, $context, %args);
1072             } else {
1073             ### X !(:iri1|...|:irii|^:irii+1|...|^:irim) Y
1074 0         0 my @fwd = grep { blessed($_) } @nodes;
  0         0  
1075 0         0 my @bwd = grep { not(blessed($_)) } @nodes;
  0         0  
1076 0         0 my $fplan = $self->__path_plan($start, ['!', @fwd], $end, $graph, $context, %args);
1077 0         0 my $bplan = $self->__path_plan($start, ['!', @bwd], $end, $graph, $context, %args);
1078 0         0 return RDF::Query::Plan::Union->new($fplan, $bplan);
1079             }
1080             } elsif ($op eq '^') {
1081             ### X ^path Y
1082 0         0 return $self->__path_plan( $end, $nodes[0], $start, $graph, $context, %args);
1083             } elsif ($op eq '/') {
1084 2         4 my $count = scalar(@nodes);
1085 2 50       4 if ($count == 1) {
1086 0         0 return $self->__path_plan( $start, $nodes[0], $end, $graph, $context, %args );
1087             } else {
1088 2         6 my $joinvar = RDF::Query::Node::Variable->new();
1089 2         27 my @plans = $self->__path_plan( $start, $nodes[0], $joinvar, $graph, $context, %args );
1090 2         5 foreach my $i (2 .. $count) {
1091 2 50       5 my $endvar = ($i == $count) ? $end : RDF::Query::Node::Variable->new();
1092 2         13 my ($rhs) = $self->__path_plan( $joinvar, $nodes[$i-1], $endvar, $graph, $context, %args );
1093 2         3 push(@plans, $rhs);
1094 2         3 $joinvar = $endvar;
1095             }
1096 2         7 my @join_types = RDF::Query::Plan::Join->join_classes( $config );
1097 2         3 my @jplans;
1098 2         4 foreach my $jclass (@join_types) {
1099 4         28 push(@jplans, $jclass->new( @plans[0,1], 0 ));
1100             }
1101 2         8 $l->trace("expanded /-path to: " . $jplans[0]->sse);
1102 2         15 return $jplans[0];
1103             }
1104             } elsif ($op eq '|') {
1105             ### X path1 | path2 Y
1106 0         0 my @plans = map { $self->__path_plan($start, $_, $end, $graph, $context, %args) } @nodes;
  0         0  
1107 0         0 return RDF::Query::Plan::Union->new(@plans);
1108             } elsif ($op eq '?') {
1109             ### X path? Y
1110 0         0 my $upath = $nodes[0];
1111 0         0 my $zplan = $self->__path_plan($start, ['0', $upath], $end, $graph, $context, %args );
1112 0         0 my $oplan = $self->__path_plan($start, $upath, $end, $graph, $context, %args);
1113            
1114             # project away any non-distinguished variables introduced by plan-to-bgp expansion
1115 0 0       0 my @vars = grep { blessed($_) and $_->isa('RDF::Query::Node::Variable') } ($start, $end);
  0         0  
1116 0         0 my $odplan = RDF::Query::Plan::Project->new( $oplan, \@vars );
1117            
1118 0         0 my $pplan = RDF::Query::Plan::Union->new($zplan, $odplan);
1119            
1120             # distinct the results
1121 0         0 my $plan = RDF::Query::Plan::Distinct->new( $pplan );
1122 0         0 return $plan;
1123             } elsif ($op eq '*') {
1124             ### X path* Y
1125 1         7 return RDF::Query::Plan::Path->new( 'ZeroOrMorePath', $start, $nodes[0], $end, $graph, $distinct, %args );
1126             } elsif ($op eq '+') {
1127             ### X path+ Y
1128 1         11 return RDF::Query::Plan::Path->new( 'OneOrMorePath', $start, $nodes[0], $end, $graph, $distinct, %args );
1129             } elsif ($op eq '0') {
1130             ### X path{0} Y
1131 0         0 return RDF::Query::Plan::Path->new( 'ZeroLengthPath', $start, $nodes[0], $end, $graph, $distinct, %args );
1132             } elsif ($op =~ /^\d+$/) {
1133             ### X path{n} Y where n > 0
1134 0         0 my $count = $op;
1135 0 0       0 if ($count == 1) {
1136 0         0 return $self->__path_plan( $start, $nodes[0], $end, $graph, $context, %args );
1137             } else {
1138 0         0 my $joinvar = RDF::Query::Node::Variable->new();
1139 0         0 my @plans = $self->__path_plan( $start, $nodes[0], $joinvar, $graph, $context, %args );
1140 0         0 foreach my $i (2 .. $count) {
1141 0 0       0 my $endvar = ($i == $count) ? $end : RDF::Query::Node::Variable->new();
1142 0         0 my ($rhs) = $self->__path_plan( $joinvar, $nodes[0], $endvar, $graph, $context, %args );
1143 0         0 push(@plans, $rhs);
1144 0         0 $joinvar = $endvar;
1145             }
1146 0         0 my @join_types = RDF::Query::Plan::Join->join_classes( $config );
1147 0         0 my @jplans;
1148            
1149 0         0 my @plan = shift(@plans);
1150 0         0 while (@plans) {
1151 0         0 my $q = shift(@plans);
1152 0         0 my @p;
1153 0         0 foreach my $p (@plan) {
1154 0         0 foreach my $jclass (@join_types) {
1155 0         0 push(@p, $jclass->new( $p, $q, 0 ));
1156             }
1157             }
1158 0         0 @plan = @p;
1159             }
1160 0         0 return $plan[0];
1161             }
1162             } elsif ($op =~ /^(\d+)-(\d+)$/) {
1163             ### X path{n,m} Y
1164 0         0 my ($n,$m) = split('-', $op, 2);
1165             # warn "$1- to $2-length path";
1166 0         0 my @range = sort { $a <=> $b } ($n, $m);
  0         0  
1167 0         0 my $from = $range[0];
1168 0         0 my $to = $range[1];
1169 0         0 my @plans;
1170 0         0 foreach my $i ($from .. $to) {
1171 0 0       0 if ($i == 0) {
1172 0         0 push(@plans, $self->__path_plan($start, ['0', []], $end, $graph, $context, %args ));
1173             } else {
1174 0         0 push(@plans, $self->__path_plan( $start, [$i, $nodes[0]], $end, $graph, $context, %args ));
1175             }
1176             }
1177 0         0 while (scalar(@plans) > 1) {
1178 0         0 my $lhs = shift(@plans);
1179 0         0 my $rhs = shift(@plans);
1180 0         0 unshift(@plans, RDF::Query::Plan::Union->new( $lhs, $rhs ));
1181             }
1182 0         0 return $plans[0];
1183             } elsif ($op =~ /^(\d+)-$/) {
1184             ### X path{n,} Y where n > 0
1185 0         0 my ($min) = split('-', $op);
1186             # expand :p{n,} into :p{n}/:p*
1187 0         0 my $path = [ '/', [ $1, @nodes ], [ '*', @nodes ] ];
1188 0         0 my $plan = $self->__path_plan( $start, $path, $end, $graph, $context, %args );
1189 0         0 return $plan;
1190             } else {
1191 0         0 throw RDF::Query::Error -text => "Cannot generate plan for unknown path type $op";
1192             }
1193             }
1194              
1195             sub _simple_path {
1196 55     55   44 my $self = shift;
1197 55         38 my $start = shift;
1198 55         41 my $path = shift;
1199 55         41 my $end = shift;
1200 55         37 my $graph = shift;
1201 55 100       142 if (blessed($path)) {
1202 46 100       113 return ($graph)
1203             ? RDF::Query::Algebra::Quad->new( $start, $path, $end, $graph )
1204             : RDF::Query::Algebra::Triple->new( $start, $path, $end );
1205             }
1206 9 50       26 return unless (reftype($path) eq 'ARRAY');
1207 9         12 my $op = $path->[0];
1208 9 100 33     50 if ($op eq '/') {
    50 33        
    50 33        
1209 5         5 my @patterns;
1210 5         7 my @jvars = map { RDF::Query::Node::Variable->new() } (2 .. $#{ $path });
  5         14  
  5         10  
1211 5         27 foreach my $i (1 .. $#{ $path }) {
  5         14  
1212 8 100       15 my $s = ($i == 1) ? $start : $jvars[ $i-2 ];
1213 8 100       7 my $e = ($i == $#{ $path }) ? $end : $jvars[ $i-1 ];
  8         23  
1214 8         23 my $triple = $self->_simple_path( $s, $path->[ $i ], $e, $graph );
1215 8 100       88 return unless ($triple);
1216 6         8 push(@patterns, $triple);
1217             }
1218 3 50       6 my @triples = map { $_->isa('RDF::Query::Algebra::BasicGraphPattern') ? $_->triples : $_ } @patterns;
  6         28  
1219 3         9 return RDF::Query::Algebra::BasicGraphPattern->new( @triples );
1220             } elsif ($op eq '^' and scalar(@$path) == 2 and blessed($path->[1])) {
1221 0 0       0 return ($graph)
1222             ? RDF::Query::Algebra::Quad->new( $end, $path->[1], $start, $graph )
1223             : RDF::Query::Algebra::Triple->new( $end, $path->[1], $start );
1224             } elsif ($op =~ /^\d+$/ and $op == 1) {
1225 0         0 return $self->_simple_path( $start, $path->[1], $end, $graph );
1226             }
1227            
1228 4         7 return;
1229             }
1230              
1231             =item C<< plan_node_name >>
1232              
1233             Returns the string name of this plan node, suitable for use in serialization.
1234              
1235             =cut
1236              
1237             sub plan_node_name;
1238              
1239             =item C<< plan_prototype >>
1240              
1241             Returns a list of scalar identifiers for the type of the content (children)
1242             nodes of this plan node. These identifiers are recognized:
1243              
1244             * 'A' - An RDF::Query::Algebra object
1245             * 'b' - A boolean integer value (0 or 1)
1246             * 'E' - An expression (either an RDF::Query::Expression object or an RDF node)
1247             * 'i' - An integer
1248             * 'J' - A valid Project node (an RDF::Query::Expression object or an Variable node)
1249             * 'N' - An RDF node
1250             * 'P' - A RDF::Query::Plan object
1251             * 'q' - A RDF::Query object
1252             * 'Q' - An RDF::Trine::Statement::Quad object
1253             * 's' - A string
1254             * 'T' - An RDF::Trine::Statement object
1255             * 'u' - A valid URI string
1256             * 'V' - A variable binding set (an object of type RDF::Query::VariableBindings)
1257             * 'w' - A bareword string
1258             * 'W' - An RDF node or wildcard ('*')
1259             * '*X' - A list of X nodes (where X is another identifier scalar)
1260             * '\X' - An array reference of X nodes (where X is another identifier scalar)
1261              
1262             =cut
1263              
1264             sub plan_prototype;
1265              
1266             =item C<< plan_node_data >>
1267              
1268             Returns the data for this plan node that corresponds to the values described by
1269             the signature returned by C<< plan_prototype >>.
1270              
1271             =cut
1272              
1273             sub plan_node_data;
1274              
1275              
1276             =item C<< subplans_of_type ( $type [, $block] ) >>
1277              
1278             Returns a list of Plan objects matching C<< $type >> (tested with C<< isa >>).
1279             If C<< $block >> is given, then matching stops descending a subtree if the current
1280             node is of type C<< $block >>, continuing matching on other subtrees.
1281             This list includes the current plan object if it matches C<< $type >>, and is
1282             generated in infix order.
1283              
1284             =cut
1285              
1286             sub subplans_of_type {
1287 55     55 1 71 my $self = shift;
1288 55         66 my $type = shift;
1289 55         55 my $block = shift;
1290            
1291 55 50 33     146 return if ($block and $self->isa($block));
1292            
1293 55         53 my @patterns;
1294 55 50       271 push(@patterns, $self) if ($self->isa($type));
1295            
1296            
1297 55         179 foreach my $p ($self->plan_node_data) {
1298 192 100 100     959 if (blessed($p) and $p->isa('RDF::Query::Plan')) {
1299 11         49 push(@patterns, $p->subplans_of_type($type, $block));
1300             }
1301             }
1302 55         162 return @patterns;
1303             }
1304              
1305             1;
1306              
1307             __END__
1308              
1309             =back
1310              
1311             =head1 AUTHOR
1312              
1313             Gregory Todd Williams <gwilliams@cpan.org>
1314              
1315             =cut