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.916.
11              
12             =head1 METHODS
13              
14             =over 4
15              
16             =cut
17              
18             package RDF::Query::Plan;
19              
20 35     35   207 use strict;
  35         81  
  35         958  
21 35     35   225 use warnings;
  35         81  
  35         992  
22 35     35   192 use Data::Dumper;
  35         76  
  35         1731  
23 35     35   193 use List::Util qw(reduce);
  35         85  
  35         2129  
24 35     35   191 use Scalar::Util qw(blessed reftype refaddr);
  35         74  
  35         2043  
25 35     35   198 use RDF::Query::Error qw(:try);
  35         99  
  35         270  
26 35     35   23752 use RDF::Query::BGPOptimizer;
  35         111  
  35         1117  
27              
28 35     35   23881 use RDF::Query::Plan::Aggregate;
  35         116  
  35         1085  
29 35     35   22042 use RDF::Query::Plan::BasicGraphPattern;
  35         110  
  35         1071  
30 35     35   20703 use RDF::Query::Plan::Constant;
  35         105  
  35         1035  
31 35     35   20970 use RDF::Query::Plan::Construct;
  35         113  
  35         1108  
32 35     35   20808 use RDF::Query::Plan::Distinct;
  35         108  
  35         1197  
33 35     35   20770 use RDF::Query::Plan::Filter;
  35         113  
  35         1227  
34 35     35   23573 use RDF::Query::Plan::Join::NestedLoop;
  35         101  
  35         1431  
35 35     35   23109 use RDF::Query::Plan::Join::PushDownNestedLoop;
  35         89  
  35         1556  
36 35     35   20853 use RDF::Query::Plan::Limit;
  35         95  
  35         1538  
37 35     35   20895 use RDF::Query::Plan::Offset;
  35         103  
  35         1524  
38 35     35   21824 use RDF::Query::Plan::Project;
  35         98  
  35         1646  
39 35     35   21339 use RDF::Query::Plan::Extend;
  35         112  
  35         1783  
40 35     35   21670 use RDF::Query::Plan::Quad;
  35         114  
  35         1940  
41 35     35   23364 use RDF::Query::Plan::Service;
  35         115  
  35         2105  
42 35     35   21627 use RDF::Query::Plan::Sort;
  35         102  
  35         2243  
43 35     35   21943 use RDF::Query::Plan::ComputedStatement;
  35         108  
  35         2085  
44 35     35   21894 use RDF::Query::Plan::ThresholdUnion;
  35         103  
  35         2077  
45 35     35   20826 use RDF::Query::Plan::Union;
  35         104  
  35         2065  
46 35     35   21642 use RDF::Query::Plan::SubSelect;
  35         126  
  35         2245  
47 35     35   20457 use RDF::Query::Plan::Iterator;
  35         108  
  35         2100  
48 35     35   21076 use RDF::Query::Plan::Load;
  35         107  
  35         2139  
49 35     35   21097 use RDF::Query::Plan::Clear;
  35         108  
  35         2271  
50 35     35   22107 use RDF::Query::Plan::Update;
  35         109  
  35         2390  
51 35     35   21777 use RDF::Query::Plan::Minus;
  35         107  
  35         2402  
52 35     35   20974 use RDF::Query::Plan::Sequence;
  35         105  
  35         2324  
53 35     35   22724 use RDF::Query::Plan::Path;
  35         117  
  35         2825  
54 35     35   22363 use RDF::Query::Plan::NamedGraph;
  35         117  
  35         2630  
55 35     35   21221 use RDF::Query::Plan::Copy;
  35         109  
  35         2533  
56 35     35   21555 use RDF::Query::Plan::Move;
  35         107  
  35         2568  
57              
58 35     35   238 use RDF::Trine::Statement;
  35         76  
  35         1135  
59 35     35   206 use RDF::Trine::Statement::Quad;
  35         72  
  35         1107  
60              
61 35     35   208 use constant READY => 0x01;
  35         71  
  35         3145  
62 35     35   189 use constant OPEN => 0x02;
  35         77  
  35         2250  
63 35     35   198 use constant CLOSED => 0x04;
  35         70  
  35         3760  
