File Coverage

blib/lib/RDF/Trine/Store/Hexastore.pm
Criterion Covered Total %
statement 477 514 92.8
branch 109 136 80.1
condition 28 36 77.7
subroutine 52 57 91.2
pod 13 13 100.0
total 679 756 89.8


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             RDF::Trine::Store::Hexastore - RDF store implemented with the hexastore index
4              
5             =head1 VERSION
6              
7             This document describes RDF::Trine::Store::Hexastore version 1.017
8              
9             =head1 SYNOPSIS
10              
11             use RDF::Trine::Store::Hexastore;
12              
13             =head1 DESCRIPTION
14              
15             RDF::Trine::Store::Hexastore provides an in-memory triple-store based on
16             six-way indexing as popularized by Hexastore.
17              
18             =cut
19              
20             package RDF::Trine::Store::Hexastore;
21              
22 68     68   447 use strict;
  68         168  
  68         1862  
23 68     68   419 use warnings;
  68         179  
  68         1899  
24 68     68   327 no warnings 'redefine';
  68         146  
  68         2124  
25 68     68   392 use base qw(RDF::Trine::Store);
  68         165  
  68         4727  
26              
27 68     68   431 use Data::Dumper;
  68         157  
  68         3113  
28 68     68   382 use RDF::Trine qw(iri);
  68         156  
  68         2479  
29 68     68   375 use RDF::Trine::Error;
  68         143  
  68         422  
30 68     68   3185 use List::Util qw(first);
  68         150  
  68         3650  
31 68     68   465 use Scalar::Util qw(refaddr reftype blessed);
  68         226  
  68         3159  
32 68     68   406 use Storable qw(nstore retrieve);
  68         147  
  68         4347  
33 68     68   419 use Carp qw(croak);
  68         154  
  68         2594  
34 68     68   389 use Time::HiRes qw ( time );
  68         151  
  68         720  
35 68     68   11793 use Log::Log4perl;
  68         156  
  68         679  
36              
37 68     68   4707 use constant NODES => qw(subject predicate object);
  68         154  
  68         5587  
38 68     68   419 use constant NODEMAP => { subject => 0, predicate => 1, object => 2, context => 3 };
  68         149  
  68         4194  
39 68         5049 use constant OTHERNODES => {
40             subject => [qw(predicate object)],
41             predicate => [qw(subject object)],
42             object => [qw(subject predicate)],
43 68     68   392 };
  68         157  
