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.915_01.
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   181 use strict;
  35         67  
  35         878  
24 35     35   189 use warnings;
  35         65  
  35         927  
25 35     35   171 use base qw(RDF::Query::Plan::Join);
  35         58  
  35         2587  
26 35     35   185 use Scalar::Util qw(blessed refaddr);
  35         62  
  35         1853  
27 35     35   186 use Data::Dumper;
  35         81  
  35         2887  
28              
29             ######################################################################
30              
31             our ($VERSION);
32             BEGIN {
33 35     35   95 $VERSION = '2.915_01';
34 35         717 $RDF::Query::Plan::Join::JOIN_CLASSES{ 'RDF::Query::Plan::Join::PushDownNestedLoop' }++;
35             }
36              
37             ######################################################################
38              
39 35     35   192 use RDF::Query::ExecutionContext;
  35         66  
  35         52598  
40              
41             =item C<< new ( $lhs, $rhs, $opt ) >>
42              
43             =cut
44              
45             sub new {
46 31     31 1 69 my $class = shift;
47 31         57 my $lhs = shift;
48 31         57 my $rhs = shift;
49            
50 31 50       238 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     156 my $opt = shift || 0;
55 31 100 100     246 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         6 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         184 my @aggs = $rhs->subplans_of_type('RDF::Query::Plan::Aggregate');
63 30 50       104 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         236 my $self = $class->SUPER::new( $lhs, $rhs, $opt );
68 30         99 return $self;
69             }
70              
71             =item C<< execute ( $execution_context ) >>
72              
73             =cut
74              
75             sub execute ($) {
76 30     30 1 61 my $self = shift;
77 30         56 my $context = shift;
78 30         110 $self->[0]{delegate} = $context->delegate;
79 30 50       154 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         125 my $l = Log::Log4perl->get_logger("rdf.query.plan.join.pushdownnestedloop");
84 30         6138 $l->trace("executing bind join with plans:");
85 30         311 $l->trace($self->lhs->sse);
86 30         340 $l->trace($self->rhs->sse);
87            
88 30         279 $self->lhs->execute( $context );
89 30 50       123 if ($self->lhs->state == $self->OPEN) {
90 30         65 delete $self->[0]{stats};
91 30         79 $self->[0]{context} = $context;
92 30         102 $self->[0]{outer} = $self->lhs;
93 30         82 $self->[0]{needs_new_outer} = 1;
94 30         87 $self->[0]{inner_count} = 0;
95 30         137 $self->state( $self->OPEN );
96             } else {
97 0         0 warn "no iterator in execute()";
98             }
99             # warn '########################################';
100 30         102 $self;
101             }
102              
103             =item C<< next >>
104              
105             =cut
106              
107             sub next {
108 89     89 1 162 my $self = shift;
109 89 50       266 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         189 my $outer = $self->[0]{outer};
113 89         287 my $inner = $self->rhs;
114 89         161 my $opt = $self->[3];
115            
116 89         269 my $l = Log::Log4perl->get_logger("rdf.query.plan.join.pushdownnestedloop");
117 89         1616 while (1) {
118 162 100       419 if ($self->[0]{needs_new_outer}) {
119 115         403 $self->[0]{outer_row} = $outer->next;
120 115         338 my $outer = $self->[0]{outer_row};
121 115 100       938 if (ref($outer)) {
122 90         202 $self->[0]{stats}{outer_rows}++;
123 90         168 my $context = $self->[0]{context};
124 90         142 $self->[0]{needs_new_outer} = 0;
125 90         151 $self->[0]{inner_count} = 0;
126 90 50       247 if ($self->[0]{inner}) {
127 0         0 $self->[0]{inner}->close();
128             }
129 90         128 my %bound = %{ $context->bound };
  90         301  
130 90         392 @bound{ keys %$outer } = values %$outer;
131 90         377 my $copy = $context->copy( bound => \%bound );
132 90         357 $l->trace( "executing inner plan with bound: " . Dumper(\%bound) );
133 90 50       8607 if ($inner->state == $inner->OPEN) {
134 0         0 $inner->close();
135             }
136 90         310 $self->[0]{inner} = $inner->execute( $copy );
137             } else {
138             # we've exhausted the outer iterator. we're now done.
139 25         91 $l->trace("exhausted outer plan in bind join");
140 25         205 return undef;
141             }
142             }
143            
144 137         519 while (defined(my $inner_row = $self->[0]{inner}->next)) {
145 66         186 $self->[0]{stats}{inner_rows}++;
146 66         259 $l->trace( "using inner row: " . $inner_row->as_string );
147 66 100       3902 if (defined(my $joined = $inner_row->join( $self->[0]{outer_row} ))) {
148 50         1105 $self->[0]{stats}{results}++;
149 50 50       169 if ($l->is_trace) {
150 0         0 $l->trace("joined bindings: $inner_row ⋈ $self->[0]{outer_row}");
151             }
152             # warn "-> joined\n";
153 50         377 $self->[0]{inner_count}++;
154 50 50       208 if (my $d = $self->delegate) {
155 0         0 $d->log_result( $self, $joined );
156             }
157 50         198 return $joined;
158             } else {
159 16         1159 $l->trace("failed to join bindings: $inner_row |><| $self->[0]{outer_row}");
160 16 50       2005 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         174 $self->[0]{needs_new_outer} = 1;
171 87 50       294 if ($self->[0]{inner}->state == $self->OPEN) {
172 87         311 $self->[0]{inner}->close();
173             }
174 87         169 delete $self->[0]{inner};
175 87 100 100     320 if ($opt and $self->[0]{inner_count} == 0) {
176 14 50       57 if (my $d = $self->delegate) {
177 0         0 $d->log_result( $self, $self->[0]{outer_row} );
178             }
179 14         61 return $self->[0]{outer_row};
180             }
181             }
182             }
183              
184             =item C<< close >>
185              
186             =cut
187              
188             sub close {
189 30     30 1 65 my $self = shift;
190 30 50       105 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         68 delete $self->[0]{inner};
194 30         67 delete $self->[0]{outer};
195 30         58 delete $self->[0]{needs_new_outer};
196 30         67 delete $self->[0]{inner_count};
197 30 50 33     122 if (blessed($self->lhs) and $self->lhs->state == $self->lhs->OPEN) {
198 30         96 $self->lhs->close();
199             }
200 30         167 $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 16 my $self = shift;
211 7 100       44 my $jtype = $self->optional ? 'leftjoin' : 'join';
212 7         26 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   206 use strict;
  35         75  
  35         805  
258 35     35   178 use warnings;
  35         69  
  35         1060  
259 35     35   248 use base qw(RDF::Query::Plan::Join::PushDownNestedLoop);
  35         64  
  35         4100  
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