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.916.
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   180 use strict;
  35         70  
  35         878  
24 35     35   179 use warnings;
  35         77  
  35         923  
25 35     35   181 use base qw(RDF::Query::Plan::Join);
  35         60  
  35         2647  
26 35     35   184 use Scalar::Util qw(blessed refaddr);
  35         62  
  35         1824  
27 35     35   192 use Data::Dumper;
  35         87  
  35         2892  
28              
29             ######################################################################
30              
31             our ($VERSION);
32             BEGIN {
33 35     35   95 $VERSION = '2.916';
34 35         798 $RDF::Query::Plan::Join::JOIN_CLASSES{ 'RDF::Query::Plan::Join::PushDownNestedLoop' }++;
35             }
36              
37             ######################################################################
38              
39 35     35   213 use RDF::Query::ExecutionContext;
  35         80  
  35         54189  
40              
41             =item C<< new ( $lhs, $rhs, $opt ) >>
42              
43             =cut
44              
45             sub new {
46 31     31 1 58 my $class = shift;
47 31         54 my $lhs = shift;
48 31         62 my $rhs = shift;
49            
50 31 50       253 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     137 my $opt = shift || 0;
55 31 100 100     274 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         175 my @aggs = $rhs->subplans_of_type('RDF::Query::Plan::Aggregate');
63 30 50       99 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         207 my $self = $class->SUPER::new( $lhs, $rhs, $opt );
68 30         97 return $self;
69             }
70              
71             =item C<< execute ( $execution_context ) >>
72              
73             =cut
74              
75             sub execute ($) {
76 30     30 1 66 my $self = shift;
77 30         57 my $context = shift;
78 30         116 $self->[0]{delegate} = $context->delegate;
79 30 50       156 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         144 my $l = Log::Log4perl->get_logger("rdf.query.plan.join.pushdownnestedloop");
84 30         6036 $l->trace("executing bind join with plans:");
85 30         312 $l->trace($self->lhs->sse);
86 30         327 $l->trace($self->rhs->sse);
87            
88 30         277 $self->lhs->execute( $context );
89 30 50       125 if ($self->lhs->state == $self->OPEN) {
90 30         67 delete $self->[0]{stats};
91 30         81 $self->[0]{context} = $context;
92 30         122 $self->[0]{outer} = $self->lhs;
93 30         76 $self->[0]{needs_new_outer} = 1;
94 30         91 $self->[0]{inner_count} = 0;
95 30         136 $self->state( $self->OPEN );
96             } else {
97 0         0 warn "no iterator in execute()";
98             }
99             # warn '########################################';
100 30         93 $self;
101             }
102              
103             =item C<< next >>
104              
105             =cut
106              
107             sub next {
108 89     89 1 170 my $self = shift;
109 89 50       276 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         193 my $outer = $self->[0]{outer};
113 89         291 my $inner = $self->rhs;
114 89         159 my $opt = $self->[3];
115            
116 89         310 my $l = Log::Log4perl->get_logger("rdf.query.plan.join.pushdownnestedloop");
117 89         1648 while (1) {
118 162 100       454 if ($self->[0]{needs_new_outer}) {
119 115         428 $self->[0]{outer_row} = $outer->next;
120 115         319 my $outer = $self->[0]{outer_row};
121 115 100       959 if (ref($outer)) {
122 90         240 $self->[0]{stats}{outer_rows}++;
123 90         161 my $context = $self->[0]{context};
124 90         170 $self->[0]{needs_new_outer} = 0;
125 90         141 $self->[0]{inner_count} = 0;
126 90 50       252 if ($self->[0]{inner}) {
127 0         0 $self->[0]{inner}->close();
128             }
129 90         113 my %bound = %{ $context->bound };
  90         309  
130 90         395 @bound{ keys %$outer } = values %$outer;
131 90         393 my $copy = $context->copy( bound => \%bound );
132 90         366 $l->trace( "executing inner plan with bound: " . Dumper(\%bound) );
133 90 50       8721 if ($inner->state == $inner->OPEN) {
134 0         0 $inner->close();
135             }
136 90         317 $self->[0]{inner} = $inner->execute( $copy );
137             } else {
138             # we've exhausted the outer iterator. we're now done.
139 25         103 $l->trace("exhausted outer plan in bind join");
140 25         208 return undef;
141             }
142             }
143            
144 137         524 while (defined(my $inner_row = $self->[0]{inner}->next)) {
145 66         171 $self->[0]{stats}{inner_rows}++;
146 66         253 $l->trace( "using inner row: " . $inner_row->as_string );
147 66 100       3827 if (defined(my $joined = $inner_row->join( $self->[0]{outer_row} ))) {
148 50         1149 $self->[0]{stats}{results}++;
149 50 50       181 if ($l->is_trace) {
150 0         0 $l->trace("joined bindings: $inner_row ⋈ $self->[0]{outer_row}");
151             }
152             # warn "-> joined\n";
153 50         365 $self->[0]{inner_count}++;
154 50 50       219 if (my $d = $self->delegate) {
155 0         0 $d->log_result( $self, $joined );
156             }
157 50         239 return $joined;
158             } else {
159 16         1167 $l->trace("failed to join bindings: $inner_row |><| $self->[0]{outer_row}");
160 16 50       2064 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         202 $self->[0]{needs_new_outer} = 1;
171 87 50       505 if ($self->[0]{inner}->state == $self->OPEN) {
172 87         308 $self->[0]{inner}->close();
173             }
174 87         171 delete $self->[0]{inner};
175 87 100 100     340 if ($opt and $self->[0]{inner_count} == 0) {
176 14 50       65 if (my $d = $self->delegate) {
177 0         0 $d->log_result( $self, $self->[0]{outer_row} );
178             }
179 14         63 return $self->[0]{outer_row};
180             }
181             }
182             }
183              
184             =item C<< close >>
185              
186             =cut
187              
188             sub close {
189 30     30 1 67 my $self = shift;
190 30 50       109 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         77 delete $self->[0]{inner};
194 30         65 delete $self->[0]{outer};
195 30         60 delete $self->[0]{needs_new_outer};
196 30         69 delete $self->[0]{inner_count};
197 30 50 33     135 if (blessed($self->lhs) and $self->lhs->state == $self->lhs->OPEN) {
198 30         101 $self->lhs->close();
199             }
200 30         173 $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       52 my $jtype = $self->optional ? 'leftjoin' : 'join';
212 7         27 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   227 use strict;
  35         77  
  35         814  
258 35     35   176 use warnings;
  35         61  
  35         1081  
259 35     35   187 use base qw(RDF::Query::Plan::Join::PushDownNestedLoop);
  35         88  
  35         4170  
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