File Coverage

blib/lib/Attean/TripleModel.pm
Criterion Covered Total %
statement 172 182 94.5
branch 21 40 52.5
condition 8 18 44.4
subroutine 37 38 97.3
pod 12 15 80.0
total 250 293 85.3


line stmt bran cond sub pod time code
1 50     50   625 use v5.14;
  50         173  
2 50     50   269 use warnings;
  50         127  
  50         2244  
3              
4             =head1 NAME
5              
6             Attean::TripleModel - RDF model backed by a set of triple-stores
7              
8             =head1 VERSION
9              
10             This document describes Attean::TripleModel version 0.032
11              
12             =head1 SYNOPSIS
13              
14             use v5.14;
15             use Attean;
16             my $model = Attean::TripleModel->new( stores => {
17             'http://example.org/graph1' => $store1,
18             'http://example.org/graph2' => $store2,
19             } );
20              
21             =head1 DESCRIPTION
22              
23             The Attean::TripleModel class represents a model that is backed by a set of
24             L<Attean::API::TripleStore|Attean::API::Store> objects, identified by an IRI
25             string. It conforms to the L<Attean::API::Model> role.
26              
27             The Attean::TripleModel constructor requires one named argument:
28              
29             =over 4
30              
31             =item stores
32              
33             A hash mapping graph IRI values to L<Attean::API::TripleStore|Attean::API::Store>
34             objects representing the backing triple-store for that graph.
35              
36             =back
37              
38             =head1 METHODS
39              
40             =over 4
41              
42             =cut
43              
44             use Moo;
45 50     50   309 use Types::Standard qw(ArrayRef ConsumerOf HashRef);
  50         106  
  50         267  
46 50     50   16209 use Scalar::Util qw(reftype blessed);
  50         136  
  50         430  
47 50     50   32985 use namespace::clean;
  50         108  
  50         2484  
48 50     50   311  
  50         136  
  50         345  
49             with 'MooX::Log::Any';
50             with 'Attean::API::Model';
51             with 'Attean::API::CostPlanner';
52              
53             has 'stores' => (
54             is => 'ro',
55             isa => HashRef[ConsumerOf['Attean::API::TripleStore']],
56             required => 1,
57             default => sub { +{} },
58             );
59            
60             =item C<< size >>
61              
62             =cut
63              
64             my $self = shift;
65             my $count = 0;
66 5     5 1 1185 foreach my $store (values %{ $self->stores }) {
67 5         8 $count += $store->size;
68 5         9 }
  5         23  
69 5         26 return $count;
70             }
71 5         37
72              
73             =item C<< count_quads >>
74              
75             =cut
76              
77             my $self = shift;
78             # TODO: don't materialize results here just to count them
79             my $iter = $self->get_quads( @_ );
80 12     12 1 23 my $count = 0;
81             while (my $r = $iter->next) {
82 12         44 $count++;
83 12         23 }
84 12         54 return $count;
85 22         69 }
86              
87 12         115 =item C<< count_quads_estimate >>
88              
89             =cut
90              
91             my $self = shift;
92             my ($s, $p, $o, $g) = @_;
93             if (blessed($g) and $g->does('Attean::API::IRI')) {
94             if (my $store = $self->stores->{ $g->value }) {
95 7     7 1 14 return $store->count_quads_estimate(@_);
96 7         19 } else {
97 7 50 33     29 return 0;
98 0 0       0 }
99 0         0 } else {
100             return $self->count_quads(@_);
101 0         0 }
102             }
103            
104 7         23 =item C<< holds >>
105              
106             =cut
107              
108             my $self = shift;
109             return ($self->count_quads_estimate(@_) > 0)
110             }
111            
112             =item C<< get_graphs >>
113 0     0 1 0  
114 0         0 =cut
115              
116             my $self = shift;
117             my @graphs = map { Attean::IRI->new($_) } keys %{ $self->stores };
118             return Attean::ListIterator->new( values => \@graphs, item_type => 'Attean::API::Term' );
119             }
120            
121             =item C<< get_quads ( $subject, $predicate, $object, $graph ) >>
122 6     6 1 3759  
123 6         13 Returns an L<Attean::API::Iterator> for quads in the model that match the
  9         825  
  6         33  
124 6         1292 supplied C<< $subject >>, C<< $predicate >>, C<< $object >>, and C<< $graph >>.
125             Any of these terms may be undefined or a L<Attean::API::Variable> object, in
126             which case that term will be considered as a wildcard for the purposes of
127             matching.
128              
129             The returned iterator conforms to both L<Attean::API::Iterator> and
130             L<Attean::API::QuadIterator>.
131              
132             =cut
133              
134             my $self = shift;
135             my @nodes = @_[0..3];
136             foreach my $i (0..3) {
137             my $t = $nodes[$i] // Attean::Variable->new();
138             if (not(ref($t)) or reftype($t) ne 'ARRAY') {
139             $nodes[$i] = [$t];
140             }
141 22     22 1 239 }
142 22         64
143 22         66 my @iters;
144 88   66     1020 foreach my $s (@{ $nodes[0] }) {
145 88 100 66     2670 foreach my $p (@{ $nodes[1] }) {
146 83         198 foreach my $o (@{ $nodes[2] }) {
147             foreach my $g (@{ $nodes[3] }) {
148             my $iter = $self->_get_quads($s, $p, $o, $g);
149             push(@iters, $iter);
150 22         60 }
151 22         33 }
  22         51  
152 22         37 }
  22         41  
153 23         33 }
  23         98  
