File Coverage

blib/lib/Attean/Expression.pm
Criterion Covered Total %
statement 211 277 76.1
branch 10 30 33.3
condition 0 4 0.0
subroutine 57 80 71.2
pod 0 41 0.0
total 278 432 64.3


line stmt bran cond sub pod time code
1 50     50   817 use v5.14;
  50         167  
2 50     50   291 use warnings;
  50         111  
  50         1786  
3              
4             =head1 NAME
5              
6             Attean::Expression - SPARQL Expressions
7              
8             =head1 VERSION
9              
10             This document describes Attean::Expression version 0.032
11              
12             =head1 SYNOPSIS
13              
14             use v5.14;
15             use Attean;
16              
17             my $binding = Attean::Result->new();
18             my $value = Attean::ValueExpression->new( value => Attean::Literal->integer(2) );
19             my $plus = Attean::BinaryExpression->new( children => [$value, $value], operator => '+' );
20             my $result = $plus->evaluate($binding);
21             say $result->numeric_value; # 4
22              
23             =head1 DESCRIPTION
24              
25             This is a utility package that defines all the Attean SPARQL expression classes
26             consisting of logical, numeric, and function operators, constant terms, and
27             variables. Expressions may be evaluated in the context of a
28             L<Attean::API::Result> object, and either return a L<Attean::API::Term> object
29             or throw a type error exception.
30              
31             The expression classes are:
32              
33             =over 4
34              
35             =cut
36              
37 50     50   269 use Attean::API::Expression;
  50         140  
  50         1673  
38              
39             =item * L<Attean::ValueExpression>
40              
41             =cut
42              
43             use Moo;
44 50     50   261 use Types::Standard qw(ConsumerOf);
  50         109  
  50         315  
45 50     50   16068 use AtteanX::SPARQL::Constants;
  50         131  
  50         461  
46 50     50   24924 use AtteanX::SPARQL::Token;
  50         150  
  50         8939  
47 50     50   352 use namespace::clean;
  50         112  
  50         1337  
48 50     50   309  
  50         110  
  50         531  
49             with 'Attean::API::SPARQLSerializable';
50             with 'Attean::API::Expression';
51              
52             has 'value' => (is => 'ro', isa => ConsumerOf['Attean::API::TermOrVariableOrTriplePattern']);
53              
54             my $class = shift;
55 1     1 0 5 return $class->SUPER::BUILDARGS(@_, operator => '_value');
56             }
57 1439     1439 0 56383
58 1439         3823  
59             return 1;
60             }
61 9     9 0 23
62             my $self = shift;
63             my $str = $self->value->ntriples_string;
64 0     0 0 0 if ($str =~ m[^"(true|false)"\^\^<http://www[.]w3[.]org/2001/XMLSchema#boolean>$]) {
65             return $1;
66             } elsif ($str =~ m[^"(\d+)"\^\^<http://www[.]w3[.]org/2001/XMLSchema#integer>$]) {
67             return $1
68 29     29 0 5012 }
69 29         642 return $str;
70 29 100       1071 }
    100          
71 6         35
72             my $self = shift;
73 4         38 if ($self->value->does('Attean::API::Variable')) {
74             return $self->value->value;
75 19         146 }
76             return;
77             }
78            
79 0     0 0 0 my $self = shift;
80 0 0       0 return $self->value->sparql_tokens;
81 0         0 }
82              
83 0         0 my $self = shift;
84             if ($self->value->does('Attean::API::Variable')) {
85             return $self->value;
86             }
87 22     22 0 68 return;
88 22         148 }
89             }
90              
91             =item * L<Attean::UnaryExpression>
92 1     1 0 25  
93 1 50       5 =cut
94 1         14  
95             use Moo;
96 0         0 use Types::Standard qw(Enum);
97             use namespace::clean;
98              
99             with 'Attean::API::UnaryExpression', 'Attean::API::Expression', 'Attean::API::UnaryQueryTree';
100              
101             my %map = ('NOT' => '!');
102             around 'BUILDARGS' => sub {
103             my $orig = shift;
104             my $class = shift;
105 50     50   150510 my $args = $class->$orig(@_);
  50         136  
  50         286  
106 50     50   15431 my $op = $args->{operator};
  50         127  
  50         310  
107 50     50   21649 $args->{operator} = $map{uc($op)} if (exists $map{uc($op)});
  50         119  
  50         246  
108             return $args;
109             };
110             my $self = shift;
111             state $type = Enum[qw(+ - !)];
112             $type->assert_valid($self->operator);
113             }
114            
115              
116             my $self = shift;
117             foreach my $c (@{ $self->children }) {
118             return 0 unless ($c->is_stable);
119             }
120             return 1;
121 3     3 0 307 }
122 3         25 }
123 3         1420  
124             =item * L<Attean::BinaryExpression>
125              
126 0     0 0 0 =cut
127              
128             use Moo;
129 0     0 0 0 use Types::Standard qw(Enum);
130 0         0 use namespace::clean;
  0         0  
