| 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 |