154 23         27 if (scalar(@iters) <= 1) {
  23         49  
155 23         85 return shift(@iters);
156 23         268 } else {
157             return Attean::IteratorSequence->new( iterators => \@iters, item_type => $iters[0]->item_type );
158             }
159             }
160            
161 22 100       64 my $self = shift;
162 21         121 my $s = shift;
163             my $p = shift;
164 1         28 my $o = shift;
165             my $g = shift;
166             if (blessed($g) and $g->does('Attean::API::IRI')) {
167             if (my $store = $self->stores->{ $g->value }) {
168             my $iter = $store->get_triples($s, $p, $o);
169 23     23   37 return $iter->as_quads($g);
170 23         33 }
171 23         39 } elsif (blessed($g) and $g->does('Attean::API::Variable')) {
172 23         31 my @iters;
173 23         42 while (my ($g, $store) = each %{ $self->stores }) {
174 23 100 66     157 my $iter = $store->get_triples($s, $p, $o);
    50 33        
175 5 50       109 my $graph = Attean::IRI->new($g);
176 5         103 my $quads = $iter->map(sub { $_->as_quad($graph) }, 'Attean::API::Quad');
177 5         167 push(@iters, $quads);
178             }
179             my $iter = Attean::IteratorSequence->new( iterators => \@iters, item_type => $iters[0]->item_type );
180 18         763 return $iter;
181 18         34 } else {
  36         190  
182 18         382 my $name = (blessed($g) and $g->can('as_string')) ? $g->as_string : "$g";
183 18         922 $self->log->warn("TripleModel cannot produce quads for non-IRI graph: $name");
184 18     36   4202 }
  36         129  
185 18         606 return Attean::ListIterator->new( values => [], item_type => 'Attean::API::Quad' );
186             }
187 18         372
188 18         584 =item C<< plans_for_algebra( $algebra, $planner, $active_graphs, $default_graphs ) >>
189              
190 0 0 0     0 Delegates to an underlying store if the active graph is bound to the store,
191 0         0 and the store consumes Attean::API::CostPlanner.
192              
193 0         0 =cut
194              
195             my $self = shift;
196             my $algebra = shift;
197             my $planner = shift;
198             my $active_graphs = shift;
199             my $default_graphs = shift;
200             my @plans;
201             if (scalar(@$active_graphs) == 1) {
202             my $graph = $active_graphs->[0];
203             if (my $store = $self->stores->{ $graph->value }) {
204 5     5 1 59 if ($store->does('Attean::API::CostPlanner')) {
205 5         8 push(@plans, $store->plans_for_algebra($algebra, $planner, $active_graphs, $default_graphs));
206 5         8 }
207 5         7 }
208 5         9 }
209 5         8 return @plans;
210 5 50       13 }
211 5         10
212 5 50       27 =item C<< cost_for_plan( $plan ) >>
213 5 50       16  
214 0         0 Attempts to delegate to all the underlying stores if that store consumes Attean::API::CostPlanner.
215              
216             =cut
217              
218 5         112 my $self = shift;
219             my $plan = shift;
220             foreach my $store (values %{ $self->stores }) {
221             if ($store->does('Attean::API::CostPlanner')) {
222             if (defined(my $cost = $store->cost_for_plan($plan, @_))) {
223             return $cost;
224             }
225             }
226             }
227             return;
228             }
229             }
230              
231             use Scalar::Util qw(blessed);
232             use Types::Standard qw(CodeRef);
233              
234             use Moo::Role;
235            
236             with 'Attean::API::Model';
237             has 'store_constructor' => (is => 'ro', isa => CodeRef, required => 1);
238            
239             =item C<< add_store( $graph => $store ) >>
240              
241             Add the L<Attean::TripleStore> C<< $store >> object to the model using the
242 50     50   87097 IRI string value C<< $graph >> as the graph name.
  50         119  
  50         2195  
243 50     50   379  
  50         124  
  50         282  
244             =cut
245 50     50   21989  
  50         121  
  50         343  