64              
65             ######################################################################
66              
67             our ($VERSION, %PLAN_CLASSES);
68             BEGIN {
69 35     35   110 $VERSION = '2.916';
70 35         20794 %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 864 my $class = shift;
83 556         1245 my @args = @_;
84 556         4797 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 28 my $self = shift;
107 12 50       39 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         29 my @rows;
111 12         75 while (my $row = $self->next) {
112 34         146 push(@rows, $row);
113             }
114 12         62 return @rows;
115             }
116              
117             =item C<< close >>
118              
119             =cut
120              
121             sub close {
122 590     590 1 881 my $self = shift;
123 590         1243 $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 5081     5081 1 6781 my $self = shift;
135 5081 100       10970 if (scalar(@_)) {
136 1190         2095 $self->[0]{__state} = shift;
137             }
138 5081         26580 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   230 no warnings 'uninitialized';
  35         85  
  35         19218  
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 421 my $self = shift;
194 188   100     717 my $context = shift || {};
195 188   100     680 my $indent = shift || '';
196 188         274 my $more = ' ';
197 188         1480 my @proto = $self->plan_prototype;
198 188         633 my @data = $self->plan_node_data;
199 188         629 my $name = $self->plan_node_name;
200            
201 188         277 my @args;
202 188         267 my $list = \@data;
203 188         450 foreach my $i (0 .. $#proto) {
204 583         6635 my $p = $proto[ $i ];
205 583         1530 push(@args, $self->_sse( $context, $indent, $more, $p, $list ));
206             }
207 188 100       1113 return "(${name} " . join(' ', map { defined($_) ? $_ : '()' } @args) . ")";
  583         2501  
208             }
209              
210             sub _sse {
211 687     687   948 my $self = shift;
212 687         856 my $context = shift;
213 687         881 my $indent = shift;
214 687         834 my $more = shift;
215 687         909 my $p = shift;
216 687         836 my $list = shift;
217 687 100       2175 if ($p =~ m/^[PQTNWEJVqibswu]$/) {
    50          
    100          
    50          
    0          
218 630         907 my $v = shift(@$list);
219 630         1652 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         56 my $rest = substr($p, 1);
229 26         42 my $v = shift(@$list);
230 26         56 my @args;
231 26         95 while (@$v) {
232 30         213 push(@args, $self->_sse( $context, $indent, $more, $rest, $v ));
233             }
234 26         580 return '(' . join(' ', @args) . ')';
235             } elsif (substr($p, 0, 1) eq '*') {
236 31         64 my $rest = substr($p, 1);
237 31         55 my @args;
238 31         92 while (@$list) {
239 74         2622 push(@args, $self->_sse( $context, $indent, $more, $rest, $list ));
240             }
241 35     35   208 no warnings 'uninitialized';
  35         79  
  35         10562  
242 31         1522 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   833 my $self = shift;
257 630   50     1356 my $context = shift || {};
258 630         826 my $indent = shift;
259 630         799 my $more = shift;
260 630         768 my $p = shift;
261 630         761 my $v = shift;
262 35     35   211 no warnings 'uninitialized';
  35         79  
  35         340378  
263            
264 630   100     2196 my $ns = $context->{ namespaces } || {};
265 630         1371 my %ns = %$ns;
266            
267 630 100       3998 if ($p eq 's') {
    50          
    50          
    100          
    50          
    50          
    100          
    50          
268 8         15 for ($v) {
269 8         13 s/\\/\\\\/g;
270 8         14 s/"/\\"/g;
271 8         33 s/\n/\\n/g;
272 8         18 s/\t/\\t/g;
273             }
274 8         36 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         14 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       1861 if (blessed($v)) {
291            
292 580 50       2323 Carp::cluck unless ($v->can('sse'));
293 580         2824 return $v->sse( { namespaces => \%ns }, "${indent}${more}" );
294             } else {
295 9         38 return '()';
296             }
297             } elsif ($p eq 'J') {
298 30 100       164 if ($v->isa('RDF::Query::Node::Variable')) {
299 2         6 return $v->name;
300             } else {
301 28         175 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 981     981 1 1602 my $self = shift;
325 981         4582 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 567 my $self = shift;
336 366         641 my $refs = $self->[0]{referenced_variables};
337 366         495 return @{ $refs };
  366         1653  
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 300 my $self = shift;
348 153         258 my $context = shift;
349 153         682 my $vars = $context->requested_variables;
350 153     321   1977 my $stream = RDF::Trine::Iterator::Bindings->new( sub { $self->next }, $vars, distinct => $self->distinct, sorted_by => $self->ordered );
  321         169173  
351 153         7391 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 1060 my $self = shift;
374 779         1040 my $label = shift;
375 779 50       1745 if (@_) {
376 779         1036 my $value = shift;
377 779         2241 $self->[0]{labels}{ $label } = $value;
378             }
379 779         1985 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   80959 my $self = shift;
402 546 100       1383 if ($self->state == OPEN) {
403 205         860 $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 1261 my $self = shift;
417 773   33     3065 my $class = ref($self) || $self;
418 773         1068 my $algebra = shift;
419 773         995 my $context = shift;
420 773   100     2503 my $config = $context->options || {};
421            
422 773         2001 my %args = @_;
423 773   66     4306 my $active_graph = $args{ active_graph } || RDF::Trine::Node::Nil->new();
424            
425 773         13222 my $l = Log::Log4perl->get_logger("rdf.query.plan");
426 773 50 33     30033 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         4198 $l->trace("generating query plan for " . $algebra->sse({ indent => ' ' }, ''));
431            
432             ############################################################################
433             ### Optimize simple COUNT(*) aggregates over BGPs
434 773 100       23181 if ($algebra->isa('RDF::Query::Algebra::Extend')) {
435 24         83 my $agg = $algebra->pattern;
436 24 100       131 if ($agg->isa('RDF::Query::Algebra::Aggregate')) {
437 15         48 my @group = $agg->groupby;
438 15 100       51 if (scalar(@group) == 0) {
439 9         31 my @ops = $agg->ops;
440 9 50 66     75 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         1150 my ($project);
477 773         1370 my $constant = delete $args{ constants };
478            
479 773 100 100     6048 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         1048 $project = delete $args{ project };
482             }
483            
484 773         1107 my @return_plans;
485 773         1339 my $aclass = ref($algebra);
486 773         4753 my ($type) = ($aclass =~ m<::(\w+)$>);
487 773 100 100     7779 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         65 my @base = $self->generate_plans( $algebra->pattern, $context, %args );
489 16         99 my @groups = $algebra->groupby;
490 16         30 my @ops;
491 16         50 foreach my $o ($algebra->ops) {
492 18         58 my ($alias, $op, $opts, @cols) = @$o;
493 18         68 push(@ops, [ $alias, $op, $opts, @cols ]);
494             }
495 16         51 my @plans = map { RDF::Query::Plan::Aggregate->new( $_, \@groups, expressions => \@ops ) } @base;
  16         98  
496 16         42 push(@return_plans, @plans);
497             } elsif ($type eq 'Construct') {
498 4         16 my $triples = $algebra->triples;
499 4         19 my @base = $self->generate_plans( $algebra->pattern, $context, %args );
500 4         18 my @plans = map { RDF::Query::Plan::Construct->new( $_, $triples ) } @base;
  4         29  
501 4         14 push(@return_plans, @plans);
502             } elsif ($type eq 'Distinct') {
503 10         40 my @base = $self->generate_plans( $algebra->pattern, $context, %args );
504 10         26 my @plans = map { RDF::Query::Plan::Distinct->new( $_ ) } @base;
  10         104  
505 10         32 push(@return_plans, @plans);
506             } elsif ($type eq 'Filter') {
507 37         140 my @base = $self->generate_plans( $algebra->pattern, $context, %args, active_graph => $active_graph );
508 37         158 my $expr = $algebra->expr;
509 37         86 my @plans = map { RDF::Query::Plan::Filter->new( $expr, $_, $active_graph ) } @base;
  37         249  
510 37         93 push(@return_plans, @plans);
511             } elsif ($type eq 'BasicGraphPattern') {
512             my @triples = map {
513 146         539 ($args{ prevent_distinguishing_bnodes })
514 277 100       3439 ? $_
515             : $_->distinguish_bnode_variables
516             } $algebra->triples;
517 146         2705 my @normal_triples;
518             my @csg_triples;
519 146         350 foreach my $t (@triples) {
520 277 100       1091 if (my @csg_plans = $self->_csg_plans( $context, $t )) {
521 1         12 push(@csg_triples, $t);
522             } else {
523 276         813 my @nodes = $t->nodes;
524 276         2469 $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         6012 push(@normal_triples, $t);
530             }
531             }
532            
533 146         284 my @plans;
534 146 50       553 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         981 push(@plans, $self->generate_plans( @normal_triples, $context, %args ));
540             } else {
541 76         647 my $plan = RDF::Query::Plan::BasicGraphPattern->new( @normal_triples );
542 76         189 push(@plans, $plan);
543             }
544            
545 146 100       427 if (@csg_triples) {
546 1         2 my @csg_plans;
547 1         3 foreach my $t (@csg_triples) {
548 1         5 push(@csg_plans, [ $self->generate_plans( $t, $context, %args ) ]);
549             }
550 1         11 my @join_types = RDF::Query::Plan::Join->join_classes( $config );
551 1         5 while (my $cps = shift(@csg_plans)) {
552 1         2 my @temp_plans = @plans;
553 1         2 @plans = ();
554 1         2 foreach my $p (@temp_plans) {
555 1         3 foreach my $cp (@$cps) {
556 1         8 foreach my $join_type (@join_types) {
557 2         20 my $plan = $join_type->new( $p, $cp, 0, {} );
558 2         9 push(@plans, $plan);
559             }
560             }
561             }
562             }
563 1         4 push(@return_plans, @plans);
564             } else {
565 145         640 push(@return_plans, @plans);
566             }
567            
568             } elsif ($type eq 'GroupGraphPattern') {
569 205         762 my @input = $algebra->patterns();
570 205         397 my @patterns;
571 205         754 while (my $a = shift(@input)) {
572 201 50       1344 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         849 push(@patterns, $a);
584             }
585            
586 205         324 my @plans;
587 205 100       764 if (scalar(@patterns) == 0) {
    100          
588 18         135 my $v = RDF::Query::VariableBindings->new( {} );
589 18         144 my $plan = RDF::Query::Plan::Constant->new( $v );
590 18         44 push(@plans, $plan);
591             } elsif (scalar(@patterns) == 1) {
592 174         2188 push(@plans, $self->generate_plans( @patterns, $context, %args ));
593             } else {
594 13         90 push(@plans, map { $_->[0] } $self->_join_plans( $context, \@patterns, %args, method => 'patterns' ));
  13         52  
595             }
596            
597 205         548 push(@return_plans, @plans);
598             } elsif ($type eq 'Limit') {
599 8         39 my @base = $self->generate_plans( $algebra->pattern, $context, %args );
600 8         27 my @plans = map { RDF::Query::Plan::Limit->new( $algebra->limit, $_ ) } @base;
  8         33  
601 8         24 push(@return_plans, @plans);
602             } elsif ($type eq 'NamedGraph') {
603 14         34 my @plans;
604 14 100       56 if ($algebra->graph->isa('RDF::Query::Node::Resource')) {
605 4         22 @plans = $self->generate_plans( $algebra->pattern, $context, %args, active_graph => $algebra->graph );
606             } else {
607 10         40 @plans = map { RDF::Query::Plan::NamedGraph->new( $algebra->graph, $_ ) } $self->generate_plans( $algebra->pattern, $context, %args, active_graph => $algebra->graph );
  10         42  
608             }
609 14         38 push(@return_plans, @plans);
610             } elsif ($type eq 'Offset') {
611 3         13 my @base = $self->generate_plans( $algebra->pattern, $context, %args );
612 3         7 my @plans = map { RDF::Query::Plan::Offset->new( $algebra->offset, $_ ) } @base;
  3         13  
613 3         9 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         43 my @patterns = ($algebra->pattern, $algebra->optional);
617 11         28 my @base_plans = map { [ $self->generate_plans( $_, $context, %args ) ] } @patterns;
  22         153  
618 11         80 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         26 my @plans;
621 11         27 my $base_a = shift(@base_plans);
622 11         24 my $base_b = shift(@base_plans);
623 11         19 foreach my $i (0 .. $#{ $base_a }) {
  11         33  
624 11         27 foreach my $j (0 .. $#{ $base_b }) {
  11         28  
625 11         24 my $a = $base_a->[ $i ];
626 11         19 my $b = $base_b->[ $j ];
627 11         22 foreach my $join_type (@join_types) {
628             try {
629 22     20   689 my $plan = $join_type->new( $a, $b, 1, {} );
630 11         40 push( @plans, $plan );
631       10     } catch RDF::Query::Error::MethodInvocationError with {
632             # my $e = shift;
633             # warn "caught MethodInvocationError: " . Dumper($e);
634 22         335 };
635             }
636             }
637             }
638 11         277 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         476 my $pattern = $algebra->pattern;
656 134         458 my $vars = $algebra->vars;
657 134         1561 my @base = $self->generate_plans( $pattern, $context, %args );
658            
659 134 100       408 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         14 @base = $self->_add_constant_join( $context, $constant, @plans );
664 3         6 $constant = undef;
665             }
666            
667 134         228 my @plans;
668 134         307 foreach my $plan (@base) {
669 138         1716 push(@return_plans, RDF::Query::Plan::Project->new( $plan, $vars ));
670             }
671 134         350 push(@return_plans, @plans);
672             } elsif ($type eq 'Extend') {
673 24         87 my $pattern = $algebra->pattern;
674 24         76 my $vars = $algebra->vars;
675 24         258 my @base = $self->generate_plans( $pattern, $context, %args );
676 24         41 my @plans;
677 24         56 foreach my $plan (@base) {
678 24         161 push(@plans, RDF::Query::Plan::Extend->new( $plan, $vars ));
679             }
680 24         60 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         6 my $model = $context->model;
722 1         5 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         5 my ($plan) = $query->prepare( $context->model, planner_args => \%pargs );
742 1         20 push(@return_plans, RDF::Query::Plan::SubSelect->new( $query, $plan ));
743             }
744             } elsif ($type eq 'Sort') {
745 12         53 my @base = $self->generate_plans( $algebra->pattern, $context, %args );
746 12         56 my @order = $algebra->orderby;
747 12         25 my @neworder;
748 12         31 foreach my $o (@order) {
749 12         34 my ($dirname, $expr) = @$o;
750 12 100       44 my $dir = ($dirname eq 'ASC') ? 0 : 1;
751 12         40 push(@neworder, [$expr, $dir]);
752             }
753 12         29 my @plans = map { RDF::Query::Plan::Sort->new( $_, @neworder ) } @base;
  12         118  
754 12         36 push(@return_plans, @plans);
755             } elsif ($type eq 'Triple' or $type eq 'Quad') {
756 133         206 my $st;
757 133 100       334 if ($args{ prevent_distinguishing_bnodes }) {
758 40         53 $st = $algebra;
759             } else {
760 93         422 $st = $algebra->distinguish_bnode_variables;
761             }
762 133         2151 my $pred = $st->predicate;
763 133         1006 my @nodes = $st->nodes;
764            
765 133 100       940 if (my @csg_plans = $self->_csg_plans( $context, $st )) {
    100          
766 1         5 push(@return_plans, @csg_plans);
767             } elsif ($type eq 'Triple') {
768 24         112 my $plan = RDF::Query::Plan::Quad->new( @nodes[0..2], $active_graph, { sparql => $algebra->as_sparql, bf => $algebra->bf } );
769 24         124 push(@return_plans, $plan);
770             } else {
771 108 50       572 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         545 push(@return_plans, $plan);
775             }
776             } elsif ($type eq 'Path') {
777 5         31 my @plans = $self->_path_plans( $algebra, $context, %args );
778 5         15 push(@return_plans, @plans);
779             } elsif ($type eq 'Union') {
780 2         10 my @plans = map { [ $self->generate_plans( $_, $context, %args ) ] } $algebra->patterns;
  4         53  
781 2         5 my $plan = RDF::Query::Plan::Union->new( map { $_->[0] } @plans );
  4         21  
782 2         7 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     35 my $ds = $algebra->dataset || {};
797 8   100     39 my $default = $ds->{'default'} || [];
798 8   50     43 my $named = $ds->{'named'} || {};
799 8         17 my $dcount = scalar(@$default);
800 8         13 my $ncount = scalar(@{[ keys %$named ]});
  8         25  
801             # warn 'Update dataset: ' . Dumper($algebra->dataset);
802 8         10 my @plans;
803            
804 8         19 my @dataset = ($ds);
805 8 100 66     65 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         3 @dataset = ();
810 1         5 @plans = $self->generate_plans( $algebra->pattern, $context, %args, active_graph => $default->[0] );
811             } elsif ($dcount == 0 and $ncount == 0) {
812 7         12 @dataset = ();
813 7         26 @plans = $self->generate_plans( $algebra->pattern, $context, %args );
814             } else {
815 0         0 @plans = $self->generate_plans( $algebra->pattern, $context, %args );
816             }
817 8         22 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     1857 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         1377 foreach my $p (@return_plans) {
844 779 50       3216 Carp::confess 'not a plan: ' . Dumper($p) unless ($p->isa('RDF::Query::Plan'));
845 779         2258 $p->label( algebra => $algebra );
846             }
847            
848 773 50       1722 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         3003 return @return_plans;
852             }
853              
854             sub _csg_plans {
855 410     410   686 my $self = shift;
856 410         590 my $context = shift;
857 410         654 my $st = shift;
858 410         1132 my $pred = $st->predicate;
859 410 50       3123 return unless (blessed($context));
860 410         1464 my $query = $context->query;
861 410         755 my @return_plans;
862 410 100 66     3409 if (blessed($query) and $pred->isa('RDF::Trine::Node::Resource') and scalar(@{ $query->get_computed_statement_generators( $st->predicate->uri_value ) })) {
  406   100     1198  
863 2         6 my $csg = $query->get_computed_statement_generators( $pred->uri_value );
864 2         7 my @nodes = $st->nodes;
865 2 50       13 my $quad = (scalar(@nodes) == 4) ? 1 : 0;
866 2         15 my $mp = RDF::Query::Plan::ComputedStatement->new( @nodes[0..3], $quad );
867 2         5 push(@return_plans, $mp);
868             }
869 410         1858 return @return_plans;
870             }
871              
872             sub _join_plans {
873 27     27   57 my $self = shift;
874 27         48 my $context = shift;
875 27         49 my $triples = shift;
876 27         87 my %args = @_;
877 27   50     105 my $config = $context->options || {};
878            
879 27         99 my $method = $args{ method };
880 27         186 my @join_types = RDF::Query::Plan::Join->join_classes( $config );
881            
882 27         46 my @plans;
883 27         100 my $opt = $context->optimize;
884 27 50       103 my @slice = ($opt) ? (0 .. $#{ $triples }) : (0);
  0         0  
885 27         65 foreach my $i (@slice) {
886 27         68 my @triples = @$triples;
887             # pick a triple to use as the LHS
888 27         68 my ($t) = splice( @triples, $i, 1 );
889            
890 27         215 my @_lhs = $self->generate_plans( $t, $context, %args );
891 27         63 my @lhs_plans = map { [ $_, [$t] ] } @_lhs;
  27         112  
892 27 100       77 if (@triples) {
893 14         136 my @rhs_plans = $self->_join_plans( $context, \@triples, %args );
894 14         50 foreach my $i (0 .. $#lhs_plans) {
895 14         34 foreach my $j (0 .. $#rhs_plans) {
896 14         35 my $a = $lhs_plans[ $i ][0];
897 14         27 my $b = $rhs_plans[ $j ][0];
898 14         29 my $algebra_a = $lhs_plans[ $i ][1];
899 14         31 my $algebra_b = $rhs_plans[ $j ][1];
900 14 50       89 Carp::confess 'no lhs for join: ' . Dumper(\@lhs_plans) unless (blessed($a));
901 14 50       69 Carp::confess 'no rhs for join: ' . Dumper(\@triples, \@rhs_plans) unless (blessed($b));
902 14         37 foreach ($algebra_a, $algebra_b) {
903 28 50 33     215 unless (ref($_) and reftype($_) eq 'ARRAY') {
904 0         0 Carp::cluck Dumper($_)
905             }
906             }
907 14         40 foreach my $join_type (@join_types) {
908 28 50 66     451 next if ($join_type eq 'RDF::Query::Plan::Join::PushDownNestedLoop' and $b->subplans_of_type('RDF::Query::Plan::Service'));
909             try {
910 28     28   793 my @algebras;
911 28         62 foreach ($algebra_a, $algebra_b) {
912 56 50       205 if (reftype($_) eq 'ARRAY') {
913 56         122 push(@algebras, @$_);
914             }
915             }
916 28         45 my %logging_keys;
917 28 50       72 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         117 my $ggp = RDF::Query::Algebra::GroupGraphPattern->new( @algebras );
925 28         135 my $sparql = $ggp->as_sparql;
926 28         108 $logging_keys{ sparql } = $sparql;
927             }
928 28         250 my $plan = $join_type->new( $b, $a, 0, \%logging_keys );
929 27         124 push( @plans, [ $plan, [ @algebras ] ] );
930       1     } catch RDF::Query::Error::MethodInvocationError with {
931             # warn "caught MethodInvocationError.";
932 28         299 };
933             }
934             }
935             }
936             } else {
937 13         50 @plans = @lhs_plans;
938             }
939             }
940            
941 27 50       346 if ($opt) {
942 0         0 return @plans;
943             } else {
944 27 50       77 if (@plans) {
945 27         129 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   5 my $self = shift;
954 3         6 my $context = shift;
955 3         5 my $constant = shift;
956 3         8 my @return_plans = @_;
957 3   50     11 my $config = $context->options || {};
958 3         23 my @join_types = RDF::Query::Plan::Join->join_classes( $config );
959 3         13 while (my $const = shift(@$constant)) {
960 3         7 my @plans = splice(@return_plans);
961 3         8 foreach my $p (@plans) {
962 3         9 foreach my $join_type (@join_types) {
963             try {
964 6     6   180 my $plan = $join_type->new( $p, $const );
965 6         15 push( @return_plans, $plan );
966       0     } catch RDF::Query::Error::MethodInvocationError with {
967             # warn "caught MethodInvocationError.";
968 6         87 };
969             }
970             }
971             }
972 3         54 return @return_plans;
973             }
974              
975             sub _path_plans {
976 5     5   8 my $self = shift;
977 5         9 my $algebra = shift;
978 5         8 my $context = shift;
979 5         12 my %args = @_;
980 5         17 my $path = $algebra->path;
981 5         16 my $start = $algebra->start;
982 5         13 my $end = $algebra->end;
983 5         15 for ($start, $end) {
984 10 50       56 if ($_->isa('RDF::Query::Node::Blank')) {
985 0         0 $_ = $_->make_distinguished_variable;
986             }
987             }
988            
989 5         20 my $npath = $self->_normalize_path( $path );
990 5         31 return $self->__path_plan( $start, $npath, $end, $args{ active_graph }, $context, %args );
991             }
992              
993             sub _normalize_path {
994 17     17   23 my $self = shift;
995 17         24 my $path = shift;
996 17 100       56 if (blessed($path)) {
997 10         29 return $path;
998             }
999            
1000 7         13 my $op = $path->[0];
1001 7         15 my @nodes = map { $self->_normalize_path($_) } @{ $path }[ 1 .. $#{ $path } ];
  12         36  
  7         15  
  7         15  
1002 7 50       47 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       20 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         24 return [$op, @nodes];
1019             }
1020              
1021             sub __path_plan {
1022 47     47   67 my $self = shift;
1023 47         56 my $start = shift;
1024 47         56 my $path = shift;
1025 47         60 my $end = shift;
1026 47         66 my $graph = shift;
1027 47         59 my $context = shift;
1028 47         136 my %args = @_;
1029 47         67 my $distinct = 1; #$args{distinct} ? 1 : 0;
1030 47   50     144 my $config = $context->options || {};
1031 47         158 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       1397 if (my $a = $self->_simple_path( $start, $path, $end, $graph )) {
1037 43         1014 my ($plan) = $self->generate_plans( $a, $context, %args, prevent_distinguishing_bnodes => 1 );
1038 43         134 $l->trace('expanded path to pattern: ' . $plan->sse);
1039 43         426 return $plan;
1040             }
1041            
1042            
1043 4 50       15 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         12 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       28 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         5 my $count = scalar(@nodes);
1085 2 50       7 if ($count == 1) {
1086 0         0 return $self->__path_plan( $start, $nodes[0], $end, $graph, $context, %args );
1087             } else {
1088 2         8 my $joinvar = RDF::Query::Node::Variable->new();
1089 2         32 my @plans = $self->__path_plan( $start, $nodes[0], $joinvar, $graph, $context, %args );
1090 2         6 foreach my $i (2 .. $count) {
1091 2 50       8 my $endvar = ($i == $count) ? $end : RDF::Query::Node::Variable->new();
1092 2         9 my ($rhs) = $self->__path_plan( $joinvar, $nodes[$i-1], $endvar, $graph, $context, %args );
1093 2         5 push(@plans, $rhs);
1094 2         6 $joinvar = $endvar;
1095             }
1096 2         10 my @join_types = RDF::Query::Plan::Join->join_classes( $config );
1097 2         4 my @jplans;
1098 2         5 foreach my $jclass (@join_types) {
1099 4         31 push(@jplans, $jclass->new( @plans[0,1], 0 ));
1100             }
1101 2         9 $l->trace("expanded /-path to: " . $jplans[0]->sse);
1102 2         37 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         9 return RDF::Query::Plan::Path->new( 'ZeroOrMorePath', $start, $nodes[0], $end, $graph, $distinct, %args );
1126             } elsif ($op eq '+') {
1127             ### X path+ Y
1128 1         14 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   78 my $self = shift;
1197 55         71 my $start = shift;
1198 55         66 my $path = shift;
1199 55         63 my $end = shift;
1200 55         75 my $graph = shift;
1201 55 100       201 if (blessed($path)) {
1202 46 100       160 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       34 return unless (reftype($path) eq 'ARRAY');
1207 9         20 my $op = $path->[0];
1208 9 100 33     46 if ($op eq '/') {
    50 33        
    50 33        
1209 5         9 my @patterns;
1210 5         10 my @jvars = map { RDF::Query::Node::Variable->new() } (2 .. $#{ $path });
  5         19  
  5         14  
1211 5         41 foreach my $i (1 .. $#{ $path }) {
  5         15  
1212 8 100       21 my $s = ($i == 1) ? $start : $jvars[ $i-2 ];
1213 8 100       10 my $e = ($i == $#{ $path }) ? $end : $jvars[ $i-1 ];
  8         28  
1214 8         29 my $triple = $self->_simple_path( $s, $path->[ $i ], $e, $graph );
1215 8 100       146 return unless ($triple);
1216 6         14 push(@patterns, $triple);
1217             }
1218 3 50       6 my @triples = map { $_->isa('RDF::Query::Algebra::BasicGraphPattern') ? $_->triples : $_ } @patterns;
  6         38  
1219 3         14 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         10 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 99 my $self = shift;
1288 55         97 my $type = shift;
1289 55         86 my $block = shift;
1290            
1291 55 50 33     178 return if ($block and $self->isa($block));
1292            
1293 55         78 my @patterns;
1294 55 50       361 push(@patterns, $self) if ($self->isa($type));
1295            
1296            
1297 55         246 foreach my $p ($self->plan_node_data) {
1298 192 100 100     1497 if (blessed($p) and $p->isa('RDF::Query::Plan')) {
1299 11         52 push(@patterns, $p->subplans_of_type($type, $block));
1300             }
1301             }
1302 55         218 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