131 0 0       0  
132             with 'Attean::API::BinaryExpression';
133 0         0  
134             my $self = shift;
135             state $type = Enum[qw(+ - * / < <= > >= != = && ||)];
136             $type->assert_valid($self->operator);
137             }
138            
139              
140             my $self = shift;
141             foreach my $c (@{ $self->children }) {
142 50     50   35832 return 0 unless ($c->is_stable);
  50         136  
  50         281  
143 50     50   14932 }
  50         131  
  50         269  
144 50     50   20717 return 1;
  50         125  
  50         221  
145             }
146             }
147              
148             =item * L<Attean::FunctionExpression>
149 39     39 0 32787  
150 39         135 =cut
151 39         8551  
152             use Moo;
153             use Types::Standard qw(Enum ConsumerOf HashRef);
154 3     3 0 11 use Types::Common::String qw(UpperCaseStr);
155             use AtteanX::SPARQL::Constants;
156             use AtteanX::SPARQL::Token;
157 0     0 0 0 use namespace::clean;
158 0         0  
  0         0  
159 0 0       0 has 'operator' => (is => 'ro', isa => UpperCaseStr, coerce => UpperCaseStr->coercion, required => 1);
160             has 'base' => (is => 'rw', isa => ConsumerOf['Attean::IRI'], predicate => 'has_base');
161 0         0  
162             with 'Attean::API::NaryExpression';
163             with 'Attean::API::SPARQLSerializable';
164              
165              
166             around 'BUILDARGS' => sub {
167             my $orig = shift;
168             my $class = shift;
169             my $args = $class->$orig(@_);
170 50     50   30697 if ($args->{operator} eq 'ISURI') {
  50         124  
  50         248  
171 50     50   15105 $args->{operator} = 'ISIRI';
  50         120  
  50         255  
172 50     50   55809 }
  50         1019108  
  50         622  
173 50     50   22043 $args->{operator} = UpperCaseStr->coercion->($args->{operator});
  50         118  
  50         7163  
174 50     50   337 return $args;
  50         113  
  50         936  
175 50     50   244 };
  50         107  
  50         355  
176            
177             my $self = shift;
178             state $type = Enum[qw(INVOKE IN NOTIN STR LANG LANGMATCHES DATATYPE BOUND IRI URI BNODE RAND ABS CEIL FLOOR ROUND CONCAT SUBSTR STRLEN REPLACE UCASE LCASE ENCODE_FOR_URI CONTAINS STRSTARTS STRENDS STRBEFORE STRAFTER YEAR MONTH DAY HOURS MINUTES SECONDS TIMEZONE TZ NOW UUID STRUUID MD5 SHA1 SHA256 SHA384 SHA512 COALESCE IF STRLANG STRDT SAMETERM ISIRI ISBLANK ISLITERAL ISNUMERIC REGEX TRIPLE ISTRIPLE SUBJECT PREDICATE OBJECT)];
179             $type->assert_valid($self->operator);
180             }
181              
182              
183             my $self = shift;
184             return 0 if ($self->operator =~ m/^(?:RAND|BNODE|UUID|STRUUID|NOW)$/);
185             foreach my $c (@{ $self->children }) {
186             return 0 unless ($c->is_stable);
187             }
188             return 1;
189             }
190            
191             my $self = shift;
192             my $func = AtteanX::SPARQL::Token->keyword($self->operator);
193             my $lparen = AtteanX::SPARQL::Token->lparen;
194             my $rparen = AtteanX::SPARQL::Token->rparen;
195             my $comma = AtteanX::SPARQL::Token->comma;
196 6     6 0 1116  
197 6         88 my @tokens;
198 6         6337 push(@tokens, $func, $lparen);
199             foreach my $t (@{ $self->children }) {
200             push(@tokens, $t->sparql_tokens->elements);
201 0     0 0 0 push(@tokens, $comma);
202             }
203             if (scalar(@tokens) > 2) {
204 0     0 0 0 pop(@tokens); # remove the last comma
205 0 0       0 }
206 0         0 push(@tokens, $rparen);
  0         0  
