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.916.
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   251 use strict;
  35         70  
  35         1021  
24 35     35   190 use warnings;
  35         67  
  35         946  
25 35     35   176 use base qw(RDF::Query::Plan);
  35         71  
  35         2629  
26              
27 35     35   198 use Scalar::Util qw(blessed refaddr);
  35         69  
  35         1850  
28              
29 35     35   189 use RDF::Query::ExecutionContext;
  35         76  
  35         848  
30 35     35   19123 use RDF::Query::VariableBindings;
  35         98  
  35         1469  
31              
32             ######################################################################
33              
34             our ($VERSION);
35             BEGIN {
36 35     35   29621 $VERSION = '2.916';
37             }
38              
39             ######################################################################
40              
41             =item C<< new ( @quad ) >>
42              
43             =cut
44              
45             sub new {
46 132     132 1 252 my $class = shift;
47 132         349 my @quad = @_;
48 132         660 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         225 my %var_to_position;
61 132         406 my @methodmap = qw(subject predicate object context);
62 132         208 my %counts;
63             my @dup_vars;
64 132         339 foreach my $idx (0 .. 3) {
65 528         753 my $node = $quad[ $idx ];
66 528 100 66     4327 if (blessed($node) and $node->isa('RDF::Trine::Node::Variable')) {
67 186         536 my $name = $node->name;
68 186         1083 $var_to_position{ $name } = $methodmap[ $idx ];
69 186         360 $counts{ $name }++;
70 186 100       694 if ($counts{ $name } >= 2) {
71 1         3 push(@dup_vars, $name);
72             }
73             }
74             }
75 132         637 $self->[0]{referenced_variables} = [ keys %counts ];
76            
77 132         222 my %positions;
78 132 100       424 if (@dup_vars) {
79 1         3 foreach my $dup_var (@dup_vars) {
80 1         3 foreach my $idx (0 .. 3) {
81 4         6 my $var = $quad[ $idx ];
82 4 100 66     40 if (blessed($var) and ($var->isa('RDF::Trine::Node::Variable') or $var->isa('RDF::Trine::Node::Blank'))) {
      33        
83 3 50       21 my $name = ($var->isa('RDF::Trine::Node::Blank')) ? '__' . $var->blank_identifier : $var->name;
84 3 100       19 if ($name eq $dup_var) {
85 2         4 push(@{ $positions{ $dup_var } }, $methodmap[ $idx ]);
  2         8  
86             }
87             }
88             }
89             }
90             }
91            
92 132         316 $self->[0]{mappings} = \%var_to_position;
93            
94 132 100       354 if (%positions) {
95 1         3 $self->[0]{dups} = \%positions;
96             }
97            
98 132         604 return $self;
99             }
100              
101             =item C<< execute ( $execution_context ) >>
102              
103             =cut
104              
105             sub execute ($) {
106 183     183 1 303 my $self = shift;
107 183         306 my $context = shift;
108 183         575 $self->[0]{delegate} = $context->delegate;
109 183 50       679 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         689 my $l = Log::Log4perl->get_logger("rdf.query.plan.quad");
114 183         11175 $l->trace( "executing RDF::Query::Plan::Quad:" );
115            
116 183         1229 my @quad = @{ $self }[ 1..4 ];
  183         526  
117 183         566 my $bound = $context->bound;
118 183 100       563 if (%$bound) {
119 86         200 foreach my $i (0 .. $#quad) {
120 344 100       2453 next unless ($quad[$i]->isa('RDF::Trine::Node::Variable'));
121 160 100       532 next unless (blessed($bound->{ $quad[$i]->name }));
122 85         739 $quad[ $i ] = $bound->{ $quad[$i]->name };
123             }
124             }
125            
126 183         1337 my $model = $context->model;
127            
128 183         521 my @names = qw(subject predicate object context);
129 183         358 foreach my $i (0 .. 3) {
130 732         16760 $l->trace( sprintf("- quad %10s: %s", $names[$i], $quad[$i]) );
131             }
132            
133 183         2753 my $iter = $model->get_statements( @quad[0..3] );
134 183 50       155111 if (blessed($iter)) {
135 183         644 $l->trace("got quad iterator");
136 183         1543 $self->[0]{iter} = $iter;
137 183         370 $self->[0]{bound} = $bound;
138 183         853 $self->state( $self->OPEN );
139             } else {
140 0         0 warn "no iterator in execute()";
141             }
142 183         1045 $self;
143             }
144              
145             =item C<< next >>
146              
147             =cut
148              
149             sub next {
150 341     341 1 489 my $self = shift;
151 341 50       917 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 341         671 my $iter = $self->[0]{iter};
155            
156 341         1098 my $l = Log::Log4perl->get_logger("rdf.query.plan.quad");
157 341         6979 $l->trace("next() called on Quad plan");
158 341         2770 LOOP: while (my $row = $iter->next) {
159 316         9067 $l->trace("got quad: " . $row->as_string);
160 316 100       22245 if (my $data = $self->[0]{dups}) {
161 138         292 foreach my $pos (values %$data) {
162 138         271 my @pos = @$pos;
163 138         193 my $first_method = shift(@pos);
164 138         444 my $first = $row->$first_method();
165 138         733 foreach my $p (@pos) {
166 138 100       372 unless ($first->equal( $row->$p() )) {
167 35     35   283 use Data::Dumper;
  35         79  
  35         34215  
168 137         20066 $l->trace("Quad $first_method and $p didn't match: " . Dumper($first, $row->$p()));
169 137         12208 next LOOP;
170             }
171             }
172             }
173             }
174            
175             # if ($row->context->isa('RDF::Trine::Node::Nil')) {
176             # next;
177             # }
178            
179 179         475 my $binding = {};
180 179         280 foreach my $key (keys %{ $self->[0]{mappings} }) {
  179         674  
181 301         1187 my $method = $self->[0]{mappings}{ $key };
182 301         1027 $binding->{ $key } = $row->$method();
183             }
184 179         1257 my $pre_bound = $self->[0]{bound};
185 179         843 my $bindings = RDF::Query::VariableBindings->new( $binding );
186 179 50       1133 if ($row->can('label')) {
187 0 0       0 if (my $o = $row->label('origin')) {
188 0         0 $bindings->label( origin => [ $o ] );
189             }
190             }
191 179         406 @{ $bindings }{ keys %$pre_bound } = values %$pre_bound;
  179         858  
192 179 50       742 if (my $d = $self->delegate) {
193 0         0 $d->log_result( $self, $bindings );
194             }
195 179         931 return $bindings;
196             }
197 162         3337 $l->trace("No more quads");
198 162         1265 return;
199             }
200              
201             =item C<< close >>
202              
203             =cut
204              
205             sub close {
206 182     182 1 275 my $self = shift;
207 182 50       499 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         1249 delete $self->[0]{iter};
212 182         8509 delete $self->[0]{bound};
213 182         687 $self->SUPER::close();
214             }
215              
216             =item C<< nodes () >>
217              
218             =cut
219              
220             sub nodes {
221 150     150 1 202 my $self = shift;
222 150         205 return @{ $self }[1,2,3,4];
  150         582  
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 179 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 260 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 247 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 162 my $self = shift;
291 110         449 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 216 my $self = shift;
303 150         377 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