246             my $self = shift;
247             my $graph = shift;
248             my $iri = blessed($graph) ? $graph->value : $graph;
249             my $store = shift;
250            
251             die if exists $self->stores->{ $iri };
252             $self->stores->{ $iri } = $store;
253             }
254              
255             =item C<< create_graph( $graph ) >>
256              
257             Create a new L<Attean::TripleStore> and add it to the model using the
258 1     1 0 2 L<Attean::API::IRI> C<< $graph >> as the graph name.
259 1         3  
260 1 50       10 The store is constructed by using this object's C<< store_constructor >>
261 1         4 attribute:
262              
263 1 50       7 my $store = $self->store_constructor->($graph);
264 1         5  
265             =cut
266              
267             my $self = shift;
268             my $graph = shift;
269             my $iri = $graph->value;
270             return if exists $self->stores->{ $iri };
271              
272             my $store = $self->store_constructor->($graph);
273             $self->stores->{ $iri } = $store;
274             };
275              
276             =item C<< drop_graph( $graph ) >>
277              
278             Removes the store associated with the given C<< $graph >>.
279              
280 1     1 0 201 =cut
281 1         2  
282 1         3 my $self = shift;
283 1 50       7 my $g = shift;
284             if ($g->does('Attean::API::IRI')) {
285 1         8 delete $self->stores->{ $g->value };
286 1         43 }
287             }
288             }
289              
290              
291             use Moo;
292             use Types::Standard qw(ArrayRef ConsumerOf HashRef);
293             use Scalar::Util qw(reftype);
294             use namespace::clean;
295              
296 1     1 0 199 extends 'Attean::TripleModel';
297 1         2 with 'Attean::API::MutableModel';
298 1 50       5
299 1         30 has 'stores' => (
300             is => 'ro',
301             isa => HashRef[ConsumerOf['Attean::API::MutableTripleStore']],
302             required => 1,
303             default => sub { +{} },
304             );
305              
306 50     50   29921 =item C<< add_quad ( $quad ) >>
  50         158  
  50         325  
307 50     50   15613  
  50         140  
  50         436  
308 50     50   32161 Adds the specified C<$quad> to the underlying model.
  50         122  
  50         2108  
309 50     50   302  
  50         128  
  50         271  
310             =cut
311              
312             my $self = shift;
313             my $q = shift;
314             my $g = $q->graph;
315             die "Cannot add a quad whose graph is not an IRI" unless ($g->does('Attean::API::IRI'));
316             my $v = $g->value;
317             if (my $store = $self->stores->{ $v }) {
318             $store->add_triple( $q->as_triple );
319             } else {
320             Carp::confess "No such graph: $v";
321             }
322             }
323              
324             =item C<< remove_quad ( $quad ) >>
325              
326             Removes the specified C<< $quad >> from the underlying store.
327              
328 14     14 1 1355 =cut
329 14         24  
330 14         28 my $self = shift;
331 14 50       31 my $q = shift;
332 14         185 my $g = $q->graph;
333 14 50       58 if ($g->does('Attean::API::IRI')) {
334 14         41 my $v = $g->value;
335             if (my $store = $self->stores->{ $v }) {
336 0         0 $store->remove_triple( $q->as_triple );
337             }
338             }
339             }
340            
341              
342             =item C<< drop_graph( $graph ) >>
343              
344             Removes the store associated with the given C<< $graph >>.
345              
346             =cut
347 1     1 1 1409  
348 1         3 my $self = shift;
349 1         6 my $g = shift;
350 1 50       5 if ($g->does('Attean::API::IRI')) {
351 1         24 delete $self->stores->{ $g->value };
352 1 50       7 }
353 1         6 }
354            
355             =item C<< clear_graph( $graph ) >>
356              
357             Removes all quads with the given C<< $graph >>.
358 2     2 1 354  
359             =cut
360              
361             my $self = shift;
362             my $g = shift;
363             $self->drop_graph($g);
364             $self->create_graph($g);
365             }
366             }
367 2     2 1 853  
368 2         5 use Moo;
369 2 50       8 use Scalar::Util qw(blessed);
370 2         48 use Types::Standard qw(CodeRef);
371             use namespace::clean;
372            
373             extends 'Attean::TripleModel';
374             with 'Attean::AddativeTripleModelRole';
375             }
376              
377             use Moo;
378             use Scalar::Util qw(blessed);
379             use Types::Standard qw(CodeRef);
380             use namespace::clean;
381 1     1 1 332
382 1         2 extends 'Attean::MutableTripleModel';
383 1         4 with 'Attean::AddativeTripleModelRole';
384 1         4 }
385              
386              
387             1;
388              
389 50     50   48490  
  50         130  
  50         247  
390 50     50   15279 =back
  50         155  
  50         2193  
391 50     50   327  
  50         126  
  50         298  
392 50     50   20780 =head1 BUGS
  50         114  
  50         203  
393              
394             Please report any bugs or feature requests to through the GitHub web interface
395             at L<https://github.com/kasei/attean/issues>.
396              
397             =head1 SEE ALSO
398              
399 50     50   25939  
  50         121  
  50         231  
400 50     50   15435  
  50         124  
  50         2179  
401 50     50   318 =head1 AUTHOR
  50         147  
  50         276  
402 50     50   20758  
  50         122  
  50         205  
403             Gregory Todd Williams C<< <gwilliams@cpan.org> >>
404              
405             =head1 COPYRIGHT
406              
407             Copyright (c) 2014--2022 Gregory Todd Williams.
408             This program is free software; you can redistribute it and/or modify it under
409             the same terms as Perl itself.
410              
411             =cut