File Coverage

blib/lib/RDF/Trine/Store/SPARQL.pm
Criterion Covered Total %
statement 45 334 13.4
branch 0 96 0.0
condition 0 21 0.0
subroutine 13 36 36.1
pod 12 12 100.0
total 70 499 14.0


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             RDF::Trine::Store::SPARQL - RDF Store proxy for a SPARQL endpoint
4              
5             =head1 VERSION
6              
7             This document describes RDF::Trine::Store::SPARQL version 1.017
8              
9             =head1 SYNOPSIS
10              
11             use RDF::Trine::Store::SPARQL;
12              
13             =head1 DESCRIPTION
14              
15             RDF::Trine::Store::SPARQL provides a RDF::Trine::Store API to interact with a
16             remote SPARQL endpoint.
17              
18             =cut
19              
20             package RDF::Trine::Store::SPARQL;
21              
22 68     68   442 use strict;
  68         160  
  68         1758  
23 68     68   334 use warnings;
  68         147  
  68         1717  
24 68     68   368 no warnings 'redefine';
  68         168  
  68         2018  
25 68     68   365 use base qw(RDF::Trine::Store);
  68         145  
  68         4707  
26              
27 68     68   436 use URI::Escape;
  68         154  
  68         3544  
28 68     68   402 use Data::Dumper;
  68         149  
  68         2670  
29 68     68   400 use List::Util qw(first);
  68         154  
  68         3628  
30              
31 68     68   390 use Scalar::Util qw(refaddr reftype blessed);
  68         158  
  68         2984  
32 68     68   29966 use HTTP::Request::Common;
  68         115404  
  68         3978  
33 68     68   827 use RDF::Trine::Error qw(:try);
  68         154  
  68         482  
