File Coverage

blib/lib/AtteanX/RDFQueryTranslator.pm
Criterion Covered Total %
statement 90 278 32.3
branch 48 144 33.3
condition 1 9 11.1
subroutine 17 19 89.4
pod 5 5 100.0
total 161 455 35.3


line stmt bran cond sub pod time code
1 2     2   61242 use v5.14;
  2         5  
2 2     2   6 use warnings;
  2         3  
  2         75  
3              
4             =head1 NAME
5              
6             AtteanX::RDFQueryTranslator - Translate RDF::Query objects to Attean::API::Algebra objects.
7              
8             =head1 VERSION
9              
10             This document describes AtteanX::RDFQueryTranslator version 0.100
11              
12             =head1 DESCRIPTION
13              
14             The AtteanX::RDFQueryTranslator class translates RDF::Query objects into an
15             L<Attean::API::Algebra> tree.
16              
17             =head1 ATTRIBUTES
18              
19             =over 4
20              
21             =item C<< base >>
22              
23             =back
24              
25             =head1 METHODS
26              
27             =over 4
28              
29             =item C<< has_base >>
30              
31             =cut
32              
33             package AtteanX::RDFQueryTranslator {
34 2     2   10 use v5.14;
  2         7  
35 2     2   5 use warnings;
  2         2  
  2         40  
36 2     2   983 use Moo;
  2         16482  
  2         7  
37 2     2   3086 use Data::Dumper;
  2         10884  
  2         90  
38 2     2   797 use namespace::clean;
  2         16385  
  2         6  
39            
40             our $VERSION = '0.100';
41            
42 2     2   1321 use RDF::Query;
  2         2265731  
  2         67  
43 2     2   856 use Attean;
  2         1677237  
  2         9  
44 2     2   69 use Attean::Algebra;
  2         2  
  2         33  
45 2     2   7 use Attean::Expression;
  2         2  
  2         28  
46 2     2   6 use Attean::RDF;
  2         2  
  2         122  
47 2     2   8 use Scalar::Util qw(blessed);
  2         3  
  2         65  
48 2     2   16 use Types::Standard qw(Bool ConsumerOf HashRef);
  2         2  
  2         8  
49             has '_in_expr' => (is => 'rw', isa => Bool, default => 0);
50             has 'base' => (is => 'rw', isa => ConsumerOf['Attean::IRI'], predicate => 'has_base');
51            
52             =item C<< translate_query( $query ) >>
53              
54             Returns an algebra object for the given L<RDF::Query> object.
55              
56             =cut
57              
58             sub translate_query {
59 2     2 1 12281 my $class = shift;
60 2         3 my $query = shift;
61             # unless ($self->has_base) {
62             # $self->base(Attean::IRI->new(value => $query->{base_uri}->uri_value));
63             # }
64 2 50       9 return unless blessed($query);
65 2         3 my $parsed = $query->{parsed};
66 2         31 my $t = $class->new();
67 2 50 33     46 if (exists $parsed->{base} and my $base = $t->translate($parsed->{base})) {
68 0         0 $t->base($base);
69             }
70 2         4 my $method = $parsed->{method};
71 2         7 my $algebra = $t->translate($query->pattern);
72 2 50       3180 if (my $b = $parsed->{bindings}) {
73 0         0 my @vars = map { $t->translate($_) } @{ $b->{vars} };
  0         0  
  0         0  
74 0         0 my $terms = $b->{terms};
75 0         0 my @bindings;
76 0         0 foreach my $row (@$terms) {
77 0         0 my %binding;
78 0         0 foreach my $i (0 .. $#vars) {
79 0 0       0 if (my $term = $row->[$i]) {
80 0         0 $binding{ $vars[$i]->value } = $t->translate($term);
81             }
82             }
83 0         0 push(@bindings, Attean::Result->new( bindings => \%binding ));
84             }
85 0         0 my $table = Attean::Algebra::Table->new( rows => \@bindings, variables => \@vars );
86 0         0 $algebra = Attean::Algebra::Join->new( children => [$algebra, $table] );
87             }
88 2 50       5 if ($method eq 'ASK') {
89 0         0 $algebra = Attean::Algebra::Ask->new( children => [$algebra] );
90             }
91 2         10 return $algebra;
92             }
93            
94             =item C<< translate( $algebra ) >>
95              
96             Returns an algebra object for the given L<RDF::Query::Algebra>,
97             L<RDF::Query::Expression>, or L<RDF::Query::Node> object.
98              
99             =cut
100              
101             sub translate {
102 17     17 1 72 my $self = shift;
103 17         12 my $a = shift;
104 17 50       46 Carp::confess "Not a reference? " . Dumper($a) unless blessed($a);
105 17 50       251 if ($a->isa('RDF::Query::Algebra::Construct')) {
    100          
    100          
    100          
    100          
    100          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    100          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
106 0         0 my $p = $self->translate($a->pattern);
107 0 0       0 my @triples = @{ $a->triples || [] };
  0         0  
108 0 0 0     0 if (scalar(@triples) == 1 and $triples[0]->isa('RDF::Query::Algebra::BasicGraphPattern')) {
109 0         0 @triples = $triples[0]->triples;
110             }
111 0         0 return Attean::Algebra::Construct->new( children => [$p], triples => [map { $self->translate($_) } @triples] );
  0         0  
112             } elsif ($a->isa('RDF::Query::Algebra::Project')) {
113 2         15 my $p = $a->pattern;
114 2         11 my $v = $a->vars;
115 2         6 my @vars = map { variable($_->name) } @$v;
  4         1379  
116 2         183 return Attean::Algebra::Project->new(
117             children => [ $self->translate($p) ],
118             variables => [ @vars ],
119             );
120             } elsif ($a->isa('RDF::Query::Algebra::GroupGraphPattern')) {
121 2         5 my @p = map { $self->translate($_) } $a->patterns;
  2         17  
122 2 50       1793 if (scalar(@p) == 0) {
123 0         0 return Attean::Algebra::BGP->new();
124             } else {
125 2         6 while (scalar(@p) > 1) {
126 0         0 my ($l, $r) = splice(@p, 0, 2);
127 0         0 unshift(@p, Attean::Algebra::Join->new( children => [$l, $r] ));
128             }
129 2         12 return shift(@p);
130             }
131             } elsif ($a->isa('RDF::Query::Algebra::BasicGraphPattern')) {
132 2         5 my @p = map { $self->translate($_) } $a->triples;
  2         15  
133 2         874 return Attean::Algebra::BGP->new( triples => \@p );
134             } elsif ($a->isa('RDF::Query::Algebra::Triple')) {
135 2         6 my @nodes = map { $self->translate($_) } $a->nodes;
  6         40  
136 2         23 return Attean::TriplePattern->new(@nodes);
137             } elsif ($a->isa('RDF::Query::Node::Variable')) {
138 7 50       31 my $value = variable($a->isa("RDF::Query::Node::Variable::ExpressionProxy") ? ("." . $a->name) : $a->name);
139 7 100       675 $value = Attean::ValueExpression->new(value => $value) if ($self->_in_expr);
140 7         2245 return $value;
141             } elsif ($a->isa('RDF::Query::Node::Resource')) {
142 0         0 my $value = iri($a->uri_value);
143 0 0       0 $value = Attean::ValueExpression->new(value => $value) if ($self->_in_expr);
144 0         0 return $value;
145             } elsif ($a->isa('RDF::Query::Node::Blank')) {
146 0         0 my $value = blank($a->blank_identifier);
147 0 0       0 $value = Attean::ValueExpression->new(value => $value) if ($self->_in_expr);
148 0         0 return $value;
149             } elsif ($a->isa('RDF::Query::Node::Literal')) {
150 0         0 my $value;
151 0 0       0 if ($a->has_language) {
    0          
152 0         0 $value = langliteral($a->literal_value, $a->literal_value_language);
153             } elsif ($a->has_datatype) {
154 0         0 $value = dtliteral($a->literal_value, $a->literal_datatype);
155             } else {
156 0         0 $value = literal($a->literal_value);
157             }
158 0 0       0 $value = Attean::ValueExpression->new(value => $value) if ($self->_in_expr);
159 0         0 return $value;
160             } elsif ($a->isa('RDF::Query::Algebra::Limit')) {
161 0         0 my $child = $a->pattern;
162 0 0       0 if ($child->isa('RDF::Query::Algebra::Offset')) {
163 0         0 my $p = $self->translate($child->pattern);
164 0         0 return Attean::Algebra::Slice->new( children => [$p], limit => $a->limit, offset => $child->offset );
165             } else {
166 0         0 my $p = $self->translate($child);
167 0         0 return Attean::Algebra::Slice->new( children => [$p], limit => $a->limit );
168             }
169             } elsif ($a->isa('RDF::Query::Algebra::Offset')) {
170 0         0 my $p = $self->translate($a->pattern);
171 0         0 return Attean::Algebra::Slice->new( children => [$p], offset => $a->offset );
172             } elsif ($a->isa('RDF::Query::Algebra::Path')) {
173 0         0 my $s = $self->translate($a->start);
174 0         0 my $o = $self->translate($a->end);
175 0         0 my $path = $self->translate_path($a->path);
176 0         0 return Attean::Algebra::Path->new( subject => $s, path => $path, object => $o );
177             } elsif ($a->isa('RDF::Query::Algebra::Service')) {
178 0         0 my $endpoint = $self->translate($a->endpoint);
179 0         0 my $p = $self->translate($a->pattern);
180 0         0 return Attean::Algebra::Service->new( children => [$p], endpoint => $endpoint );
181             } elsif ($a->isa('RDF::Query::Algebra::NamedGraph')) {
182 0         0 my $graph = $self->translate($a->graph);
183 0         0 my $p = $self->translate($a->pattern);
184 0         0 return Attean::Algebra::Graph->new( children => [$p], graph => $graph );
185             } elsif ($a->isa('RDF::Query::Algebra::Filter')) {
186 1         3 my $p = $self->translate($a->pattern);
187 1         7 my $expr = $self->translate_expr($a->expr);
188 1         9 return Attean::Algebra::Filter->new( children => [$p], expression => $expr );
189             } elsif ($a->isa('RDF::Query::Expression::Binary')) {
190 0         0 my $op = $a->op;
191 0 0       0 $op = '=' if ($op eq '==');
192 0         0 my @ops = $a->operands;
193 0         0 my @operands = map { $self->translate($_) } @ops;
  0         0  
194 0         0 my $expr = Attean::BinaryExpression->new( operator => $op, children => \@operands );
195 0         0 return $expr;
196             } elsif ($a->isa('RDF::Query::Expression::Unary')) {
197 0         0 my $op = $a->op;
198 0 0       0 $op = '=' if ($op eq '==');
199 0         0 my ($child) = $a->operands;
200 0         0 my $expr = Attean::UnaryExpression->new( operator => $op, children => [$self->translate($child)] );
201 0         0 return $expr;
202             } elsif ($a->isa('RDF::Query::Algebra::Extend')) {
203 0         0 my $p = $self->translate($a->pattern);
204 0         0 my $vars = $a->vars;
205 0         0 foreach my $v (@$vars) {
206 0 0       0 if ($v->isa('RDF::Query::Expression::Alias')) {
207 0         0 my $var = variable($v->name);
208 0         0 my $expr = $v->expression;
209 0         0 $p = Attean::Algebra::Extend->new( children => [$p], variable => $var, expression => $self->translate_expr( $expr ) );
210             } else {
211 0         0 die "Unexpected extend expression: " . Dumper($v);
212             }
213             }
214 0         0 return $p;
215             } elsif ($a->isa('RDF::Query::VariableBindings')) {
216 0         0 my %bindings;
217 0         0 foreach my $v ($a->variables) {
218 0 0       0 if (my $term = $a->{ $v }) {
219 0         0 $bindings{ $v } = $self->translate( $term );
220             }
221             }
222 0         0 return Attean::Result->new( bindings => \%bindings );
223             } elsif ($a->isa('RDF::Query::Algebra::Table')) {
224 0         0 my @vars = map { variable($_) } $a->variables;
  0         0  
225 0         0 my @rows = map { $self->translate($_) } $a->rows;
  0         0  
226 0         0 return Attean::Algebra::Table->new( variables => \@vars, rows => \@rows );
227             } elsif ($a->isa('RDF::Query::Algebra::Aggregate')) {
228 0         0 my $p = $self->translate($a->pattern);
229 0         0 my @group;
230 0         0 foreach my $g ($a->groupby) {
231 0 0       0 if ($g->isa('RDF::Query::Expression::Alias')) {
232 0         0 my $var = $self->translate($g->alias);
233 0         0 my $varexpr = $self->translate_expr($g->alias);
234 0         0 push(@group, $varexpr);
235 0         0 my $expr = $self->translate_expr( $g->expression );
236 0         0 $p = Attean::Algebra::Extend->new( children => [$p], variable => $var, expression => $expr );
237             } else {
238 0         0 push(@group, $self->translate_expr($g));
239             }
240             }
241 0         0 my @ops = $a->ops;
242            
243 0         0 my @aggs;
244 0         0 foreach my $o (@ops) {
245 0         0 my ($str, $op, $scalar_vars, @vars) = @$o;
246 0         0 my $operands = [map { $self->translate_expr($_) } grep { blessed($_) } @vars];
  0         0  
  0         0  
247 0         0 my $distinct = ($op =~ /-DISTINCT$/);
248 0         0 $op =~ s/-DISTINCT$//;
249 0         0 my $expr = Attean::AggregateExpression->new(
250             distinct => $distinct,
251             operator => $op,
252             children => $operands,
253             scalar_vars => $scalar_vars,
254             variable => variable(".$str"),
255             );
256 0         0 push(@aggs, $expr);
257             }
258 0         0 return Attean::Algebra::Group->new(
259             children => [$p],
260             groupby => \@group,
261             aggregates => \@aggs,
262             );
263             } elsif ($a->isa('RDF::Query::Algebra::Sort')) {
264 0         0 my $p = $self->translate($a->pattern);
265 0         0 my @order = $a->orderby;
266 0         0 my @cmps;
267 0         0 foreach my $o (@order) {
268 0         0 my ($dir, $e) = @$o;
269 0         0 my $asc = ($dir eq 'ASC');
270 0         0 my $expr = $self->translate_expr($e);
271 0         0 push(@cmps, Attean::Algebra::Comparator->new(ascending => $asc, expression => $expr));
272             }
273 0         0 return Attean::Algebra::OrderBy->new( children => [$p], comparators => \@cmps );
274             } elsif ($a->isa('RDF::Query::Algebra::Distinct')) {
275 0         0 my $p = $self->translate($a->pattern);
276 0         0 return Attean::Algebra::Distinct->new( children => [$p] );
277             } elsif ($a->isa('RDF::Query::Algebra::Minus')) {
278 0         0 my $p = $self->translate($a->pattern);
279 0         0 my $m = $self->translate($a->minus);
280 0         0 return Attean::Algebra::Minus->new( children => [$p, $m] );
281             } elsif ($a->isa('RDF::Query::Algebra::Union')) {
282 0         0 my @p = map { $self->translate($_) } $a->patterns;
  0         0  
283 0         0 return Attean::Algebra::Union->new( children => \@p );
284             } elsif ($a->isa('RDF::Query::Algebra::Optional')) {
285 0         0 my $p = $self->translate($a->pattern);
286 0         0 my $o = $self->translate($a->optional);
287 0         0 return Attean::Algebra::LeftJoin->new( children => [$p, $o] );
288             } elsif ($a->isa('RDF::Query::Algebra::SubSelect')) {
289 0         0 my $q = $a->query;
290 0         0 my $p = $self->translate_query($q);
291 0         0 return $p;
292             } elsif ($a->isa('RDF::Query::Expression::Function')) {
293 1         4 my $uri = $a->uri->uri_value;
294 1         62 my @args = map { $self->translate_expr($_) } $a->arguments;
  1         10  
295 1 50       14 if ($uri eq 'sparql:logical-and') {
    50          
    50          
    0          
296 0         0 my $algebra = Attean::BinaryExpression->new( operator => '&&', children => [splice(@args, 0, 2)] );
297 0         0 while (scalar(@args)) {
298 0         0 $algebra = Attean::BinaryExpression->new( operator => '&&', children => [$algebra, shift(@args)] );
299             }
300 0         0 return $algebra;
301             } elsif ($uri eq 'sparql:logical-or') {
302 0         0 my $algebra = Attean::BinaryExpression->new( operator => '||', children => [splice(@args, 0, 2)] );
303 0         0 while (scalar(@args)) {
304 0         0 $algebra = Attean::BinaryExpression->new( operator => '||', children => [$algebra, shift(@args)] );
305             }
306 0         0 return $algebra;
307             } elsif ($uri =~ /^sparql:(.+)$/) {
308 1 50       3 if ($1 eq 'exists') {
309             # re-translate the pattern as a pattern, not an expression:
310 0         0 my ($p) = map { $self->translate_pattern($_) } $a->arguments;
  0         0  
311 0         0 return Attean::ExistsExpression->new( pattern => $p );
312             } else {
313 1 50       10 return Attean::FunctionExpression->new( children => \@args, operator => $1, ($self->has_base ? (base => $self->base) : ()) );
314             }
315             } elsif ($uri =~ m<^http://www[.]w3[.]org/2001/XMLSchema#(?<cast>integer|decimal|float|double|string|boolean|dateTime)$>) {
316 0         0 my $cast = $+{cast};
317 0 0       0 if ($cast =~ /^(?:integer|decimal|float|double)$/) {
    0          
    0          
    0          
318 0         0 return Attean::CastExpression->new( children => \@args, datatype => iri($uri) );
319             } elsif ($cast eq 'string') {
320 0 0       0 return Attean::FunctionExpression->new( children => \@args, operator => 'STR', ($self->has_base ? (base => $self->base) : ()) );
321             } elsif ($cast eq 'boolean') {
322            
323             } elsif ($cast eq 'dateTime') {
324            
325             }
326             }
327 0         0 warn "Unrecognized function: " . Dumper($uri, \@args);
328             }
329 0         0 Carp::confess "Unrecognized algebra " . ref($a);
330             }
331              
332             =item C<< translate_expr( $algebra ) >>
333              
334             Returns an expression object for the given L<RDF::Query::Expression> object.
335              
336             =cut
337              
338             sub translate_expr {
339 2     2 1 6 my $self = shift;
340 2         1 my $a = shift;
341 2         33 my $prev = $self->_in_expr;
342 2         32 $self->_in_expr(1);
343 2         28 my $expr = $self->translate($a);
344 2         3489 $self->_in_expr($prev);
345 2         29 return $expr;
346             }
347            
348             =item C<< translate_pattern( $algebra ) >>
349              
350             Translate expression, algebra, or node in a non-expression context.
351              
352             =cut
353              
354             sub translate_pattern {
355 0     0 1   my $self = shift;
356 0           my $a = shift;
357 0           my $prev = $self->_in_expr;
358 0           $self->_in_expr(0);
359 0           my $expr = $self->translate($a);
360 0           $self->_in_expr($prev);
361 0           return $expr;
362             }
363            
364             =item C<< translate_path( $path ) >>
365              
366             Translate a node or property path into an equivalent property path algebra.
367              
368             =cut
369              
370             sub translate_path {
371 0     0 1   my $self = shift;
372 0           my $path = shift;
373 0 0 0       if (blessed($path) and $path->isa('RDF::Query::Node::Resource')) {
374 0           return Attean::Algebra::PredicatePath->new( predicate => $self->translate($path) );
375             }
376            
377 0           my ($op, @args) = @$path;
378 0 0         if ($op eq '!') {
    0          
    0          
    0          
    0          
    0          
    0          
379 0           my @nodes = map { $self->translate($_) } @args;
  0            
380 0           return Attean::Algebra::NegatedPropertySet->new( predicates => \@nodes );
381             } elsif ($op eq '/') {
382 0           my @paths = map { $self->translate_path($_) } @args;
  0            
383 0           foreach (@paths) {
384 0 0         if ($_->does('Attean::API::IRI')) {
385 0           $_ = Attean::Algebra::PredicatePath->new( predicate => $_ );
386             }
387             }
388 0           return Attean::Algebra::SequencePath->new( children => \@paths );
389             } elsif ($op eq '?') {
390 0           my $path = $self->translate_path(shift(@args));
391 0           return Attean::Algebra::ZeroOrOnePath->new( children => [$path] );
392             } elsif ($op eq '*') {
393 0           my $path = $self->translate_path(shift(@args));
394 0           return Attean::Algebra::ZeroOrMorePath->new( children => [$path] );
395             } elsif ($op eq '+') {
396 0           my $path = $self->translate_path(shift(@args));
397 0           return Attean::Algebra::OneOrMorePath->new( children => [$path] );
398             } elsif ($op eq '^') {
399 0           my $path = $self->translate_path(shift(@args));
400 0 0         if ($path->does('Attean::API::IRI')) {
401 0           $path = Attean::Algebra::PredicatePath->new( predicate => $path );
402             }
403 0           return Attean::Algebra::InversePath->new( children => [$path] );
404             } elsif ($op eq '|') {
405 0           my @paths = map { $self->translate_path($_) } @args;
  0            
406 0           foreach (@paths) {
407 0 0         if ($_->does('Attean::API::IRI')) {
408 0           $_ = Attean::Algebra::PredicatePath->new( predicate => $_ );
409             }
410             }
411 0           return Attean::Algebra::AlternativePath->new( children => \@paths );
412             }
413 0           die "Unrecognized path: $op";
414             }
415             }
416              
417             1;
418              
419             __END__
420              
421             =back
422              
423             =head1 BUGS
424              
425             Please report any bugs or feature requests to through the GitHub web interface
426             at L<https://github.com/kasei/attean/issues>.
427              
428             =head1 SEE ALSO
429              
430             L<http://www.perlrdf.org/>
431              
432             =head1 AUTHOR
433              
434             Gregory Todd Williams C<< <gwilliams@cpan.org> >>
435              
436             =head1 COPYRIGHT
437              
438             Copyright (c) 2014--2016 Gregory Todd Williams.
439             This program is free software; you can redistribute it and/or modify it under
440             the same terms as Perl itself.
441              
442             =cut