207 0 0       0 return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
208             }
209 0         0 }
210              
211             use Moo;
212             use Types::Standard qw(Bool Enum Str HashRef ConsumerOf Maybe);
213 3     3 0 8 use Types::Common::String qw(UpperCaseStr);
214 3         18 use AtteanX::SPARQL::Constants;
215 3         143 use AtteanX::SPARQL::Token;
216 3         132 use namespace::clean;
217 3         124  
218             around 'BUILDARGS' => sub {
219 3         134 my $orig = shift;
220 3         7 my $class = shift;
221 3         7 my $args = $class->$orig(@_);
  3         17  
222 3         17 $args->{operator} = UpperCaseStr->coercion->($args->{operator});
223 3         13 return $args;
224             };
225 3 50       11 state $type = Enum[qw(COUNT SUM MIN MAX AVG GROUP_CONCAT SAMPLE RANK CUSTOM)];
226 3         7 $type->assert_valid(shift->operator);
227             }
228 3         6 has 'custom_iri' => (is => 'ro', isa => Maybe[Str]);
229 3         76 has 'operator' => (is => 'ro', isa => UpperCaseStr, coerce => UpperCaseStr->coercion, required => 1);
230             has 'scalar_vars' => (is => 'ro', isa => HashRef, default => sub { +{} });
231             has 'distinct' => (is => 'ro', isa => Bool, default => 0);
232             has 'variable' => (is => 'ro', isa => ConsumerOf['Attean::API::Variable'], required => 1);
233              
234 50     50   158748 with 'Attean::API::AggregateExpression';
  50         131  
  50         356  
235 50     50   16287 with 'Attean::API::SPARQLSerializable';
  50         139  
  50         378  
236 50     50   57521  
  50         117  
  50         217  
237 50     50   16632  
  50         124  
  50         6478  
238 50     50   329 my $self = shift;
  50         119  
  50         819  
239 50     50   227 foreach my $expr (@{ $self->groups }, values %{ $self->aggregates }) {
  50         111  
  50         259  
240             return 0 unless ($expr->is_stable);
241             }
242             return 1;
243             }
244              
245             my $self = shift;
246             my $distinct = AtteanX::SPARQL::Token->keyword('DISTINCT');
247             my $func = AtteanX::SPARQL::Token->keyword($self->operator);
248             my $lparen = AtteanX::SPARQL::Token->lparen;
249 9     9 0 2435 my $rparen = AtteanX::SPARQL::Token->rparen;
250 9         5206 my $comma = AtteanX::SPARQL::Token->comma;
251              
252             my @tokens;
253             push(@tokens, $func);
254             push(@tokens, $lparen);
255             if ($self->distinct) {
256             push(@tokens, $distinct);
257             }
258             foreach my $t (@{ $self->children }) {
259             push(@tokens, $t->sparql_tokens->elements);
260             push(@tokens, $comma);
261 0     0 0 0 }
262             if (scalar(@tokens) > 2) {
263             pop(@tokens); # remove the last comma
264 0     0 0 0 }
265 0         0 my $vars = $self->scalar_vars;
  0         0  
  0         0  
266 0 0       0 my @keys = keys %$vars;
267             if (scalar(@keys)) {
268 0         0 die "TODO: Implement SPARQL serialization for aggregate scalar vars";
269             }
270             push(@tokens, $rparen);
271             return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
272 1     1 0 2 }
273 1         5 }
274 1         50  
275 1         42 use Moo;
276 1         44 use Types::Standard qw(Enum ConsumerOf);
277 1         43 use AtteanX::SPARQL::Constants;
278             use AtteanX::SPARQL::Token;
279 1         40 use namespace::clean;
280 1         3  
281 1         3 with 'Attean::API::SPARQLSerializable';
282 1 50       8 with 'Attean::API::UnaryExpression', 'Attean::API::Expression', 'Attean::API::UnaryQueryTree';
283 0         0  
284             has 'datatype' => (is => 'ro', isa => ConsumerOf['Attean::API::IRI']);
285 1         4 my $class = shift;
  1         7  
286 1         8 return $class->SUPER::BUILDARGS(@_, operator => '_cast');
287 1         5 }
288             my $self = shift;
289 1 50       7 state $type = Enum[map { "http://www.w3.org/2001/XMLSchema#$_" } qw(integer decimal float double string boolean dateTime)];
290 1         2 $type->assert_valid($self->datatype->value);
291             }
292 1         6
293 1         5  
294 1 50       5 my $self = shift;
295 0         0 foreach my $c (@{ $self->children }) {
296             return 0 unless ($c->is_stable);
297 1         3 }
298 1         18 return 1;
299             }
300              
301             my $self = shift;
302             my $dt = AtteanX::SPARQL::Token->fast_constructor( IRI, -1, -1, -1, -1, [$self->datatype->value] ),
303 50     50   156944 my $lparen = AtteanX::SPARQL::Token->lparen;
  50         155  
  50         283  
304 50     50   15484 my $rparen = AtteanX::SPARQL::Token->rparen;
  50         122  
  50         303  
305 50     50   37670 my $comma = AtteanX::SPARQL::Token->comma;
  50         119  
  50         6649  
306 50     50   330  
  50         113  
  50         786  
307 50     50   226 my @tokens;
  50         111  
  50         247  