34              
35             ######################################################################
36              
37             my @pos_names;
38             our $VERSION;
39             BEGIN {
40 68     68   11555 $VERSION = "1.017";
41 68         164 my $class = __PACKAGE__;
42 68         189 $RDF::Trine::Store::STORE_CLASSES{ $class } = $VERSION;
43 68         176316 @pos_names = qw(subject predicate object context);
44             }
45              
46             ######################################################################
47              
48             =head1 METHODS
49              
50             Beyond the methods documented below, this class inherits methods from the
51             L<RDF::Trine::Store> class.
52              
53             =over 4
54              
55             =item C<< new ( $url ) >>
56              
57             Returns a new storage object that will act as a proxy for the SPARQL endpoint
58             accessible via the supplied C<$url>.
59              
60             =item C<new_with_config ( $hashref )>
61              
62             Returns a new storage object configured with a hashref with certain
63             keys as arguments.
64              
65             The C<storetype> key must be C<SPARQL> for this backend.
66              
67             The following key must also be used:
68              
69             =over
70              
71             =item C<url>
72              
73             The URL of the remote endpoint.
74              
75             =back
76              
77             =cut
78              
79             sub new {
80 1     1 1 3 my $class = shift;
81 1         2 my $url = shift;
82 1         6 my $u = RDF::Trine->default_useragent->clone;
83 1         290 $u->default_headers->push_header( 'Accept' => "application/sparql-results+xml;q=0.9,application/rdf+xml;q=0.5,text/turtle;q=0.7,text/xml" );
84            
85 1         35 push(@{ $u->requests_redirectable }, 'POST');
  1         5  
86            
87 1         21 my $self = bless({
88             ua => $u,
89             url => $url,
90             }, $class);
91 1         6 return $self;
92             }
93              
94             sub _new_with_string {
95 1     1   4 my $class = shift;
96 1         2 my $config = shift;
97 1         5 return $class->new( $config );
98             }
99              
100             =item C<< new_with_config ( \%config ) >>
101              
102             Returns a new RDF::Trine::Store object based on the supplied configuration hashref.
103              
104             =cut
105              
106             sub new_with_config {
107 0     0 1   my $proto = shift;
108 0           my $config = shift;
109 0           $config->{storetype} = 'SPARQL';
110 0           return $proto->SUPER::new_with_config( $config );
111             }
112              
113             sub _new_with_config {
114 0     0     my $class = shift;
115 0           my $config = shift;
116 0           return $class->new( $config->{url} );
117             }
118              
119             sub _config_meta {
120             return {
121 0     0     required_keys => [qw(url)],
122             fields => {
123             url => { description => 'Endpoint URL', type => 'string' },
124             }
125             }
126             }
127              
128              
129             =item C<< get_statements ( $subject, $predicate, $object [, $context] ) >>
130              
131             Returns a stream object of all statements matching the specified subject,
132             predicate and objects. Any of the arguments may be undef to match any value.
133              
134             =cut
135              
136             sub get_statements {
137 0     0 1   my $self = shift;
138 0           my @nodes = @_[0..3];
139 0           my $bound = 0;
140 0           my %bound;
141            
142 0           my $use_quad = 0;
143 0 0         if (scalar(@_) >= 4) {
144 0           my $g = $nodes[3];
145 0 0 0       if (blessed($g) and not($g->is_variable) and not($g->is_nil)) {
      0        
146 0           $use_quad = 1;
147 0           $bound++;
148 0           $bound{ 3 } = $g;
149             }
150             }
151            
152 0           my @var_map = qw(s p o g);
153 0           my %var_map = map { $var_map[$_] => $_ } (0 .. $#var_map);
  0            
154 0           my @node_map;
155 0           foreach my $i (0 .. $#nodes) {
156 0 0 0       if (not(blessed($nodes[$i])) or $nodes[$i]->is_variable) {
157 0           $nodes[$i] = RDF::Trine::Node::Variable->new( $var_map[ $i ] );
158             }
159             }
160            
161 0 0         my $node_count = ($use_quad) ? 4 : 3;
162 0 0         my $st_class = ($use_quad) ? 'RDF::Trine::Statement::Quad' : 'RDF::Trine::Statement';
163 0           my @triple = @nodes[ 0..2 ];
164 0           my $iter;
165 0 0         if ($use_quad) {
166 0           my @vars = grep { $_->is_variable } @nodes;
  0            
167 0           my $names = join(' ', map { '?' . $_->name } @vars);
  0            
168 0 0         my $nodes = join(' ', map { ($_->is_variable) ? '?' . $_->name : $_->as_ntriples } @triple);
  0            
169 0 0         my $g = $nodes[3]->is_variable ? '?g' : $nodes[3]->as_ntriples;
170 0           $iter = $self->get_sparql( <<"END" );
171             SELECT $names WHERE {
172             GRAPH $g {
173             $nodes
174             }
175             }
176             END
177             } else {
178 0           my @vars = grep { $_->is_variable } @triple;
  0            
179 0           my $names = join(' ', map { '?' . $_->name } @vars);
  0            
180 0 0         my $nodes = join(' ', map { ($_->is_variable) ? '?' . $_->name : $_->as_ntriples } @triple);
  0            
181 0           $iter = $self->get_sparql( <<"END" );
182             SELECT $names WHERE { $nodes }
183             END
184             }
185             my $sub = sub {
186 0     0     my $row = $iter->next;
187 0 0         return unless $row;
188 0           my @triple;
189 0           foreach my $i (0 .. ($node_count-1)) {
190 0 0         if ($nodes[$i]->is_variable) {
191 0           $triple[$i] = $row->{ $nodes[$i]->name };
192             } else {
193 0           $triple[$i] = $nodes[$i];
194             }
195             }
196 0           my $triple = $st_class->new( @triple );
197 0           return $triple;
198 0           };
199 0           return RDF::Trine::Iterator::Graph->new( $sub );
200             }
201              
202             =item C<< get_pattern ( $bgp [, $context] ) >>
203              
204             Returns an iterator object of all bindings matching the specified graph pattern.
205              
206             =cut
207              
208             sub get_pattern {
209 0     0 1   my $self = shift;
210 0           my $bgp = shift;
211 0           my $context = shift;
212 0           my @args = @_;
213 0           my %args = @args;
214            
215 0 0         if ($bgp->isa('RDF::Trine::Statement')) {
216 0           $bgp = RDF::Trine::Pattern->new($bgp);
217             }
218            
219 0           my %iter_args;
220 0           my @triples = grep { $_->type eq 'TRIPLE' } $bgp->triples;
  0            
221 0           my @quads = grep { $_->type eq 'QUAD' } $bgp->triples;
  0            
222            
223 0           my @tripless;
224 0           foreach my $t (@triples) {
225 0           my @nodes = $t->nodes;
226 0           my @nodess;
227 0           foreach my $n (@nodes) {
228 0 0         push(@nodess, ($n->is_variable ? '?' . $n->name : $n->as_ntriples));
229             }
230 0           push(@tripless, join(' ', @nodess) . ' .');
231             }
232 0           my $triples = join("\n\t", @tripless);
233 0           my $quads = '';
234 0 0         if (@quads) {
235 0           return $self->SUPER::get_pattern( $bgp, $context, @args );
236 0           throw RDF::Trine::Error::UnimplementedError -text => "SPARQL get_pattern quad support not implemented";
237             }
238            
239 0           my $sparql = <<"END";
240             SELECT * WHERE {
241             $triples
242             $quads
243             }
244             END
245 0 0         if (my $o = delete $args{orderby}) {
246 0           my @order;
247 0           while (@$o) {
248 0           my ($k,$order) = splice(@$o,0,2,());
249 0           push(@order, "${order}(?$k)");
250             }
251 0 0         if (@order) {
252 0           $sparql .= "ORDER BY " . join(' ', @order);
253             }
254             }
255            
256 0           my $iter = $self->get_sparql( $sparql );
257 0           return $iter;
258             }
259              
260             =item C<< get_contexts >>
261              
262             Returns an RDF::Trine::Iterator over the RDF::Trine::Node objects comprising
263             the set of contexts of the stored quads.
264              
265             =cut
266              
267             sub get_contexts {
268 0     0 1   my $self = shift;
269 0           my $sparql = 'SELECT DISTINCT ?g WHERE { GRAPH ?g {} }';
270 0           my $iter = $self->get_sparql( $sparql );
271             my $sub = sub {
272 0     0     my $row = $iter->next;
273 0 0         return unless $row;
274 0           my $g = $row->{g};
275 0           return $g;
276 0           };
277 0           return RDF::Trine::Iterator->new( $sub );
278             }
279              
280             =item C<< add_statement ( $statement [, $context] ) >>
281              
282             Adds the specified C<$statement> to the underlying model.
283              
284             =cut
285              
286             sub add_statement {
287 0     0 1   my $self = shift;
288 0           my $st = shift;
289 0           my $context = shift;
290 0 0 0       unless (blessed($st) and $st->isa('RDF::Trine::Statement')) {
291 0           throw RDF::Trine::Error::MethodInvocationError -text => "Not a valid statement object passed to add_statement";
292             }
293            
294 0 0         if ($self->_bulk_ops) {
295 0           push(@{ $self->{ ops } }, ['_add_statements', $st, $context]);
  0            
296             } else {
297 0           my $sparql = $self->_add_statements_sparql( [ $st, $context ] );
298 0           my $iter = $self->_get_post_iterator( $sparql );
299 0           my $row = $iter->next;
300             }
301 0           return;
302             }
303              
304             sub _add_statements_sparql {
305 0     0     my $self = shift;
306 0           my @parts;
307 0           foreach my $op (@_) {
308 0           my $st = $op->[0];
309 0           my $context = $op->[1];
310 0 0         if ($st->isa('RDF::Trine::Statement::Quad')) {
311 0           push(@parts, 'GRAPH ' . $st->context->as_ntriples . ' { ' . join(' ', map { $_->as_ntriples } ($st->nodes)[0..2]) . ' }');
  0            
312             } else {
313 0           push(@parts, join(' ', map { $_->as_ntriples } $st->nodes) . ' .');
  0            
314             }
315             }
316 0           my $sparql = sprintf( 'INSERT DATA { %s }', join("\n\t", @parts) );
317 0           return $sparql;
318             }
319              
320             =item C<< remove_statement ( $statement [, $context]) >>
321              
322             Removes the specified C<$statement> from the underlying model.
323              
324             =cut
325              
326             sub remove_statement {
327 0     0 1   my $self = shift;
328 0           my $st = shift;
329 0           my $context = shift;
330            
331 0 0 0       unless (blessed($st) and $st->isa('RDF::Trine::Statement')) {
332 0           throw RDF::Trine::Error::MethodInvocationError -text => "Not a valid statement object passed to remove_statement";
333             }
334            
335 0 0         if ($self->_bulk_ops) {
336 0           push(@{ $self->{ ops } }, ['_remove_statements', $st, $context]);
  0            
337             } else {
338 0           my $sparql = $self->_remove_statements_sparql( [ $st, $context ] );
339 0           my $iter = $self->_get_post_iterator( $sparql );
340 0           my $row = $iter->next;
341             }
342 0           return;
343             }
344              
345             sub _remove_statements_sparql {
346 0     0     my $self = shift;
347 0           my @parts;
348 0           foreach my $op (@_) {
349 0           my $st = $op->[0];
350 0           my $context = $op->[1];
351 0 0         if ($st->isa('RDF::Trine::Statement::Quad')) {
352 0           push(@parts, 'GRAPH ' . $st->context->as_ntriples . ' { ' . join(' ', map { $_->as_ntriples } ($st->nodes)[0..2]) . ' }');
  0            
353             } else {
354 0           push(@parts, join(' ', map { $_->as_ntriples } $st->nodes) . ' .');
  0            
355             }
356             }
357 0           my $sparql = sprintf( 'DELETE DATA { %s }', join("\n\t", @parts) );
358 0           return $sparql;
359             }
360              
361             =item C<< remove_statements ( $subject, $predicate, $object [, $context]) >>
362              
363             Removes the specified C<$statement> from the underlying model.
364              
365             =cut
366              
367             sub remove_statements {
368 0     0 1   my $self = shift;
369 0           my $st = $_[0];
370 0           my $context;
371 0 0         if ($st->isa("RDF::Trine::Statement")) {
372 0           $context = $_[1];
373             } else {
374 0           my ($subj,$pred,$obj) = @_[0..2];
375 0           $context = $_[3];
376 0 0         if ($context) {
377 0           $st = RDF::Trine::Statement::Quad->new($subj, $pred, $obj, $context);
378             } else {
379 0           $st = RDF::Trine::Statement->new($subj, $pred, $obj);
380             }
381             }
382            
383 0 0 0       unless (blessed($st) and $st->isa('RDF::Trine::Statement')) {
384 0           throw RDF::Trine::Error::MethodInvocationError -text => "Not a valid statement object passed to remove_statements";
385             }
386            
387 0 0         if ($self->_bulk_ops) {
388 0           push(@{ $self->{ ops } }, ['_remove_statement_patterns', $st, $context]);
  0            
389             } else {
390 0           my $sparql = $self->_remove_statement_patterns_sparql( [ $st, $context ] );
391 0           my $iter = $self->_get_post_iterator( $sparql );
392 0           my $row = $iter->next;
393             }
394 0           return;
395             }
396              
397             sub _remove_statement_patterns_sparql {
398 0     0     my $self = shift;
399 0           my @parts;
400 0           foreach my $op (@_) {
401 0           my $st = $op->[0];
402 0           my $context = $op->[1];
403 0           my $sparql;
404 0 0         if ($st->isa('RDF::Trine::Statement::Quad')) {
405 0 0         push(@parts, 'GRAPH ' . $st->context->as_ntriples . ' { ' . join(' ', map { $_->is_variable ? '?' . $_->name : $_->as_ntriples } ($st->nodes)[0..2]) . ' }');
  0            
406             } else {
407 0 0         push(@parts, join(' ', map { $_->is_variable ? '?' . $_->name : $_->as_ntriples } $st->nodes) . ' .');
  0            
408             }
409            
410             }
411 0           my $sparql = sprintf( 'DELETE WHERE { %s }', join("\n\t", @parts));
412 0           return $sparql;
413             }
414              
415             =item C<< count_statements ( $subject, $predicate, $object, $context ) >>
416              
417             Returns a count of all the statements matching the specified subject,
418             predicate, object, and context. Any of the arguments may be undef to match any
419             value.
420              
421             =cut
422              
423             sub count_statements {
424 0     0 1   my $self = shift;
425 0           my @nodes = @_[0..3];
426 0           my $bound = 0;
427 0           my %bound;
428            
429 0           my $use_quad = 0;
430 0 0         if (scalar(@_) >= 4) {
431 0           $use_quad = 1;
432             # warn "count statements with quad" if ($::debug);
433 0           my $g = $nodes[3];
434 0 0 0       if (blessed($g) and not($g->is_variable)) {
435 0           $bound++;
436 0           $bound{ 3 } = $g;
437             }
438             }
439            
440 0           foreach my $i (0 .. $#nodes) {
441 0           my $node = $nodes[$i];
442 0 0         unless (defined($node)) {
443 0           $nodes[$i] = RDF::Trine::Node::Variable->new( "rt__" . $pos_names[$i] );
444             }
445             }
446            
447            
448 0           my $sparql;
449 0 0         my $triple = join(' ', map { $_->is_variable ? '?' . $_->name : $_->as_ntriples } @nodes[0..2]);
  0            
450 0 0         if ($use_quad) {
451 0           my $nodes;
452 0 0         if ($nodes[3]->isa('RDF::Trine::Node::Variable')) {
    0          
453 0           $nodes = "GRAPH ?rt__graph { $triple }";
454             } elsif ($nodes[3]->isa('RDF::Trine::Node::Nil')) {
455 0 0         $nodes = join(' ', map { $_->is_variable ? '?' . $_->name : $_->as_ntriples } @nodes[0..2]);
  0            
456             } else {
457 0 0         my $graph = $nodes[3]->is_variable ? '?' . $nodes[3]->name : $nodes[3]->as_ntriples;
458 0           $nodes = "GRAPH $graph { $triple }";
459             }
460 0           $sparql = "SELECT (COUNT(*) AS ?count) WHERE { $nodes }";
461             } else {
462 0           $sparql = "SELECT (COUNT(*) AS ?count) WHERE { $triple }";
463             }
464 0           my $iter = $self->get_sparql( $sparql );
465 0           my $row = $iter->next;
466 0           my $count = $row->{count};
467 0 0         return unless ($count);
468 0           return $count->literal_value;
469            
470             #
471             #
472             #
473             #
474             #
475             # # XXX try to send a COUNT() query and fall back if it fails
476             # my $iter = $self->get_statements( @_ );
477             # my $count = 0;
478             # while (my $st = $iter->next) {
479             # $count++;
480             # }
481             # return $count;
482             }
483              
484             =item C<< size >>
485              
486             Returns the number of statements in the store.
487              
488             =cut
489              
490             sub size {
491 0     0 1   my $self = shift;
492 0           return $self->count_statements( undef, undef, undef, undef );
493             }
494              
495             =item C<< supports ( [ $feature ] ) >>
496              
497             If C<< $feature >> is specified, returns true if the feature is supported by the
498             store, false otherwise. If C<< $feature >> is not specified, returns a list of
499             supported features.
500              
501             =cut
502              
503             sub supports {
504 0     0 1   my $self = shift;
505 0           my %features = map { $_ => 1 } (
  0            
506             'http://www.w3.org/ns/sparql-service-description#SPARQL10Query',
507             'http://www.w3.org/ns/sparql-service-description#SPARQL11Query',
508             'http://www.w3.org/ns/sparql-service-description#SPARQL11Update',
509             );
510 0 0         if (@_) {
511 0           my $f = shift;
512 0           return $features{ $f };
513             } else {
514 0           return keys %features;
515             }
516             }
517              
518             =item C<< get_sparql ( $sparql ) >>
519              
520             Returns an iterator object of all bindings matching the specified SPARQL query.
521              
522             =cut
523              
524             sub get_sparql {
525 0     0 1   my $self = shift;
526 0           my $sparql = shift;
527 0           my $handler = RDF::Trine::Iterator::SAXHandler->new();
528 0           my $p = XML::SAX::ParserFactory->parser(Handler => $handler);
529 0           my $ua = $self->{ua};
530            
531             # warn $sparql;
532            
533 0 0         my $urlchar = ($self->{url} =~ /\?/ ? '&' : '?');
534 0           my $url = $self->{url} . $urlchar . 'query=' . uri_escape($sparql);
535 0           my $response = $ua->get( $url );
536 0 0         if ($response->is_success) {
537 0           $p->parse_string( $response->decoded_content );
538 0           return $handler->iterator;
539             } else {
540 0           my $status = $response->status_line;
541 0           my $endpoint = $self->{url};
542             # warn "url: $url\n";
543             # warn $sparql;
544 0           warn Dumper($response);
545 0           throw RDF::Trine::Error -text => "Error making remote SPARQL call to endpoint $endpoint ($status)";
546             }
547             }
548              
549             sub _get_post_iterator {
550 0     0     my $self = shift;
551 0           my $sparql = shift;
552 0           my $ua = $self->{ua};
553            
554             # warn $sparql;
555            
556 0           my $url = $self->{url};
557 0           my $req = POST($url, [ update => $sparql ]);
558 0           my $response = $ua->request($req);
559 0 0         if ($response->is_success) {
560 0           return RDF::Trine::Iterator::Boolean->new( [ 1 ] );
561             } else {
562 0           my $status = $response->status_line;
563 0           my $endpoint = $self->{url};
564             # warn "url: $url\n";
565             # warn $sparql;
566 0           warn Dumper($response);
567 0           throw RDF::Trine::Error -text => "Error making remote SPARQL call to endpoint $endpoint ($status)";
568             }
569             }
570              
571             sub _bulk_ops {
572 0     0     my $self = shift;
573 0           return $self->{BulkOps};
574             }
575              
576             sub _begin_bulk_ops {
577 0     0     my $self = shift;
578 0           $self->{BulkOps} = 1;
579             }
580              
581             sub _end_bulk_ops {
582 0     0     my $self = shift;
583 0 0         if (scalar(@{ $self->{ ops } || []})) {
  0 0          
584 0           my @ops = splice(@{ $self->{ ops } });
  0            
585 0           my @aggops = $self->_group_bulk_ops( @ops );
586 0           my @sparql;
587 0           foreach my $aggop (@aggops) {
588 0           my ($type, $ops) = @$aggop;
589 0           my $method = "${type}_sparql";
590 0           push(@sparql, $self->$method( @$ops ));
591             }
592 0           my $sparql = join(";\n", @sparql);
593 0           my $iter = $self->_get_post_iterator( $sparql );
594 0           my $row = $iter->next;
595             }
596 0           $self->{BulkOps} = 0;
597             }
598              
599             sub _group_bulk_ops {
600 0     0     my $self = shift;
601 0 0         return unless (scalar(@_));
602 0           my @ops = @_;
603 0           my @bulkops;
604            
605 0           my $op = shift(@ops);
606 0           my $type = $op->[0];
607 0           push(@bulkops, [$type, [[ @{$op}[1 .. $#{ $op }] ]]]);
  0            
  0            
608 0           while (scalar(@ops)) {
609 0           my $op = shift(@ops);
610 0           my $type = $op->[0];
611 0 0         if ($op->[0] eq $bulkops[ $#bulkops ][0]) {
612 0           push( @{ $bulkops[ $#bulkops ][1] }, [ @{$op}[1 .. $#{ $op }] ] );
  0            
  0            
  0            
613             } else {
614 0           push(@bulkops, [$type, [[ @{$op}[1 .. $#{ $op }] ]]]);
  0            
  0            
615             }
616             }
617            
618 0           return @bulkops;
619             }
620              
621             1;
622              
623             __END__
624              
625             =back
626              
627             =head1 BUGS
628              
629             Please report any bugs or feature requests to through the GitHub web interface
630             at L<https://github.com/kasei/perlrdf/issues>.
631              
632             =head1 AUTHOR
633              
634             Gregory Todd Williams C<< <gwilliams@cpan.org> >>
635              
636             =head1 COPYRIGHT
637              
638             Copyright (c) 2006-2012 Gregory Todd Williams. This
639             program is free software; you can redistribute it and/or modify it under
640             the same terms as Perl itself.
641              
642             =cut