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.915_01.
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   185 use strict;
  35         72  
  35         901  
24 35     35   178 use warnings;
  35         74  
  35         951  
25 35     35   172 use base qw(RDF::Query::Plan);
  35         66  
  35         2777  
26              
27 35     35   191 use Scalar::Util qw(blessed refaddr);
  35         91  
  35         1891  
28              
29 35     35   257 use RDF::Query::ExecutionContext;
  35         74  
  35         882  
30 35     35   19271 use RDF::Query::VariableBindings;
  35         97  
  35         1447  
31              
32             ######################################################################
33              
34             our ($VERSION);
35             BEGIN {
36 35     35   29234 $VERSION = '2.915_01';
37             }
38              
39             ######################################################################
40              
41             =item C<< new ( @quad ) >>
42              
43             =cut
44              
45             sub new {
46 132     132 1 233 my $class = shift;
47 132         340 my @quad = @_;
48 132         644 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         226 my %var_to_position;
61 132         401 my @methodmap = qw(subject predicate object context);
62 132         189 my %counts;
63             my @dup_vars;
64 132         339 foreach my $idx (0 .. 3) {
65 528         740 my $node = $quad[ $idx ];
66 528 100 66     4269 if (blessed($node) and $node->isa('RDF::Trine::Node::Variable')) {
67 186         526 my $name = $node->name;
68 186         1064 $var_to_position{ $name } = $methodmap[ $idx ];
69 186         370 $counts{ $name }++;
70 186 100       728 if ($counts{ $name } >= 2) {
71 1         4 push(@dup_vars, $name);
72             }
73             }
74             }
75 132         577 $self->[0]{referenced_variables} = [ keys %counts ];
76            
77 132         242 my %positions;
78 132 100       350 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     39 if (blessed($var) and ($var->isa('RDF::Trine::Node::Variable') or $var->isa('RDF::Trine::Node::Blank'))) {
      33        
83 3 50       19 my $name = ($var->isa('RDF::Trine::Node::Blank')) ? '__' . $var->blank_identifier : $var->name;
84 3 100       20 if ($name eq $dup_var) {
85 2         4 push(@{ $positions{ $dup_var } }, $methodmap[ $idx ]);
  2         7  
86             }
87             }
88             }
89             }
90             }
91            
92 132         308 $self->[0]{mappings} = \%var_to_position;
93            
94 132 100       382 if (%positions) {
95 1         4 $self->[0]{dups} = \%positions;
96             }
97            
98 132         691 return $self;
99             }
100              
101             =item C<< execute ( $execution_context ) >>
102              
103             =cut
104              
105             sub execute ($) {
106 183     183 1 277 my $self = shift;
107 183         264 my $context = shift;
108 183         575 $self->[0]{delegate} = $context->delegate;
109 183 50       663 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         722 my $l = Log::Log4perl->get_logger("rdf.query.plan.quad");
114 183         10878 $l->trace( "executing RDF::Query::Plan::Quad:" );
115            
116 183         1265 my @quad = @{ $self }[ 1..4 ];
  183         561  
117 183         593 my $bound = $context->bound;
118 183 100       562 if (%$bound) {
119 86         180 foreach my $i (0 .. $#quad) {
120 344 100       2241 next unless ($quad[$i]->isa('RDF::Trine::Node::Variable'));
121 160 100       509 next unless (blessed($bound->{ $quad[$i]->name }));
122 85         720 $quad[ $i ] = $bound->{ $quad[$i]->name };
123             }
124             }
125            
126 183         1461 my $model = $context->model;
127            
128 183         532 my @names = qw(subject predicate object context);
129 183         341 foreach my $i (0 .. 3) {
130 732         16622 $l->trace( sprintf("- quad %10s: %s", $names[$i], $quad[$i]) );
131             }
132            
133 183         2729 my $iter = $model->get_statements( @quad[0..3] );
134 183 50       151606 if (blessed($iter)) {
135 183         633 $l->trace("got quad iterator");
136 183         1486 $self->[0]{iter} = $iter;
137 183         391 $self->[0]{bound} = $bound;
138 183         821 $self->state( $self->OPEN );
139             } else {
140 0         0 warn "no iterator in execute()";
141             }
142 183         1004 $self;
143             }
144              
145             =item C<< next >>
146              
147             =cut
148              
149             sub next {
150 340     340 1 486 my $self = shift;
151 340 50       951 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         628 my $iter = $self->[0]{iter};
155            
156 340         1080 my $l = Log::Log4perl->get_logger("rdf.query.plan.quad");
157 340         6766 $l->trace("next() called on Quad plan");
158 340         2768 LOOP: while (my $row = $iter->next) {
159 315         7799 $l->trace("got quad: " . $row->as_string);
160 315 100       21111 if (my $data = $self->[0]{dups}) {
161 138         291 foreach my $pos (values %$data) {
162 138         289 my @pos = @$pos;
163 138         187 my $first_method = shift(@pos);
164 138         447 my $first = $row->$first_method();
165 138         714 foreach my $p (@pos) {
166 138 100       381 unless ($first->equal( $row->$p() )) {
167 35     35   224 use Data::Dumper;
  35         72  
  35         33624  
168 137         17762 $l->trace("Quad $first_method and $p didn't match: " . Dumper($first, $row->$p()));
169 137         11084 next LOOP;
170             }
171             }
172             }
173             }
174            
175             # if ($row->context->isa('RDF::Trine::Node::Nil')) {
176             # next;
177             # }
178            
179 178         520 my $binding = {};
180 178         345 foreach my $key (keys %{ $self->[0]{mappings} }) {
  178         656  
181 299         1185 my $method = $self->[0]{mappings}{ $key };
182 299         1005 $binding->{ $key } = $row->$method();
183             }
184 178         1250 my $pre_bound = $self->[0]{bound};
185 178         788 my $bindings = RDF::Query::VariableBindings->new( $binding );
186 178 50       1102 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         362 @{ $bindings }{ keys %$pre_bound } = values %$pre_bound;
  178         893  
192 178 50       724 if (my $d = $self->delegate) {
193 0         0 $d->log_result( $self, $bindings );
194             }
195 178         865 return $bindings;
196             }
197 162         3213 $l->trace("No more quads");
198 162         1276 return;
199             }
200              
201             =item C<< close >>
202              
203             =cut
204              
205             sub close {
206 182     182 1 276 my $self = shift;
207 182 50       515 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         1261 delete $self->[0]{iter};
212 182         8848 delete $self->[0]{bound};
213 182         657 $self->SUPER::close();
214             }
215              
216             =item C<< nodes () >>
217              
218             =cut
219              
220             sub nodes {
221 150     150 1 200 my $self = shift;
222 150         208 return @{ $self }[1,2,3,4];
  150         602  
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 155 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 238 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 246 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 172 my $self = shift;
291 110         391 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 253 my $self = shift;
303 150         383 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