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.915_01.
11              
12             =head1 METHODS
13              
14             =over 4
15              
16             =cut
17              
18             package RDF::Query::Plan;
19              
20 35     35   208 use strict;
  35         77  
  35         942  
21 35     35   195 use warnings;
  35         82  
  35         954  
22 35     35   253 use Data::Dumper;
  35         118  
  35         1732  
23 35     35   196 use List::Util qw(reduce);
  35         74  
  35         2097  
24 35     35   182 use Scalar::Util qw(blessed reftype refaddr);
  35         83  
  35         1981  
25 35     35   218 use RDF::Query::Error qw(:try);
  35         86  
  35         247  
26 35     35   23555 use RDF::Query::BGPOptimizer;
  35         108  
  35         1093  
27              
28 35     35   23293 use RDF::Query::Plan::Aggregate;
  35         117  
  35         1086  
29 35     35   21459 use RDF::Query::Plan::BasicGraphPattern;
  35         107  
  35         1001  
30 35     35   20411 use RDF::Query::Plan::Constant;
  35         142  
  35         1039  
31 35     35   20745 use RDF::Query::Plan::Construct;
  35         109  
  35         1095  
32 35     35   20761 use RDF::Query::Plan::Distinct;
  35         105  
  35         1270  
33 35     35   21032 use RDF::Query::Plan::Filter;
  35         102  
  35         1232  
34 35     35   22560 use RDF::Query::Plan::Join::NestedLoop;
  35         165  
  35         1376  
35 35     35   23101 use RDF::Query::Plan::Join::PushDownNestedLoop;
  35         91  
  35         1505  
36 35     35   20635 use RDF::Query::Plan::Limit;
  35         94  
  35         1451  
37 35     35   20582 use RDF::Query::Plan::Offset;
  35         91  
  35         1498  
38 35     35   20592 use RDF::Query::Plan::Project;
  35         98  
  35         1630  
39 35     35   20857 use RDF::Query::Plan::Extend;
  35         109  
  35         1813  
40 35     35   21786 use RDF::Query::Plan::Quad;
  35         109  
  35         1908  
41 35     35   22987 use RDF::Query::Plan::Service;
  35         115  
  35         2012  
42 35     35   21814 use RDF::Query::Plan::Sort;
  35         102  
  35         2102  
43 35     35   21933 use RDF::Query::Plan::ComputedStatement;
  35         106  
  35         1964  
44 35     35   21293 use RDF::Query::Plan::ThresholdUnion;
  35         112  
  35         1996  
45 35     35   21099 use RDF::Query::Plan::Union;
  35         108  
  35         2026  
46 35     35   21043 use RDF::Query::Plan::SubSelect;
  35         106  
  35         2004  
47 35     35   20711 use RDF::Query::Plan::Iterator;
  35         102  
  35         2190  
48 35     35   20317 use RDF::Query::Plan::Load;
  35         109  
  35         2235  
49 35     35   20934 use RDF::Query::Plan::Clear;
  35         103  
  35         2175  
50 35     35   21576 use RDF::Query::Plan::Update;
  35         102  
  35         2292  
51 35     35   21534 use RDF::Query::Plan::Minus;
  35         101  
  35         2472  
52 35     35   21058 use RDF::Query::Plan::Sequence;
  35         102  
  35         2380  
53 35     35   22247 use RDF::Query::Plan::Path;
  35         116  
  35         2668  
54 35     35   21934 use RDF::Query::Plan::NamedGraph;
  35         110  
  35         2409  
55 35     35   20770 use RDF::Query::Plan::Copy;
  35         104  
  35         2471  
56 35     35   21273 use RDF::Query::Plan::Move;
  35         112  
  35         2522  
57              
58 35     35   230 use RDF::Trine::Statement;
  35         73  
  35         1055  
59 35     35   204 use RDF::Trine::Statement::Quad;
  35         73  
  35         1151  
60              
61 35     35   191 use constant READY => 0x01;
  35         68  
  35         3107  
