File Coverage

blib/lib/RDF/Query/Plan/Quad.pm
Criterion Covered Total %
statement 121 162 74.6
branch 29 46 63.0
condition 5 9 55.5
subroutine 18 21 85.7
pod 13 13 100.0
total 186 251 74.1


line stmt bran cond sub pod time code
1             # RDF::Query::Plan::Quad
2             # -----------------------------------------------------------------------------
3              
4             =head1 NAME
5              
6             RDF::Query::Plan::Quad - Executable query plan for Quads.
7              
8             =head1 VERSION
9              
10             This document describes RDF::Query::Plan::Quad 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> class.
16              
17             =over 4
18              
19             =cut
20              
21             package RDF::Query::Plan::Quad;
22              
23 35     35   134 use strict;
  35         49  
  35         835  
24 35     35   122 use warnings;
  35         43  
  35         776  
25 35     35   115 use base qw(RDF::Query::Plan);
  35         42  
  35         2246  
26              
27 35     35   142 use Scalar::Util qw(blessed refaddr);
  35         49  
  35         1422  
28              
29 35     35   144 use RDF::Query::ExecutionContext;
  35         51  
  35         633  
30 35     35   11527 use RDF::Query::VariableBindings;
  35         64  
  35         1136  
31              
32             ######################################################################
33              
34             our ($VERSION);
35             BEGIN {
36 35     35   20356 $VERSION = '2.918';
37             }
38              
39             ######################################################################
40              
41             =item C<< new ( @quad ) >>
42              
43             =cut
44              
45             sub new {
46 132     132 1 169 my $class = shift;
47 132         262 my @quad = @_;
48 132         520 my $self = $class->SUPER::new( @quad );
49            
50             ### the next two loops look for repeated variables because some backends
51             ### can't distinguish a pattern like { ?a ?a ?b }
52             ### from { ?a ?b ?c }. if we find repeated variables (there can be at most
53             ### two since there are only four nodes in a quad), we save the positions
54             ### in the quad that hold the variable(s), and the code in next() will filter
55             ### out any results that don't have the same value in those positions.
56             ###
57             ### in the first pass, we also set up the mapping that will let us pull out
58             ### values from the result quads to construct result bindings.
59            
60 132         159 my %var_to_position;
61 132         273 my @methodmap = qw(subject predicate object context);
62 132         154 my %counts;
63             my @dup_vars;
64 132         271 foreach my $idx (0 .. 3) {
65 528         448 my $node = $quad[ $idx ];
66 528 100 66     2926 if (blessed($node) and $node->isa('RDF::Trine::Node::Variable')) {
67 186         346 my $name = $node->name;
68 186         707 $var_to_position{ $name } = $methodmap[ $idx ];
69 186         326 $counts{ $name }++;
70 186 100       472 if ($counts{ $name } >= 2) {
71 1         2 push(@dup_vars, $name);
72             }
73             }
74             }
75 132         457 $self->[0]{referenced_variables} = [ keys %counts ];
76            
77 132         163 my %positions;
78 132 100       265 if (@dup_vars) {
79 1         3 foreach my $dup_var (@dup_vars) {
80 1         2 foreach my $idx (0 .. 3) {
81 4         3 my $var = $quad[ $idx ];
82 4 100 66     25 if (blessed($var) and ($var->isa('RDF::Trine::Node::Variable') or $var->isa('RDF::Trine::Node::Blank'))) {
      33        
83 3 50       12 my $name = ($var->isa('RDF::Trine::Node::Blank')) ? '__' . $var->blank_identifier : $var->name;
84 3 100       12 if ($name eq $dup_var) {
85 2         2 push(@{ $positions{ $dup_var } }, $methodmap[ $idx ]);
  2         6  
86             }
87             }
88             }
89             }
90             }
91            
92 132         242 $self->[0]{mappings} = \%var_to_position;
93            
94 132 100       239 if (%positions) {
95 1         3 $self->[0]{dups} = \%positions;
96             }
97            
98 132         406 return $self;
99             }
100              
101             =item C<< execute ( $execution_context ) >>
102              
103             =cut
104              
105             sub execute ($) {
106 183     183 1 194 my $self = shift;
107 183         175 my $context = shift;
108 183         423 $self->[0]{delegate} = $context->delegate;
109 183 50       410 if ($self->state == $self->OPEN) {
110 0         0 throw RDF::Query::Error::ExecutionError -text => "QUAD plan can't be executed while already open";
111             }
112            
113 183         535 my $l = Log::Log4perl->get_logger("rdf.query.plan.quad");
114 183         7135 $l->trace( "executing RDF::Query::Plan::Quad:" );
115            
116 183         824 my @quad = @{ $self }[ 1..4 ];
  183         399  
117 183         433 my $bound = $context->bound;
118 183 100       977 if (%$bound) {
119 86         167 foreach my $i (0 .. $#quad) {
120 344 100       1659 next unless ($quad[$i]->isa('RDF::Trine::Node::Variable'));
121 160 100       367 next unless (blessed($bound->{ $quad[$i]->name }));
122 85         567 $quad[ $i ] = $bound->{ $quad[$i]->name };
123             }
124             }
125            
126 183         456 my $model = $context->model;
127            
128 183         409 my @names = qw(subject predicate object context);
129 183         259 foreach my $i (0 .. 3) {
130 732         12243 $l->trace( sprintf("- quad %10s: %s", $names[$i], $quad[$i]) );
131             }
132            
133 183         1802 my $iter = $model->get_statements( @quad[0..3] );
134 183 50       102591 if (blessed($iter)) {
135 183         536 $l->trace("got quad iterator");
136 183         1023 $self->[0]{iter} = $iter;
137 183         249 $self->[0]{bound} = $bound;
138 183         651 $self->state( $self->OPEN );
139             } else {
140 0         0 warn "no iterator in execute()";
141             }
142 183         755 $self;
143             }
144              
145             =item C<< next >>
146              
147             =cut
148              
149             sub next {
150 340     340 1 343 my $self = shift;
151 340 50       621 unless ($self->state == $self->OPEN) {
152 0         0 throw RDF::Query::Error::ExecutionError -text => "next() cannot be called on an un-open QUAD";
153             }
154 340         400 my $iter = $self->[0]{iter};
155            
156 340         770 my $l = Log::Log4perl->get_logger("rdf.query.plan.quad");
157 340         4725 $l->trace("next() called on Quad plan");
158 340         1945 LOOP: while (my $row = $iter->next) {
159 315         5423 $l->trace("got quad: " . $row->as_string);
160 315 100       15349 if (my $data = $self->[0]{dups}) {
161 138         228 foreach my $pos (values %$data) {
162 138         207 my @pos = @$pos;
163 138         153 my $first_method = shift(@pos);
164 138         284 my $first = $row->$first_method();
165 138         487 foreach my $p (@pos) {
166 138 100       224 unless ($first->equal( $row->$p() )) {
167 35     35   183 use Data::Dumper;
  35         79  
  35         22898  
168 137         11445 $l->trace("Quad $first_method and $p didn't match: " . Dumper($first, $row->$p()));
169 137         7409 next LOOP;
170             }
171             }
172             }
173             }
174            
175             # if ($row->context->isa('RDF::Trine::Node::Nil')) {
176             # next;
177             # }
178            
179 178         345 my $binding = {};
180 178         189 foreach my $key (keys %{ $self->[0]{mappings} }) {
  178         555  
181 299         749 my $method = $self->[0]{mappings}{ $key };
182 299         705 $binding->{ $key } = $row->$method();
183             }
184 178         827 my $pre_bound = $self->[0]{bound};
185 178         664 my $bindings = RDF::Query::VariableBindings->new( $binding );
186 178 50       885 if ($row->can('label')) {
187 0 0       0 if (my $o = $row->label('origin')) {
188 0         0 $bindings->label( origin => [ $o ] );
189             }
190             }
191 178         283 @{ $bindings }{ keys %$pre_bound } = values %$pre_bound;
  178         522  
192 178 50       603 if (my $d = $self->delegate) {
193 0         0 $d->log_result( $self, $bindings );
194             }
195 178         669 return $bindings;
196             }
197 162         2326 $l->trace("No more quads");
198 162         795 return;
199             }
200              
201             =item C<< close >>
202              
203             =cut
204              
205             sub close {
206 182     182 1 215 my $self = shift;
207 182 50       323 unless ($self->state == $self->OPEN) {
208 0         0 Carp::cluck;
209 0         0 throw RDF::Query::Error::ExecutionError -text => "close() cannot be called on an un-open QUAD";
210             }
211 182         1092 delete $self->[0]{iter};
212 182         4647 delete $self->[0]{bound};
213 182         504 $self->SUPER::close();
214             }
215              
216             =item C<< nodes () >>
217              
218             =cut
219              
220             sub nodes {
221 150     150 1 139 my $self = shift;
222 150         151 return @{ $self }[1,2,3,4];
  150         377  
223             }
224              
225             =item C<< bf () >>
226              
227             Returns a string representing the state of the nodes of the triple (bound or free).
228              
229             =cut
230              
231             sub bf {
232 0     0 1 0 my $self = shift;
233 0         0 my $context = shift;
234 0         0 my $bf = '';
235 0         0 my $bound = $context->bound;
236 0         0 foreach my $n (@{ $self }[1,2,3,4]) {
  0         0  
237 0 0       0 if ($n->isa('RDF::Trine::Node::Variable')) {
238 0 0       0 if (my $b = $bound->{ $n->name }) {
239 0         0 $bf .= 'b';
240             } else {
241 0         0 $bf .= 'f';
242             }
243             } else {
244 0         0 $bf .= 'b';
245             }
246             }
247 0         0 return $bf;
248             }
249              
250             =item C<< distinct >>
251              
252             Returns true if the pattern is guaranteed to return distinct results.
253              
254             =cut
255              
256             sub distinct {
257 38     38 1 122 return 0;
258             }
259              
260             =item C<< ordered >>
261              
262             Returns true if the pattern is guaranteed to return ordered results.
263              
264             =cut
265              
266             sub ordered {
267 29     29 1 207 return [];
268             }
269              
270              
271             =item C<< plan_node_name >>
272              
273             Returns the string name of this plan node, suitable for use in serialization.
274              
275             =cut
276              
277             sub plan_node_name {
278 110     110 1 157 return 'quad';
279             }
280              
281             =item C<< plan_prototype >>
282              
283             Returns a list of scalar identifiers for the type of the content (children)
284             nodes of this plan node. See L<RDF::Query::Plan> for a list of the allowable
285             identifiers.
286              
287             =cut
288              
289             sub plan_prototype {
290 110     110 1 101 my $self = shift;
291 110         305 return qw(N N N N);
292             }
293              
294             =item C<< plan_node_data >>
295              
296             Returns the data for this plan node that corresponds to the values described by
297             the signature returned by C<< plan_prototype >>.
298              
299             =cut
300              
301             sub plan_node_data {
302 150     150 1 143 my $self = shift;
303 150         281 return ($self->nodes);
304             }
305              
306             =item C<< explain >>
307              
308             Returns a string serialization of the query plan appropriate for display
309             on the command line.
310              
311             =cut
312              
313             sub explain {
314 0     0 1   my $self = shift;
315 0           my ($s, $count) = (' ', 0);
316 0 0         if (@_) {
317 0           $s = shift;
318 0           $count = shift;
319             }
320 0           my $indent = '' . ($s x $count);
321 0           my $type = $self->plan_node_name;
322             my $string = sprintf("%s%s (0x%x)\n", $indent, $type, refaddr($self))
323             . "${indent}${s}"
324 0 0         . join(' ', map { ($_->isa('RDF::Trine::Node::Nil')) ? "(nil)" : $_->as_sparql } $self->plan_node_data) . "\n";
  0            
325 0           return $string;
326             }
327              
328             =item C<< graph ( $g ) >>
329              
330             =cut
331              
332             sub graph {
333 0     0 1   my $self = shift;
334 0           my $g = shift;
335 0           $g->add_node( "$self", label => "Quad" . $self->graph_labels );
336 0           my @names = qw(subject predicate object context);
337 0           foreach my $i (0 .. 3) {
338 0           my $n = $self->[ $i + 1 ];
339 0           my $rel = $names[ $i ];
340 0           my $str = $n->sse( {}, '' );
341 0           $g->add_node( "${self}$n", label => $str );
342 0           $g->add_edge( "$self" => "${self}$n", label => $names[ $i ] );
343             }
344 0           return "$self";
345             }
346              
347             1;
348              
349             __END__
350              
351             =back
352              
353             =head1 AUTHOR
354              
355             Gregory Todd Williams <gwilliams@cpan.org>
356              
357             =cut