308             push(@tokens, $dt, $lparen);
309             foreach my $t (@{ $self->children }) {
310             push(@tokens, $t->sparql_tokens->elements);
311             push(@tokens, $comma);
312             }
313             if (scalar(@tokens) > 2) {
314 2     2 0 5812 pop(@tokens); # remove the last comma
315 2         23 }
316             push(@tokens, $rparen);
317             return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
318 2     2 0 405 }
319 2         11 }
  14         41  
320 2         3091  
321             use Moo;
322             use AtteanX::SPARQL::Constants;
323 0     0 0 0 use AtteanX::SPARQL::Token;
324             use Types::Standard qw(ConsumerOf);
325             use namespace::clean;
326 0     0 0 0  
327 0         0 with 'Attean::API::SPARQLSerializable';
  0         0  
328 0 0       0 with 'Attean::API::Expression';
329              
330 0         0 my $class = shift;
331             return $class->SUPER::BUILDARGS(@_, operator => '_exists');
332             }
333             has 'pattern' => (is => 'ro', isa => ConsumerOf['Attean::API::Algebra']);
334 1     1 0 3 my $self = shift;
335 1         17 my $sparql = $self->pattern->as_sparql;
336             $sparql =~ s/\s+/ /g;
337 1         48 return "EXISTS { $sparql }";
338 1         44 }
339              
340 1         41  
341 1         3 my $self = shift;
342 1         2 # TODO: need deep analysis of exists pattern to tell if this is stable
  1         8  
343 1         7 # (there might be an unstable filter expression deep inside the pattern)
344 1         5 return 0;
345             }
346 1 50       6  
347 1         4 my $self = shift;
348             my $exists = AtteanX::SPARQL::Token->keyword('EXISTS');
349 1         4 my $lbrace = AtteanX::SPARQL::Token->lbrace;
350 1         19 my $rbrace = AtteanX::SPARQL::Token->rbrace;
351             my $child = $self->pattern;
352            
353             my @tokens;
354             push(@tokens, $exists, $lbrace);
355 50     50   142800 push(@tokens, $child->sparql_tokens->elements);
  50         122  
  50         256  
356 50     50   14893 push(@tokens, $rbrace);
  50         118  
  50         6738  
357 50     50   320 return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
  50         104  
  50         1038  
358 50     50   245 }
  50         110  
  50         273  
359 50     50   21730  
  50         119  
  50         243  
360             my $self = shift;
361             return map { Attean::Variable->new($_) } $self->pattern->in_scope_variables;
362             }
363             }
364 0     0 0 0  
365             use Moo;
366 1     1 0 3312 use Types::Standard qw(ConsumerOf);
367 1         14 use namespace::clean;
368              
369             with 'Attean::API::Expression';
370             my $class = shift;
371 0     0 0 0 return $class->SUPER::BUILDARGS(@_, operator => '_existsplan');
372 0         0 }
373 0         0 has 'plan' => (is => 'ro', isa => ConsumerOf['Attean::API::BindingSubstitutionPlan']);
374 0         0 my $self = shift;
375             # TODO: implement as_string for EXISTS patterns
376             return "Attean::ExistsPlanExpression { ... }";
377 0     0 0 0 }
378             my $self = shift;
379             my %args = @_;
380 0     0 0 0 my $level = $args{level} // 0;
381             my $sp = $args{indent} // ' ';
382             my $indent = $sp x $level;
383 0         0  
384             # TODO: implement as_string for EXISTS patterns
385             return "EXISTS { " . $self->pattern->as_sparql( level => $level+1, indent => $sp ) . " }";
386             }
387 1     1 0 2  
388 1         9  
389 1         62 my $self = shift;
390 1         65 # TODO: need deep analysis of exists pattern to tell if this is stable
391 1         504 # (there might be an unstable filter expression deep inside the pattern)
392             return 0;
393 1         5 }
394 1         5  
395 1         6 my $self = shift;
396 1         4 die "unaggregated_variables cannot be called on Attean::ExistsPlanExpression";
397 1         20 }
398             }
399              
400              
401 0     0 0   1;
402 0            
  0            
403              
404             =back
405              
406             =head1 BUGS
407 50     50   144670  
  50         128  
  50         255  
408 50     50   15117 Please report any bugs or feature requests to through the GitHub web interface
  50         123  
  50         292  
409 50     50   21319 at L<https://github.com/kasei/attean/issues>.
  50         135  
  50         283  
410              
411             =head1 SEE ALSO
412 0     0 0    
413              
414 0     0 0    
415 0           =head1 AUTHOR
416              
417             Gregory Todd Williams C<< <gwilliams@cpan.org> >>
418              
419 0     0 0   =head1 COPYRIGHT
420              
421 0           Copyright (c) 2014--2022 Gregory Todd Williams.
422             This program is free software; you can redistribute it and/or modify it under
423             the same terms as Perl itself.
424 0     0 0    
425 0           =cut