line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
3
|
|
|
3
|
|
4218
|
use v5.14; |
|
3
|
|
|
|
|
10
|
|
2
|
3
|
|
|
3
|
|
14
|
use warnings; |
|
3
|
|
|
|
|
8
|
|
|
3
|
|
|
|
|
89
|
|
3
|
|
|
|
|
|
|
|
4
|
|
|
|
|
|
|
=head1 NAME |
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
Attean::SimpleQueryEvaluator - Simple query evaluator |
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
=head1 VERSION |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
This document describes Attean::SimpleQueryEvaluator version 0.032 |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
=head1 SYNOPSIS |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
use v5.14; |
15
|
|
|
|
|
|
|
use Attean; |
16
|
|
|
|
|
|
|
my $algebra = Attean->get_parser('SPARQL')->parse('SELECT * WHERE { ... }'); |
17
|
|
|
|
|
|
|
my $active_graph = Attean::IRI->new('http://example.org/'); |
18
|
|
|
|
|
|
|
my $e = Attean::SimpleQueryEvaluator->new( model => $model ); |
19
|
|
|
|
|
|
|
my $iter = $e->evaluate( $algebra, $active_graph ); |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
=head1 DESCRIPTION |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
The Attean::SimpleQueryEvaluator class implements a simple query evaluator that, |
24
|
|
|
|
|
|
|
given an L<Attean::API::Algebra|Attean::API::Query> and a L<Attean::API::Model> |
25
|
|
|
|
|
|
|
object, evaluates the query represented by the algebra using data from the |
26
|
|
|
|
|
|
|
model, and returns a query result. |
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
=head1 ATTRIBUTES |
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
=over 4 |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
=cut |
33
|
|
|
|
|
|
|
|
34
|
3
|
|
|
3
|
|
15
|
use Attean::Algebra; |
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
61
|
|
35
|
3
|
|
|
3
|
|
17
|
use Attean::Expression; |
|
3
|
|
|
|
|
9
|
|
|
3
|
|
|
|
|
80
|
|
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
use Moo; |
38
|
3
|
|
|
3
|
|
14
|
use Encode qw(encode); |
|
3
|
|
|
|
|
8
|
|
|
3
|
|
|
|
|
23
|
|
39
|
3
|
|
|
3
|
|
973
|
use Attean::RDF; |
|
3
|
|
|
|
|
8
|
|
|
3
|
|
|
|
|
123
|
|
40
|
3
|
|
|
3
|
|
16
|
use LWP::UserAgent; |
|
3
|
|
|
|
|
13
|
|
|
3
|
|
|
|
|
21
|
|
41
|
3
|
|
|
3
|
|
2043
|
use Scalar::Util qw(blessed); |
|
3
|
|
|
|
|
8
|
|
|
3
|
|
|
|
|
88
|
|
42
|
3
|
|
|
3
|
|
17
|
use List::Util qw(all any reduce); |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
130
|
|
43
|
3
|
|
|
3
|
|
18
|
use Types::Standard qw(ConsumerOf InstanceOf Bool Object); |
|
3
|
|
|
|
|
10
|
|
|
3
|
|
|
|
|
197
|
|
44
|
3
|
|
|
3
|
|
19
|
use URI::Escape; |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
21
|
|
45
|
3
|
|
|
3
|
|
2148
|
use Attean::SPARQLClient; |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
147
|
|
46
|
3
|
|
|
3
|
|
28
|
use namespace::clean; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
50
|
|
47
|
3
|
|
|
3
|
|
14
|
|
|
3
|
|
|
|
|
15
|
|
|
3
|
|
|
|
|
18
|
|
48
|
|
|
|
|
|
|
=item C<< model >> |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
The L<Attean::API::Model> object used for query evaluation. |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
=cut |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
has 'model' => (is => 'ro', isa => ConsumerOf['Attean::API::Model'], required => 1); |
55
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
=item C<< default_graph >> |
57
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
The L<Attean::API::IRI> object representing the default graph in the C<< model >>. |
59
|
|
|
|
|
|
|
The default graph will be excluded from enumeration of graph names for query |
60
|
|
|
|
|
|
|
features such as C<< GRAPH ?g {} >>. |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
=cut |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
has 'default_graph' => (is => 'ro', isa => ConsumerOf['Attean::API::IRI'], required => 1); |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
has 'user_agent' => (is => 'rw', isa => InstanceOf['LWP::UserAgent'], default => sub { my $ua = LWP::UserAgent->new(); $ua->agent("Attean/$Attean::VERSION " . $ua->_agent); $ua }); |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
=item C<< request_signer >> |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
If set, used to modify HTTP::Request objects used in evaluating SERVICE calls |
71
|
|
|
|
|
|
|
before the request is made. This may be used to, for example, add cryptographic |
72
|
|
|
|
|
|
|
signature headers to the request. The modification is performed by calling |
73
|
|
|
|
|
|
|
C<< $request_signer->sign( $request ) >>. |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
=cut |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
has 'request_signer' => (is => 'rw', isa => Object); |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
has 'ground_blanks' => (is => 'rw', isa => Bool, default => 0); |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
=back |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
=head1 METHODS |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
=over 4 |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
=item C<< evaluate( $algebra, $active_graph ) >> |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
Returns an L<Attean::API::Iterator> object with results produced by evaluating |
90
|
|
|
|
|
|
|
the query C<< $algebra >> against the evaluator's C<< model >>, using the |
91
|
|
|
|
|
|
|
supplied C<< $active_graph >>. |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
=cut |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
my $self = shift; |
96
|
|
|
|
|
|
|
my $algebra = shift; |
97
|
65
|
|
|
65
|
1
|
2602
|
my $active_graph = shift || Carp::confess "No active-graph passed to Attean::SimpleQueryEvaluator->evaluate"; |
98
|
65
|
|
|
|
|
87
|
|
99
|
65
|
|
33
|
|
|
127
|
Carp::confess "No algebra passed for evaluation" unless ($algebra); |
100
|
|
|
|
|
|
|
|
101
|
65
|
50
|
|
|
|
126
|
my $expr_eval = Attean::SimpleQueryEvaluator::ExpressionEvaluator->new( evaluator => $self ); |
102
|
|
|
|
|
|
|
|
103
|
65
|
|
|
|
|
1029
|
my @children = @{ $algebra->children }; |
104
|
|
|
|
|
|
|
my ($child) = $children[0]; |
105
|
65
|
|
|
|
|
3362
|
if ($algebra->isa('Attean::Algebra::Query') or $algebra->isa('Attean::Algebra::Update')) { |
|
65
|
|
|
|
|
193
|
|
106
|
65
|
|
|
|
|
127
|
return $self->evaluate($algebra->child, $active_graph, @_); |
107
|
65
|
50
|
33
|
|
|
1295
|
} elsif ($algebra->isa('Attean::Algebra::BGP')) { |
|
|
100
|
66
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
108
|
0
|
|
|
|
|
0
|
my @triples = @{ $algebra->triples }; |
109
|
|
|
|
|
|
|
if (scalar(@triples) == 0) { |
110
|
21
|
|
|
|
|
35
|
my $b = Attean::Result->new( bindings => {} ); |
|
21
|
|
|
|
|
55
|
|
111
|
21
|
50
|
|
|
|
45
|
return Attean::ListIterator->new(variables => [], values => [$b], item_type => 'Attean::API::Result'); |
112
|
0
|
|
|
|
|
0
|
} else { |
113
|
0
|
|
|
|
|
0
|
my @iters; |
114
|
|
|
|
|
|
|
my @new_vars; |
115
|
21
|
|
|
|
|
50
|
my %blanks; |
116
|
|
|
|
|
|
|
foreach my $t (@triples) { |
117
|
21
|
|
|
|
|
0
|
push(@iters, $self->evaluate_pattern($t, $active_graph, \@new_vars, \%blanks)); |
118
|
21
|
|
|
|
|
34
|
} |
119
|
24
|
|
|
|
|
184
|
while (scalar(@iters) > 1) { |
120
|
|
|
|
|
|
|
my ($lhs, $rhs) = splice(@iters, 0, 2); |
121
|
21
|
|
|
|
|
785
|
unshift(@iters, $lhs->join($rhs)); |
122
|
3
|
|
|
|
|
9
|
} |
123
|
3
|
|
|
|
|
14
|
return shift(@iters)->map(sub { shift->project_complement(@new_vars) }); |
124
|
|
|
|
|
|
|
} |
125
|
21
|
|
|
60
|
|
1471
|
} elsif ($algebra->isa('Attean::Algebra::Distinct') or $algebra->isa('Attean::Algebra::Reduced')) { |
|
60
|
|
|
|
|
156
|
|
126
|
|
|
|
|
|
|
my %seen; |
127
|
|
|
|
|
|
|
my $iter = $self->evaluate( $child, $active_graph ); |
128
|
1
|
|
|
|
|
3
|
return $iter->grep(sub { |
129
|
1
|
|
|
|
|
3
|
my $r = shift; |
130
|
|
|
|
|
|
|
my $str = $r->as_string; |
131
|
4
|
|
|
4
|
|
7
|
my $ok = not($seen{ $str }) ? 1 : 0; |
132
|
4
|
|
|
|
|
10
|
$seen{ $str }++; |
133
|
4
|
100
|
|
|
|
13
|
return $ok; |
134
|
4
|
|
|
|
|
18
|
}); |
135
|
4
|
|
|
|
|
18
|
} elsif ($algebra->isa('Attean::Algebra::Extend')) { |
136
|
1
|
|
|
|
|
62
|
my $child = $algebra; |
137
|
|
|
|
|
|
|
my @extends; |
138
|
2
|
|
|
|
|
4
|
my %extends; |
139
|
2
|
|
|
|
|
5
|
while ($child->isa('Attean::Algebra::Extend')) { |
140
|
|
|
|
|
|
|
my $expr = $child->expression; |
141
|
2
|
|
|
|
|
7
|
my $var = $child->variable->value; |
142
|
2
|
|
|
|
|
7
|
$extends{ $var } = $expr; |
143
|
2
|
|
|
|
|
7
|
unshift(@extends, $var); |
144
|
2
|
|
|
|
|
5
|
($child) = @{ $child->children }; |
145
|
2
|
|
|
|
|
4
|
} |
146
|
2
|
|
|
|
|
4
|
return $self->evaluate( $child, $active_graph )->map(sub { |
|
2
|
|
|
|
|
10
|
|
147
|
|
|
|
|
|
|
my $r = shift; |
148
|
|
|
|
|
|
|
my %extension; |
149
|
8
|
|
|
8
|
|
13
|
my %row_cache; |
150
|
8
|
|
|
|
|
12
|
foreach my $var (@extends) { |
151
|
|
|
|
|
|
|
my $expr = $extends{ $var }; |
152
|
8
|
|
|
|
|
13
|
my $val = $expr_eval->evaluate_expression( $expr, $r, $active_graph, \%row_cache ); |
153
|
8
|
|
|
|
|
15
|
if ($val->does('Attean::API::Binding')) { |
154
|
8
|
|
|
|
|
23
|
# patterns need to be made ground to be bound as values (e.g. TriplePattern -> Triple) |
155
|
8
|
50
|
|
|
|
362
|
$val = $val->ground($r); |
156
|
|
|
|
|
|
|
} |
157
|
0
|
|
|
|
|
0
|
# warn "Extend error: $@" if ($@); |
158
|
|
|
|
|
|
|
$r = Attean::Result->new( bindings => { $var => $val } )->join($r) if ($val); |
159
|
|
|
|
|
|
|
} |
160
|
8
|
50
|
|
|
|
313
|
return $r; |
161
|
|
|
|
|
|
|
}); |
162
|
8
|
|
|
|
|
33
|
} elsif ($algebra->isa('Attean::Algebra::Filter')) { |
163
|
2
|
|
|
|
|
5
|
# TODO: Merge adjacent filter evaluation so that they can share a row_cache hash (as is done for Extend above) |
164
|
|
|
|
|
|
|
my $expr = $algebra->expression; |
165
|
|
|
|
|
|
|
my $iter = $self->evaluate( $child, $active_graph ); |
166
|
1
|
|
|
|
|
5
|
return $iter->grep(sub { |
167
|
1
|
|
|
|
|
4
|
my $t = $expr_eval->evaluate_expression( $expr, shift, $active_graph, {} ); |
168
|
|
|
|
|
|
|
# if ($@) { warn "Filter evaluation: $@\n" }; |
169
|
4
|
|
|
4
|
|
13
|
return ($t ? $t->ebv : 0); |
170
|
|
|
|
|
|
|
}); |
171
|
4
|
50
|
|
|
|
21
|
} elsif ($algebra->isa('Attean::Algebra::OrderBy')) { |
172
|
1
|
|
|
|
|
55
|
local($Attean::API::Binding::ALLOW_IRI_COMPARISON) = 1; |
173
|
|
|
|
|
|
|
my $iter = $self->evaluate( $child, $active_graph ); |
174
|
3
|
|
|
|
|
33
|
my @rows = $iter->elements; |
175
|
3
|
|
|
|
|
11
|
my @cmps = @{ $algebra->comparators }; |
176
|
3
|
|
|
|
|
101
|
my @exprs = map { $_->expression } @cmps; |
177
|
3
|
|
|
|
|
6
|
my @dirs = map { $_->ascending } @cmps; |
|
3
|
|
|
|
|
10
|
|
178
|
3
|
|
|
|
|
7
|
my @sorted = map { $_->[0] } sort { |
|
4
|
|
|
|
|
10
|
|
179
|
3
|
|
|
|
|
5
|
my ($ar, $avalues) = @$a; |
|
4
|
|
|
|
|
9
|
|
180
|
12
|
|
|
|
|
19
|
my ($br, $bvalues) = @$b; |
181
|
15
|
|
|
|
|
21
|
my $c = 0; |
182
|
15
|
|
|
|
|
20
|
foreach my $i (0 .. $#cmps) { |
183
|
15
|
|
|
|
|
19
|
my ($av, $bv) = map { $_->[$i] } ($avalues, $bvalues); |
184
|
15
|
|
|
|
|
29
|
# Mirrors code in Attean::Plan::OrderBy->sort_rows |
185
|
16
|
|
|
|
|
21
|
if (blessed($av) and $av->does('Attean::API::Binding') and (not(defined($bv)) or not($bv->does('Attean::API::Binding')))) { |
|
32
|
|
|
|
|
49
|
|
186
|
|
|
|
|
|
|
$c = 1; |
187
|
16
|
50
|
33
|
|
|
56
|
} elsif (blessed($bv) and $bv->does('Attean::API::Binding') and (not(defined($av)) or not($av->does('Attean::API::Binding')))) { |
|
|
50
|
0
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
33
|
|
|
|
|
188
|
0
|
|
|
|
|
0
|
$c = -1; |
189
|
|
|
|
|
|
|
} else { |
190
|
0
|
|
|
|
|
0
|
$c = eval { $av ? $av->compare($bv) : 1 }; |
191
|
|
|
|
|
|
|
if ($@) { |
192
|
16
|
50
|
|
|
|
603
|
$c = 1; |
|
16
|
|
|
|
|
40
|
|
193
|
16
|
50
|
|
|
|
28
|
} |
194
|
0
|
|
|
|
|
0
|
} |
195
|
|
|
|
|
|
|
$c *= -1 if ($dirs[$i] == 0); |
196
|
|
|
|
|
|
|
last unless ($c == 0); |
197
|
16
|
100
|
|
|
|
32
|
} |
198
|
16
|
100
|
|
|
|
28
|
$c |
199
|
|
|
|
|
|
|
} map { my $r = $_; [$r, [map { $expr_eval->evaluate_expression( $_, $r, $active_graph, {} ) } @exprs]] } @rows; |
200
|
|
|
|
|
|
|
return Attean::ListIterator->new( values => \@sorted, item_type => $iter->item_type, variables => $iter->variables); |
201
|
3
|
|
|
|
|
7
|
} elsif ($algebra->isa('Attean::Algebra::Service')) { |
|
12
|
|
|
|
|
18
|
|
|
12
|
|
|
|
|
14
|
|
|
16
|
|
|
|
|
34
|
|
202
|
3
|
|
|
|
|
66
|
my $endpoint = $algebra->endpoint->value; |
203
|
|
|
|
|
|
|
my ($pattern) = @{ $algebra->children }; |
204
|
1
|
|
|
|
|
6
|
my $sparql = Attean::Algebra::Project->new( variables => [ map { variable($_) } $pattern->in_scope_variables ], children => [ $pattern ] )->as_sparql; |
205
|
1
|
|
|
|
|
3
|
my $silent = $algebra->silent; |
|
1
|
|
|
|
|
4
|
|
206
|
1
|
|
|
|
|
6
|
my $client = Attean::SPARQLClient->new( |
|
3
|
|
|
|
|
144
|
|
207
|
1
|
|
|
|
|
26
|
endpoint => $endpoint, |
208
|
1
|
|
|
|
|
21
|
silent => $silent, |
209
|
|
|
|
|
|
|
user_agent => $self->user_agent, |
210
|
|
|
|
|
|
|
request_signer => $self->request_signer, |
211
|
|
|
|
|
|
|
); |
212
|
|
|
|
|
|
|
return $client->query($sparql); |
213
|
|
|
|
|
|
|
} elsif ($algebra->isa('Attean::Algebra::Graph')) { |
214
|
1
|
|
|
|
|
302
|
my $graph = $algebra->graph; |
215
|
|
|
|
|
|
|
return $self->evaluate($child, $graph) if ($graph->does('Attean::API::Term')); |
216
|
3
|
|
|
|
|
10
|
|
217
|
3
|
100
|
|
|
|
11
|
my @iters; |
218
|
|
|
|
|
|
|
my $graphs = $self->model->get_graphs(); |
219
|
1
|
|
|
|
|
24
|
my %vars; |
220
|
1
|
|
|
|
|
22
|
while (my $g = $graphs->next) { |
221
|
1
|
|
|
|
|
27
|
next if ($g->value eq $self->default_graph->value); |
222
|
1
|
|
|
|
|
7
|
my $gr = Attean::Result->new( bindings => { $graph->value => $g } ); |
223
|
2
|
100
|
|
|
|
15
|
my $iter = $self->evaluate($child, $g)->map(sub { if (my $result = shift->join($gr)) { return $result } else { return } }); |
224
|
1
|
|
|
|
|
20
|
foreach my $v (@{ $iter->variables }) { |
225
|
1
|
50
|
|
4
|
|
23
|
$vars{$v}++; |
|
4
|
|
|
|
|
13
|
|
|
4
|
|
|
|
|
15
|
|
|
0
|
|
|
|
|
0
|
|
226
|
1
|
|
|
|
|
32
|
} |
|
1
|
|
|
|
|
17
|
|
227
|
2
|
|
|
|
|
8
|
push(@iters, $iter); |
228
|
|
|
|
|
|
|
} |
229
|
1
|
|
|
|
|
5
|
return Attean::IteratorSequence->new( variables => [keys %vars], iterators => \@iters, item_type => 'Attean::API::Result' ); |
230
|
|
|
|
|
|
|
} elsif ($algebra->isa('Attean::Algebra::Group')) { |
231
|
1
|
|
|
|
|
18
|
my @groupby = @{ $algebra->groupby }; |
232
|
|
|
|
|
|
|
my $iter = $self->evaluate($child, $active_graph); |
233
|
0
|
|
|
|
|
0
|
my %groups; |
|
0
|
|
|
|
|
0
|
|
234
|
0
|
|
|
|
|
0
|
while (my $r = $iter->next) { |
235
|
0
|
|
|
|
|
0
|
my %vars; |
236
|
0
|
|
|
|
|
0
|
my %row_cache; |
237
|
0
|
|
|
|
|
0
|
my @group_terms = map { $expr_eval->evaluate_expression( $_, $r, $active_graph, \%row_cache ) } @groupby; |
238
|
|
|
|
|
|
|
my $key = join(' ', map { blessed($_) ? $_->as_string : '' } @group_terms); |
239
|
0
|
|
|
|
|
0
|
my %group_bindings; |
|
0
|
|
|
|
|
0
|
|
240
|
0
|
0
|
|
|
|
0
|
foreach my $i (0 .. $#group_terms) { |
|
0
|
|
|
|
|
0
|
|
241
|
0
|
|
|
|
|
0
|
my $v = $groupby[$i]; |
242
|
0
|
|
|
|
|
0
|
if (blessed($v) and $v->isa('Attean::ValueExpression') and $v->value->does('Attean::API::Variable') and $group_terms[$i]) { |
243
|
0
|
|
|
|
|
0
|
$group_bindings{$v->value->value} = $group_terms[$i]; |
244
|
0
|
0
|
0
|
|
|
0
|
} |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
245
|
0
|
|
|
|
|
0
|
} |
246
|
|
|
|
|
|
|
$groups{$key} = [Attean::Result->new( bindings => \%group_bindings ), []] unless (exists($groups{$key})); |
247
|
|
|
|
|
|
|
push(@{ $groups{$key}[1] }, $r); |
248
|
0
|
0
|
|
|
|
0
|
} |
249
|
0
|
|
|
|
|
0
|
my @keys = keys %groups; |
|
0
|
|
|
|
|
0
|
|
250
|
|
|
|
|
|
|
$groups{''} = [Attean::Result->new( bindings => {} ), []] if (scalar(@keys) == 0); |
251
|
0
|
|
|
|
|
0
|
my $aggs = $algebra->aggregates; |
252
|
0
|
0
|
|
|
|
0
|
my @results; |
253
|
0
|
|
|
|
|
0
|
my %vars; |
254
|
0
|
|
|
|
|
0
|
foreach my $key (keys %groups) { |
255
|
|
|
|
|
|
|
my %row_cache; |
256
|
0
|
|
|
|
|
0
|
my ($binding, $rows) = @{ $groups{$key} }; |
257
|
0
|
|
|
|
|
0
|
my $count = scalar(@$rows); |
258
|
0
|
|
|
|
|
0
|
my %bindings; |
|
0
|
|
|
|
|
0
|
|
259
|
0
|
|
|
|
|
0
|
foreach my $i (0 .. $#{ $aggs }) { |
260
|
0
|
|
|
|
|
0
|
my $name = $aggs->[$i]->variable->value; |
261
|
0
|
|
|
|
|
0
|
my $term = $expr_eval->evaluate_expression( $aggs->[$i], $rows, $active_graph, {} ); |
|
0
|
|
|
|
|
0
|
|
262
|
0
|
|
|
|
|
0
|
# warn "AGGREGATE error: $@" if ($@); |
263
|
0
|
|
|
|
|
0
|
$vars{$name}++; |
264
|
|
|
|
|
|
|
$bindings{ $name } = $term if ($term); |
265
|
0
|
|
|
|
|
0
|
} |
266
|
0
|
0
|
|
|
|
0
|
push(@results, Attean::Result->new( bindings => \%bindings )->join($binding)); |
267
|
|
|
|
|
|
|
} |
268
|
0
|
|
|
|
|
0
|
return Attean::ListIterator->new(variables => [keys %vars], values => \@results, item_type => 'Attean::API::Result'); |
269
|
|
|
|
|
|
|
} elsif ($algebra->isa('Attean::Algebra::Join')) { |
270
|
0
|
|
|
|
|
0
|
my ($lhs, $rhs) = map { $self->evaluate($_, $active_graph) } @children; |
271
|
|
|
|
|
|
|
return $lhs->join($rhs); |
272
|
2
|
|
|
|
|
8
|
} elsif ($algebra->isa('Attean::Algebra::LeftJoin')) { |
|
4
|
|
|
|
|
72
|
|
273
|
2
|
|
|
|
|
73
|
my $expr = $algebra->expression; |
274
|
|
|
|
|
|
|
my ($lhs_iter, $rhs_iter) = map { $self->evaluate($_, $active_graph) } @children; |
275
|
0
|
|
|
|
|
0
|
my @rhs = $rhs_iter->elements; |
276
|
0
|
|
|
|
|
0
|
my @results; |
|
0
|
|
|
|
|
0
|
|
277
|
0
|
|
|
|
|
0
|
my %vars = map { $_ => 1 } (@{ $lhs_iter->variables }, @{ $rhs_iter->variables }); |
278
|
0
|
|
|
|
|
0
|
while (my $lhs = $lhs_iter->next) { |
279
|
0
|
|
|
|
|
0
|
my $joined = 0; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
280
|
0
|
|
|
|
|
0
|
foreach my $rhs (@rhs) { |
281
|
0
|
|
|
|
|
0
|
if (my $j = $lhs->join($rhs)) { |
282
|
0
|
|
|
|
|
0
|
if ($expr_eval->evaluate_expression( $expr, $j, $active_graph, {} )->ebv) { |
283
|
0
|
0
|
|
|
|
0
|
$joined++; |
284
|
0
|
0
|
|
|
|
0
|
push(@results, $j); |
285
|
0
|
|
|
|
|
0
|
} |
286
|
0
|
|
|
|
|
0
|
} |
287
|
|
|
|
|
|
|
} |
288
|
|
|
|
|
|
|
push(@results, $lhs) unless ($joined); |
289
|
|
|
|
|
|
|
} |
290
|
0
|
0
|
|
|
|
0
|
return Attean::ListIterator->new( variables => [keys %vars], values => \@results, item_type => 'Attean::API::Result'); |
291
|
|
|
|
|
|
|
} elsif ($algebra->isa('Attean::Algebra::Minus')) { |
292
|
0
|
|
|
|
|
0
|
my ($lhsi, $rhs) = map { $self->evaluate($_, $active_graph) } @children; |
293
|
|
|
|
|
|
|
my @rhs = $rhs->elements; |
294
|
0
|
|
|
|
|
0
|
my @results; |
|
0
|
|
|
|
|
0
|
|
295
|
0
|
|
|
|
|
0
|
while (my $lhs = $lhsi->next) { |
296
|
0
|
|
|
|
|
0
|
my @compatible; |
297
|
0
|
|
|
|
|
0
|
my @disjoint; |
298
|
0
|
|
|
|
|
0
|
RHS: foreach my $rhs (@rhs) { |
299
|
|
|
|
|
|
|
if (my $j = $lhs->join($rhs)) { |
300
|
0
|
|
|
|
|
0
|
push(@compatible, 1); |
301
|
0
|
0
|
|
|
|
0
|
} else { |
302
|
0
|
|
|
|
|
0
|
push(@compatible, 0); |
303
|
|
|
|
|
|
|
} |
304
|
0
|
|
|
|
|
0
|
|
305
|
|
|
|
|
|
|
my $intersects = 0; |
306
|
|
|
|
|
|
|
my %lhs_dom = map { $_ => 1 } $lhs->variables; |
307
|
0
|
|
|
|
|
0
|
foreach my $rvar ($rhs->variables) { |
308
|
0
|
|
|
|
|
0
|
if (exists $lhs_dom{$rvar}) { |
|
0
|
|
|
|
|
0
|
|
309
|
0
|
|
|
|
|
0
|
$intersects = 1; |
310
|
0
|
0
|
|
|
|
0
|
} |
311
|
0
|
|
|
|
|
0
|
} |
312
|
|
|
|
|
|
|
push(@disjoint, not($intersects)); |
313
|
|
|
|
|
|
|
} |
314
|
0
|
|
|
|
|
0
|
|
315
|
|
|
|
|
|
|
my $count = scalar(@rhs); |
316
|
|
|
|
|
|
|
my $keep = 1; |
317
|
0
|
|
|
|
|
0
|
foreach my $i (0 .. $#rhs) { |
318
|
0
|
|
|
|
|
0
|
$keep = 0 unless ($compatible[$i] == 0 or $disjoint[$i] == 1); |
319
|
0
|
|
|
|
|
0
|
} |
320
|
0
|
0
|
0
|
|
|
0
|
|
321
|
|
|
|
|
|
|
push(@results, $lhs) if ($keep); |
322
|
|
|
|
|
|
|
} |
323
|
0
|
0
|
|
|
|
0
|
return Attean::ListIterator->new( variables => $lhsi->variables, values => \@results, item_type => 'Attean::API::Result'); |
324
|
|
|
|
|
|
|
} elsif ($algebra->isa('Attean::Algebra::Path')) { |
325
|
0
|
|
|
|
|
0
|
my $s = $algebra->subject; |
326
|
|
|
|
|
|
|
my $path = $algebra->path; |
327
|
24
|
|
|
|
|
50
|
my $o = $algebra->object; |
328
|
24
|
|
|
|
|
41
|
my @children = @{ $path->children }; |
329
|
24
|
|
|
|
|
42
|
my ($child) = $children[0]; |
330
|
24
|
|
|
|
|
36
|
|
|
24
|
|
|
|
|
54
|
|
331
|
24
|
|
|
|
|
42
|
return $self->model->get_bindings( $s, $path->predicate, $o, $active_graph ) if ($path->isa('Attean::Algebra::PredicatePath')); |
332
|
|
|
|
|
|
|
if ($path->isa('Attean::Algebra::InversePath')) { |
333
|
24
|
100
|
|
|
|
131
|
my $path = Attean::Algebra::Path->new( subject => $o, path => $child, object => $s ); |
334
|
11
|
50
|
66
|
|
|
138
|
return $self->evaluate( $path, $active_graph ); |
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
335
|
0
|
|
|
|
|
0
|
} elsif ($path->isa('Attean::Algebra::AlternativePath')) { |
336
|
0
|
|
|
|
|
0
|
my @children = @{ $path->children }; |
337
|
|
|
|
|
|
|
my @algebras = map { Attean::Algebra::Path->new( subject => $s, path => $_, object => $o ) } @children; |
338
|
0
|
|
|
|
|
0
|
my @iters = map { $self->evaluate($_, $active_graph) } @algebras; |
|
0
|
|
|
|
|
0
|
|
339
|
0
|
|
|
|
|
0
|
return Attean::IteratorSequence->new( iterators => \@iters, item_type => $iters[0]->item_type, variables => [$algebra->in_scope_variables] ); |
|
0
|
|
|
|
|
0
|
|
340
|
0
|
|
|
|
|
0
|
} elsif ($path->isa('Attean::Algebra::NegatedPropertySet')) { |
|
0
|
|
|
|
|
0
|
|
341
|
0
|
|
|
|
|
0
|
my $preds = $path->predicates; |
342
|
|
|
|
|
|
|
my %preds = map { $_->value => 1 } @$preds; |
343
|
1
|
|
|
|
|
221
|
my $filter = $self->model->get_quads($s, undef, $o, $active_graph)->grep(sub { |
344
|
1
|
|
|
|
|
6
|
my $q = shift; |
|
1
|
|
|
|
|
7
|
|
345
|
|
|
|
|
|
|
my $p = $q->predicate; |
346
|
2
|
|
|
2
|
|
4
|
return not exists $preds{ $p->value }; |
347
|
2
|
|
|
|
|
6
|
}); |
348
|
2
|
|
|
|
|
11
|
my %vars; |
349
|
1
|
|
|
|
|
7
|
$vars{subject} = $s->value if ($s->does('Attean::API::Variable')); |
350
|
1
|
|
|
|
|
31
|
$vars{object} = $o->value if ($o->does('Attean::API::Variable')); |
351
|
1
|
50
|
|
|
|
3
|
return $filter->map(sub { |
352
|
1
|
50
|
|
|
|
20
|
my $q = shift; |
353
|
|
|
|
|
|
|
return unless $q; |
354
|
1
|
|
|
1
|
|
3
|
my %bindings = map { $vars{$_} => $q->$_() } (keys %vars); |
355
|
1
|
50
|
|
|
|
3
|
return Attean::Result->new( bindings => \%bindings ); |
356
|
1
|
|
|
|
|
4
|
}, 'Attean::API::Result', variables => [values %vars]); |
|
1
|
|
|
|
|
5
|
|
357
|
1
|
|
|
|
|
18
|
} elsif ($path->isa('Attean::Algebra::SequencePath')) { |
358
|
1
|
|
|
|
|
23
|
if (scalar(@children) == 1) { |
359
|
|
|
|
|
|
|
my $path = Attean::Algebra::Path->new( subject => $s, path => $children[0], object => $o ); |
360
|
1
|
50
|
|
|
|
4
|
return $self->evaluate($path, $active_graph); |
361
|
0
|
|
|
|
|
0
|
} else { |
362
|
0
|
|
|
|
|
0
|
my @paths; |
363
|
|
|
|
|
|
|
my $first = shift(@children); |
364
|
1
|
|
|
|
|
3
|
my $join = Attean::Variable->new(); |
365
|
1
|
|
|
|
|
2
|
my @new_vars = ($join->value); |
366
|
1
|
|
|
|
|
20
|
push(@paths, Attean::Algebra::Path->new( subject => $s, path => $first, object => $join )); |
367
|
1
|
|
|
|
|
60
|
foreach my $i (0 .. $#children) { |
368
|
1
|
|
|
|
|
18
|
my $newjoin = Attean::Variable->new(); |
369
|
1
|
|
|
|
|
4
|
my $obj = ($i == $#children) ? $o : $newjoin; |
370
|
1
|
|
|
|
|
18
|
push(@new_vars, $newjoin->value); |
371
|
1
|
50
|
|
|
|
49
|
push(@paths, Attean::Algebra::Path->new( subject => $join, path => $children[$i], object => $obj )); |
372
|
1
|
|
|
|
|
4
|
$join = $newjoin; |
373
|
1
|
|
|
|
|
16
|
} |
374
|
1
|
|
|
|
|
3
|
|
375
|
|
|
|
|
|
|
while (scalar(@paths) > 1) { |
376
|
|
|
|
|
|
|
my ($l, $r) = splice(@paths, 0, 2); |
377
|
1
|
|
|
|
|
3
|
unshift(@paths, Attean::Algebra::Join->new( children => [$l, $r] )); |
378
|
1
|
|
|
|
|
4
|
} |
379
|
1
|
|
|
|
|
18
|
return $self->evaluate(shift(@paths), $active_graph)->map(sub { shift->project_complement(@new_vars) }); |
380
|
|
|
|
|
|
|
} |
381
|
1
|
|
|
1
|
|
4
|
} elsif ($path->isa('Attean::Algebra::ZeroOrMorePath') or $path->isa('Attean::Algebra::OneOrMorePath')) { |
|
1
|
|
|
|
|
6
|
|
382
|
|
|
|
|
|
|
if ($s->does('Attean::API::TermOrTriple') and $o->does('Attean::API::Variable')) { |
383
|
|
|
|
|
|
|
my $v = {}; |
384
|
4
|
100
|
66
|
|
|
14
|
if ($path->isa('Attean::Algebra::ZeroOrMorePath')) { |
|
|
50
|
33
|
|
|
|
|
|
|
0
|
0
|
|
|
|
|
385
|
3
|
|
|
|
|
76
|
$self->_ALP($active_graph, $s, $child, $v); |
386
|
3
|
50
|
|
|
|
10
|
} else { |
387
|
3
|
|
|
|
|
9
|
my $iter = $self->_eval($active_graph, $s, $child); |
388
|
|
|
|
|
|
|
while (my $n = $iter->next) { |
389
|
0
|
|
|
|
|
0
|
$self->_ALP($active_graph, $n, $child, $v); |
390
|
0
|
|
|
|
|
0
|
} |
391
|
0
|
|
|
|
|
0
|
} |
392
|
|
|
|
|
|
|
my @results = map { Attean::Result->new( bindings => { $o->value => $_ } ) } (values %$v); |
393
|
|
|
|
|
|
|
return Attean::ListIterator->new(variables => [$o->value], values => \@results, item_type => 'Attean::API::Result'); |
394
|
3
|
|
|
|
|
10
|
} elsif ($s->does('Attean::API::Variable') and $o->does('Attean::API::Variable')) { |
|
6
|
|
|
|
|
155
|
|
395
|
3
|
|
|
|
|
111
|
my $nodes = $self->model->graph_nodes( $active_graph ); |
396
|
|
|
|
|
|
|
my @results; |
397
|
1
|
|
|
|
|
72
|
while (my $t = $nodes->next) { |
398
|
1
|
|
|
|
|
33
|
my $tr = Attean::Result->new( bindings => { $s->value => $t } ); |
399
|
1
|
|
|
|
|
4
|
my $p = Attean::Algebra::Path->new( subject => $t, path => $path, object => $o ); |
400
|
3
|
|
|
|
|
53
|
my $iter = $self->evaluate($p, $active_graph); |
401
|
3
|
|
|
|
|
98
|
while (my $r = $iter->next) { |
402
|
3
|
|
|
|
|
30
|
push(@results, $r->join($tr)); |
403
|
3
|
|
|
|
|
336
|
} |
404
|
6
|
|
|
|
|
20
|
} |
405
|
|
|
|
|
|
|
my %vars = map { $_ => 1 } ($s->value, $o->value); |
406
|
|
|
|
|
|
|
return Attean::ListIterator->new(variables => [keys %vars], values => \@results, item_type => 'Attean::API::Result'); |
407
|
1
|
|
|
|
|
6
|
} elsif ($s->does('Attean::API::Variable') and $o->does('Attean::API::TermOrTriple')) { |
|
2
|
|
|
|
|
7
|
|
408
|
1
|
|
|
|
|
21
|
my $pp = Attean::Algebra::InversePath->new( children => [$child] ); |
409
|
|
|
|
|
|
|
my $p = Attean::Algebra::Path->new( subject => $o, path => $pp, object => $s ); |
410
|
0
|
|
|
|
|
0
|
return $self->evaluate($p, $active_graph); |
411
|
0
|
|
|
|
|
0
|
} else { # Term ZeroOrMorePath(path) Term |
412
|
0
|
|
|
|
|
0
|
my $v = {}; |
413
|
|
|
|
|
|
|
$self->_ALP($active_graph, $s, $child, $v); |
414
|
0
|
|
|
|
|
0
|
my @results; |
415
|
0
|
|
|
|
|
0
|
foreach my $v (values %$v) { |
416
|
0
|
|
|
|
|
0
|
return Attean::ListIterator->new(variables => [], values => [Attean::Result->new()], item_type => 'Attean::API::Result') |
417
|
0
|
|
|
|
|
0
|
if ($v->equals($o)); |
418
|
0
|
0
|
|
|
|
0
|
} |
419
|
|
|
|
|
|
|
return Attean::ListIterator->new(variables => [], values => [], item_type => 'Attean::API::Result'); |
420
|
|
|
|
|
|
|
} |
421
|
0
|
|
|
|
|
0
|
} elsif ($path->isa('Attean::Algebra::ZeroOrOnePath')) { |
422
|
|
|
|
|
|
|
my $path = Attean::Algebra::Path->new( subject => $s, path => $child, object => $o ); |
423
|
|
|
|
|
|
|
my @iters; |
424
|
5
|
|
|
|
|
88
|
my %seen; |
425
|
5
|
|
|
|
|
10
|
push(@iters, $self->evaluate( $path, $active_graph )->grep(sub { return not($seen{shift->as_string}++); })); |
426
|
|
|
|
|
|
|
push(@iters, $self->_zeroLengthPath($s, $o, $active_graph)); |
427
|
5
|
|
|
1
|
|
14
|
my %vars; |
|
1
|
|
|
|
|
4
|
|
428
|
5
|
|
|
|
|
163
|
foreach my $iter (@iters) { |
429
|
5
|
|
|
|
|
453
|
$vars{$_}++ for (@{ $iter->variables }); |
430
|
5
|
|
|
|
|
11
|
} |
431
|
10
|
|
|
|
|
37
|
return Attean::IteratorSequence->new( iterators => \@iters, item_type => 'Attean::API::Result', variables => [keys %vars] ); |
|
10
|
|
|
|
|
148
|
|
432
|
|
|
|
|
|
|
} |
433
|
5
|
|
|
|
|
94
|
die "Unimplemented path type: $path"; |
434
|
|
|
|
|
|
|
} elsif ($algebra->isa('Attean::Algebra::Project')) { |
435
|
0
|
|
|
|
|
0
|
my $iter = $self->evaluate( $child, $active_graph ); |
436
|
|
|
|
|
|
|
my @vars = map { $_->value } @{ $algebra->variables }; |
437
|
3
|
|
|
|
|
92
|
return $iter->map(sub { |
438
|
3
|
|
|
|
|
98
|
my $r = shift; |
|
3
|
|
|
|
|
12
|
|
|
3
|
|
|
|
|
12
|
|
439
|
|
|
|
|
|
|
my $b = { map { my $t = $r->value($_); $t ? ($_ => $t) : () } @vars }; |
440
|
9
|
|
|
9
|
|
15
|
return Attean::Result->new( bindings => $b ); |
441
|
9
|
50
|
|
|
|
14
|
}, undef, variables => \@vars); #->debug('Project result'); |
|
9
|
|
|
|
|
20
|
|
|
9
|
|
|
|
|
45
|
|
442
|
9
|
|
|
|
|
151
|
} elsif ($algebra->isa('Attean::Algebra::Slice')) { |
443
|
3
|
|
|
|
|
19
|
my $iter = $self->evaluate( $child, $active_graph ); |
444
|
|
|
|
|
|
|
$iter = $iter->offset($algebra->offset) if ($algebra->offset > 0); |
445
|
3
|
|
|
|
|
10
|
$iter = $iter->limit($algebra->limit) if ($algebra->limit >= 0); |
446
|
3
|
100
|
|
|
|
113
|
return $iter; |
447
|
3
|
100
|
|
|
|
18
|
} elsif ($algebra->isa('Attean::Algebra::Union')) { |
448
|
3
|
|
|
|
|
71
|
my ($lhs, $rhs) = map { $self->evaluate($_, $active_graph) } @children; |
449
|
|
|
|
|
|
|
return Attean::IteratorSequence->new( |
450
|
0
|
|
|
|
|
0
|
iterators => [$lhs, $rhs], |
|
0
|
|
|
|
|
0
|
|
451
|
0
|
|
|
|
|
0
|
item_type => 'Attean::API::Result', |
452
|
|
|
|
|
|
|
variables => [$algebra->in_scope_variables] |
453
|
|
|
|
|
|
|
); |
454
|
|
|
|
|
|
|
} elsif ($algebra->isa('Attean::Algebra::Ask')) { |
455
|
|
|
|
|
|
|
my $iter = $self->evaluate($child, $active_graph); |
456
|
|
|
|
|
|
|
my $result = $iter->next; |
457
|
0
|
|
|
|
|
0
|
return Attean::ListIterator->new(values => [$result ? Attean::Literal->true : Attean::Literal->false], item_type => 'Attean::API::Term'); |
458
|
0
|
|
|
|
|
0
|
} elsif ($algebra->isa('Attean::Algebra::Construct')) { |
459
|
0
|
0
|
|
|
|
0
|
my $iter = $self->evaluate($child, $active_graph); |
460
|
|
|
|
|
|
|
my $patterns = $algebra->triples; |
461
|
1
|
|
|
|
|
4
|
use Data::Dumper; |
462
|
1
|
|
|
|
|
35
|
my %seen; |
463
|
3
|
|
|
3
|
|
12328
|
return Attean::CodeIterator->new( |
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
3786
|
|
464
|
1
|
|
|
|
|
2
|
generator => sub { |
465
|
|
|
|
|
|
|
my $r = $iter->next; |
466
|
|
|
|
|
|
|
return unless ($r); |
467
|
2
|
|
|
2
|
|
6
|
my %mapping = map { my $t = $r->value($_); $t ? ("?$_" => $t) : (); } ($r->variables); |
468
|
2
|
100
|
|
|
|
6
|
my $mapper = Attean::TermMap->rewrite_map(\%mapping); |
469
|
1
|
50
|
|
|
|
3
|
my @triples; |
|
2
|
|
|
|
|
5
|
|
|
2
|
|
|
|
|
9
|
|
470
|
1
|
|
|
|
|
12
|
PATTERN: foreach my $p (@$patterns) { |
471
|
1
|
|
|
|
|
44
|
my @terms = map { |
472
|
1
|
|
|
|
|
3
|
($_->does('Attean::API::TriplePattern')) |
473
|
|
|
|
|
|
|
? $_->as_triple |
474
|
1
|
50
|
|
|
|
5
|
: $_ |
|
3
|
|
|
|
|
43
|
|
475
|
|
|
|
|
|
|
} $p->apply_map($mapper)->values; |
476
|
|
|
|
|
|
|
unless (all { $_->does('Attean::API::TermOrTriple') } @terms) { |
477
|
|
|
|
|
|
|
next PATTERN; |
478
|
1
|
50
|
|
|
|
25
|
} |
|
3
|
|
|
|
|
25
|
|
479
|
0
|
|
|
|
|
0
|
push(@triples, Attean::Triple->new(@terms)); |
480
|
|
|
|
|
|
|
} |
481
|
1
|
|
|
|
|
37
|
return @triples; |
482
|
|
|
|
|
|
|
}, |
483
|
1
|
|
|
|
|
31
|
item_type => 'Attean::API::Triple' |
484
|
|
|
|
|
|
|
)->grep(sub { return not($seen{shift->as_string}++); }); |
485
|
|
|
|
|
|
|
} elsif ($algebra->isa('Attean::Algebra::Table')) { |
486
|
1
|
|
|
1
|
|
23
|
my $vars = [map { $_->value } @{ $algebra->variables }]; |
|
1
|
|
|
|
|
5
|
|
487
|
|
|
|
|
|
|
return Attean::ListIterator->new(variables => $vars, values => $algebra->rows, item_type => 'Attean::API::Result'); |
488
|
0
|
|
|
|
|
0
|
} |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
489
|
0
|
|
|
|
|
0
|
die "Unimplemented algebra evaluation for: $algebra"; |
490
|
|
|
|
|
|
|
} |
491
|
0
|
|
|
|
|
0
|
|
492
|
|
|
|
|
|
|
|
493
|
|
|
|
|
|
|
=item C<< evaluate_pattern( $pattern, $active_graph, \@new_vars, \%blanks ) >> |
494
|
|
|
|
|
|
|
|
495
|
|
|
|
|
|
|
Returns an L<Attean::API::Iterator> object with results produced by evaluating |
496
|
|
|
|
|
|
|
the triple- or quad-pattern C<< $pattern >> against the evaluator's |
497
|
|
|
|
|
|
|
C<< model >>, using the supplied C<< $active_graph >>. |
498
|
|
|
|
|
|
|
|
499
|
|
|
|
|
|
|
If the C<< ground_blanks >> option is false, replaces blank nodes in the |
500
|
|
|
|
|
|
|
pattern with fresh variables before evaluation, and populates C<< %blanks >> |
501
|
|
|
|
|
|
|
with pairs ($variable_name => $variable_node). Each new variable is also |
502
|
|
|
|
|
|
|
appended to C<< @new_vars >> as it is created. |
503
|
|
|
|
|
|
|
|
504
|
|
|
|
|
|
|
=cut |
505
|
|
|
|
|
|
|
|
506
|
|
|
|
|
|
|
my $self = shift; |
507
|
|
|
|
|
|
|
my $t = shift; |
508
|
|
|
|
|
|
|
my $active_graph = shift || Carp::confess "No active-graph passed to Attean::SimpleQueryEvaluator->evaluate"; |
509
|
24
|
|
|
24
|
1
|
43
|
my $new_vars = shift; |
510
|
24
|
|
|
|
|
34
|
my $blanks = shift; |
511
|
24
|
|
33
|
|
|
51
|
my $q = $t->as_quad_pattern($active_graph); |
512
|
24
|
|
|
|
|
27
|
my @values; |
513
|
24
|
|
|
|
|
28
|
foreach my $v ($q->values) { |
514
|
24
|
|
|
|
|
84
|
if (not($self->ground_blanks) and $v->does('Attean::API::Blank')) { |
515
|
24
|
|
|
|
|
812
|
unless (exists $blanks->{$v->value}) { |
516
|
24
|
|
|
|
|
68
|
$blanks->{$v->value} = Attean::Variable->new(); |
517
|
96
|
100
|
100
|
|
|
1351
|
push(@$new_vars, $blanks->{$v->value}->value); |
518
|
2
|
100
|
|
|
|
56
|
} |
519
|
1
|
|
|
|
|
15
|
push(@values, $blanks->{$v->value}); |
520
|
1
|
|
|
|
|
54
|
} else { |
521
|
|
|
|
|
|
|
push(@values, $v); |
522
|
2
|
|
|
|
|
6
|
} |
523
|
|
|
|
|
|
|
} |
524
|
94
|
|
|
|
|
2041
|
return $self->model->get_bindings( @values ); |
525
|
|
|
|
|
|
|
} |
526
|
|
|
|
|
|
|
|
527
|
24
|
|
|
|
|
112
|
my $self = shift; |
528
|
|
|
|
|
|
|
my $graph = shift; |
529
|
|
|
|
|
|
|
my $term = shift; |
530
|
|
|
|
|
|
|
my $path = shift; |
531
|
6
|
|
|
6
|
|
10
|
my $v = shift; |
532
|
6
|
|
|
|
|
7
|
return if (exists $v->{ $term->as_string }); |
533
|
6
|
|
|
|
|
7
|
$v->{ $term->as_string } = $term; |
534
|
6
|
|
|
|
|
8
|
|
535
|
6
|
|
|
|
|
8
|
my $iter = $self->_eval($graph, $term, $path); |
536
|
6
|
50
|
|
|
|
16
|
while (my $n = $iter->next) { |
537
|
6
|
|
|
|
|
326
|
$self->_ALP($graph, $n, $path, $v); |
538
|
|
|
|
|
|
|
} |
539
|
6
|
|
|
|
|
294
|
} |
540
|
6
|
|
|
|
|
179
|
|
541
|
3
|
|
|
|
|
11
|
my $self = shift; |
542
|
|
|
|
|
|
|
my $graph = shift; |
543
|
|
|
|
|
|
|
my $term = shift; |
544
|
|
|
|
|
|
|
my $path = shift; |
545
|
|
|
|
|
|
|
my $pp = Attean::Algebra::Path->new( subject => $term, path => $path, object => variable('o') ); |
546
|
6
|
|
|
6
|
|
9
|
my $iter = $self->evaluate($pp, $graph); |
547
|
6
|
|
|
|
|
8
|
my $terms = $iter->map(sub { shift->value('o') }, 'Attean::API::Term'); |
548
|
6
|
|
|
|
|
18
|
my %seen; |
549
|
6
|
|
|
|
|
9
|
return $terms->grep(sub { not $seen{ shift->as_string }++ }); |
550
|
6
|
|
|
|
|
18
|
} |
551
|
6
|
|
|
|
|
19
|
|
552
|
6
|
|
|
3
|
|
226
|
my $self = shift; |
|
3
|
|
|
|
|
13
|
|
553
|
6
|
|
|
|
|
167
|
my $s = shift; |
554
|
6
|
|
|
3
|
|
39
|
my $o = shift; |
|
3
|
|
|
|
|
11
|
|
555
|
|
|
|
|
|
|
my $graph = shift; |
556
|
|
|
|
|
|
|
my $s_term = ($s->does('Attean::API::TermOrTriple')); |
557
|
|
|
|
|
|
|
my $o_term = ($o->does('Attean::API::TermOrTriple')); |
558
|
5
|
|
|
5
|
|
9
|
if ($s_term and $o_term) { |
559
|
5
|
|
|
|
|
8
|
my @r; |
560
|
5
|
|
|
|
|
6
|
push(@r, Attean::Result->new()) if ($s->equals($o)); |
561
|
5
|
|
|
|
|
7
|
return Attean::ListIterator->new(variables => [], values => \@r, item_type => 'Attean::API::Result'); |
562
|
5
|
|
|
|
|
11
|
} elsif ($s_term) { |
563
|
5
|
|
|
|
|
71
|
my $name = $o->value; |
564
|
5
|
100
|
100
|
|
|
78
|
my $r = Attean::Result->new( bindings => { $name => $s } ); |
|
|
100
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
565
|
2
|
|
|
|
|
4
|
return Attean::ListIterator->new(variables => [$name], values => [$r], item_type => 'Attean::API::Result'); |
566
|
2
|
100
|
|
|
|
6
|
} elsif ($o_term) { |
567
|
2
|
|
|
|
|
54
|
my $name = $s->value; |
568
|
|
|
|
|
|
|
my $r = Attean::Result->new( bindings => { $name => $o } ); |
569
|
1
|
|
|
|
|
4
|
return Attean::ListIterator->new(variables => [$name], values => [$r], item_type => 'Attean::API::Result'); |
570
|
1
|
|
|
|
|
21
|
} else { |
571
|
1
|
|
|
|
|
36
|
my @vars = map { $_->value } ($s, $o); |
572
|
|
|
|
|
|
|
my $nodes = $self->model->graph_nodes( $graph ); |
573
|
1
|
|
|
|
|
4
|
return $nodes->map( |
574
|
1
|
|
|
|
|
27
|
sub { |
575
|
1
|
|
|
|
|
40
|
my $term = shift; |
576
|
|
|
|
|
|
|
Attean::Result->new( bindings => { map { $_ => $term } @vars } ); |
577
|
1
|
|
|
|
|
3
|
}, |
|
2
|
|
|
|
|
7
|
|
578
|
1
|
|
|
|
|
8
|
'Attean::API::Result', |
579
|
|
|
|
|
|
|
variables => \@vars |
580
|
|
|
|
|
|
|
); |
581
|
5
|
|
|
5
|
|
8
|
} |
582
|
5
|
|
|
|
|
10
|
} |
|
10
|
|
|
|
|
99
|
|
583
|
|
|
|
|
|
|
} |
584
|
1
|
|
|
|
|
38
|
|
585
|
|
|
|
|
|
|
use Moo; |
586
|
|
|
|
|
|
|
use Attean::RDF; |
587
|
|
|
|
|
|
|
use Scalar::Util qw(blessed); |
588
|
|
|
|
|
|
|
use Types::Standard qw(InstanceOf); |
589
|
|
|
|
|
|
|
use URI::Escape qw(uri_escape_utf8); |
590
|
|
|
|
|
|
|
use Encode qw(encode); |
591
|
|
|
|
|
|
|
use POSIX qw(ceil floor); |
592
|
3
|
|
|
3
|
|
2557
|
use Digest; |
|
3
|
|
|
|
|
9
|
|
|
3
|
|
|
|
|
13
|
|
593
|
3
|
|
|
3
|
|
934
|
use UUID::Tiny ':std'; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
23
|
|
594
|
3
|
|
|
3
|
|
2186
|
use List::MoreUtils qw(zip); |
|
3
|
|
|
|
|
8
|
|
|
3
|
|
|
|
|
105
|
|
595
|
3
|
|
|
3
|
|
15
|
use DateTime::Format::W3CDTF; |
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
14
|
|
596
|
3
|
|
|
3
|
|
1232
|
use I18N::LangTags; |
|
3
|
|
|
|
|
8
|
|
|
3
|
|
|
|
|
126
|
|
597
|
3
|
|
|
3
|
|
18
|
use namespace::clean; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
108
|
|
598
|
3
|
|
|
3
|
|
17
|
|
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
30
|
|
599
|
3
|
|
|
3
|
|
1700
|
has 'evaluator' => (is => 'ro', isa => InstanceOf['Attean::SimpleQueryEvaluator']); |
|
3
|
|
|
|
|
1507
|
|
|
3
|
|
|
|
|
84
|
|
600
|
3
|
|
|
3
|
|
19
|
my $self = shift; |
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
577
|
|
601
|
3
|
|
|
3
|
|
21
|
my $expr = shift; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
21
|
|
602
|
3
|
|
|
3
|
|
2694
|
my $row = shift; |
|
3
|
|
|
|
|
8
|
|
|
3
|
|
|
|
|
62
|
|
603
|
3
|
|
|
3
|
|
15
|
my $active_graph = shift; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
79
|
|
604
|
3
|
|
|
3
|
|
13
|
my $row_cache = shift || {}; |
|
3
|
|
|
|
|
8
|
|
|
3
|
|
|
|
|
16
|
|
605
|
|
|
|
|
|
|
my $impl = $self->impl($expr, $active_graph); |
606
|
|
|
|
|
|
|
return eval { $impl->($row, row_cache => $row_cache) }; |
607
|
|
|
|
|
|
|
} |
608
|
53
|
|
|
53
|
0
|
1922
|
|
609
|
53
|
|
|
|
|
68
|
my $self = shift; |
610
|
53
|
|
|
|
|
68
|
my $expr = shift; |
611
|
53
|
|
|
|
|
64
|
my $active_graph = shift; |
612
|
53
|
|
100
|
|
|
152
|
my $op = $expr->operator; |
613
|
53
|
|
|
|
|
114
|
my $true = Attean::Literal->true; |
614
|
53
|
|
|
|
|
82
|
my $false = Attean::Literal->false; |
|
53
|
|
|
|
|
92
|
|
615
|
|
|
|
|
|
|
if ($expr->isa('Attean::ExistsExpression')) { |
616
|
|
|
|
|
|
|
my $pattern = $expr->pattern; |
617
|
|
|
|
|
|
|
return sub { |
618
|
127
|
|
|
127
|
0
|
148
|
my $r = shift; |
619
|
127
|
|
|
|
|
142
|
my $table = Attean::Algebra::Table->new( variables => [map { variable($_) } $r->variables], rows => [$r] ); |
620
|
127
|
|
|
|
|
155
|
my $join = Attean::Algebra::Join->new( children => [$table, $pattern] ); |
621
|
127
|
|
|
|
|
217
|
# TODO: substitute variables at top-level of EXISTS pattern |
622
|
127
|
|
|
|
|
273
|
my $iter = $self->evaluator->evaluate($join, $active_graph); |
623
|
127
|
|
|
|
|
219
|
return ($iter->next) ? $true : $false; |
624
|
127
|
50
|
|
|
|
613
|
}; |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
625
|
0
|
|
|
|
|
0
|
} elsif ($expr->isa('Attean::ValueExpression')) { |
626
|
|
|
|
|
|
|
my $node = $expr->value; |
627
|
0
|
|
|
0
|
|
0
|
if ($node->does('Attean::API::Variable')) { |
628
|
0
|
|
|
|
|
0
|
return sub { return shift->value($node->value); }; |
|
0
|
|
|
|
|
0
|
|
629
|
0
|
|
|
|
|
0
|
} else { |
630
|
|
|
|
|
|
|
return sub { return $node }; |
631
|
0
|
|
|
|
|
0
|
} |
632
|
0
|
0
|
|
|
|
0
|
} elsif ($expr->isa('Attean::UnaryExpression')) { |
633
|
0
|
|
|
|
|
0
|
my ($child) = @{ $expr->children }; |
634
|
|
|
|
|
|
|
my $impl = $self->impl($child, $active_graph); |
635
|
88
|
|
|
|
|
151
|
if ($op eq '!') { |
636
|
88
|
100
|
|
|
|
182
|
return sub { |
637
|
29
|
|
|
29
|
|
440
|
my $term = $impl->(@_); |
|
29
|
|
|
|
|
69
|
|
638
|
|
|
|
|
|
|
return ($term->ebv) ? $false : $true; |
639
|
59
|
|
|
55
|
|
1355
|
} |
|
55
|
|
|
|
|
97
|
|
640
|
|
|
|
|
|
|
} elsif ($op eq '-' or $op eq '+') { |
641
|
|
|
|
|
|
|
return sub { |
642
|
0
|
|
|
|
|
0
|
my $term = $impl->(@_); |
|
0
|
|
|
|
|
0
|
|
643
|
0
|
|
|
|
|
0
|
die "TypeError $op" unless (blessed($term) and $term->does('Attean::API::NumericLiteral')); |
644
|
0
|
0
|
0
|
|
|
0
|
my $v = $term->numeric_value; |
|
|
0
|
|
|
|
|
|
645
|
|
|
|
|
|
|
return Attean::Literal->new( value => eval "$op$v", datatype => $term->datatype ); |
646
|
0
|
|
|
0
|
|
0
|
}; |
647
|
0
|
0
|
|
|
|
0
|
} |
648
|
|
|
|
|
|
|
die "Unimplemented UnaryExpression evaluation: " . $expr->operator; |
649
|
0
|
|
|
|
|
0
|
} elsif ($expr->isa('Attean::BinaryExpression')) { |
650
|
|
|
|
|
|
|
my ($lhs, $rhs) = @{ $expr->children }; |
651
|
0
|
|
|
0
|
|
0
|
my ($lhsi, $rhsi) = map { $self->impl($_, $active_graph) } ($lhs, $rhs); |
652
|
0
|
0
|
0
|
|
|
0
|
if ($op eq '&&') { |
653
|
0
|
|
|
|
|
0
|
return sub { |
654
|
0
|
|
|
|
|
0
|
my ($r, %args) = @_; |
655
|
0
|
|
|
|
|
0
|
my $lbv = eval { $lhsi->($r, %args) }; |
656
|
|
|
|
|
|
|
my $rbv = eval { $rhsi->($r, %args) }; |
657
|
0
|
|
|
|
|
0
|
die "TypeError $op" unless ($lbv or $rbv); |
658
|
|
|
|
|
|
|
return $false if (not($lbv) and not($rbv->ebv)); |
659
|
35
|
|
|
|
|
45
|
return $false if (not($rbv) and not($lbv->ebv)); |
|
35
|
|
|
|
|
71
|
|
660
|
35
|
|
|
|
|
61
|
die "TypeError $op" unless ($lbv and $rbv); |
|
70
|
|
|
|
|
131
|
|
661
|
35
|
100
|
|
|
|
101
|
return ($lbv->ebv && $rbv->ebv) ? $true : $false; |
|
|
100
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
662
|
|
|
|
|
|
|
} |
663
|
9
|
|
|
9
|
|
20
|
} elsif ($op eq '||') { |
664
|
9
|
|
|
|
|
15
|
return sub { |
|
9
|
|
|
|
|
23
|
|
665
|
9
|
|
|
|
|
506
|
my ($r, %args) = @_; |
|
9
|
|
|
|
|
20
|
|
666
|
9
|
100
|
100
|
|
|
556
|
my $lbv = eval { $lhsi->($r, %args) }; |
667
|
8
|
100
|
100
|
|
|
22
|
return $true if ($lbv and $lbv->ebv); |
668
|
7
|
100
|
100
|
|
|
17
|
my $rbv = eval { $rhsi->($r, %args) }; |
669
|
6
|
100
|
100
|
|
|
49
|
die "TypeError $op" unless ($rbv); |
670
|
4
|
100
|
100
|
|
|
13
|
return $true if ($rbv->ebv); |
671
|
|
|
|
|
|
|
return $false if ($lbv); |
672
|
9
|
|
|
|
|
37
|
die "TypeError $op"; |
673
|
|
|
|
|
|
|
} |
674
|
9
|
|
|
9
|
|
21
|
} elsif ($op =~ m#^(?:[-+*/])$#) { # numeric operators: - + * / |
675
|
9
|
|
|
|
|
13
|
return sub { |
|
9
|
|
|
|
|
24
|
|
676
|
9
|
100
|
100
|
|
|
517
|
my ($r, %args) = @_; |
677
|
6
|
|
|
|
|
12
|
($lhs, $rhs) = map { $_->($r, %args) } ($lhsi, $rhsi); |
|
6
|
|
|
|
|
14
|
|
678
|
6
|
100
|
|
|
|
446
|
for ($lhs, $rhs) { die "TypeError $op" unless (blessed($_) and $_->does('Attean::API::NumericLiteral')); } |
679
|
4
|
100
|
|
|
|
11
|
my $lv = $lhs->numeric_value; |
680
|
2
|
100
|
|
|
|
12
|
my $rv = $rhs->numeric_value; |
681
|
1
|
|
|
|
|
17
|
return Attean::Literal->new( value => eval "$lv $op $rv", datatype => $lhs->binary_promotion_type($rhs, $op) ); |
682
|
|
|
|
|
|
|
}; |
683
|
9
|
|
|
|
|
34
|
} elsif ($op =~ /^!?=$/) { |
684
|
|
|
|
|
|
|
return sub { |
685
|
16
|
|
|
16
|
|
35
|
my ($r, %args) = @_; |
686
|
16
|
|
|
|
|
27
|
($lhs, $rhs) = map { $_->($r, %args) } ($lhsi, $rhsi); |
|
32
|
|
|
|
|
59
|
|
687
|
16
|
100
|
66
|
|
|
29
|
for ($lhs, $rhs) { die "TypeError $op" unless (blessed($_) and $_->does('Attean::API::TermOrTriple')); } |
|
32
|
|
|
|
|
383
|
|
688
|
15
|
|
|
|
|
204
|
my $ok; |
689
|
15
|
|
|
|
|
43
|
if ($lhs->does('Attean::API::Binding')) { |
690
|
15
|
|
|
|
|
551
|
$ok = $lhs->equals($rhs); |
691
|
17
|
|
|
|
|
77
|
} else { |
692
|
|
|
|
|
|
|
$ok = $lhs->equals($rhs); |
693
|
|
|
|
|
|
|
} |
694
|
0
|
|
|
0
|
|
0
|
$ok = not($ok) if ($op eq '!='); |
695
|
0
|
|
|
|
|
0
|
return $ok ? $true : $false; |
|
0
|
|
|
|
|
0
|
|
696
|
0
|
0
|
0
|
|
|
0
|
} |
|
0
|
|
|
|
|
0
|
|
697
|
0
|
|
|
|
|
0
|
} elsif ($op =~ /^[<>]=?$/) { |
698
|
0
|
0
|
|
|
|
0
|
return sub { |
699
|
0
|
|
|
|
|
0
|
my ($r, %args) = @_; |
700
|
|
|
|
|
|
|
($lhs, $rhs) = map { $_->($r, %args) } ($lhsi, $rhsi); |
701
|
0
|
|
|
|
|
0
|
for ($lhs, $rhs) { |
702
|
|
|
|
|
|
|
die "TypeError $op" unless $_->does('Attean::API::TermOrTriple'); |
703
|
0
|
0
|
|
|
|
0
|
die "TypeError $op" if ($_->does('Attean::API::IRI')); # comparison of IRIs is only defined for `ORDER BY`, not for general expressions |
704
|
0
|
0
|
|
|
|
0
|
} |
705
|
|
|
|
|
|
|
|
706
|
0
|
|
|
|
|
0
|
my $c = ($lhs->compare($rhs)); |
707
|
|
|
|
|
|
|
return $true if (($c < 0 and ($op =~ /<=?/)) or ($c > 0 and ($op =~ />=?/)) or ($c == 0 and ($op =~ /=/))); |
708
|
0
|
|
|
0
|
|
0
|
return $false; |
709
|
0
|
|
|
|
|
0
|
} |
|
0
|
|
|
|
|
0
|
|
710
|
0
|
|
|
|
|
0
|
} |
711
|
0
|
0
|
|
|
|
0
|
die "Unexpected operator evaluation: $op"; |
712
|
0
|
0
|
|
|
|
0
|
} elsif ($expr->isa('Attean::FunctionExpression')) { |
713
|
|
|
|
|
|
|
my $func = $expr->operator; |
714
|
|
|
|
|
|
|
my @children = map { $self->impl($_, $active_graph) } @{ $expr->children }; |
715
|
0
|
|
|
|
|
0
|
my %type_roles = qw(URI IRI IRI IRI BLANK Blank LITERAL Literal NUMERIC NumericLiteral TRIPLE Triple); |
716
|
0
|
0
|
0
|
|
|
0
|
my %type_classes = qw(URI Attean::IRI IRI Attean::IRI STR Attean::Literal); |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
717
|
0
|
|
|
|
|
0
|
return sub { |
718
|
|
|
|
|
|
|
my ($r, %args) = @_; |
719
|
0
|
|
|
|
|
0
|
my $row_cache = $args{row_cache} || {}; |
720
|
0
|
|
|
|
|
0
|
|
721
|
|
|
|
|
|
|
if ($func eq 'IF') { |
722
|
0
|
|
|
|
|
0
|
my $term = $children[0]->( $r, %args ); |
723
|
0
|
|
|
|
|
0
|
return ($term->ebv) ? $children[1]->( $r, %args ) : $children[2]->( $r, %args ); |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
724
|
0
|
|
|
|
|
0
|
} elsif ($func eq 'IN' or $func eq 'NOTIN') { |
725
|
0
|
|
|
|
|
0
|
($true, $false) = ($false, $true) if ($func eq 'NOTIN'); |
726
|
|
|
|
|
|
|
my $child = shift(@children); |
727
|
0
|
|
|
0
|
|
0
|
my $term = $child->( $r, %args ); |
728
|
0
|
|
0
|
|
|
0
|
foreach my $c (@children) { |
729
|
|
|
|
|
|
|
if (my $value = eval { $c->( $r, %args ) }) { |
730
|
0
|
0
|
0
|
|
|
0
|
return $true if ($term->equals($value)); |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
731
|
0
|
|
|
|
|
0
|
} |
732
|
0
|
0
|
|
|
|
0
|
} |
733
|
|
|
|
|
|
|
return $false; |
734
|
0
|
0
|
|
|
|
0
|
} elsif ($func eq 'COALESCE') { |
735
|
0
|
|
|
|
|
0
|
foreach my $c (@children) { |
736
|
0
|
|
|
|
|
0
|
my $t = eval { $c->( $r, %args ) }; |
737
|
0
|
|
|
|
|
0
|
next if ($@); |
738
|
0
|
0
|
|
|
|
0
|
return $t if $t; |
|
0
|
|
|
|
|
0
|
|
739
|
0
|
0
|
|
|
|
0
|
} |
740
|
|
|
|
|
|
|
return; |
741
|
|
|
|
|
|
|
} |
742
|
0
|
|
|
|
|
0
|
|
743
|
|
|
|
|
|
|
my @operands = map { $_->( $r, %args ) } @children; |
744
|
0
|
|
|
|
|
0
|
if ($func =~ /^(STR)$/) { |
745
|
0
|
|
|
|
|
0
|
return $type_classes{$1}->new($operands[0]->value); |
|
0
|
|
|
|
|
0
|
|
746
|
0
|
0
|
|
|
|
0
|
} elsif ($func =~ /^(SUBJECT|PREDICATE|OBJECT)$/) { |
747
|
0
|
0
|
|
|
|
0
|
my $pos = lc($func); |
748
|
|
|
|
|
|
|
my $term = $operands[0]->$pos(); |
749
|
0
|
|
|
|
|
0
|
return $term; |
750
|
|
|
|
|
|
|
} elsif ($func =~ /^([UI]RI)$/) { |
751
|
|
|
|
|
|
|
my @base = $expr->has_base ? (base => $expr->base) : (); |
752
|
0
|
|
|
|
|
0
|
return $type_classes{$1}->new(value => $operands[0]->value, @base); |
|
0
|
|
|
|
|
0
|
|
753
|
0
|
0
|
0
|
|
|
0
|
} elsif ($func eq 'BNODE') { |
|
|
0
|
0
|
|
|
|
|
|
|
0
|
0
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
754
|
0
|
|
|
|
|
0
|
if (scalar(@operands)) { |
755
|
|
|
|
|
|
|
my $name = $operands[0]->value; |
756
|
0
|
|
|
|
|
0
|
if (my $b = $row_cache->{bnodes}{$name}) { |
757
|
0
|
|
|
|
|
0
|
return $b; |
758
|
0
|
|
|
|
|
0
|
} else { |
759
|
|
|
|
|
|
|
my $b = Attean::Blank->new(); |
760
|
0
|
0
|
|
|
|
0
|
$row_cache->{bnodes}{$name} = $b; |
761
|
0
|
|
|
|
|
0
|
return $b; |
762
|
|
|
|
|
|
|
} |
763
|
0
|
0
|
|
|
|
0
|
} |
764
|
0
|
|
|
|
|
0
|
return Attean::Blank->new(); |
765
|
0
|
0
|
|
|
|
0
|
} elsif ($func eq 'LANG') { |
766
|
0
|
|
|
|
|
0
|
die "TypeError: LANG" unless ($operands[0]->does('Attean::API::Literal')); |
767
|
|
|
|
|
|
|
return Attean::Literal->new($operands[0]->language // ''); |
768
|
0
|
|
|
|
|
0
|
} elsif ($func eq 'LANGMATCHES') { |
769
|
0
|
|
|
|
|
0
|
my ($lang, $match) = map { $_->value } @operands; |
770
|
0
|
|
|
|
|
0
|
if ($match eq '*') { |
771
|
|
|
|
|
|
|
# """A language-range of "*" matches any non-empty language-tag string.""" |
772
|
|
|
|
|
|
|
return ($lang ? $true : $false); |
773
|
0
|
|
|
|
|
0
|
} else { |
774
|
|
|
|
|
|
|
return (I18N::LangTags::is_dialect_of( $lang, $match )) ? $true : $false; |
775
|
0
|
0
|
|
|
|
0
|
} |
776
|
0
|
|
0
|
|
|
0
|
} elsif ($func eq 'DATATYPE') { |
777
|
|
|
|
|
|
|
return $operands[0]->datatype; |
778
|
0
|
|
|
|
|
0
|
} elsif ($func eq 'BOUND') { |
|
0
|
|
|
|
|
0
|
|
779
|
0
|
0
|
|
|
|
0
|
return $operands[0] ? $true : $false; |
780
|
|
|
|
|
|
|
} elsif ($func eq 'RAND') { |
781
|
0
|
0
|
|
|
|
0
|
return Attean::Literal->new( value => rand(), datatype => 'http://www.w3.org/2001/XMLSchema#double' ); |
782
|
|
|
|
|
|
|
} elsif ($func eq 'ABS') { |
783
|
0
|
0
|
|
|
|
0
|
return Attean::Literal->new( value => abs($operands[0]->value), $operands[0]->construct_args ); |
784
|
|
|
|
|
|
|
} elsif ($func =~ /^(?:CEIL|FLOOR)$/) { |
785
|
|
|
|
|
|
|
my $v = $operands[0]->value; |
786
|
0
|
|
|
|
|
0
|
return Attean::Literal->new( value => (($func eq 'CEIL') ? ceil($v) : floor($v)), $operands[0]->construct_args ); |
787
|
|
|
|
|
|
|
} elsif ($func eq 'ROUND') { |
788
|
0
|
0
|
|
|
|
0
|
return Attean::Literal->new( value => sprintf('%.0f', (0.000000000000001 + $operands[0]->numeric_value)), $operands[0]->construct_args ); |
789
|
|
|
|
|
|
|
} elsif ($func eq 'CONCAT') { |
790
|
0
|
|
|
|
|
0
|
my $all_lang = 1; |
791
|
|
|
|
|
|
|
my $all_str = 1; |
792
|
0
|
|
|
|
|
0
|
my $lang; |
793
|
|
|
|
|
|
|
foreach my $n (@operands) { |
794
|
0
|
|
|
|
|
0
|
die "CONCAT called with a non-literal argument" unless ($n->does('Attean::API::Literal')); |
795
|
0
|
0
|
|
|
|
0
|
if ($n->datatype->value ne 'http://www.w3.org/2001/XMLSchema#string') { |
796
|
|
|
|
|
|
|
die "CONCAT called with a datatyped-literal other than xsd:string"; |
797
|
0
|
|
|
|
|
0
|
} elsif ($n->language) { |
798
|
|
|
|
|
|
|
$all_str = 0; |
799
|
0
|
|
|
|
|
0
|
if (defined($lang) and $lang ne $n->language) { |
800
|
0
|
|
|
|
|
0
|
$all_lang = 0; |
801
|
0
|
|
|
|
|
0
|
} else { |
802
|
0
|
|
|
|
|
0
|
$lang = $n->language; |
803
|
0
|
0
|
|
|
|
0
|
} |
804
|
0
|
0
|
|
|
|
0
|
} else { |
|
|
0
|
|
|
|
|
|
805
|
0
|
|
|
|
|
0
|
$all_lang = 0; |
806
|
|
|
|
|
|
|
$all_str = 0; |
807
|
0
|
|
|
|
|
0
|
} |
808
|
0
|
0
|
0
|
|
|
0
|
} |
809
|
0
|
|
|
|
|
0
|
my %strtype; |
810
|
|
|
|
|
|
|
if ($all_lang and $lang) { |
811
|
0
|
|
|
|
|
0
|
$strtype{language} = $lang; |
812
|
|
|
|
|
|
|
} elsif ($all_str) { |
813
|
|
|
|
|
|
|
$strtype{datatype} = 'http://www.w3.org/2001/XMLSchema#string' |
814
|
0
|
|
|
|
|
0
|
} |
815
|
0
|
|
|
|
|
0
|
return Attean::Literal->new( value => join('', map { $_->value } @operands), %strtype ); |
816
|
|
|
|
|
|
|
} elsif ($func eq 'SUBSTR') { |
817
|
|
|
|
|
|
|
my $str = shift(@operands); |
818
|
0
|
|
|
|
|
0
|
my @args = map { $_->numeric_value } @operands; |
819
|
0
|
0
|
0
|
|
|
0
|
my $v = scalar(@args == 1) ? substr($str->value, $args[0]-1) : substr($str->value, $args[0]-1, $args[1]); |
|
|
0
|
|
|
|
|
|
820
|
0
|
|
|
|
|
0
|
return Attean::Literal->new( value => $v, $str->construct_args ); |
821
|
|
|
|
|
|
|
} elsif ($func eq 'STRLEN') { |
822
|
0
|
|
|
|
|
0
|
return Attean::Literal->integer(length($operands[0]->value)); |
823
|
|
|
|
|
|
|
} elsif ($func eq 'REPLACE') { |
824
|
0
|
|
|
|
|
0
|
my ($node, $pat, $rep) = @operands; |
|
0
|
|
|
|
|
0
|
|
825
|
|
|
|
|
|
|
die "TypeError: REPLACE called without a literal arg1 term" unless (blessed($node) and $node->does('Attean::API::Literal')); |
826
|
0
|
|
|
|
|
0
|
die "TypeError: REPLACE called without a literal arg2 term" unless (blessed($pat) and $pat->does('Attean::API::Literal')); |
827
|
0
|
|
|
|
|
0
|
die "TypeError: REPLACE called without a literal arg3 term" unless (blessed($rep) and $rep->does('Attean::API::Literal')); |
|
0
|
|
|
|
|
0
|
|
828
|
0
|
0
|
|
|
|
0
|
die "TypeError: REPLACE called with a datatyped (non-xsd:string) literal" if ($node->datatype and $node->datatype->value ne 'http://www.w3.org/2001/XMLSchema#string'); |
829
|
0
|
|
|
|
|
0
|
my ($value, $pattern, $replace) = map { $_->value } @operands; |
830
|
|
|
|
|
|
|
die "EvaluationError: REPLACE called with unsafe ?{} match pattern" if (index($pattern, '(?{') != -1 or index($pattern, '(??{') != -1); |
831
|
0
|
|
|
|
|
0
|
die "EvaluationError: REPLACE called with unsafe ?{} replace pattern" if (index($replace, '(?{') != -1 or index($replace, '(??{') != -1); |
832
|
|
|
|
|
|
|
|
833
|
0
|
|
|
|
|
0
|
$replace =~ s/\\/\\\\/g; |
834
|
0
|
0
|
0
|
|
|
0
|
$replace =~ s/\$(\d+)/\$$1/g; |
835
|
0
|
0
|
0
|
|
|
0
|
$replace =~ s/"/\\"/g; |
836
|
0
|
0
|
0
|
|
|
0
|
$replace = qq["$replace"]; |
837
|
0
|
0
|
0
|
|
|
0
|
no warnings 'uninitialized'; |
838
|
0
|
|
|
|
|
0
|
$value =~ s/$pattern/"$replace"/eeg; |
|
0
|
|
|
|
|
0
|
|
839
|
0
|
0
|
0
|
|
|
0
|
return Attean::Literal->new(value => $value, $node->construct_args); |
840
|
0
|
0
|
0
|
|
|
0
|
} elsif ($func =~ /^[UL]CASE$/) { |
841
|
|
|
|
|
|
|
return Attean::Literal->new( value => ($func eq 'UCASE' ? uc($operands[0]->value) : lc($operands[0]->value) ), $operands[0]->construct_args ); |
842
|
0
|
|
|
|
|
0
|
} elsif ($func eq 'ENCODE_FOR_URI') { |
843
|
0
|
|
|
|
|
0
|
return Attean::Literal->new( uri_escape_utf8($operands[0]->value) ); |
844
|
0
|
|
|
|
|
0
|
} elsif ($func eq 'CONTAINS') { |
845
|
0
|
|
|
|
|
0
|
my ($node, $pat) = @operands; |
846
|
3
|
|
|
3
|
|
9209
|
my ($lit, $plit) = map { $_->value } @operands; |
|
3
|
|
|
|
|
7
|
|
|
3
|
|
|
|
|
8799
|
|
847
|
0
|
|
|
|
|
0
|
die "TypeError: CONTAINS" if ($node->language and $pat->language and $node->language ne $pat->language); |
|
0
|
|
|
|
|
0
|
|
848
|
0
|
|
|
|
|
0
|
return (index($lit, $plit) >= 0) ? $true : $false; |
849
|
|
|
|
|
|
|
} elsif ($func eq 'STRSTARTS' or $func eq 'STRENDS') { |
850
|
0
|
0
|
|
|
|
0
|
my ($lit, $plit) = map { $_->value } @operands; |
851
|
|
|
|
|
|
|
if ($func eq 'STRENDS') { |
852
|
0
|
|
|
|
|
0
|
my $pos = length($lit) - length($plit); |
853
|
|
|
|
|
|
|
return (rindex($lit, $plit) == $pos) ? $true : $false; |
854
|
0
|
|
|
|
|
0
|
} else { |
855
|
0
|
|
|
|
|
0
|
return (index($lit, $plit) == 0) ? $true : $false; |
|
0
|
|
|
|
|
0
|
|
856
|
0
|
0
|
0
|
|
|
0
|
} |
|
|
|
0
|
|
|
|
|
857
|
0
|
0
|
|
|
|
0
|
} elsif ($func eq 'STRBEFORE' or $func eq 'STRAFTER') { |
858
|
|
|
|
|
|
|
my ($node, $substr) = @operands; |
859
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
860
|
0
|
0
|
|
|
|
0
|
die "$func called without a literal arg1 term" unless (blessed($node) and $node->does('Attean::API::Literal')); |
861
|
0
|
|
|
|
|
0
|
die "$func called without a literal arg2 term" unless (blessed($substr) and $substr->does('Attean::API::Literal')); |
862
|
0
|
0
|
|
|
|
0
|
die "$func called with a datatyped (non-xsd:string) literal" if ($node->datatype and $node->datatype->value ne 'http://www.w3.org/2001/XMLSchema#string'); |
863
|
|
|
|
|
|
|
|
864
|
0
|
0
|
|
|
|
0
|
my $lhs_simple = (not($node->language) and ($node->datatype->value eq 'http://www.w3.org/2001/XMLSchema#string')); |
865
|
|
|
|
|
|
|
my $rhs_simple = (not($substr->language) and ($substr->datatype->value eq 'http://www.w3.org/2001/XMLSchema#string')); |
866
|
|
|
|
|
|
|
if ($lhs_simple and $rhs_simple) { |
867
|
0
|
|
|
|
|
0
|
# ok |
868
|
|
|
|
|
|
|
} elsif ($node->language and $substr->language and $node->language eq $substr->language) { |
869
|
0
|
0
|
0
|
|
|
0
|
# ok |
870
|
0
|
0
|
0
|
|
|
0
|
} elsif ($node->language and $rhs_simple) { |
871
|
0
|
0
|
0
|
|
|
0
|
# ok |
872
|
|
|
|
|
|
|
} else { |
873
|
0
|
|
0
|
|
|
0
|
die "$func called with literals that are not argument compatible"; |
874
|
0
|
|
0
|
|
|
0
|
} |
875
|
0
|
0
|
0
|
|
|
0
|
|
|
|
0
|
0
|
|
|
|
|
|
|
0
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
876
|
|
|
|
|
|
|
my $value = $node->value; |
877
|
|
|
|
|
|
|
my $match = $substr->value; |
878
|
|
|
|
|
|
|
my $i = index($value, $match, 0); |
879
|
|
|
|
|
|
|
if ($i < 0) { |
880
|
|
|
|
|
|
|
return Attean::Literal->new(''); |
881
|
|
|
|
|
|
|
} else { |
882
|
0
|
|
|
|
|
0
|
if ($func eq 'STRBEFORE') { |
883
|
|
|
|
|
|
|
return Attean::Literal->new(value => substr($value, 0, $i), $node->construct_args); |
884
|
|
|
|
|
|
|
} else { |
885
|
0
|
|
|
|
|
0
|
return Attean::Literal->new(value => substr($value, $i+length($match)), $node->construct_args); |
886
|
0
|
|
|
|
|
0
|
} |
887
|
0
|
|
|
|
|
0
|
} |
888
|
0
|
0
|
|
|
|
0
|
} elsif ($func =~ /^(?:YEAR|MONTH|DAY|HOURS|MINUTES)$/) { |
889
|
0
|
|
|
|
|
0
|
my $method = lc($func =~ s/^(HOUR|MINUTE)S$/$1/r); |
890
|
|
|
|
|
|
|
my $dt = $operands[0]->datetime; |
891
|
0
|
0
|
|
|
|
0
|
return Attean::Literal->integer($dt->$method()); |
892
|
0
|
|
|
|
|
0
|
} elsif ($func eq 'SECONDS') { |
893
|
|
|
|
|
|
|
my $dt = $operands[0]->datetime; |
894
|
0
|
|
|
|
|
0
|
return Attean::Literal->decimal($dt->second()); |
895
|
|
|
|
|
|
|
} elsif ($func eq 'TZ' or $func eq 'TIMEZONE') { |
896
|
|
|
|
|
|
|
my $dt = $operands[0]->datetime; |
897
|
|
|
|
|
|
|
my $tz = $dt->time_zone; |
898
|
0
|
|
|
|
|
0
|
if ($tz->is_floating) { |
899
|
0
|
|
|
|
|
0
|
return Attean::Literal->new('') if ($func eq 'TZ'); |
900
|
0
|
|
|
|
|
0
|
die "TIMEZONE called with a dateTime without a timezone"; |
901
|
|
|
|
|
|
|
} |
902
|
0
|
|
|
|
|
0
|
return Attean::Literal->new('Z') if ($func eq 'TZ' and $tz->is_utc); |
903
|
0
|
|
|
|
|
0
|
if ($tz) { |
904
|
|
|
|
|
|
|
my $offset = $tz->offset_for_datetime( $dt ); |
905
|
0
|
|
|
|
|
0
|
my $hours = 0; |
906
|
0
|
|
|
|
|
0
|
my $minutes = 0; |
907
|
0
|
0
|
|
|
|
0
|
my $minus = ($func eq 'TZ') ? '+' : ''; |
908
|
0
|
0
|
|
|
|
0
|
if ($offset < 0) { |
909
|
0
|
|
|
|
|
0
|
$minus = '-'; |
910
|
|
|
|
|
|
|
$offset = -$offset; |
911
|
0
|
0
|
0
|
|
|
0
|
} |
912
|
0
|
0
|
|
|
|
0
|
|
913
|
0
|
|
|
|
|
0
|
my $duration = "${minus}PT"; |
914
|
0
|
|
|
|
|
0
|
if ($offset >= 60*60) { |
915
|
0
|
|
|
|
|
0
|
my $h = int($offset / (60*60)); |
916
|
0
|
0
|
|
|
|
0
|
$duration .= "${h}H" if ($h > 0); |
917
|
0
|
0
|
|
|
|
0
|
$hours = int($offset / (60*60)); |
918
|
0
|
|
|
|
|
0
|
$offset = $offset % (60*60); |
919
|
0
|
|
|
|
|
0
|
} |
920
|
|
|
|
|
|
|
if ($offset >= 60) { |
921
|
|
|
|
|
|
|
my $m = int($offset / 60); |
922
|
0
|
|
|
|
|
0
|
$duration .= "${m}M" if ($m > 0); |
923
|
0
|
0
|
|
|
|
0
|
$minutes = int($offset / 60); |
924
|
0
|
|
|
|
|
0
|
$offset = $offset % 60; |
925
|
0
|
0
|
|
|
|
0
|
} |
926
|
0
|
|
|
|
|
0
|
my $seconds = int($offset); |
927
|
0
|
|
|
|
|
0
|
my $s = int($offset); |
928
|
|
|
|
|
|
|
$duration .= "${s}S" if ($s > 0 or $duration eq 'PT'); |
929
|
0
|
0
|
|
|
|
0
|
|
930
|
0
|
|
|
|
|
0
|
return ($func eq 'TZ') |
931
|
0
|
0
|
|
|
|
0
|
? Attean::Literal->new(sprintf('%s%02d:%02d', $minus, $hours, $minutes)) |
932
|
0
|
|
|
|
|
0
|
: Attean::Literal->new( value => $duration, datatype => "http://www.w3.org/2001/XMLSchema#dayTimeDuration"); |
933
|
0
|
|
|
|
|
0
|
} else { |
934
|
|
|
|
|
|
|
return Attean::Literal->new('') if ($func eq 'TZ'); |
935
|
0
|
|
|
|
|
0
|
die "TIMEZONE called without a valid dateTime"; |
936
|
0
|
|
|
|
|
0
|
} |
937
|
0
|
0
|
0
|
|
|
0
|
} elsif ($func eq 'NOW') { |
938
|
|
|
|
|
|
|
my $value = DateTime::Format::W3CDTF->new->format_datetime( DateTime->now ); |
939
|
0
|
0
|
|
|
|
0
|
return Attean::Literal->new( value => $value, datatype => 'http://www.w3.org/2001/XMLSchema#dateTime' ); |
940
|
|
|
|
|
|
|
} elsif ($func =~ /^(?:STR)?UUID$/) { |
941
|
|
|
|
|
|
|
return Attean::Literal->new(uc(uuid_to_string(create_uuid()))) if ($func eq 'STRUUID'); |
942
|
|
|
|
|
|
|
return Attean::IRI->new('urn:uuid:' . uc(uuid_to_string(create_uuid()))); |
943
|
0
|
0
|
|
|
|
0
|
} elsif ($func =~ /^(MD5|SHA1|SHA256|SHA384|SHA512)$/) { |
944
|
0
|
|
|
|
|
0
|
my $hash = $func =~ s/SHA/SHA-/r; |
945
|
|
|
|
|
|
|
my $digest = eval { Digest->new($hash)->add(encode('UTF-8', $operands[0]->value, Encode::FB_CROAK))->hexdigest }; |
946
|
|
|
|
|
|
|
return Attean::Literal->new($digest); |
947
|
0
|
|
|
|
|
0
|
} elsif ($func eq 'STRLANG') { |
948
|
0
|
|
|
|
|
0
|
my ($str, $lang) = @operands; |
949
|
|
|
|
|
|
|
my @values = map { $_->value } @operands; |
950
|
0
|
0
|
|
|
|
0
|
die "TypeError: STRLANG must be called with two plain literals" unless (blessed($str) and $str->does('Attean::API::Literal') and blessed($lang) and $lang->does('Attean::API::Literal')); |
951
|
0
|
|
|
|
|
0
|
die "TypeError: STRLANG not called with a simple literal" unless ($str->datatype->value eq 'http://www.w3.org/2001/XMLSchema#string' and not($str->language)); |
952
|
|
|
|
|
|
|
return Attean::Literal->new( value => $values[0], language => $values[1] ); |
953
|
0
|
|
|
|
|
0
|
} elsif ($func eq 'STRDT') { |
954
|
0
|
|
|
|
|
0
|
die "TypeError: STRDT" unless ($operands[0]->does('Attean::API::Literal') and not($operands[0]->language)); |
|
0
|
|
|
|
|
0
|
|
955
|
0
|
|
|
|
|
0
|
if (my $dt = $operands[0]->datatype) { |
956
|
|
|
|
|
|
|
die "TypeError: STRDT" unless ($dt->value eq 'http://www.w3.org/2001/XMLSchema#string'); |
957
|
0
|
|
|
|
|
0
|
} |
958
|
0
|
|
|
|
|
0
|
die "TypeError: STRDT" unless ($operands[1]->does('Attean::API::IRI')); |
|
0
|
|
|
|
|
0
|
|
959
|
0
|
0
|
0
|
|
|
0
|
my @values = map { $_->value } @operands; |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
960
|
0
|
0
|
0
|
|
|
0
|
return Attean::Literal->new( value => $values[0], datatype => $values[1] ); |
961
|
0
|
|
|
|
|
0
|
} elsif ($func eq 'SAMETERM') { |
962
|
|
|
|
|
|
|
my ($a, $b) = @operands; |
963
|
0
|
0
|
0
|
|
|
0
|
die "TypeError: SAMETERM" unless (blessed($operands[0]) and blessed($operands[1])); |
964
|
0
|
0
|
|
|
|
0
|
if ($a->compare($b)) { |
965
|
0
|
0
|
|
|
|
0
|
return $false; |
966
|
|
|
|
|
|
|
} |
967
|
0
|
0
|
|
|
|
0
|
if ($a->does('Attean::API::Binding')) { |
968
|
0
|
|
|
|
|
0
|
my $ok = ($a->sameTerms($b)); |
|
0
|
|
|
|
|
0
|
|
969
|
0
|
|
|
|
|
0
|
return $ok ? $true : $false; |
970
|
|
|
|
|
|
|
} else { |
971
|
0
|
|
|
|
|
0
|
my $ok = ($a->value eq $b->value); |
972
|
0
|
0
|
0
|
|
|
0
|
return $ok ? $true : $false; |
973
|
0
|
0
|
|
|
|
0
|
} |
974
|
0
|
|
|
|
|
0
|
} elsif ($func =~ /^IS([UI]RI|BLANK|LITERAL|NUMERIC|TRIPLE)$/) { |
975
|
|
|
|
|
|
|
return $operands[0]->does("Attean::API::$type_roles{$1}") ? $true : $false; |
976
|
0
|
0
|
|
|
|
0
|
} elsif ($func eq 'REGEX') { |
977
|
0
|
|
|
|
|
0
|
my ($value, $pattern) = map { $_->value } @operands; |
978
|
0
|
0
|
|
|
|
0
|
return ($value =~ /$pattern/) ? $true : $false; |
979
|
|
|
|
|
|
|
} elsif ($func eq 'INVOKE') { |
980
|
0
|
|
|
|
|
0
|
my $furi = shift(@operands)->value; |
981
|
0
|
0
|
|
|
|
0
|
my $func = Attean->get_global_function($furi); |
982
|
|
|
|
|
|
|
unless (ref($func)) { |
983
|
|
|
|
|
|
|
die "No extension registered for <$furi>"; |
984
|
0
|
0
|
|
|
|
0
|
} |
985
|
|
|
|
|
|
|
return $func->(@operands); |
986
|
0
|
|
|
|
|
0
|
} |
|
0
|
|
|
|
|
0
|
|
987
|
0
|
0
|
|
|
|
0
|
die "Unimplemented FunctionExpression evaluation: " . $expr->operator; |
988
|
|
|
|
|
|
|
}; |
989
|
0
|
|
|
|
|
0
|
} elsif ($expr->isa('Attean::AggregateExpression')) { |
990
|
0
|
|
|
|
|
0
|
my $agg = $expr->operator; |
991
|
0
|
0
|
|
|
|
0
|
my ($child) = @{ $expr->children }; |
992
|
0
|
|
|
|
|
0
|
if ($agg eq 'COUNT') { |
993
|
|
|
|
|
|
|
if ($child) { |
994
|
0
|
|
|
|
|
0
|
my $impl = $self->impl($child, $active_graph); |
995
|
|
|
|
|
|
|
return sub { |
996
|
0
|
|
|
|
|
0
|
my ($rows, %args) = @_; |
997
|
0
|
|
|
|
|
0
|
my @terms = grep { blessed($_) } map { $impl->($_, %args) } @{ $rows }; |
998
|
|
|
|
|
|
|
if ($expr->distinct) { |
999
|
0
|
|
|
|
|
0
|
my %seen; |
1000
|
0
|
|
|
|
|
0
|
@terms = grep { not($seen{$_->as_string}++) } @terms; |
|
0
|
|
|
|
|
0
|
|
1001
|
0
|
0
|
|
|
|
0
|
} |
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1002
|
0
|
0
|
|
|
|
0
|
return Attean::Literal->integer(scalar(@terms)); |
1003
|
0
|
|
|
|
|
0
|
}; |
1004
|
|
|
|
|
|
|
} else { |
1005
|
0
|
|
|
0
|
|
0
|
return sub { |
1006
|
0
|
|
|
|
|
0
|
my ($rows, %args) = @_; |
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
|
0
|
|
|
|
|
0
|
|
1007
|
0
|
0
|
|
|
|
0
|
return Attean::Literal->integer(scalar(@$rows)); |
1008
|
0
|
|
|
|
|
0
|
}; |
1009
|
0
|
|
|
|
|
0
|
} |
|
0
|
|
|
|
|
0
|
|
1010
|
|
|
|
|
|
|
} elsif ($agg =~ /^(?:SAMPLE|MIN|MAX|SUM|AVG|GROUP_CONCAT)$/) { |
1011
|
0
|
|
|
|
|
0
|
my $impl = $self->impl($child, $active_graph); |
1012
|
0
|
|
|
|
|
0
|
if ($agg eq 'SAMPLE') { |
1013
|
|
|
|
|
|
|
return sub { |
1014
|
|
|
|
|
|
|
my ($rows, %args) = @_; |
1015
|
0
|
|
|
0
|
|
0
|
return $impl->( shift(@$rows), %args ) |
1016
|
0
|
|
|
|
|
0
|
}; |
1017
|
0
|
|
|
|
|
0
|
} elsif ($agg eq 'MIN' or $agg eq 'MAX') { |
1018
|
|
|
|
|
|
|
my $expect = ($agg eq 'MIN') ? 1 : -1; |
1019
|
|
|
|
|
|
|
return sub { |
1020
|
0
|
|
|
|
|
0
|
my ($rows, %args) = @_; |
1021
|
0
|
0
|
0
|
|
|
0
|
my $extrema; |
|
|
0
|
0
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
1022
|
|
|
|
|
|
|
foreach my $r (@$rows) { |
1023
|
0
|
|
|
0
|
|
0
|
my $t = $impl->( $r, %args ); |
1024
|
0
|
|
|
|
|
0
|
return if (not($t) and $agg eq 'MIN'); # unbound is always minimal |
1025
|
0
|
|
|
|
|
0
|
next if (not($t)); # unbound need not be considered for MAX |
1026
|
|
|
|
|
|
|
$extrema = $t if (not($extrema) or $extrema->compare($t) == $expect); |
1027
|
0
|
0
|
|
|
|
0
|
} |
1028
|
|
|
|
|
|
|
return $extrema; |
1029
|
0
|
|
|
0
|
|
0
|
}; |
1030
|
0
|
|
|
|
|
0
|
} elsif ($agg eq 'SUM' or $agg eq 'AVG') { |
1031
|
0
|
|
|
|
|
0
|
return sub { |
1032
|
0
|
|
|
|
|
0
|
my ($rows, %args) = @_; |
1033
|
0
|
0
|
0
|
|
|
0
|
my $count = 0; |
1034
|
0
|
0
|
|
|
|
0
|
my $sum = Attean::Literal->integer(0); |
1035
|
0
|
0
|
0
|
|
|
0
|
my %seen; |
1036
|
|
|
|
|
|
|
foreach my $r (@$rows) { |
1037
|
0
|
|
|
|
|
0
|
my $term = $impl->( $r, %args ); |
1038
|
0
|
|
|
|
|
0
|
if ($expr->distinct) { |
1039
|
|
|
|
|
|
|
next if ($seen{ $term->as_string }++); |
1040
|
|
|
|
|
|
|
} |
1041
|
0
|
|
|
0
|
|
0
|
if ($term->does('Attean::API::NumericLiteral')) { |
1042
|
0
|
|
|
|
|
0
|
$count++; |
1043
|
0
|
|
|
|
|
0
|
$sum = Attean::Literal->new( value => ($sum->numeric_value + $term->numeric_value), datatype => $sum->binary_promotion_type($term, '+') ); |
1044
|
0
|
|
|
|
|
0
|
} else { |
1045
|
0
|
|
|
|
|
0
|
die "TypeError: AVG"; |
1046
|
0
|
|
|
|
|
0
|
} |
1047
|
0
|
0
|
|
|
|
0
|
} |
1048
|
0
|
0
|
|
|
|
0
|
if ($agg eq 'AVG') { |
1049
|
|
|
|
|
|
|
$sum = not($count) ? undef : Attean::Literal->new( value => ($sum->numeric_value / $count), datatype => $sum->binary_promotion_type(Attean::Literal->integer($count), '/') ); |
1050
|
0
|
0
|
|
|
|
0
|
} |
1051
|
0
|
|
|
|
|
0
|
return $sum; |
1052
|
0
|
|
|
|
|
0
|
}; |
1053
|
|
|
|
|
|
|
} elsif ($agg eq 'GROUP_CONCAT') { |
1054
|
0
|
|
|
|
|
0
|
my $sep = $expr->scalar_vars->{ 'seperator' } // ' '; |
1055
|
|
|
|
|
|
|
return sub { |
1056
|
|
|
|
|
|
|
my ($rows, %args) = @_; |
1057
|
0
|
0
|
|
|
|
0
|
my %seen; |
1058
|
0
|
0
|
|
|
|
0
|
my @strings; |
1059
|
|
|
|
|
|
|
foreach my $r (@$rows) { |
1060
|
0
|
|
|
|
|
0
|
my $term = eval { $impl->( $r, %args ) }; |
1061
|
0
|
|
|
|
|
0
|
if ($expr->distinct) { |
1062
|
|
|
|
|
|
|
next if ($seen{ blessed($term) ? $term->as_string : '' }++); |
1063
|
0
|
|
0
|
|
|
0
|
} |
1064
|
|
|
|
|
|
|
push(@strings, $term->value // ''); |
1065
|
0
|
|
|
0
|
|
0
|
} |
1066
|
0
|
|
|
|
|
0
|
return Attean::Literal->new(join($sep, sort @strings)); |
1067
|
|
|
|
|
|
|
}; |
1068
|
0
|
|
|
|
|
0
|
} |
1069
|
0
|
|
|
|
|
0
|
} elsif ($agg eq 'CUSTOM') { |
|
0
|
|
|
|
|
0
|
|
1070
|
0
|
0
|
|
|
|
0
|
my $iri = $expr->custom_iri; |
1071
|
0
|
0
|
|
|
|
0
|
my $data = Attean->get_global_aggregate($iri); |
|
|
0
|
|
|
|
|
|
1072
|
|
|
|
|
|
|
unless ($data) { |
1073
|
0
|
|
0
|
|
|
0
|
die "No extension aggregate registered for <$iri>"; |
1074
|
|
|
|
|
|
|
} |
1075
|
0
|
|
|
|
|
0
|
my $start = $data->{'start'}; |
1076
|
0
|
|
|
|
|
0
|
my $process = $data->{'process'}; |
1077
|
|
|
|
|
|
|
my $finalize = $data->{'finalize'}; |
1078
|
|
|
|
|
|
|
|
1079
|
0
|
|
|
|
|
0
|
my $impl = $self->impl($child, $active_graph); |
1080
|
0
|
|
|
|
|
0
|
return sub { |
1081
|
0
|
0
|
|
|
|
0
|
my ($rows, %args) = @_; |
1082
|
0
|
|
|
|
|
0
|
my $thunk = $start->(); |
1083
|
|
|
|
|
|
|
foreach my $r (@$rows) { |
1084
|
0
|
|
|
|
|
0
|
my $t = $impl->( $r, %args ); |
1085
|
0
|
|
|
|
|
0
|
$process->($thunk, $t); |
1086
|
0
|
|
|
|
|
0
|
} |
1087
|
|
|
|
|
|
|
return $finalize->($thunk); |
1088
|
0
|
|
|
|
|
0
|
}; |
1089
|
|
|
|
|
|
|
} |
1090
|
0
|
|
|
0
|
|
0
|
die "Unimplemented AggregateExpression evaluation: " . $expr->operator; |
1091
|
0
|
|
|
|
|
0
|
} elsif ($expr->isa('Attean::CastExpression')) { |
1092
|
0
|
|
|
|
|
0
|
my ($child) = @{ $expr->children }; |
1093
|
0
|
|
|
|
|
0
|
my $impl = $self->impl( $child, $active_graph ); |
1094
|
0
|
|
|
|
|
0
|
my $type = $expr->datatype; |
1095
|
|
|
|
|
|
|
return sub { |
1096
|
0
|
|
|
|
|
0
|
my ($r, %args) = @_; |
1097
|
0
|
|
|
|
|
0
|
my $term = $impl->($r, %args); |
1098
|
|
|
|
|
|
|
# TODO: reformat syntax for xsd:double |
1099
|
0
|
|
|
|
|
0
|
my $cast = Attean::Literal->new( value => $term->value, datatype => $type ); |
1100
|
|
|
|
|
|
|
return $cast->canonicalized_term if ($cast->does('Attean::API::CanonicalizingLiteral')); |
1101
|
4
|
|
|
|
|
6
|
return $cast; |
|
4
|
|
|
|
|
38
|
|
1102
|
4
|
|
|
|
|
19
|
} |
1103
|
4
|
|
|
|
|
11
|
} else { |
1104
|
|
|
|
|
|
|
Carp::confess "No impl for expression " . $expr->as_string; |
1105
|
4
|
|
|
4
|
|
10
|
} |
1106
|
4
|
|
|
|
|
11
|
} |
1107
|
|
|
|
|
|
|
} |
1108
|
4
|
|
|
|
|
83
|
|
1109
|
4
|
50
|
|
|
|
327
|
1; |
1110
|
0
|
|
|
|
|
|
|
1111
|
|
|
|
|
|
|
|
1112
|
4
|
|
|
|
|
17
|
=back |
1113
|
0
|
|
|
|
|
|
|
1114
|
|
|
|
|
|
|
=head1 BUGS |
1115
|
|
|
|
|
|
|
|
1116
|
|
|
|
|
|
|
Please report any bugs or feature requests to through the GitHub web interface |
1117
|
|
|
|
|
|
|
at L<https://github.com/kasei/attean/issues>. |
1118
|
|
|
|
|
|
|
|
1119
|
|
|
|
|
|
|
=head1 SEE ALSO |
1120
|
|
|
|
|
|
|
|
1121
|
|
|
|
|
|
|
|
1122
|
|
|
|
|
|
|
|
1123
|
|
|
|
|
|
|
=head1 AUTHOR |
1124
|
|
|
|
|
|
|
|
1125
|
|
|
|
|
|
|
Gregory Todd Williams C<< <gwilliams@cpan.org> >> |
1126
|
|
|
|
|
|
|
|
1127
|
|
|
|
|
|
|
=head1 COPYRIGHT |
1128
|
|
|
|
|
|
|
|
1129
|
|
|
|
|
|
|
Copyright (c) 2014--2022 Gregory Todd Williams. |
1130
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it under |
1131
|
|
|
|
|
|
|
the same terms as Perl itself. |
1132
|
|
|
|
|
|
|
|
1133
|
|
|
|
|
|
|
=cut |