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   753 use v5.14;
  50         171  
2 50     50   275 use warnings;
  50         135  
  50         1823  
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   270 use Attean::API::Expression;
  50         113  
  50         2006  
38              
39             =item * L<Attean::ValueExpression>
40              
41             =cut
42              
43             use Moo;
44 50     50   258 use Types::Standard qw(ConsumerOf);
  50         123  
  50         303  
45 50     50   16467 use AtteanX::SPARQL::Constants;
  50         136  
  50         456  
46 50     50   24920 use AtteanX::SPARQL::Token;
  50         131  
  50         8812  
47 50     50   379 use namespace::clean;
  50         125  
  50         1439  
48 50     50   319  
  50         114  
  50         486  
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 55053
58 1439         4108  
59             return 1;
60             }
61 9     9 0 26
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 5262 }
69 29         541 return $str;
70 29 100       877 }
    100          
71 6         37
72             my $self = shift;
73 4         37 if ($self->value->does('Attean::API::Variable')) {
74             return $self->value->value;
75 19         129 }
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 42 return;
88 22         86 }
89             }
90              
91             =item * L<Attean::UnaryExpression>
92 1     1 0 22  
93 1 50       4 =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   151243 my $args = $class->$orig(@_);
  50         129  
  50         263  
106 50     50   15562 my $op = $args->{operator};
  50         142  
  50         320  
107 50     50   22483 $args->{operator} = $map{uc($op)} if (exists $map{uc($op)});
  50         119  
  50         255  
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 299 }
122 3         13 }
123 3         1398  
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   35798 return 0 unless ($c->is_stable);
  50         151  
  50         248  
143 50     50   15751 }
  50         143  
  50         286  
144 50     50   20491 return 1;
  50         134  
  50         262  
145             }
146             }
147              
148             =item * L<Attean::FunctionExpression>
149 39     39 0 31082  
150 39         115 =cut
151 39         7853  
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   31504 if ($args->{operator} eq 'ISURI') {
  50         158  
  50         242  
171 50     50   15273 $args->{operator} = 'ISIRI';
  50         159  
  50         308  
172 50     50   57238 }
  50         1036769  
  50         585  
173 50     50   22844 $args->{operator} = UpperCaseStr->coercion->($args->{operator});
  50         127  
  50         7542  
174 50     50   334 return $args;
  50         108  
  50         904  
175 50     50   250 };
  50         106  
  50         390  
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 1100  
197 6         72 my @tokens;
198 6         6131 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 6 use Types::Common::String qw(UpperCaseStr);
214 3         14 use AtteanX::SPARQL::Constants;
215 3         140 use AtteanX::SPARQL::Token;
216 3         119 use namespace::clean;
217 3         137  
218             around 'BUILDARGS' => sub {
219 3         122 my $orig = shift;
220 3         10 my $class = shift;
221 3         6 my $args = $class->$orig(@_);
  3         13  
222 3         12 $args->{operator} = UpperCaseStr->coercion->($args->{operator});
223 3         12 return $args;
224             };
225 3 50       12 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         4 has 'custom_iri' => (is => 'ro', isa => Maybe[Str]);
229 3         51 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   158841 with 'Attean::API::AggregateExpression';
  50         130  
  50         351  
235 50     50   16388 with 'Attean::API::SPARQLSerializable';
  50         139  
  50         411  
236 50     50   58128  
  50         124  
  50         212  
237 50     50   16642  
  50         131  
  50         6781  
238 50     50   342 my $self = shift;
  50         111  
  50         830  
239 50     50   241 foreach my $expr (@{ $self->groups }, values %{ $self->aggregates }) {
  50         105  
  50         266  
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 2055 my $rparen = AtteanX::SPARQL::Token->rparen;
250 9         4134 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         49  
275 1         43 use Moo;
276 1         45 use Types::Standard qw(Enum ConsumerOf);
277 1         41 use AtteanX::SPARQL::Constants;
278             use AtteanX::SPARQL::Token;
279 1         41 use namespace::clean;
280 1         4  
281 1         3 with 'Attean::API::SPARQLSerializable';
282 1 50       6 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         3 my $class = shift;
  1         6  
286 1         4 return $class->SUPER::BUILDARGS(@_, operator => '_cast');
287 1         5 }
288             my $self = shift;
289 1 50       5 state $type = Enum[map { "http://www.w3.org/2001/XMLSchema#$_" } qw(integer decimal float double string boolean dateTime)];
290 1         3 $type->assert_valid($self->datatype->value);
291             }
292 1         4
293 1         4  
294 1 50       4 my $self = shift;
295 0         0 foreach my $c (@{ $self->children }) {
296             return 0 unless ($c->is_stable);
297 1         2 }
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   156603 my $lparen = AtteanX::SPARQL::Token->lparen;
  50         134  
  50         283  
304 50     50   16227 my $rparen = AtteanX::SPARQL::Token->rparen;
  50         122  
  50         359  
305 50     50   39668 my $comma = AtteanX::SPARQL::Token->comma;
  50         129  
  50         6835  
306 50     50   309  
  50         124  
  50         794  
307 50     50   243 my @tokens;
  50         111  
  50         263  
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 5017 pop(@tokens); # remove the last comma
315 2         18 }
316             push(@tokens, $rparen);
317             return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
318 2     2 0 354 }
319 2         7 }
  14         40  
320 2         2922  
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         10 my $sparql = $self->pattern->as_sparql;
336             $sparql =~ s/\s+/ /g;
337 1         42 return "EXISTS { $sparql }";
338 1         41 }
339              
340 1         38  
341 1         3 my $self = shift;
342 1         2 # TODO: need deep analysis of exists pattern to tell if this is stable
  1         7  
343 1         4 # (there might be an unstable filter expression deep inside the pattern)
344 1         5 return 0;
345             }
346 1 50       6  
347 1         2 my $self = shift;
348             my $exists = AtteanX::SPARQL::Token->keyword('EXISTS');
349 1         4 my $lbrace = AtteanX::SPARQL::Token->lbrace;
350 1         16 my $rbrace = AtteanX::SPARQL::Token->rbrace;
351             my $child = $self->pattern;
352            
353             my @tokens;
354             push(@tokens, $exists, $lbrace);
355 50     50   143283 push(@tokens, $child->sparql_tokens->elements);
  50         124  
  50         282  
356 50     50   15709 push(@tokens, $rbrace);
  50         134  
  50         6976  
357 50     50   352 return Attean::ListIterator->new( values => \@tokens, item_type => 'AtteanX::SPARQL::Token' );
  50         108  
  50         1094  
358 50     50   254 }
  50         101  
  50         290  
359 50     50   23146  
  50         135  
  50         247  
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 2425 use Types::Standard qw(ConsumerOf);
367 1         8 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 17  
388 1         5  
389 1         66 my $self = shift;
390 1         42 # TODO: need deep analysis of exists pattern to tell if this is stable
391 1         41 # (there might be an unstable filter expression deep inside the pattern)
392             return 0;
393 1         3 }
394 1         2  
395 1         4 my $self = shift;
396 1         4 die "unaggregated_variables cannot be called on Attean::ExistsPlanExpression";
397 1         17 }
398             }
399              
400              
401 0     0 0   1;
402 0            
  0            
403              
404             =back
405              
406             =head1 BUGS
407 50     50   144764  
  50         108  
  50         271  
408 50     50   15512 Please report any bugs or feature requests to through the GitHub web interface
  50         131  
  50         299  
409 50     50   22110 at L<https://github.com/kasei/attean/issues>.
  50         141  
  50         276  
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