62 35     35   185 use constant OPEN => 0x02;
  35         76  
  35         2259  
63 35     35   187 use constant CLOSED => 0x04;
  35         76  
  35         3574  
64              
65             ######################################################################
66              
67             our ($VERSION, %PLAN_CLASSES);
68             BEGIN {
69 35     35   112 $VERSION = '2.915_01';
70 35         20587 %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 853 my $class = shift;
83 556         1059 my @args = @_;
84 556         4714 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 29 my $self = shift;
107 12 50       33 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         23 my @rows;
111 12         58 while (my $row = $self->next) {
112 34         138 push(@rows, $row);
113             }
114 12         58 return @rows;
115             }
116              
117             =item C<< close >>
118              
119             =cut
120              
121             sub close {
122 590     590 1 892 my $self = shift;
123 590         1277 $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 5078     5078 1 6737 my $self = shift;
135 5078 100       10900 if (scalar(@_)) {
136 1190         2062 $self->[0]{__state} = shift;
137             }
138 5078         25858 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   212 no warnings 'uninitialized';
  35         87  
  35         19186  
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 398 my $self = shift;
194 188   100     702 my $context = shift || {};
195 188   100     717 my $indent = shift || '';
196 188         272 my $more = ' ';
197 188         1389 my @proto = $self->plan_prototype;
198 188         680 my @data = $self->plan_node_data;
199 188         631 my $name = $self->plan_node_name;
200            
201 188         268 my @args;
202 188         265 my $list = \@data;
203 188         424 foreach my $i (0 .. $#proto) {
204 583         6667 my $p = $proto[ $i ];
205 583         1517 push(@args, $self->_sse( $context, $indent, $more, $p, $list ));
206             }
207 188 100       1126 return "(${name} " . join(' ', map { defined($_) ? $_ : '()' } @args) . ")";
  583         2488  
208             }
209              
210             sub _sse {
211 687     687   889 my $self = shift;
212 687         808 my $context = shift;
213 687         818 my $indent = shift;
214 687         830 my $more = shift;
215 687         877 my $p = shift;
216 687         873 my $list = shift;
217 687 100       2096 if ($p =~ m/^[PQTNWEJVqibswu]$/) {
    50          
    100          
    50          
    0          
218 630         893 my $v = shift(@$list);
219 630         1622 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         52 my $rest = substr($p, 1);
229 26         43 my $v = shift(@$list);
230 26         44 my @args;
231 26         151 while (@$v) {
232 30         198 push(@args, $self->_sse( $context, $indent, $more, $rest, $v ));
233             }
234 26         567 return '(' . join(' ', @args) . ')';
235             } elsif (substr($p, 0, 1) eq '*') {
236 31         58 my $rest = substr($p, 1);
237 31         44 my @args;
238 31         90 while (@$list) {
239 74         2568 push(@args, $self->_sse( $context, $indent, $more, $rest, $list ));
240             }
241 35     35   198 no warnings 'uninitialized';
  35         85  
  35         10223  
242 31         1494 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   788 my $self = shift;
257 630   50     1375 my $context = shift || {};
258 630         807 my $indent = shift;
259 630         777 my $more = shift;
260 630         745 my $p = shift;
261 630         718 my $v = shift;
262 35     35   208 no warnings 'uninitialized';
  35         74  
  35         335978  
263            
264 630   100     2159 my $ns = $context->{ namespaces } || {};
265 630         1309 my %ns = %$ns;
266            
267 630 100       3902 if ($p eq 's') {
    50          
    50          
    100          
    50          
    50          
    100          
    50          
268 8         16 for ($v) {
269 8         13 s/\\/\\\\/g;
270 8         13 s/"/\\"/g;
271 8         13 s/\n/\\n/g;
272 8         15 s/\t/\\t/g;
273             }
274 8         38 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       1865 if (blessed($v)) {
291            
292 580 50       2247 Carp::cluck unless ($v->can('sse'));
293 580         2928 return $v->sse( { namespaces => \%ns }, "${indent}${more}" );
294             } else {
295 9         37 return '()';
296             }
297             } elsif ($p eq 'J') {
298 30 100       147 if ($v->isa('RDF::Query::Node::Variable')) {
299 2         6 return $v->name;
300             } else {
301 28         166 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 978     978 1 1661 my $self = shift;
325 978         4506 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 550 my $self = shift;
336 366         621 my $refs = $self->[0]{referenced_variables};
337 366         489 return @{ $refs };
  366         1679  
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 282 my $self = shift;
348 153         280 my $context = shift;
349 153         649 my $vars = $context->requested_variables;
350 153     321   1890 my $stream = RDF::Trine::Iterator::Bindings->new( sub { $self->next }, $vars, distinct => $self->distinct, sorted_by => $self->ordered );
  321         156350  
351 153         7013 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 1018 my $self = shift;
374 779         1053 my $label = shift;
375 779 50       1707 if (@_) {
376 779         946 my $value = shift;
377 779         2120 $self->[0]{labels}{ $label } = $value;
378             }
379 779         1987 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   81299 my $self = shift;
402 546 100       1303 if ($self->state == OPEN) {
403 205         818 $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 1239 my $self = shift;
417 773   33     2959 my $class = ref($self) || $self;
418 773         1052 my $algebra = shift;
419 773         994 my $context = shift;
420 773   100     2389 my $config = $context->options || {};
421            
422 773         1885 my %args = @_;
423 773   66     4047 my $active_graph = $args{ active_graph } || RDF::Trine::Node::Nil->new();
424            
425 773         12774 my $l = Log::Log4perl->get_logger("rdf.query.plan");
426 773 50 33     29250 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         3995 $l->trace("generating query plan for " . $algebra->sse({ indent => ' ' }, ''));
431            
432             ############################################################################
433             ### Optimize simple COUNT(*) aggregates over BGPs
434 773 100       22809 if ($algebra->isa('RDF::Query::Algebra::Extend')) {
435 24         87 my $agg = $algebra->pattern;
436 24 100       131 if ($agg->isa('RDF::Query::Algebra::Aggregate')) {
437 15         47 my @group = $agg->groupby;
438 15 100       53 if (scalar(@group) == 0) {
439 9         30 my @ops = $agg->ops;
440 9 50 66     70 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         1106 my ($project);
477 773         1311 my $constant = delete $args{ constants };
478            
479 773 100 100     5978 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         994 $project = delete $args{ project };
482             }
483            
484 773         1090 my @return_plans;
485 773         1330 my $aclass = ref($algebra);
486 773         4602 my ($type) = ($aclass =~ m<::(\w+)$>);
487 773 100 100     7602 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         60 my @base = $self->generate_plans( $algebra->pattern, $context, %args );
489 16         60 my @groups = $algebra->groupby;
490 16         29 my @ops;
491 16         48 foreach my $o ($algebra->ops) {
492 18         54 my ($alias, $op, $opts, @cols) = @$o;
493 18         62 push(@ops, [ $alias, $op, $opts, @cols ]);
494             }
495 16         43 my @plans = map { RDF::Query::Plan::Aggregate->new( $_, \@groups, expressions => \@ops ) } @base;
  16         89  
496 16         39 push(@return_plans, @plans);
497             } elsif ($type eq 'Construct') {
498 4         15 my $triples = $algebra->triples;
499 4         17 my @base = $self->generate_plans( $algebra->pattern, $context, %args );
500 4         11 my @plans = map { RDF::Query::Plan::Construct->new( $_, $triples ) } @base;
  4         28  
501 4         11 push(@return_plans, @plans);
502             } elsif ($type eq 'Distinct') {
503 10         43 my @base = $self->generate_plans( $algebra->pattern, $context, %args );
504 10         25 my @plans = map { RDF::Query::Plan::Distinct->new( $_ ) } @base;
  10         94  
505 10         26 push(@return_plans, @plans);
506             } elsif ($type eq 'Filter') {
507 37         156 my @base = $self->generate_plans( $algebra->pattern, $context, %args, active_graph => $active_graph );
508 37         149 my $expr = $algebra->expr;
509 37         91 my @plans = map { RDF::Query::Plan::Filter->new( $expr, $_, $active_graph ) } @base;
  37         229  
510 37         88 push(@return_plans, @plans);
511             } elsif ($type eq 'BasicGraphPattern') {
512             my @triples = map {
513 146         535 ($args{ prevent_distinguishing_bnodes })
514 277 100       3366 ? $_
515             : $_->distinguish_bnode_variables
516             } $algebra->triples;
517 146         2638 my @normal_triples;
518             my @csg_triples;
519 146         341 foreach my $t (@triples) {
520 277 100       1001 if (my @csg_plans = $self->_csg_plans( $context, $t )) {
521 1         9 push(@csg_triples, $t);
522             } else {
523 276         814 my @nodes = $t->nodes;
524 276         2339 $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         5947 push(@normal_triples, $t);
530             }
531             }
532            
533 146         254 my @plans;
534 146 50       530 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         941 push(@plans, $self->generate_plans( @normal_triples, $context, %args ));
540             } else {
541 76         615 my $plan = RDF::Query::Plan::BasicGraphPattern->new( @normal_triples );
542 76         179 push(@plans, $plan);
543             }
544            
545 146 100       416 if (@csg_triples) {
546 1         3 my @csg_plans;
547 1         2 foreach my $t (@csg_triples) {
548 1         5 push(@csg_plans, [ $self->generate_plans( $t, $context, %args ) ]);
549             }
550 1         13 my @join_types = RDF::Query::Plan::Join->join_classes( $config );
551 1         5 while (my $cps = shift(@csg_plans)) {
552 1         3 my @temp_plans = @plans;
553 1         3 @plans = ();
554 1         2 foreach my $p (@temp_plans) {
555 1         3 foreach my $cp (@$cps) {
556 1         2 foreach my $join_type (@join_types) {
557 2         23 my $plan = $join_type->new( $p, $cp, 0, {} );
558 2         8 push(@plans, $plan);
559             }
560             }
561             }
562             }
563 1         4 push(@return_plans, @plans);
564             } else {
565 145         569 push(@return_plans, @plans);
566             }
567            
568             } elsif ($type eq 'GroupGraphPattern') {
569 205         711 my @input = $algebra->patterns();
570 205         359 my @patterns;
571 205         678 while (my $a = shift(@input)) {
572 201 50       1342 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         689 push(@patterns, $a);
584             }
585            
586 205         296 my @plans;
587 205 100       750 if (scalar(@patterns) == 0) {
    100          
588 18         104 my $v = RDF::Query::VariableBindings->new( {} );
589 18         139 my $plan = RDF::Query::Plan::Constant->new( $v );
590 18         41 push(@plans, $plan);
591             } elsif (scalar(@patterns) == 1) {
592 174         2092 push(@plans, $self->generate_plans( @patterns, $context, %args ));
593             } else {
594 13         86 push(@plans, map { $_->[0] } $self->_join_plans( $context, \@patterns, %args, method => 'patterns' ));
  13         59  
595             }
596            
597 205         564 push(@return_plans, @plans);
598             } elsif ($type eq 'Limit') {
599 8         36 my @base = $self->generate_plans( $algebra->pattern, $context, %args );
600 8         21 my @plans = map { RDF::Query::Plan::Limit->new( $algebra->limit, $_ ) } @base;
  8         37  
601 8         19 push(@return_plans, @plans);
602             } elsif ($type eq 'NamedGraph') {
603 14         39 my @plans;
604 14 100       47 if ($algebra->graph->isa('RDF::Query::Node::Resource')) {
605 4         20 @plans = $self->generate_plans( $algebra->pattern, $context, %args, active_graph => $algebra->graph );
606             } else {
607 10         38 @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         49 push(@return_plans, @plans);
610             } elsif ($type eq 'Offset') {
611 3         14 my @base = $self->generate_plans( $algebra->pattern, $context, %args );
612 3         7 my @plans = map { RDF::Query::Plan::Offset->new( $algebra->offset, $_ ) } @base;
  3         11  
613 3         6 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         41 my @patterns = ($algebra->pattern, $algebra->optional);
617 11         27 my @base_plans = map { [ $self->generate_plans( $_, $context, %args ) ] } @patterns;
  22         186  
618 11         85 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         20 my @plans;
621 11         21 my $base_a = shift(@base_plans);
622 11         23 my $base_b = shift(@base_plans);
623 11         20 foreach my $i (0 .. $#{ $base_a }) {
  11         32  
624 11         31 foreach my $j (0 .. $#{ $base_b }) {
  11         26  
625 11         24 my $a = $base_a->[ $i ];
626 11         21 my $b = $base_b->[ $j ];
627 11         26 foreach my $join_type (@join_types) {
628             try {
629 22     20   653 my $plan = $join_type->new( $a, $b, 1, {} );
630 11         41 push( @plans, $plan );
631       10     } catch RDF::Query::Error::MethodInvocationError with {
632             # my $e = shift;
633             # warn "caught MethodInvocationError: " . Dumper($e);
634 22         323 };
635             }
636             }
637             }
638 11         255 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         488 my $pattern = $algebra->pattern;
656 134         443 my $vars = $algebra->vars;
657 134         1623 my @base = $self->generate_plans( $pattern, $context, %args );
658            
659 134 100       400 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         7 my @plans = splice( @base );
663 3         13 @base = $self->_add_constant_join( $context, $constant, @plans );
664 3         7 $constant = undef;
665             }
666            
667 134         190 my @plans;
668 134         285 foreach my $plan (@base) {
669 138         1638 push(@return_plans, RDF::Query::Plan::Project->new( $plan, $vars ));
670             }
671 134         316 push(@return_plans, @plans);
672             } elsif ($type eq 'Extend') {
673 24         79 my $pattern = $algebra->pattern;
674 24         70 my $vars = $algebra->vars;
675 24         242 my @base = $self->generate_plans( $pattern, $context, %args );
676 24         40 my @plans;
677 24         50 foreach my $plan (@base) {
678 24         144 push(@plans, RDF::Query::Plan::Extend->new( $plan, $vars ));
679             }
680 24         56 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         4 my $query = $algebra->query;
721 1         4 my $model = $context->model;
722 1         3 my %pargs = %args;
723 1         3 my $ag = $args{ active_graph };
724 1 50 33     8 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         4 my ($plan) = $query->prepare( $context->model, planner_args => \%pargs );
742 1         19 push(@return_plans, RDF::Query::Plan::SubSelect->new( $query, $plan ));
743             }
744             } elsif ($type eq 'Sort') {
745 12         52 my @base = $self->generate_plans( $algebra->pattern, $context, %args );
746 12         54 my @order = $algebra->orderby;
747 12         24 my @neworder;
748 12         30 foreach my $o (@order) {
749 12         33 my ($dirname, $expr) = @$o;
750 12 100       45 my $dir = ($dirname eq 'ASC') ? 0 : 1;
751 12         40 push(@neworder, [$expr, $dir]);
752             }
753 12         25 my @plans = map { RDF::Query::Plan::Sort->new( $_, @neworder ) } @base;
  12         100  
754 12         38 push(@return_plans, @plans);
755             } elsif ($type eq 'Triple' or $type eq 'Quad') {
756 133         201 my $st;
757 133 100       376 if ($args{ prevent_distinguishing_bnodes }) {
758 40         58 $st = $algebra;
759             } else {
760 93         443 $st = $algebra->distinguish_bnode_variables;
761             }
762 133         2059 my $pred = $st->predicate;
763 133         865 my @nodes = $st->nodes;
764            
765 133 100       900 if (my @csg_plans = $self->_csg_plans( $context, $st )) {
    100          
766 1         6 push(@return_plans, @csg_plans);
767             } elsif ($type eq 'Triple') {
768 24         118 my $plan = RDF::Query::Plan::Quad->new( @nodes[0..2], $active_graph, { sparql => $algebra->as_sparql, bf => $algebra->bf } );
769 24         114 push(@return_plans, $plan);
770             } else {
771 108 50       564 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         461 push(@return_plans, $plan);
775             }
776             } elsif ($type eq 'Path') {
777 5         27 my @plans = $self->_path_plans( $algebra, $context, %args );
778 5         17 push(@return_plans, @plans);
779             } elsif ($type eq 'Union') {
780 2         10 my @plans = map { [ $self->generate_plans( $_, $context, %args ) ] } $algebra->patterns;
  4         54  
781 2         6 my $plan = RDF::Query::Plan::Union->new( map { $_->[0] } @plans );
  4         24  
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     29 my $ds = $algebra->dataset || {};
797 8   100     42 my $default = $ds->{'default'} || [];
798 8   50     42 my $named = $ds->{'named'} || {};
799 8         16 my $dcount = scalar(@$default);
800 8         13 my $ncount = scalar(@{[ keys %$named ]});
  8         21  
801             # warn 'Update dataset: ' . Dumper($algebra->dataset);
802 8         13 my @plans;
803            
804 8         19 my @dataset = ($ds);
805 8 100 66     60 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         6 @plans = $self->generate_plans( $algebra->pattern, $context, %args, active_graph => $default->[0] );
811             } elsif ($dcount == 0 and $ncount == 0) {
812 7         17 @dataset = ();
813 7         25 @plans = $self->generate_plans( $algebra->pattern, $context, %args );
814             } else {
815 0         0 @plans = $self->generate_plans( $algebra->pattern, $context, %args );
816             }
817 8         21 foreach my $p (@plans) {
818 8         32 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     1829 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         1430 foreach my $p (@return_plans) {
844 779 50       3114 Carp::confess 'not a plan: ' . Dumper($p) unless ($p->isa('RDF::Query::Plan'));
845 779         2203 $p->label( algebra => $algebra );
846             }
847            
848 773 50       1731 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         2811 return @return_plans;
852             }
853              
854             sub _csg_plans {
855 410     410   635 my $self = shift;
856 410         616 my $context = shift;
857 410         557 my $st = shift;
858 410         1108 my $pred = $st->predicate;
859 410 50       2918 return unless (blessed($context));
860 410         1429 my $query = $context->query;
861 410         721 my @return_plans;
862 410 100 66     3312 if (blessed($query) and $pred->isa('RDF::Trine::Node::Resource') and scalar(@{ $query->get_computed_statement_generators( $st->predicate->uri_value ) })) {
  406   100     1179  
863 2         7 my $csg = $query->get_computed_statement_generators( $pred->uri_value );
864 2         9 my @nodes = $st->nodes;
865 2 50       14 my $quad = (scalar(@nodes) == 4) ? 1 : 0;
866 2         18 my $mp = RDF::Query::Plan::ComputedStatement->new( @nodes[0..3], $quad );
867 2         5 push(@return_plans, $mp);
868             }
869 410         1855 return @return_plans;
870             }
871              
872             sub _join_plans {
873 27     27   57 my $self = shift;
874 27         48 my $context = shift;
875 27         40 my $triples = shift;
876 27         92 my %args = @_;
877 27   50     103 my $config = $context->options || {};
878            
879 27         68 my $method = $args{ method };
880 27         173 my @join_types = RDF::Query::Plan::Join->join_classes( $config );
881            
882 27         44 my @plans;
883 27         101 my $opt = $context->optimize;
884 27 50       98 my @slice = ($opt) ? (0 .. $#{ $triples }) : (0);
  0         0  
885 27         60 foreach my $i (@slice) {
886 27         74 my @triples = @$triples;
887             # pick a triple to use as the LHS
888 27         68 my ($t) = splice( @triples, $i, 1 );
889            
890 27         216 my @_lhs = $self->generate_plans( $t, $context, %args );
891 27         72 my @lhs_plans = map { [ $_, [$t] ] } @_lhs;
  27         104  
892 27 100       72 if (@triples) {
893 14         117 my @rhs_plans = $self->_join_plans( $context, \@triples, %args );
894 14         52 foreach my $i (0 .. $#lhs_plans) {
895 14         37 foreach my $j (0 .. $#rhs_plans) {
896 14         33 my $a = $lhs_plans[ $i ][0];
897 14         47 my $b = $rhs_plans[ $j ][0];
898 14         27 my $algebra_a = $lhs_plans[ $i ][1];
899 14         29 my $algebra_b = $rhs_plans[ $j ][1];
900 14 50       88 Carp::confess 'no lhs for join: ' . Dumper(\@lhs_plans) unless (blessed($a));
901 14 50       66 Carp::confess 'no rhs for join: ' . Dumper(\@triples, \@rhs_plans) unless (blessed($b));
902 14         40 foreach ($algebra_a, $algebra_b) {
903 28 50 33     207 unless (ref($_) and reftype($_) eq 'ARRAY') {
904 0         0 Carp::cluck Dumper($_)
905             }
906             }
907 14         38 foreach my $join_type (@join_types) {
908 28 50 66     452 next if ($join_type eq 'RDF::Query::Plan::Join::PushDownNestedLoop' and $b->subplans_of_type('RDF::Query::Plan::Service'));
909             try {
910 28     28   795 my @algebras;
911 28         65 foreach ($algebra_a, $algebra_b) {
912 56 50       211 if (reftype($_) eq 'ARRAY') {
913 56         118 push(@algebras, @$_);
914             }
915             }
916 28         44 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         130 my $ggp = RDF::Query::Algebra::GroupGraphPattern->new( @algebras );
925 28         125 my $sparql = $ggp->as_sparql;
926 28         114 $logging_keys{ sparql } = $sparql;
927             }
928 28         241 my $plan = $join_type->new( $b, $a, 0, \%logging_keys );
929 27         147 push( @plans, [ $plan, [ @algebras ] ] );
930       1     } catch RDF::Query::Error::MethodInvocationError with {
931             # warn "caught MethodInvocationError.";
932 28         285 };
933             }
934             }
935             }
936             } else {
937 13         46 @plans = @lhs_plans;
938             }
939             }
940            
941 27 50       365 if ($opt) {
942 0         0 return @plans;
943             } else {
944 27 50       70 if (@plans) {
945 27         132 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   7 my $self = shift;
954 3         6 my $context = shift;
955 3         6 my $constant = shift;
956 3         7 my @return_plans = @_;
957 3   50     12 my $config = $context->options || {};
958 3         24 my @join_types = RDF::Query::Plan::Join->join_classes( $config );
959 3         12 while (my $const = shift(@$constant)) {
960 3         6 my @plans = splice(@return_plans);
961 3         7 foreach my $p (@plans) {
962 3         6 foreach my $join_type (@join_types) {
963             try {
964 6     6   182 my $plan = $join_type->new( $p, $const );
965 6         14 push( @return_plans, $plan );
966       0     } catch RDF::Query::Error::MethodInvocationError with {
967             # warn "caught MethodInvocationError.";
968 6         112 };
969             }
970             }
971             }
972 3         53 return @return_plans;
973             }
974              
975             sub _path_plans {
976 5     5   8 my $self = shift;
977 5         8 my $algebra = shift;
978 5         8 my $context = shift;
979 5         13 my %args = @_;
980 5         18 my $path = $algebra->path;
981 5         15 my $start = $algebra->start;
982 5         16 my $end = $algebra->end;
983 5         13 for ($start, $end) {
984 10 50       54 if ($_->isa('RDF::Query::Node::Blank')) {
985 0         0 $_ = $_->make_distinguished_variable;
986             }
987             }
988            
989 5         18 my $npath = $self->_normalize_path( $path );
990 5         28 return $self->__path_plan( $start, $npath, $end, $args{ active_graph }, $context, %args );
991             }
992              
993             sub _normalize_path {
994 17     17   24 my $self = shift;
995 17         25 my $path = shift;
996 17 100       51 if (blessed($path)) {
997 10         32 return $path;
998             }
999            
1000 7         15 my $op = $path->[0];
1001 7         14 my @nodes = map { $self->_normalize_path($_) } @{ $path }[ 1 .. $#{ $path } ];
  12         32  
  7         15  
  7         14  
1002 7 50       41 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       21 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         20 return [$op, @nodes];
1019             }
1020              
1021             sub __path_plan {
1022 47     47   67 my $self = shift;
1023 47         67 my $start = shift;
1024 47         62 my $path = shift;
1025 47         59 my $end = shift;
1026 47         57 my $graph = shift;
1027 47         66 my $context = shift;
1028 47         127 my %args = @_;
1029 47         64 my $distinct = 1; #$args{distinct} ? 1 : 0;
1030 47   50     144 my $config = $context->options || {};
1031 47         164 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       1348 if (my $a = $self->_simple_path( $start, $path, $end, $graph )) {
1037 43         973 my ($plan) = $self->generate_plans( $a, $context, %args, prevent_distinguishing_bnodes => 1 );
1038 43         129 $l->trace('expanded path to pattern: ' . $plan->sse);
1039 43         455 return $plan;
1040             }
1041            
1042            
1043 4 50       16 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       25 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       6 if ($count == 1) {
1086 0         0 return $self->__path_plan( $start, $nodes[0], $end, $graph, $context, %args );
1087             } else {
1088 2         9 my $joinvar = RDF::Query::Node::Variable->new();
1089 2         29 my @plans = $self->__path_plan( $start, $nodes[0], $joinvar, $graph, $context, %args );
1090 2         7 foreach my $i (2 .. $count) {
1091 2 50       7 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         8 my @join_types = RDF::Query::Plan::Join->join_classes( $config );
1097 2         5 my @jplans;
1098 2         4 foreach my $jclass (@join_types) {
1099 4         38 push(@jplans, $jclass->new( @plans[0,1], 0 ));
1100             }
1101 2         9 $l->trace("expanded /-path to: " . $jplans[0]->sse);
1102 2         22 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         8 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   75 my $self = shift;
1197 55         69 my $start = shift;
1198 55         65 my $path = shift;
1199 55         80 my $end = shift;
1200 55         69 my $graph = shift;
1201 55 100       199 if (blessed($path)) {
1202 46 100       163 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       35 return unless (reftype($path) eq 'ARRAY');
1207 9         18 my $op = $path->[0];
1208 9 100 33     48 if ($op eq '/') {
    50 33        
    50 33        
1209 5         10 my @patterns;
1210 5         8 my @jvars = map { RDF::Query::Node::Variable->new() } (2 .. $#{ $path });
  5         18  
  5         10  
1211 5         43 foreach my $i (1 .. $#{ $path }) {
  5         13  
1212 8 100       21 my $s = ($i == 1) ? $start : $jvars[ $i-2 ];
1213 8 100       11 my $e = ($i == $#{ $path }) ? $end : $jvars[ $i-1 ];
  8         25  
1214 8         32 my $triple = $self->_simple_path( $s, $path->[ $i ], $e, $graph );
1215 8 100       137 return unless ($triple);
1216 6         13 push(@patterns, $triple);
1217             }
1218 3 50       7 my @triples = map { $_->isa('RDF::Query::Algebra::BasicGraphPattern') ? $_->triples : $_ } @patterns;
  6         35  
1219 3         17 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 91 my $self = shift;
1288 55         93 my $type = shift;
1289 55         86 my $block = shift;
1290            
1291 55 50 33     188 return if ($block and $self->isa($block));
1292            
1293 55         79 my @patterns;
1294 55 50       347 push(@patterns, $self) if ($self->isa($type));
1295            
1296            
1297 55         224 foreach my $p ($self->plan_node_data) {
1298 192 100 100     1512 if (blessed($p) and $p->isa('RDF::Query::Plan')) {
1299 11         52 push(@patterns, $p->subplans_of_type($type, $block));
1300             }
1301             }
1302 55         226 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