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.018
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   455 use strict;
  68         161  
  68         1783  
23 68     68   364 use warnings;
  68         144  
  68         1831  
24 68     68   329 no warnings 'redefine';
  68         144  
  68         2130  
25 68     68   349 use base qw(RDF::Trine::Store);
  68         195  
  68         4481  
26              
27 68     68   401 use Data::Dumper;
  68         156  
  68         3002  
28 68     68   394 use RDF::Trine qw(iri);
  68         145  
  68         2396  
29 68     68   433 use RDF::Trine::Error;
  68         153  
  68         404  
30 68     68   3045 use List::Util qw(first);
  68         142  
  68         3352  
31 68     68   387 use Scalar::Util qw(refaddr reftype blessed);
  68         175  
  68         3060  
32 68     68   400 use Storable qw(nstore retrieve);
  68         132  
  68         4071  
33 68     68   395 use Carp qw(croak);
  68         172  
  68         2593  
34 68     68   434 use Time::HiRes qw ( time );
  68         161  
  68         697  
35 68     68   11346 use Log::Log4perl;
  68         155  
  68         626  
36              
37 68     68   4500 use constant NODES => qw(subject predicate object);
  68         146  
  68         5383  
38 68     68   394 use constant NODEMAP => { subject => 0, predicate => 1, object => 2, context => 3 };
  68         139  
  68         3988  
39 68         4803 use constant OTHERNODES => {
40             subject => [qw(predicate object)],
41             predicate => [qw(subject object)],
42             object => [qw(subject predicate)],
43 68     68   383 };
  68         149  
