File Coverage

blib/lib/RDF/Query/Plan/Join/PushDownNestedLoop.pm
Criterion Covered Total %
statement 112 153 73.2
branch 26 46 56.5
condition 11 14 78.5
subroutine 15 18 83.3
pod 7 7 100.0
total 171 238 71.8


line stmt bran cond sub pod time code
1             # RDF::Query::Plan::Join::PushDownNestedLoop
2             # -----------------------------------------------------------------------------
3              
4             =head1 NAME
5              
6             RDF::Query::Plan::Join::PushDownNestedLoop - Executable query plan for nested loop joins.
7              
8             =head1 VERSION
9              
10             This document describes RDF::Query::Plan::Join::PushDownNestedLoop version 2.918.
11              
12             =head1 METHODS
13              
14             Beyond the methods documented below, this class inherits methods from the
15             L<RDF::Query::Plan::Join> class.
16              
17             =over 4
18              
19             =cut
20              
21             package RDF::Query::Plan::Join::PushDownNestedLoop;
22              
23 35     35   122 use strict;
  35         46  
  35         848  
24 35     35   111 use warnings;
  35         41  
  35         821  
25 35     35   113 use base qw(RDF::Query::Plan::Join);
  35         86  
  35         2155  
26 35     35   155 use Scalar::Util qw(blessed refaddr);
  35         46  
  35         1478  
27 35     35   135 use Data::Dumper;
  35         44  
  35         2190  
28              
29             ######################################################################
30              
31             our ($VERSION);
32             BEGIN {
33 35     35   57 $VERSION = '2.918';
34 35         529 $RDF::Query::Plan::Join::JOIN_CLASSES{ 'RDF::Query::Plan::Join::PushDownNestedLoop' }++;
35             }
36              
37             ######################################################################
38              
39 35     35   139 use RDF::Query::ExecutionContext;
  35         44  
  35         35702  