44              
45             ######################################################################
46              
47             our $VERSION;
48             BEGIN {
49 68     68   256 $VERSION = "1.017";
50 68         148 my $class = __PACKAGE__;
51 68         251632 $RDF::Trine::Store::STORE_CLASSES{ $class } = $VERSION;
52             }
53              
54             ######################################################################
55              
56             sub _config_meta {
57             return {
58 0     0   0 required_keys => [],
59             fields => {},
60             }
61             }
62              
63              
64             =head1 METHODS
65              
66             Beyond the methods documented below, this class inherits methods from the
67             L<RDF::Trine::Store> class.
68              
69             =over 4
70              
71             =item C<< new () >>
72              
73             Returns a new storage object.
74              
75             =item C<new_with_config ( $hashref )>
76              
77             Returns a new storage object configured with a hashref with certain
78             keys as arguments.
79              
80             The C<storetype> key must be C<Hexastore> for this backend.
81              
82             This module also supports initializing the store from a file or URL,
83             in which case, a C<sources> key may be used. This holds an arrayref of
84             hashrefs. To load a file, you may give the file name with a C<file>
85             key in the hashref, and to load a URL, use C<url>. See example
86             below. Furthermore, the following keys may be used:
87              
88             =over
89              
90             =item C<syntax>
91              
92             The syntax of the parsed file or URL.
93              
94             =item C<base_uri>
95              
96             The base URI to be used for a parsed file.
97              
98             =back
99              
100             The following example initializes a Hexastore store based on a local file and a remote URL:
101              
102             my $store = RDF::Trine::Store->new_with_config( {
103             storetype => 'Hexastore',
104             sources => [
105             {
106             file => 'test-23.ttl',
107             syntax => 'turtle',
108             },
109             {
110             url => 'http://www.kjetil.kjernsmo.net/foaf',
111             syntax => 'rdfxml',
112             }
113             ]});
114              
115              
116             =cut
117              
118             sub new {
119 5     5 1 596 my $class = shift;
120 5         17 my $self = bless({}, $class);
121 5         28 $self->nuke; # nuke resets the store, thus doing the same thing as init should do
122 5         12 return $self;
123             }
124              
125             sub _new_with_string {
126 1     1   5 my ($self, $config) = @_;
127 1         5 my ($filename) = $config =~ m/file=(.+)$/; # TODO: It has a Storable part too, for later use.
128 1         5 return $self->load($filename);
129             }
130              
131             # TODO: Refactor, almost identical to Memory
132             sub _new_with_config {
133 1     1   4 my $class = shift;
134 1         2 my $config = shift;
135 1 50       2 my @sources = @{ $config->{sources} || [] };
  1         6  
136 1         5 my $self = $class->new();
137 1         7 foreach my $source (@sources) {
138 2         27 my %args;
139 2 50       8 if (my $g = $source->{graph}) {
140 0 0       0 $args{context} = (blessed($g) ? $g : iri($g));
141             }
142 2 50       9 if ($source->{url}) {
    50          
143 0         0 my $parser = RDF::Trine::Parser->new($source->{syntax});
144 0         0 my $model = RDF::Trine::Model->new( $self );
145 0         0 $parser->parse_url_into_model( $source->{url}, $model, %args );
146             } elsif ($source->{file}) {
147 2 50       88 open(my $fh, "<:encoding(UTF-8)", $source->{file}) || throw RDF::Trine::Error -text => "Couldn't open file $source->{file}";
148 2         155 my $parser = RDF::Trine::Parser->new($source->{syntax});
149 2         18 my $model = RDF::Trine::Model->new( $self );
150 2         20 $parser->parse_file_into_model( $source->{base_uri}, $source->{file}, $model, %args );
151             } else {
152 0         0 throw RDF::Trine::Error::MethodInvocationError -text => "$class needs a url or file argument";
153             }
154             }
155 1         8 return $self;
156             }
157              
158              
159              
160              
161             =item C<< store ( $filename ) >>
162              
163             Write the triples data to a file specified by C<< $filename >>.
164             This data may be read back in with the C<< load >> method.
165              
166             =cut
167              
168             sub store {
169 1     1 1 7 my $self = shift;
170 1         3 my $fname = shift;
171 1         6 nstore( $self, $fname );
172             }
173              
174             =item C<< load ( $filename ) >>
175              
176             Returns a new Hexastore object with triples data from the specified file.
177              
178             =cut
179              
180             sub load {
181 2     2 1 28 my $class = shift;
182 2         5 my $fname = shift;
183 2         8 return retrieve($fname);
184             }
185              
186             =item C<< temporary_store >>
187              
188             Returns a temporary (empty) triple store.
189              
190             =cut
191              
192             sub temporary_store {
193 1     1 1 14 my $class = shift;
194 1         4 return $class->new();
195             }
196              
197             =item C<< get_statements ($subject, $predicate, $object [, $context] ) >>
198              
199             Returns a stream object of all statements matching the specified subject,
200             predicate and objects. Any of the arguments may be undef to match any value.
201              
202             =cut
203              
204             sub get_statements {
205 32     32 1 490 my $self = shift;
206 32         106 my @nodes = splice(@_, 0, 3);
207 32         57 my $context = shift;
208 32         77 my %args = @_;
209 32 100       102 my @orderby = (ref($args{orderby})) ? @{$args{orderby}} : ();
  5         13  
210            
211 32 50 66     120 if (defined($context) and not($context->isa('RDF::Trine::Node::Nil'))) {
212 0         0 return RDF::Trine::Iterator::Graph->new( [] );
213             }
214            
215 32         57 my $defined = 0;
216 32         53 my %variable_map;
217 32         80 foreach my $i (0 .. 2) {
218 96         163 my $node = $nodes[ $i ];
219 96         180 my $pos = (NODES)[ $i ];
220 96 100 100     441 $defined++ if (defined($node) and not($node->isa('RDF::Trine::Node::Variable')));
221 96 100 100     514 if (blessed($node) and $node->isa('RDF::Trine::Node::Variable')) {
222 23         60 $variable_map{ $node->name } = $pos;
223             }
224             }
225            
226 32         82 my @ids = map { $self->_node2id( $_ ) } @nodes;
  87         192  
227 32         85 my @names = NODES;
228 32         80 my @keys = map { $names[$_], $ids[$_] } (0 .. $#names);
  96         222  
229 32 100       135 if ($defined == 3) {
    100          
    100          
230 3         10 my $index = $self->_index_from_pair( $self->_index_root, @keys[ 0,1 ] );
231 3         11 my $list = $self->_index_from_pair( $index, @keys[ 2,3 ] );
232 3 100       13 if ($self->_page_contains_node( $list, $ids[2] )) {
233 1         4 return RDF::Trine::Iterator::Graph->new( [ RDF::Trine::Statement->new( @nodes ) ] );
234             } else {
235 2         11 return RDF::Trine::Iterator::Graph->new( [] );
236             }
237             } elsif ($defined == 2) {
238 8         26 my @dkeys;
239             my $ukey;
240 8         28 foreach my $i (0 .. 2) {
241 24 100 100     136 if (defined($nodes[ $i ]) and not($nodes[ $i ]->isa('RDF::Trine::Node::Variable'))) {
242 16         39 push( @dkeys, $names[$i] );
243             } else {
244 8         21 $ukey = $names[$i];
245             }
246             }
247 8         24 @keys = map { $_ => $self->_node2id( $nodes[ NODEMAP->{ $_ } ] ) } @dkeys;
  16         46  
248            
249 8         33 my $index = $self->_index_from_pair( $self->_index_root, @keys[ 0,1 ] );
250 8         24 my $list = $self->_index_from_pair( $index, @keys[ 2,3 ] );
251            
252 8         29 my @local_list = $self->_node_values( $list );
253             my $sub = sub {
254 24 100   24   83 return unless (scalar(@local_list));
255 16         39 my $id = shift(@local_list);
256 16         36 my %data = map { $_ => $nodes[ NODEMAP->{ $_ } ] } @dkeys;
  32         107  
257 16         52 $data{ $ukey } = $self->_id2node( $id );
258 16         73 my $st = RDF::Trine::Statement->new( @data{qw(subject predicate object)} );
259 16         44 return $st;
260 8         47 };
261 8         54 return RDF::Trine::Iterator::Graph->new( $sub );
262             } elsif ($defined == 1) {
263 13         40 my $dkey;
264             my @ukeys;
265 13         0 my $uvar;
266 13         28 my $check_dup = 0;
267 13         35 foreach my $i (0 .. 2) {
268 39 100 100     183 if (defined($nodes[ $i ]) and not($nodes[ $i ]->isa('RDF::Trine::Node::Variable'))) {
269 13         32 $dkey = $names[$i];
270             } else {
271 26 100 66     118 if (blessed($nodes[ $i ]) and $nodes[ $i ]->isa('RDF::Trine::Node::Variable')) {
272 10 100       30 if (defined($uvar)) {
273 5 50       15 if ($uvar eq $nodes[ $i ]->name) {
274 0         0 $check_dup = 1;
275             }
276             } else {
277 5         16 $uvar = $nodes[ $i ]->name;
278             }
279             }
280 26         56 push( @ukeys, $names[$i] );
281             }
282             }
283 13         51 @keys = ($dkey => $self->_node2id( $nodes[ NODEMAP->{ $dkey } ] ));
284            
285 13         27 my $rev = 0;
286 13 100       34 if (@orderby) {
287 3 100       9 $rev = 1 if ($orderby[1] eq 'DESC');
288 3         8 my $sortkey = $variable_map{ $orderby[0] };
289 3 100       9 if ($sortkey ne $ukeys[0]) {
290 2         6 @ukeys = reverse(@ukeys);
291             }
292             }
293            
294 13         37 my $index = $self->_index_from_pair( $self->_index_root, @keys );
295 13         42 my $ukeys1 = $self->_index_values_from_key( $index, $ukeys[0] );
296 13         33 my @ukeys1 = $self->_index_values( $ukeys1, $rev );
297              
298 13         29 my @local_list;
299             my $ukey1;
300             my $sub = sub {
301 43     43   144 while (0 == scalar(@local_list)) {
302 30 100       91 return unless (scalar(@ukeys1));
303 21         45 $ukey1 = shift(@ukeys1);
304             # warn '>>>>>>>>> ' . Dumper( $ukeys[0], $ukey1, $data );
305 21         64 my $list = $self->_index_from_pair( $index, $ukeys[0], $ukey1 );
306 21         54 @local_list = $self->_node_values( $list );
307 21 50       81 if ($check_dup) {
308 0         0 @local_list = grep { $_ == $ukey1 } @local_list;
  0         0  
309             }
310             }
311 34         82 my $id = shift(@local_list);
312 34         112 my %data = ($dkey => $nodes[ NODEMAP->{ $dkey } ]);
313 34         71 @data{ @ukeys } = map { $self->_id2node( $_ ) } ($ukey1, $id);
  68         139  
314 34         148 my $st = RDF::Trine::Statement->new( @data{qw(subject predicate object)} );
315 34         95 return $st;
316 13         71 };
317 13         71 return RDF::Trine::Iterator::Graph->new( $sub );
318             } else {
319 8         33 my $dup_pos;
320             my $dup_var;
321 8         0 my %dup_counts;
322 8         0 my %dup_var_pos;
323 8         16 my $max = 0;
324 8         22 foreach my $i (0 .. 2) {
325 24 100 66     123 if (blessed($nodes[ $i ]) and $nodes[ $i ]->isa('RDF::Trine::Node::Variable')) {
326 9         25 my $name = $nodes[ $i ]->name;
327 9         14 push( @{ $dup_var_pos{ $name } }, $names[ $i ] );
  9         19  
328 9 100       28 if (++$dup_counts{ $name } > $max) {
329 3         6 $max = $dup_counts{ $name };
330 3         7 $dup_pos = $names[ $i ];
331 3         6 $dup_var = $name;
332             }
333             }
334             }
335             # warn Dumper($dup_pos, $dup_var, $max, \%dup_var_pos);
336            
337 8         19 my $rev = 0;
338 8         20 my (@order_keys, $final_key);
339 8 100       23 if (@orderby) {
340 2 100       8 $rev = 1 if ($orderby[1] eq 'DESC');
341 2         5 my $sortkey = $variable_map{ $orderby[0] };
342 2         5 my @nodes = ($sortkey, grep { $_ ne $sortkey } NODES);
  6         13  
343 2         8 @order_keys = @nodes[0,1];
344 2         5 $final_key = $nodes[2];
345             } else {
346 6         12 $final_key = 'object';
347 6         18 @order_keys = qw(subject predicate);
348             }
349 8 50       28 if ($max > 1) {
350 0         0 @order_keys = @{ $dup_var_pos{ $dup_var } };
  0         0  
351 0         0 my %order_keys = map { $_ => 1 } @order_keys;
  0         0  
352 0 0       0 if (3 == scalar(@order_keys)) {
353 0         0 $final_key = pop(@order_keys);
354             } else {
355 0     0   0 $final_key = first { not($order_keys{ $_ }) } @names;
  0         0  
356             }
357             }
358            
359 8         27 my $subj = $self->_index_values_from_key( $self->_index_root, $order_keys[0] );
360 8         24 my @skeys = $self->_index_values( $subj, $rev );
361 8         36 my ($sid, $pid);
362 8         0 my @pkeys;
363 8         0 my @local_list;
364             my $sub = sub {
365 59     59   155 while (0 == scalar(@local_list)) {
366             # no more objects. go to next predicate.
367 41         100 while (0 == scalar(@pkeys)) {
368             # no more predicates. go to next subject.
369 28 100       77 return unless (scalar(@skeys));
370 20         39 $sid = shift(@skeys);
371             # warn "*** using subject $sid\n";
372 20         32 @pkeys = sort { $a <=> $b } keys %{ $subj->{ $sid }{ $order_keys[1] } };
  17         52  
  20         97  
373 20 50       80 if ($max >= 2) {
374 0         0 @pkeys = grep { $_ == $sid } @pkeys;
  0         0  
375             }
376             }
377 33         60 $pid = shift(@pkeys);
378             # warn "*** using predicate $pid\n";
379 33         82 my $index = $self->_index_from_pair( $subj, $sid, $order_keys[1] );
380 33         74 my $list = $self->_node_list_from_id( $index, $pid );
381 33         79 @local_list = $self->_node_values( $list );
382 33 50       115 if ($max == 3) {
383 0         0 @local_list = grep { $_ == $pid } @local_list;
  0         0  
384             }
385             # warn "---> object list: [" . join(', ', @local_list) . "]\n";
386             }
387 51         92 my $id = shift(@local_list);
388 51         152 my %data = (
389             $order_keys[0] => $sid,
390             $order_keys[1] => $pid,
391             $final_key => $id,
392             );
393 51         110 my @nodes = map { $self->_id2node( $_ ) } (@data{qw(subject predicate object)});
  153         273  
394 51         160 my $st = RDF::Trine::Statement->new( @nodes );
395 51         131 return $st;
396 8         50 };
397 8         39 return RDF::Trine::Iterator::Graph->new( $sub );
398             }
399             }
400              
401             =item C<< get_pattern ( $bgp [, $context] ) >>
402              
403             Returns a stream object of all bindings matching the specified graph pattern.
404              
405             =cut
406              
407             sub get_pattern {
408 4     4 1 19 my $self = shift;
409 4         9 my $bgp = shift;
410 4 50       28 if ($bgp->isa('RDF::Trine::Pattern')) {
411 4         19 $bgp = $bgp->sort_for_join_variables();
412             }
413 4         15 my @triples = $bgp->triples;
414 4 100       15 if (2 == scalar(@triples)) {
415 3         9 my ($t1, $t2) = @triples;
416 3         15 my @v1 = $t1->referenced_variables;
417 3         7 my %v1 = map { $_ => 1 } @v1;
  2         13  
418 3         11 my @v2 = $t2->referenced_variables;
419 3         10 my @shared = grep { exists($v1{$_}) } @v2;
  5         14  
420 3 100       9 if (@shared) {
421             # warn 'there is a shared variable -- we can use a merge-join';
422             # there is a shared variable -- we can use a merge-join
423 1         2 my $shrkey = $shared[0];
424             # warn "- $shrkey\n";
425             # warn $t2->as_string;
426 1         4 my $i1 = $self->SUPER::_get_pattern( RDF::Trine::Pattern->new( $t1 ), undef, orderby => [ $shrkey => 'ASC' ] );
427 1         7 my $i2 = $self->SUPER::_get_pattern( RDF::Trine::Pattern->new( $t2 ), undef, orderby => [ $shrkey => 'ASC' ] );
428            
429 1         7 my $i1current = $i1->next;
430 1         4 my $i2current = $i2->next;
431 1         2 my @results;
432 1   66     9 while (defined($i1current) and defined($i2current)) {
433 1         2 my $i1cur = $i1current->{ $shrkey };
434 1         3 my $i2cur = $i2current->{ $shrkey };
435 1 50       6 if ($i1current->{ $shrkey }->equal( $i2current->{ $shrkey } )) {
    0          
436 1         2 my @matching_i2_rows;
437 1         3 my $match_value = $i1current->{ $shrkey };
438 1         4 while ($match_value->equal( $i2current->{ $shrkey } )) {
439 4         7 push( @matching_i2_rows, $i2current );
440 4 50       11 unless ($i2current = $i2->next) {
441             # warn "no more from i2";
442 0         0 last;
443             }
444             }
445            
446 1         10 while ($match_value->equal( $i1current->{ $shrkey } )) {
447 1         3 foreach my $i2_row (@matching_i2_rows) {
448 4         13 my $new = $self->_join( $i1current, $i2_row );
449 4         9 push( @results, $new );
450             }
451 1 50       5 unless ($i1current = $i1->next) {
452             # warn "no more from i1";
453 1         5 last;
454             }
455             }
456             } elsif ($i1current->{ $shrkey }->compare( $i2current->{ $shrkey } ) == -1) {
457 0         0 my $i1v = $i1current->{ $shrkey };
458 0         0 my $i2v = $i2current->{ $shrkey };
459             # warn "keys don't match: $i1v <=> $i2v\n";
460 0         0 $i1current = $i1->next;
461             } else { # ($i1current->{ $shrkey } > $i2current->{ $shrkey })
462 0         0 my $i1v = $i1current->{ $shrkey };
463 0         0 my $i2v = $i2current->{ $shrkey };
464             # warn "keys don't match: $i1v <=> $i2v\n";
465 0         0 $i2current = $i2->next;
466             }
467             }
468 1         6 return RDF::Trine::Iterator::Bindings->new( \@results, [ $bgp->referenced_variables ] );
469             } else {
470 2         10 my $l = Log::Log4perl->get_logger("rdf.trine.store.hexastore");
471 2         62 $l->info('No shared variable -- cartesian product');
472             # no shared variable -- cartesian product
473 2         21 my $i1 = $self->SUPER::_get_pattern( RDF::Trine::Pattern->new( $t1 ) );
474 2         10 my $i2 = $self->SUPER::_get_pattern( RDF::Trine::Pattern->new( $t2 ) );
475 2         5 my @i1;
476 2         15 while (my $row = $i1->next) {
477 3         13 push(@i1, $row);
478             }
479            
480 2         5 my @results;
481 2         6 while (my $row2 = $i2->next) {
482 6         19 foreach my $row1 (@i1) {
483 9         30 push(@results, { %$row1, %$row2 });
484             }
485             }
486 2         10 return RDF::Trine::Iterator::Bindings->new( \@results, [ $bgp->referenced_variables ] );
487             }
488             } else {
489 1         12 return $self->SUPER::_get_pattern( $bgp );
490             }
491             }
492              
493             =item C<< supports ( [ $feature ] ) >>
494              
495             If C<< $feature >> is specified, returns true if the feature is supported by the
496             store, false otherwise. If C<< $feature >> is not specified, returns a list of
497             supported features.
498              
499             =cut
500              
501             sub supports {
502 0     0 1 0 return;
503             }
504              
505             sub _join {
506 4     4   6 my $self = shift;
507 4         6 my $rowa = shift;
508 4         8 my $rowb = shift;
509            
510 4         5 my %keysa;
511 4         10 my @keysa = keys %$rowa;
512 4         10 @keysa{ @keysa } = (1) x scalar(@keysa);
513 4         11 my @shared = grep { exists $keysa{ $_ } } (keys %$rowb);
  12         25  
514 4         7 foreach my $key (@shared) {
515 4         8 my $val_a = $rowa->{ $key };
516 4         6 my $val_b = $rowb->{ $key };
517 4 50 33     18 next unless (defined($val_a) and defined($val_b));
518 4         11 my $equal = $val_a->equal( $val_b );
519 4 50       12 unless ($equal) {
520 0         0 return;
521             }
522             }
523            
524 4         8 my $row = { (map { $_ => $rowa->{$_} } grep { defined($rowa->{$_}) } keys %$rowa), (map { $_ => $rowb->{$_} } grep { defined($rowb->{$_}) } keys %$rowb) };
  4         11  
  4         10  
  12         31  
  12         21  
525 4         10 return $row;
526             }
527              
528             =item C<< get_contexts >>
529              
530             =cut
531              
532             sub get_contexts {
533 1     1 1 13 my $l = Log::Log4perl->get_logger("rdf.trine.store.hexastore");
534 1         499 $l->warn("Contexts not supported for the Hexastore store");
535 1         17 return RDF::Trine::Iterator->new([]);
536             }
537              
538             =item C<< add_statement ( $statement [, $context] ) >>
539              
540             Adds the specified C<$statement> to the underlying model.
541              
542             =cut
543              
544             sub add_statement {
545 55     55 1 101 my $self = shift;
546 55         89 my $st = shift;
547 55         91 my $added = 0;
548              
549             # believe it or not, these calls add up.
550 55         126 my %stmt = map { $_ => $st->$_ } NODES;
  165         468  
551 55         126 my %ids = map { $_ => $self->_node2id($stmt{$_}) } NODES;
  165         375  
552              
553 55         149 foreach my $first (NODES) {
554 165         278 my $firstnode = $stmt{$first};
555 165         251 my $id1 = $ids{$first};
556 165         236 my @others = @{ OTHERNODES->{ $first } };
  165         360  
557 165         425 my @orders = ([@others], [reverse @others]);
558 165         277 foreach my $order (@orders) {
559 330         588 my ($second, $third) = @$order;
560 330         629 my ($id2, $id3) = @ids{$second, $third};
561 330         634 my $list = $self->_get_terminal_list( $first => $id1, $second => $id2 );
562 330 100       629 if ($self->_add_node_to_page( $list, $id3 )) {
563 159         402 $added++;
564             }
565             }
566             }
567 55 100       139 if ($added) {
568 53         95 $self->{ size }++;
569 53         259 $self->{etag} = time;
570             }
571             }
572              
573             =item C<< remove_statement ( $statement [, $context]) >>
574              
575             Removes the specified C<$statement> from the underlying model.
576              
577             =cut
578              
579             sub remove_statement {
580 13     13 1 24 my $self = shift;
581 13         21 my $st = shift;
582 13         32 my @ids = map { $self->_node2id( $st->$_() ) } NODES;
  39         142  
583             # warn "*** removing statement @ids\n";
584              
585 13         28 my $removed = 0;
586 13         30 foreach my $first (NODES) {
587 39         109 my $firstnode = $st->$first();
588 39         80 my $id1 = $self->_node2id( $firstnode );
589 39         65 my @others = @{ OTHERNODES->{ $first } };
  39         98  
590 39         102 my @orders = ([@others], [reverse @others]);
591 39         70 foreach my $order (@orders) {
592 78         144 my ($second, $third) = @$order;
593 78         130 my ($id2, $id3) = map { $self->_node2id( $st->$_() ) } ($second, $third);
  156         391  
594 78         180 my $list = $self->_get_terminal_list( $first => $id1, $second => $id2 );
595 78 100       149 if ($self->_remove_node_from_page( $list, $id3 )) {
596 39         109 $removed++;
597             }
598             # warn "removing $first-$second-$third $id1-$id2-$id3 from list [" . join(', ', @$list) . "]\n";
599             # warn "\t- remaining: [" . join(', ', @$list) . "]\n";
600             }
601             }
602              
603 13 50       33 if ($removed) {
604 13         21 $self->{ size }--;
605 13         60 $self->{etag} = time;
606             }
607             }
608              
609             =item C<< remove_statements ( $subject, $predicate, $object [, $context]) >>
610              
611             Removes the specified C<$statement> from the underlying model.
612              
613             =item C<< etag >>
614              
615             Returns an Etag suitable for use in an HTTP Header.
616              
617             =cut
618              
619             sub etag {
620 0     0 1 0 return $_[0]->{etag};
621             }
622              
623              
624             =item C<< nuke >>
625              
626             Permanently removes all the data in the store.
627              
628             =cut
629              
630             sub nuke {
631 6     6 1 14 my $self = shift;
632 6         23 $self->{data} = $self->_new_index_page;
633 6         34 $self->{node2id} = {};
634 6         19 $self->{id2node} = {};
635 6         18 $self->{next_id} = 1;
636 6         17 $self->{size} = 0;
637 6         32 $self->{etag} = time;
638 6         15 return $self;
639             }
640              
641              
642              
643             =item C<< count_statements ($subject, $predicate, $object) >>
644              
645             Returns a count of all the statements matching the specified subject,
646             predicate and objects. Any of the arguments may be undef to match any value.
647              
648             =cut
649              
650             sub count_statements {
651 55     55 1 131 my $self = shift;
652 55         131 my @nodes = @_;
653 55         107 my @ids = map { $self->_node2id( $_ ) } @nodes;
  182         339  
654 55         131 my @names = NODES;
655 55         124 my @keys = map { $names[$_], $ids[$_] } (0 .. $#names);
  165         349  
656 55         114 my @dkeys;
657             my @ukeys;
658            
659 55 100 100     285 if (scalar(@nodes) > 3 and defined($nodes[3]) and not($nodes[3]->isa('RDF::Trine::Node::Nil'))) {
      66        
660 1         4 return 0;
661             }
662            
663 54         156 foreach my $i (0 .. 2) {
664 162 100       308 if (defined($nodes[ $i ])) {
665 38         73 push( @dkeys, $names[$i] );
666             } else {
667 124         216 push( @ukeys, $names[$i] );
668             }
669             }
670 54         120 @keys = map { $_ => $self->_node2id( $nodes[ NODEMAP->{ $_ } ] ) } @dkeys;
  38         99  
671 54 100       151 if (0 == scalar(@keys)) {
    100          
    100          
672 29         172 return $self->{ size };
673             } elsif (2 == scalar(@keys)) {
674 15         36 my $index = $self->_index_from_pair( $self->_index_root, @keys );
675 15         44 return $self->_count_statements( $index, @ukeys );
676             } elsif (4 == scalar(@keys)) {
677 7         19 my $index = $self->_index_from_pair( $self->_index_root, @keys[ 0,1 ] );
678 7         21 my $list = $self->_index_from_pair( $index, @keys[ 2,3 ] );
679 7         22 return $self->_node_count( $list );
680             } else {
681 3         12 my $index = $self->_index_from_pair( $self->_index_root, @keys[ 0,1 ] );
682 3         11 my $list = $self->_index_from_pair( $index, @keys[ 2,3 ] );
683 3 100       11 return ($self->_page_contains_node( $list, $keys[5] ))
684             ? 1
685             : 0;
686             }
687             }
688              
689             sub _count_statements {
690 35     35   54 my $self = shift;
691 35         51 my $data = shift;
692 35         70 my @ukeys = @_;
693 35 100       73 if (1 >= scalar(@ukeys)) {
694 20         49 return $self->_node_count( $data );
695             } else {
696 15         26 my $count = 0;
697 15         26 my $ukey = shift(@ukeys);
698 15         32 my $data = $data->{ $ukey };
699 15         46 foreach my $k (keys %$data) {
700 20         57 $count += $self->_count_statements( $data->{ $k }, @ukeys );
701             }
702 15         79 return $count;
703             }
704             }
705              
706             sub _node2id {
707 735     735   1066 my $self = shift;
708 735         999 my $node = shift;
709 735 100       2093 return unless (blessed($node));
710 567 100       2013 return if ($node->isa('RDF::Trine::Node::Variable'));
711              
712             # this gets called so much it actually significantly impacts run
713             # time. call it once per invocation of _node2id instead of twice.
714 544         1331 my $str = $node->as_string;
715 544         1169 my $id = $self->{ node2id }{ $str };
716              
717 544 100       1006 if (defined $id) {
718 499         1235 return $id;
719             } else {
720 45         126 $id = ($self->{ node2id }{ $str } = $self->{ next_id }++);
721 45         111 $self->{ id2node }{ $id } = $node;
722 45         159 return $id
723             }
724             }
725              
726             sub _id2node {
727 237     237   340 my $self = shift;
728 237         338 my $id = shift;
729 237 50       510 if (exists( $self->{ id2node }{ $id } )) {
730 237         550 return $self->{ id2node }{ $id };
731             } else {
732 0         0 return;
733             }
734             }
735              
736             sub _seen_nodes {
737 0     0   0 my $self = shift;
738 0         0 return values %{ $self->{ id2node } };
  0         0  
739             }
740              
741             ################################################################################
742             ### The methods below are the only ones that directly access and manipulate the
743             ### index structure. The terminal node lists, however, are manipulated by other
744             ### methods (add_statement, remove_statement, etc.).
745              
746             sub _index_root {
747 686     686   960 my $self = shift;
748 686         1492 return $self->{'data'};
749             }
750              
751             sub _get_terminal_list {
752 408     408   579 my $self = shift;
753 408         598 my $first = shift;
754 408         569 my $id1 = shift;
755 408         567 my $second = shift;
756 408         564 my $id2 = shift;
757 408         728 my $index = $self->_index_from_pair( $self->_index_root, $first, $id1 );
758 408         761 my $page = $self->_index_from_pair( $index, $second, $id2 );
759 408 100       804 if (ref($page)) {
760 322         554 return $page;
761             } else {
762 86         324 my ($k1, $k2) = sort { $a->[0] cmp $b->[0] } ([$first, $id1], [$second, $id2]);
  86         265  
763 86         186 my $index = $self->_index_from_pair( $self->_index_root, $k1->[0], $k1->[1] );
764 86 100       204 unless ($index) {
765 37         82 $index = $self->_add_index_page( $self->_index_root, $k1->[0], $k1->[1] );
766             }
767            
768 86         193 my $list = $self->_index_from_pair( $index, $k2->[0], $k2->[1] );
769 86 50       193 unless ($list) {
770 86         189 $list = $self->_add_list_page( $index, $k2->[0], $k2->[1] );
771             }
772            
773             ###
774            
775 86         175 my $index2 = $self->_index_from_pair( $self->_index_root, $k2->[0], $k2->[1] );
776 86 100       196 unless ($index2) {
777 12         34 $index2 = $self->_add_index_page( $self->_index_root, $k2->[0], $k2->[1] );
778             }
779 86         213 $self->_add_list_page( $index2, $k1->[0], $k1->[1], $list );
780 86         219 return $list;
781             }
782             }
783              
784             #########################################
785             #########################################
786             #########################################
787             sub _add_list_page {
788 172     172   243 my $self = shift;
789 172         248 my $index = shift;
790 172         238 my $key = shift;
791 172         263 my $value = shift;
792 172   66     420 my $list = shift || $self->_new_list_page;
793 172         409 $index->{ $key }{ $value } = $list;
794             }
795              
796             sub _add_index_page {
797 49     49   81 my $self = shift;
798 49         73 my $index = shift;
799 49         74 my $key = shift;
800 49         83 my $value = shift;
801 49         102 $index->{ $key }{ $value } = $self->_new_index_page;
802             }
803              
804             sub _index_from_pair {
805 1198     1198   1648 my $self = shift;
806 1198         1669 my $index = shift;
807 1198         1729 my $key = shift;
808 1198         1613 my $val = shift;
809 1198         2268 return $index->{ $key }{ $val };
810             }
811              
812             sub _node_list_from_id {
813 33     33   67 my $self = shift;
814 33         46 my $index = shift;
815 33         74 my $id = shift;
816 33         53 return $index->{ $id };
817             }
818              
819             sub _index_values_from_key {
820 21     21   37 my $self = shift;
821 21         33 my $index = shift;
822 21         37 my $key = shift;
823 21         46 return $index->{ $key };
824             }
825              
826             sub _index_values {
827 21     21   43 my $self = shift;
828 21         32 my $index = shift;
829 21         36 my $rev = shift;
830 21 100       56 if ($rev) {
831 2         10 my @values = sort { $b <=> $a } keys %$index;
  8         18  
832 2         9 return @values;
833             } else {
834 19         92 my @values = sort { $a <=> $b } keys %$index;
  23         68  
835 19         68 return @values;
836             }
837             }
838             #########################################
839             #########################################
840             #########################################
841              
842             sub _node_count {
843 27     27   40 my $self = shift;
844 27         42 my $list = shift;
845 27 100       37 return scalar(@{ $list || [] });
  27         116  
846             }
847              
848             sub _node_values {
849 62     62   102 my $self = shift;
850 62         97 my $list = shift;
851 62 50       148 if (ref($list)) {
852 62         195 return @$list;
853             } else {
854 0         0 return;
855             }
856             }
857              
858             sub _page_contains_node {
859 414     414   579 my $self = shift;
860 414         568 my $list = shift;
861 414         580 my $id = shift;
862 414         733 foreach (@$list) {
863 433 100       1102 return 1 if ($_ == $id);
864             }
865 201         452 return 0;
866             }
867              
868             sub _add_node_to_page {
869 330     330   469 my $self = shift;
870 330         463 my $list = shift;
871 330         438 my $id = shift;
872 330 100       621 if ($self->_page_contains_node( $list, $id )) {
873 171         483 return 0;
874             } else {
875 159         382 @$list = sort { $a <=> $b } (@$list, $id);
  128         250  
876 159         419 return 1;
877             }
878             }
879              
880             sub _remove_node_from_page {
881 78     78   112 my $self = shift;
882 78         109 my $list = shift;
883 78         112 my $id = shift;
884 78 100       151 if ($self->_page_contains_node( $list, $id )) {
885 39         72 @$list = grep { $_ != $id } @$list;
  51         120  
886 39         101 return 1;
887             } else {
888 39         115 return 0;
889             }
890             }
891              
892             sub _new_index_page {
893 55     55   218 return { __type => 'index' };
894             }
895              
896             sub _new_list_page {
897 86     86   255 return [];
898             }
899              
900             ################################################################################
901              
902             1;
903              
904             __END__
905              
906             =back
907              
908             =head1 BUGS
909              
910             Please report any bugs or feature requests to through the GitHub web interface
911             at L<https://github.com/kasei/perlrdf/issues>.
912              
913             =head1 AUTHOR
914              
915             Gregory Todd Williams C<< <gwilliams@cpan.org> >>
916              
917             =head1 COPYRIGHT
918              
919             Copyright (c) 2006-2012 Gregory Todd Williams. This
920             program is free software; you can redistribute it and/or modify it under
921             the same terms as Perl itself.
922              
923             =cut