44              
45             ######################################################################
46              
47             our $VERSION;
48             BEGIN {
49 68     68   240 $VERSION = "1.018";
50 68         139 my $class = __PACKAGE__;
51 68         245048 $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 527 my $class = shift;
120 5         17 my $self = bless({}, $class);
121 5         25 $self->nuke; # nuke resets the store, thus doing the same thing as init should do
122 5         14 return $self;
123             }
124              
125             sub _new_with_string {
126 1     1   4 my ($self, $config) = @_;
127 1         6 my ($filename) = $config =~ m/file=(.+)$/; # TODO: It has a Storable part too, for later use.
128 1         4 return $self->load($filename);
129             }
130              
131             # TODO: Refactor, almost identical to Memory
132             sub _new_with_config {
133 1     1   3 my $class = shift;
134 1         2 my $config = shift;
135 1 50       2 my @sources = @{ $config->{sources} || [] };
  1         5  
136 1         6 my $self = $class->new();
137 1         3 foreach my $source (@sources) {
138 2         23 my %args;
139 2 50       7 if (my $g = $source->{graph}) {
140 0 0       0 $args{context} = (blessed($g) ? $g : iri($g));
141             }
142 2 50       6 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       71 open(my $fh, "<:encoding(UTF-8)", $source->{file}) || throw RDF::Trine::Error -text => "Couldn't open file $source->{file}";
148 2         123 my $parser = RDF::Trine::Parser->new($source->{syntax});
149 2         12 my $model = RDF::Trine::Model->new( $self );
150 2         17 $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         6 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 13 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 27 my $class = shift;
194 1         6 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 548 my $self = shift;
206 32         115 my @nodes = splice(@_, 0, 3);
207 32         75 my $context = shift;
208 32         92 my %args = @_;
209 32 100       116 my @orderby = (ref($args{orderby})) ? @{$args{orderby}} : ();
  5         13  
210            
211 32 50 66     133 if (defined($context) and not($context->isa('RDF::Trine::Node::Nil'))) {
212 0         0 return RDF::Trine::Iterator::Graph->new( [] );
213             }
214            
215 32         67 my $defined = 0;
216 32         55 my %variable_map;
217 32         88 foreach my $i (0 .. 2) {
218 96         164 my $node = $nodes[ $i ];
219 96         195 my $pos = (NODES)[ $i ];
220 96 100 100     482 $defined++ if (defined($node) and not($node->isa('RDF::Trine::Node::Variable')));
221 96 100 100     507 if (blessed($node) and $node->isa('RDF::Trine::Node::Variable')) {
222 23         65 $variable_map{ $node->name } = $pos;
223             }
224             }
225            
226 32         80 my @ids = map { $self->_node2id( $_ ) } @nodes;
  87         182  
227 32         99 my @names = NODES;
228 32         89 my @keys = map { $names[$_], $ids[$_] } (0 .. $#names);
  96         255  
229 32 100       173 if ($defined == 3) {
    100          
    100          
230 3         15 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       14 if ($self->_page_contains_node( $list, $ids[2] )) {
233 1         13 return RDF::Trine::Iterator::Graph->new( [ RDF::Trine::Statement->new( @nodes ) ] );
234             } else {
235 2         14 return RDF::Trine::Iterator::Graph->new( [] );
236             }
237             } elsif ($defined == 2) {
238 8         25 my @dkeys;
239             my $ukey;
240 8         24 foreach my $i (0 .. 2) {
241 24 100 100     141 if (defined($nodes[ $i ]) and not($nodes[ $i ]->isa('RDF::Trine::Node::Variable'))) {
242 16         40 push( @dkeys, $names[$i] );
243             } else {
244 8         23 $ukey = $names[$i];
245             }
246             }
247 8         20 @keys = map { $_ => $self->_node2id( $nodes[ NODEMAP->{ $_ } ] ) } @dkeys;
  16         55  
248            
249 8         32 my $index = $self->_index_from_pair( $self->_index_root, @keys[ 0,1 ] );
250 8         36 my $list = $self->_index_from_pair( $index, @keys[ 2,3 ] );
251            
252 8         33 my @local_list = $self->_node_values( $list );
253             my $sub = sub {
254 24 100   24   75 return unless (scalar(@local_list));
255 16         36 my $id = shift(@local_list);
256 16         37 my %data = map { $_ => $nodes[ NODEMAP->{ $_ } ] } @dkeys;
  32         109  
257 16         58 $data{ $ukey } = $self->_id2node( $id );
258 16         79 my $st = RDF::Trine::Statement->new( @data{qw(subject predicate object)} );
259 16         43 return $st;
260 8         47 };
261 8         57 return RDF::Trine::Iterator::Graph->new( $sub );
262             } elsif ($defined == 1) {
263 13         44 my $dkey;
264             my @ukeys;
265 13         0 my $uvar;
266 13         30 my $check_dup = 0;
267 13         36 foreach my $i (0 .. 2) {
268 39 100 100     193 if (defined($nodes[ $i ]) and not($nodes[ $i ]->isa('RDF::Trine::Node::Variable'))) {
269 13         35 $dkey = $names[$i];
270             } else {
271 26 100 66     124 if (blessed($nodes[ $i ]) and $nodes[ $i ]->isa('RDF::Trine::Node::Variable')) {
272 10 100       27 if (defined($uvar)) {
273 5 50       15 if ($uvar eq $nodes[ $i ]->name) {
274 0         0 $check_dup = 1;
275             }
276             } else {
277 5         14 $uvar = $nodes[ $i ]->name;
278             }
279             }
280 26         66 push( @ukeys, $names[$i] );
281             }
282             }
283 13         56 @keys = ($dkey => $self->_node2id( $nodes[ NODEMAP->{ $dkey } ] ));
284            
285 13         31 my $rev = 0;
286 13 100       44 if (@orderby) {
287 3 100       8 $rev = 1 if ($orderby[1] eq 'DESC');
288 3         7 my $sortkey = $variable_map{ $orderby[0] };
289 3 100       7 if ($sortkey ne $ukeys[0]) {
290 2         6 @ukeys = reverse(@ukeys);
291             }
292             }
293            
294 13         47 my $index = $self->_index_from_pair( $self->_index_root, @keys );
295 13         50 my $ukeys1 = $self->_index_values_from_key( $index, $ukeys[0] );
296 13         49 my @ukeys1 = $self->_index_values( $ukeys1, $rev );
297              
298 13         31 my @local_list;
299             my $ukey1;
300             my $sub = sub {
301 43     43   126 while (0 == scalar(@local_list)) {
302 30 100       90 return unless (scalar(@ukeys1));
303 21         48 $ukey1 = shift(@ukeys1);
304             # warn '>>>>>>>>> ' . Dumper( $ukeys[0], $ukey1, $data );
305 21         73 my $list = $self->_index_from_pair( $index, $ukeys[0], $ukey1 );
306 21         65 @local_list = $self->_node_values( $list );
307 21 50       89 if ($check_dup) {
308 0         0 @local_list = grep { $_ == $ukey1 } @local_list;
  0         0  
309             }
310             }
311 34         72 my $id = shift(@local_list);
312 34         120 my %data = ($dkey => $nodes[ NODEMAP->{ $dkey } ]);
313 34         76 @data{ @ukeys } = map { $self->_id2node( $_ ) } ($ukey1, $id);
  68         156  
314 34         176 my $st = RDF::Trine::Statement->new( @data{qw(subject predicate object)} );
315 34         97 return $st;
316 13         86 };
317 13         100 return RDF::Trine::Iterator::Graph->new( $sub );
318             } else {
319 8         37 my $dup_pos;
320             my $dup_var;
321 8         0 my %dup_counts;
322 8         0 my %dup_var_pos;
323 8         19 my $max = 0;
324 8         21 foreach my $i (0 .. 2) {
325 24 100 66     142 if (blessed($nodes[ $i ]) and $nodes[ $i ]->isa('RDF::Trine::Node::Variable')) {
326 9         23 my $name = $nodes[ $i ]->name;
327 9         12 push( @{ $dup_var_pos{ $name } }, $names[ $i ] );
  9         21  
328 9 100       30 if (++$dup_counts{ $name } > $max) {
329 3         6 $max = $dup_counts{ $name };
330 3         5 $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         17 my $rev = 0;
338 8         17 my (@order_keys, $final_key);
339 8 100       25 if (@orderby) {
340 2 100       7 $rev = 1 if ($orderby[1] eq 'DESC');
341 2         5 my $sortkey = $variable_map{ $orderby[0] };
342 2         3 my @nodes = ($sortkey, grep { $_ ne $sortkey } NODES);
  6         15  
343 2         6 @order_keys = @nodes[0,1];
344 2         4 $final_key = $nodes[2];
345             } else {
346 6         14 $final_key = 'object';
347 6         18 @order_keys = qw(subject predicate);
348             }
349 8 50       29 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         24 my $subj = $self->_index_values_from_key( $self->_index_root, $order_keys[0] );
360 8         49 my @skeys = $self->_index_values( $subj, $rev );
361 8         38 my ($sid, $pid);
362 8         0 my @pkeys;
363 8         0 my @local_list;
364             my $sub = sub {
365 59     59   153 while (0 == scalar(@local_list)) {
366             # no more objects. go to next predicate.
367 41         99 while (0 == scalar(@pkeys)) {
368             # no more predicates. go to next subject.
369 28 100       80 return unless (scalar(@skeys));
370 20         51 $sid = shift(@skeys);
371             # warn "*** using subject $sid\n";
372 20         37 @pkeys = sort { $a <=> $b } keys %{ $subj->{ $sid }{ $order_keys[1] } };
  15         50  
  20         105  
373 20 50       84 if ($max >= 2) {
374 0         0 @pkeys = grep { $_ == $sid } @pkeys;
  0         0  
375             }
376             }
377 33         69 $pid = shift(@pkeys);
378             # warn "*** using predicate $pid\n";
379 33         91 my $index = $self->_index_from_pair( $subj, $sid, $order_keys[1] );
380 33         72 my $list = $self->_node_list_from_id( $index, $pid );
381 33         78 @local_list = $self->_node_values( $list );
382 33 50       126 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         83 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         108 my @nodes = map { $self->_id2node( $_ ) } (@data{qw(subject predicate object)});
  153         287  
394 51         173 my $st = RDF::Trine::Statement->new( @nodes );
395 51         135 return $st;
396 8         52 };
397 8         44 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 14 my $self = shift;
409 4         9 my $bgp = shift;
410 4 50       27 if ($bgp->isa('RDF::Trine::Pattern')) {
411 4         17 $bgp = $bgp->sort_for_join_variables();
412             }
413 4         15 my @triples = $bgp->triples;
414 4 100       14 if (2 == scalar(@triples)) {
415 3         10 my ($t1, $t2) = @triples;
416 3         16 my @v1 = $t1->referenced_variables;
417 3         19 my %v1 = map { $_ => 1 } @v1;
  2         9  
418 3         12 my @v2 = $t2->referenced_variables;
419 3         9 my @shared = grep { exists($v1{$_}) } @v2;
  5         14  
420 3 100       12 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         6 my $i2 = $self->SUPER::_get_pattern( RDF::Trine::Pattern->new( $t2 ), undef, orderby => [ $shrkey => 'ASC' ] );
428            
429 1         4 my $i1current = $i1->next;
430 1         3 my $i2current = $i2->next;
431 1         2 my @results;
432 1   66     6 while (defined($i1current) and defined($i2current)) {
433 1         3 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         3 while ($match_value->equal( $i2current->{ $shrkey } )) {
439 4         8 push( @matching_i2_rows, $i2current );
440 4 50       13 unless ($i2current = $i2->next) {
441             # warn "no more from i2";
442 0         0 last;
443             }
444             }
445            
446 1         5 while ($match_value->equal( $i1current->{ $shrkey } )) {
447 1         4 foreach my $i2_row (@matching_i2_rows) {
448 4         11 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         5 return RDF::Trine::Iterator::Bindings->new( \@results, [ $bgp->referenced_variables ] );
469             } else {
470 2         11 my $l = Log::Log4perl->get_logger("rdf.trine.store.hexastore");
471 2         59 $l->info('No shared variable -- cartesian product');
472             # no shared variable -- cartesian product
473 2         23 my $i1 = $self->SUPER::_get_pattern( RDF::Trine::Pattern->new( $t1 ) );
474 2         11 my $i2 = $self->SUPER::_get_pattern( RDF::Trine::Pattern->new( $t2 ) );
475 2         5 my @i1;
476 2         11 while (my $row = $i1->next) {
477 3         13 push(@i1, $row);
478             }
479            
480 2         4 my @results;
481 2         6 while (my $row2 = $i2->next) {
482 6         20 foreach my $row1 (@i1) {
483 9         31 push(@results, { %$row1, %$row2 });
484             }
485             }
486 2         13 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   7 my $self = shift;
507 4         6 my $rowa = shift;
508 4         5 my $rowb = shift;
509            
510 4         7 my %keysa;
511 4         9 my @keysa = keys %$rowa;
512 4         10 @keysa{ @keysa } = (1) x scalar(@keysa);
513 4         9 my @shared = grep { exists $keysa{ $_ } } (keys %$rowb);
  12         22  
514 4         9 foreach my $key (@shared) {
515 4         6 my $val_a = $rowa->{ $key };
516 4         7 my $val_b = $rowb->{ $key };
517 4 50 33     22 next unless (defined($val_a) and defined($val_b));
518 4         12 my $equal = $val_a->equal( $val_b );
519 4 50       11 unless ($equal) {
520 0         0 return;
521             }
522             }
523            
524 4         7 my $row = { (map { $_ => $rowa->{$_} } grep { defined($rowa->{$_}) } keys %$rowa), (map { $_ => $rowb->{$_} } grep { defined($rowb->{$_}) } keys %$rowb) };
  4         13  
  4         10  
  12         25  
  12         22  
525 4         13 return $row;
526             }
527              
528             =item C<< get_contexts >>
529              
530             =cut
531              
532             sub get_contexts {
533 1     1 1 16 my $l = Log::Log4perl->get_logger("rdf.trine.store.hexastore");
534 1         549 $l->warn("Contexts not supported for the Hexastore store");
535 1         22 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 104 my $self = shift;
546 55         95 my $st = shift;
547 55         89 my $added = 0;
548              
549             # believe it or not, these calls add up.
550 55         139 my %stmt = map { $_ => $st->$_ } NODES;
  165         521  
551 55         146 my %ids = map { $_ => $self->_node2id($stmt{$_}) } NODES;
  165         408  
552              
553 55         148 foreach my $first (NODES) {
554 165         308 my $firstnode = $stmt{$first};
555 165         261 my $id1 = $ids{$first};
556 165         239 my @others = @{ OTHERNODES->{ $first } };
  165         382  
557 165         436 my @orders = ([@others], [reverse @others]);
558 165         291 foreach my $order (@orders) {
559 330         608 my ($second, $third) = @$order;
560 330         645 my ($id2, $id3) = @ids{$second, $third};
561 330         699 my $list = $self->_get_terminal_list( $first => $id1, $second => $id2 );
562 330 100       618 if ($self->_add_node_to_page( $list, $id3 )) {
563 159         415 $added++;
564             }
565             }
566             }
567 55 100       161 if ($added) {
568 53         92 $self->{ size }++;
569 53         284 $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 31 my $self = shift;
581 13         26 my $st = shift;
582 13         38 my @ids = map { $self->_node2id( $st->$_() ) } NODES;
  39         142  
583             # warn "*** removing statement @ids\n";
584              
585 13         30 my $removed = 0;
586 13         40 foreach my $first (NODES) {
587 39         118 my $firstnode = $st->$first();
588 39         92 my $id1 = $self->_node2id( $firstnode );
589 39         65 my @others = @{ OTHERNODES->{ $first } };
  39         101  
590 39         111 my @orders = ([@others], [reverse @others]);
591 39         73 foreach my $order (@orders) {
592 78         143 my ($second, $third) = @$order;
593 78         145 my ($id2, $id3) = map { $self->_node2id( $st->$_() ) } ($second, $third);
  156         392  
594 78         194 my $list = $self->_get_terminal_list( $first => $id1, $second => $id2 );
595 78 100       190 if ($self->_remove_node_from_page( $list, $id3 )) {
596 39         108 $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       42 if ($removed) {
604 13         30 $self->{ size }--;
605 13         75 $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         29 $self->{data} = $self->_new_index_page;
633 6         36 $self->{node2id} = {};
634 6         22 $self->{id2node} = {};
635 6         19 $self->{next_id} = 1;
636 6         18 $self->{size} = 0;
637 6         33 $self->{etag} = time;
638 6         13 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 101 my $self = shift;
652 55         139 my @nodes = @_;
653 55         120 my @ids = map { $self->_node2id( $_ ) } @nodes;
  182         347  
654 55         130 my @names = NODES;
655 55         142 my @keys = map { $names[$_], $ids[$_] } (0 .. $#names);
  165         354  
656 55         123 my @dkeys;
657             my @ukeys;
658            
659 55 100 100     284 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         130 foreach my $i (0 .. 2) {
664 162 100       314 if (defined($nodes[ $i ])) {
665 38         72 push( @dkeys, $names[$i] );
666             } else {
667 124         235 push( @ukeys, $names[$i] );
668             }
669             }
670 54         119 @keys = map { $_ => $self->_node2id( $nodes[ NODEMAP->{ $_ } ] ) } @dkeys;
  38         103  
671 54 100       173 if (0 == scalar(@keys)) {
    100          
    100          
672 29         180 return $self->{ size };
673             } elsif (2 == scalar(@keys)) {
674 15         46 my $index = $self->_index_from_pair( $self->_index_root, @keys );
675 15         50 return $self->_count_statements( $index, @ukeys );
676             } elsif (4 == scalar(@keys)) {
677 7         22 my $index = $self->_index_from_pair( $self->_index_root, @keys[ 0,1 ] );
678 7         23 my $list = $self->_index_from_pair( $index, @keys[ 2,3 ] );
679 7         24 return $self->_node_count( $list );
680             } else {
681 3         11 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       13 return ($self->_page_contains_node( $list, $keys[5] ))
684             ? 1
685             : 0;
686             }
687             }
688              
689             sub _count_statements {
690 35     35   62 my $self = shift;
691 35         47 my $data = shift;
692 35         75 my @ukeys = @_;
693 35 100       76 if (1 >= scalar(@ukeys)) {
694 20         58 return $self->_node_count( $data );
695             } else {
696 15         33 my $count = 0;
697 15         25 my $ukey = shift(@ukeys);
698 15         34 my $data = $data->{ $ukey };
699 15         53 foreach my $k (keys %$data) {
700 20         63 $count += $self->_count_statements( $data->{ $k }, @ukeys );
701             }
702 15         95 return $count;
703             }
704             }
705              
706             sub _node2id {
707 735     735   1093 my $self = shift;
708 735         1017 my $node = shift;
709 735 100       2183 return unless (blessed($node));
710 567 100       2104 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         1394 my $str = $node->as_string;
715 544         1183 my $id = $self->{ node2id }{ $str };
716              
717 544 100       996 if (defined $id) {
718 499         1251 return $id;
719             } else {
720 45         142 $id = ($self->{ node2id }{ $str } = $self->{ next_id }++);
721 45         116 $self->{ id2node }{ $id } = $node;
722 45         171 return $id
723             }
724             }
725              
726             sub _id2node {
727 237     237   345 my $self = shift;
728 237         356 my $id = shift;
729 237 50       526 if (exists( $self->{ id2node }{ $id } )) {
730 237         599 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   980 my $self = shift;
748 686         1583 return $self->{'data'};
749             }
750              
751             sub _get_terminal_list {
752 408     408   604 my $self = shift;
753 408         625 my $first = shift;
754 408         581 my $id1 = shift;
755 408         561 my $second = shift;
756 408         561 my $id2 = shift;
757 408         770 my $index = $self->_index_from_pair( $self->_index_root, $first, $id1 );
758 408         775 my $page = $self->_index_from_pair( $index, $second, $id2 );
759 408 100       844 if (ref($page)) {
760 322         621 return $page;
761             } else {
762 86         342 my ($k1, $k2) = sort { $a->[0] cmp $b->[0] } ([$first, $id1], [$second, $id2]);
  86         267  
763 86         209 my $index = $self->_index_from_pair( $self->_index_root, $k1->[0], $k1->[1] );
764 86 100       214 unless ($index) {
765 37         88 $index = $self->_add_index_page( $self->_index_root, $k1->[0], $k1->[1] );
766             }
767            
768 86         195 my $list = $self->_index_from_pair( $index, $k2->[0], $k2->[1] );
769 86 50       199 unless ($list) {
770 86         205 $list = $self->_add_list_page( $index, $k2->[0], $k2->[1] );
771             }
772            
773             ###
774            
775 86         176 my $index2 = $self->_index_from_pair( $self->_index_root, $k2->[0], $k2->[1] );
776 86 100       205 unless ($index2) {
777 12         34 $index2 = $self->_add_index_page( $self->_index_root, $k2->[0], $k2->[1] );
778             }
779 86         228 $self->_add_list_page( $index2, $k1->[0], $k1->[1], $list );
780 86         228 return $list;
781             }
782             }
783              
784             #########################################
785             #########################################
786             #########################################
787             sub _add_list_page {
788 172     172   290 my $self = shift;
789 172         235 my $index = shift;
790 172         254 my $key = shift;
791 172         253 my $value = shift;
792 172   66     440 my $list = shift || $self->_new_list_page;
793 172         447 $index->{ $key }{ $value } = $list;
794             }
795              
796             sub _add_index_page {
797 49     49   83 my $self = shift;
798 49         81 my $index = shift;
799 49         77 my $key = shift;
800 49         84 my $value = shift;
801 49         123 $index->{ $key }{ $value } = $self->_new_index_page;
802             }
803              
804             sub _index_from_pair {
805 1198     1198   1686 my $self = shift;
806 1198         1641 my $index = shift;
807 1198         1654 my $key = shift;
808 1198         1661 my $val = shift;
809 1198         2315 return $index->{ $key }{ $val };
810             }
811              
812             sub _node_list_from_id {
813 33     33   52 my $self = shift;
814 33         50 my $index = shift;
815 33         52 my $id = shift;
816 33         58 return $index->{ $id };
817             }
818              
819             sub _index_values_from_key {
820 21     21   48 my $self = shift;
821 21         46 my $index = shift;
822 21         46 my $key = shift;
823 21         54 return $index->{ $key };
824             }
825              
826             sub _index_values {
827 21     21   48 my $self = shift;
828 21         45 my $index = shift;
829 21         36 my $rev = shift;
830 21 100       55 if ($rev) {
831 2         10 my @values = sort { $b <=> $a } keys %$index;
  8         17  
832 2         8 return @values;
833             } else {
834 19         103 my @values = sort { $a <=> $b } keys %$index;
  20         77  
835 19         71 return @values;
836             }
837             }
838             #########################################
839             #########################################
840             #########################################
841              
842             sub _node_count {
843 27     27   46 my $self = shift;
844 27         47 my $list = shift;
845 27 100       38 return scalar(@{ $list || [] });
  27         128  
846             }
847              
848             sub _node_values {
849 62     62   112 my $self = shift;
850 62         114 my $list = shift;
851 62 50       177 if (ref($list)) {
852 62         199 return @$list;
853             } else {
854 0         0 return;
855             }
856             }
857              
858             sub _page_contains_node {
859 414     414   593 my $self = shift;
860 414         584 my $list = shift;
861 414         622 my $id = shift;
862 414         712 foreach (@$list) {
863 433 100       1094 return 1 if ($_ == $id);
864             }
865 201         440 return 0;
866             }
867              
868             sub _add_node_to_page {
869 330     330   490 my $self = shift;
870 330         478 my $list = shift;
871 330         452 my $id = shift;
872 330 100       714 if ($self->_page_contains_node( $list, $id )) {
873 171         492 return 0;
874             } else {
875 159         421 @$list = sort { $a <=> $b } (@$list, $id);
  128         281  
876 159         402 return 1;
877             }
878             }
879              
880             sub _remove_node_from_page {
881 78     78   109 my $self = shift;
882 78         110 my $list = shift;
883 78         128 my $id = shift;
884 78 100       161 if ($self->_page_contains_node( $list, $id )) {
885 39         77 @$list = grep { $_ != $id } @$list;
  51         135  
886 39         113 return 1;
887             } else {
888 39         128 return 0;
889             }
890             }
891              
892             sub _new_index_page {
893 55     55   233 return { __type => 'index' };
894             }
895              
896             sub _new_list_page {
897 86     86   256 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