40              
41             =item C<< new ( $lhs, $rhs, $opt ) >>
42              
43             =cut
44              
45             sub new {
46 31     31 1 51 my $class = shift;
47 31         43 my $lhs = shift;
48 31         46 my $rhs = shift;
49            
50 31 50       196 if ($rhs->isa('RDF::Query::Plan::SubSelect')) {
51 0         0 throw RDF::Query::Error::MethodInvocationError -text => "Subselects cannot be the RHS of a PushDownNestedLoop join";
52             }
53            
54 31   100     123 my $opt = shift || 0;
55 31 100 100     196 if (not($opt) and $rhs->isa('RDF::Query::Plan::Join') and $rhs->optional) {
      66        
56             # we can't push down results to an optional pattern because of the
57             # bottom up semantics. see dawg test algebra/manifest#join-scope-1
58             # for example.
59 1         5 throw RDF::Query::Error::MethodInvocationError -text => "PushDownNestedLoop join does not support optional patterns as RHS due to bottom-up variable scoping rules (use NestedLoop instead)";
60             }
61            
62 30         140 my @aggs = $rhs->subplans_of_type('RDF::Query::Plan::Aggregate');
63 30 50       82 if (scalar(@aggs)) {
64 0         0 throw RDF::Query::Error::MethodInvocationError -text => "PushDownNestedLoop join does not support aggregates in the RHS due to aggregate group fragmentation";
65             }
66            
67 30         177 my $self = $class->SUPER::new( $lhs, $rhs, $opt );
68 30         66 return $self;
69             }
70              
71             =item C<< execute ( $execution_context ) >>
72              
73             =cut
74              
75             sub execute ($) {
76 30     30 1 55 my $self = shift;
77 30         42 my $context = shift;
78 30         87 $self->[0]{delegate} = $context->delegate;
79 30 50       114 if ($self->state == $self->OPEN) {
80 0         0 throw RDF::Query::Error::ExecutionError -text => "PushDownNestedLoop join plan can't be executed while already open";
81             }
82            
83 30         101 my $l = Log::Log4perl->get_logger("rdf.query.plan.join.pushdownnestedloop");
84 30         4071 $l->trace("executing bind join with plans:");
85 30         248 $l->trace($self->lhs->sse);
86 30         237 $l->trace($self->rhs->sse);
87            
88 30         201 $self->lhs->execute( $context );
89 30 50       115 if ($self->lhs->state == $self->OPEN) {
90 30         53 delete $self->[0]{stats};
91 30         59 $self->[0]{context} = $context;
92 30         76 $self->[0]{outer} = $self->lhs;
93 30         89 $self->[0]{needs_new_outer} = 1;
94 30         77 $self->[0]{inner_count} = 0;
95 30         105 $self->state( $self->OPEN );
96             } else {
97 0         0 warn "no iterator in execute()";
98             }
99             # warn '########################################';
100 30         73 $self;
101             }
102              
103             =item C<< next >>
104              
105             =cut
106              
107             sub next {
108 89     89 1 110 my $self = shift;
109 89 50       187 unless ($self->state == $self->OPEN) {
110 0         0 throw RDF::Query::Error::ExecutionError -text => "next() cannot be called on an un-open PushDownNestedLoop join";
111             }
112 89         125 my $outer = $self->[0]{outer};
113 89         215 my $inner = $self->rhs;
114 89         104 my $opt = $self->[3];
115            
116 89         191 my $l = Log::Log4perl->get_logger("rdf.query.plan.join.pushdownnestedloop");
117 89         1083 while (1) {
118 162 100       318 if ($self->[0]{needs_new_outer}) {
119 115         326 $self->[0]{outer_row} = $outer->next;
120 115         296 my $outer = $self->[0]{outer_row};
121 115 100       620 if (ref($outer)) {
122 90         144 $self->[0]{stats}{outer_rows}++;
123 90         115 my $context = $self->[0]{context};
124 90         114 $self->[0]{needs_new_outer} = 0;
125 90         101 $self->[0]{inner_count} = 0;
126 90 50       184 if ($self->[0]{inner}) {
127 0         0 $self->[0]{inner}->close();
128             }
129 90         85 my %bound = %{ $context->bound };
  90         255  
130 90         312 @bound{ keys %$outer } = values %$outer;
131 90         277 my $copy = $context->copy( bound => \%bound );
132 90         313 $l->trace( "executing inner plan with bound: " . Dumper(\%bound) );
133 90 50       7638 if ($inner->state == $inner->OPEN) {
134 0         0 $inner->close();
135             }
136 90         275 $self->[0]{inner} = $inner->execute( $copy );
137             } else {
138             # we've exhausted the outer iterator. we're now done.
139 25         76 $l->trace("exhausted outer plan in bind join");
140 25         133 return undef;
141             }
142             }
143            
144 137         391 while (defined(my $inner_row = $self->[0]{inner}->next)) {
145 66         128 $self->[0]{stats}{inner_rows}++;
146 66         207 $l->trace( "using inner row: " . $inner_row->as_string );
147 66 100       3133 if (defined(my $joined = $inner_row->join( $self->[0]{outer_row} ))) {
148 50         857 $self->[0]{stats}{results}++;
149 50 50       132 if ($l->is_trace) {
150 0         0 $l->trace("joined bindings: $inner_row ⋈ $self->[0]{outer_row}");
151             }
152             # warn "-> joined\n";
153 50         270 $self->[0]{inner_count}++;
154 50 50       177 if (my $d = $self->delegate) {
155 0         0 $d->log_result( $self, $joined );
156             }
157 50         184 return $joined;
158             } else {
159 16         818 $l->trace("failed to join bindings: $inner_row |><| $self->[0]{outer_row}");
160 16 50       1526 if ($opt) {
161 0         0 $l->trace( "--> but operation is OPTIONAL, so returning $self->[0]{outer_row}" );
162 0 0       0 if (my $d = $self->delegate) {
163 0         0 $d->log_result( $self, $self->[0]{outer_row} );
164             }
165 0         0 return $self->[0]{outer_row};
166             }
167             }
168             }
169            
170 87         126 $self->[0]{needs_new_outer} = 1;
171 87 50       207 if ($self->[0]{inner}->state == $self->OPEN) {
172 87         236 $self->[0]{inner}->close();
173             }
174 87         125 delete $self->[0]{inner};
175 87 100 100     276 if ($opt and $self->[0]{inner_count} == 0) {
176 14 50       45 if (my $d = $self->delegate) {
177 0         0 $d->log_result( $self, $self->[0]{outer_row} );
178             }
179 14         49 return $self->[0]{outer_row};
180             }
181             }
182             }
183              
184             =item C<< close >>
185              
186             =cut
187              
188             sub close {
189 30     30 1 49 my $self = shift;
190 30 50       80 unless ($self->state == $self->OPEN) {
191 0         0 throw RDF::Query::Error::ExecutionError -text => "close() cannot be called on an un-open PushDownNestedLoop join";
192             }
193 30         52 delete $self->[0]{inner};
194 30         49 delete $self->[0]{outer};
195 30         43 delete $self->[0]{needs_new_outer};
196 30         44 delete $self->[0]{inner_count};
197 30 50 33     107 if (blessed($self->lhs) and $self->lhs->state == $self->lhs->OPEN) {
198 30         80 $self->lhs->close();
199             }
200 30         136 $self->SUPER::close();
201             }
202              
203             =item C<< plan_node_name >>
204              
205             Returns the string name of this plan node, suitable for use in serialization.
206              
207             =cut
208              
209             sub plan_node_name {
210 7     7 1 9 my $self = shift;
211 7 100       27 my $jtype = $self->optional ? 'leftjoin' : 'join';
212 7         18 return "bind-$jtype";
213             }
214              
215             =item C<< graph ( $g ) >>
216              
217             =cut
218              
219             sub graph {
220 0     0 1   my $self = shift;
221 0           my $g = shift;
222 0 0         my $jtype = $self->optional ? 'Left Join' : 'Join';
223 0           my ($l, $r) = map { $_->graph( $g ) } ($self->lhs, $self->rhs);
  0            
224 0           $g->add_node( "$self", label => "$jtype (Bind)" . $self->graph_labels );
225 0           $g->add_edge( "$self", $l );
226 0           $g->add_edge( "$self", $r );
227 0           return "$self";
228             }
229              
230             =item C<< explain >>
231              
232             Returns a string serialization of the plan appropriate for display on the
233             command line.
234              
235             =cut
236              
237             sub explain {
238 0     0 1   my $self = shift;
239 0           my $s = shift;
240 0           my $count = shift;
241 0           my $indent = $s x $count;
242 0           my $type = $self->plan_node_name;
243 0           my $stats = '';
244 0 0         if ($self->[0]{stats}) {
245 0           $stats = sprintf(' [%d/%d/%d]', @{ $self->[0]{stats} }{qw(outer_rows inner_rows results)});
  0            
246             }
247 0           my $string = sprintf("%s%s%s (0x%x)\n", $indent, $type, $stats, refaddr($self));
248 0           foreach my $p ($self->plan_node_data) {
249 0           $string .= $p->explain( $s, $count+1 );
250             }
251 0           return $string;
252             }
253              
254              
255             package RDF::Query::Plan::Join::PushDownNestedLoop::Left;
256              
257 35     35   177 use strict;
  35         51  
  35         645  
258 35     35   117 use warnings;
  35         38  
  35         910  
259 35     35   133 use base qw(RDF::Query::Plan::Join::PushDownNestedLoop);
  35         45  
  35         3303  
260              
261             sub new {
262 0     0     my $class = shift;
263 0           my $lhs = shift;
264 0           my $rhs = shift;
265 0           return $class->SUPER::new( $lhs, $rhs, 1 );
266             }
267              
268             1;
269              
270             __END__
271              
272             =back
273              
274             =head1 AUTHOR
275              
276             Gregory Todd Williams <gwilliams@cpan.org>
277              
278             =cut