| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
50
|
|
|
50
|
|
25714
|
use v5.14; |
|
|
50
|
|
|
|
|
172
|
|
|
2
|
50
|
|
|
50
|
|
274
|
use warnings; |
|
|
50
|
|
|
|
|
129
|
|
|
|
50
|
|
|
|
|
1890
|
|
|
3
|
|
|
|
|
|
|
|
|
4
|
|
|
|
|
|
|
=head1 NAME |
|
5
|
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
Attean::QueryPlanner - Query planner |
|
7
|
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
=head1 VERSION |
|
9
|
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
This document describes Attean::QueryPlanner version 0.032 |
|
11
|
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
13
|
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
use v5.14; |
|
15
|
|
|
|
|
|
|
use Attean; |
|
16
|
|
|
|
|
|
|
my $planner = Attean::QueryPlanner->new(); |
|
17
|
|
|
|
|
|
|
my $default_graphs = [ Attean::IRI->new('http://example.org/') ]; |
|
18
|
|
|
|
|
|
|
my $plan = $planner->plan_for_algebra( $algebra, $model, $default_graphs ); |
|
19
|
|
|
|
|
|
|
my $iter = $plan->evaluate($model); |
|
20
|
|
|
|
|
|
|
while (my $result = $iter->next()) { |
|
21
|
|
|
|
|
|
|
say $result->as_string; |
|
22
|
|
|
|
|
|
|
} |
|
23
|
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
25
|
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
The Attean::QueryPlanner class is a base class implementing common behavior for |
|
27
|
|
|
|
|
|
|
query planners. Subclasses will need to consume or compose the |
|
28
|
|
|
|
|
|
|
L<Attean::API::JoinPlanner> role. |
|
29
|
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
Trivial sub-classes may consume L<Attean::API::NaiveJoinPlanner>, while more |
|
31
|
|
|
|
|
|
|
complex planners may choose to implement complex join planning (e.g. |
|
32
|
|
|
|
|
|
|
L<Attean::IDPQueryPlanner>). |
|
33
|
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
=head1 ATTRIBUTES |
|
35
|
|
|
|
|
|
|
|
|
36
|
|
|
|
|
|
|
=over 4 |
|
37
|
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
=cut |
|
39
|
|
|
|
|
|
|
|
|
40
|
50
|
|
|
50
|
|
22716
|
use Attean::Algebra; |
|
|
50
|
|
|
|
|
221
|
|
|
|
50
|
|
|
|
|
2653
|
|
|
41
|
50
|
|
|
50
|
|
31789
|
use Attean::Plan; |
|
|
50
|
|
|
|
|
226
|
|
|
|
50
|
|
|
|
|
2594
|
|
|
42
|
50
|
|
|
50
|
|
22749
|
use Attean::Expression; |
|
|
50
|
|
|
|
|
206
|
|
|
|
50
|
|
|
|
|
2538
|
|
|
43
|
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
use Moo; |
|
45
|
50
|
|
|
50
|
|
389
|
use Encode qw(encode); |
|
|
50
|
|
|
|
|
111
|
|
|
|
50
|
|
|
|
|
291
|
|
|
46
|
50
|
|
|
50
|
|
15540
|
use Attean::RDF; |
|
|
50
|
|
|
|
|
115
|
|
|
|
50
|
|
|
|
|
2538
|
|
|
47
|
50
|
|
|
50
|
|
296
|
use LWP::UserAgent; |
|
|
50
|
|
|
|
|
106
|
|
|
|
50
|
|
|
|
|
442
|
|
|
48
|
50
|
|
|
50
|
|
38518
|
use Scalar::Util qw(blessed reftype); |
|
|
50
|
|
|
|
|
128
|
|
|
|
50
|
|
|
|
|
1169
|
|
|
49
|
50
|
|
|
50
|
|
248
|
use List::Util qw(reduce); |
|
|
50
|
|
|
|
|
115
|
|
|
|
50
|
|
|
|
|
2178
|
|
|
50
|
50
|
|
|
50
|
|
288
|
use List::MoreUtils qw(all any); |
|
|
50
|
|
|
|
|
106
|
|
|
|
50
|
|
|
|
|
2548
|
|
|
51
|
50
|
|
|
50
|
|
283
|
use Types::Standard qw(Int ConsumerOf InstanceOf); |
|
|
50
|
|
|
|
|
106
|
|
|
|
50
|
|
|
|
|
380
|
|
|
52
|
50
|
|
|
50
|
|
48817
|
use URI::Escape; |
|
|
50
|
|
|
|
|
117
|
|
|
|
50
|
|
|
|
|
337
|
|
|
53
|
50
|
|
|
50
|
|
32632
|
use Algorithm::Combinatorics qw(subsets); |
|
|
50
|
|
|
|
|
106
|
|
|
|
50
|
|
|
|
|
2653
|
|
|
54
|
50
|
|
|
50
|
|
316
|
use List::Util qw(min); |
|
|
50
|
|
|
|
|
451
|
|
|
|
50
|
|
|
|
|
1815
|
|
|
55
|
50
|
|
|
50
|
|
257
|
use Math::Cartesian::Product; |
|
|
50
|
|
|
|
|
111
|
|
|
|
50
|
|
|
|
|
1711
|
|
|
56
|
50
|
|
|
50
|
|
276
|
use namespace::clean; |
|
|
50
|
|
|
|
|
97
|
|
|
|
50
|
|
|
|
|
1773
|
|
|
57
|
50
|
|
|
50
|
|
267
|
|
|
|
50
|
|
|
|
|
113
|
|
|
|
50
|
|
|
|
|
386
|
|
|
58
|
|
|
|
|
|
|
with 'Attean::API::QueryPlanner', 'MooX::Log::Any'; |
|
59
|
|
|
|
|
|
|
has 'counter' => (is => 'rw', isa => Int, default => 0); |
|
60
|
|
|
|
|
|
|
has 'table_threshold' => (is => 'rw', isa => Int, default => 10); |
|
61
|
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
=back |
|
63
|
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
=head1 METHODS |
|
65
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
=over 4 |
|
67
|
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
=item C<< new_temporary( $type ) >> |
|
69
|
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
Returns a new unique (in the context of the query planner) ID string that may |
|
71
|
|
|
|
|
|
|
be used for things like fresh (temporary) variables. The C<< $type >> string is |
|
72
|
|
|
|
|
|
|
used in the generated name to aid in identifying different uses for the names. |
|
73
|
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
=cut |
|
75
|
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
my $self = shift; |
|
77
|
|
|
|
|
|
|
my $type = shift; |
|
78
|
15
|
|
|
15
|
1
|
30
|
my $c = $self->counter; |
|
79
|
15
|
|
|
|
|
30
|
$self->counter($c+1); |
|
80
|
15
|
|
|
|
|
293
|
return sprintf('.%s-%d', $type, $c); |
|
81
|
15
|
|
|
|
|
289
|
} |
|
82
|
15
|
|
|
|
|
557
|
|
|
83
|
|
|
|
|
|
|
=item C<< plan_for_algebra( $algebra, $model, \@active_graphs, \@default_graphs ) >> |
|
84
|
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
Returns the first plan returned from C<< plans_for_algebra >>. |
|
86
|
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
=cut |
|
88
|
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
my $self = shift; |
|
90
|
|
|
|
|
|
|
my @plans = $self->plans_for_algebra(@_); |
|
91
|
|
|
|
|
|
|
return shift(@plans); |
|
92
|
26
|
|
|
26
|
1
|
2142
|
} |
|
93
|
26
|
|
|
|
|
73
|
|
|
94
|
25
|
|
|
|
|
414
|
=item C<< plans_for_algebra( $algebra, $model, \@active_graphs, \@default_graphs ) >> |
|
95
|
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
Returns L<Attean::API::Plan> objects representing alternate query plans for |
|
97
|
|
|
|
|
|
|
evaluating the query C<< $algebra >> against the C<< $model >>, using |
|
98
|
|
|
|
|
|
|
the supplied C<< $active_graph >>. |
|
99
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
=cut |
|
101
|
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
my $self = shift; |
|
103
|
|
|
|
|
|
|
my $algebra = shift; |
|
104
|
|
|
|
|
|
|
my $model = shift; |
|
105
|
|
|
|
|
|
|
my $active_graphs = shift; |
|
106
|
134
|
|
|
134
|
1
|
238
|
my $default_graphs = shift; |
|
107
|
134
|
|
|
|
|
212
|
my %args = @_; |
|
108
|
134
|
|
|
|
|
178
|
|
|
109
|
134
|
|
|
|
|
184
|
if ($model->does('Attean::API::CostPlanner')) { |
|
110
|
134
|
|
|
|
|
192
|
my @plans = $model->plans_for_algebra($algebra, $self, $active_graphs, $default_graphs, %args); |
|
111
|
134
|
|
|
|
|
256
|
if (@plans) { |
|
112
|
|
|
|
|
|
|
return @plans; # trust that the model knows better than us what plans are best |
|
113
|
134
|
50
|
|
|
|
450
|
} else { |
|
114
|
134
|
|
|
|
|
2662
|
$self->log->info("*** Model did not provide plans: $model"); |
|
115
|
134
|
100
|
|
|
|
359
|
} |
|
116
|
2
|
|
|
|
|
10
|
} |
|
117
|
|
|
|
|
|
|
|
|
118
|
132
|
|
|
|
|
2474
|
Carp::confess "No algebra passed for evaluation" unless ($algebra); |
|
119
|
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
# TODO: propagate interesting orders |
|
121
|
|
|
|
|
|
|
my $interesting = []; |
|
122
|
132
|
50
|
|
|
|
15464
|
|
|
123
|
|
|
|
|
|
|
my @children = @{ $algebra->children }; |
|
124
|
|
|
|
|
|
|
my ($child) = $children[0]; |
|
125
|
132
|
|
|
|
|
254
|
if ($algebra->isa('Attean::Algebra::Query') or $algebra->isa('Attean::Algebra::Update')) { |
|
126
|
|
|
|
|
|
|
return $self->plans_for_algebra($algebra->child, $model, $active_graphs, $default_graphs, %args); |
|
127
|
132
|
|
|
|
|
191
|
} elsif ($algebra->isa('Attean::Algebra::BGP')) { |
|
|
132
|
|
|
|
|
456
|
|
|
128
|
132
|
|
|
|
|
244
|
my $triples = $algebra->triples; |
|
129
|
132
|
100
|
66
|
|
|
2741
|
my @triples = @$triples; |
|
|
|
100
|
66
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
130
|
8
|
|
|
|
|
47
|
my %blanks; |
|
131
|
|
|
|
|
|
|
foreach my $i (0 .. $#triples) { |
|
132
|
69
|
|
|
|
|
187
|
my $t = $triples[$i]; |
|
133
|
69
|
|
|
|
|
157
|
my @nodes = $t->values; |
|
134
|
69
|
|
|
|
|
443
|
my $changed = 0; |
|
135
|
69
|
|
|
|
|
243
|
foreach (@nodes) { |
|
136
|
100
|
|
|
|
|
189
|
if ($_->does('Attean::API::Blank')) { |
|
137
|
100
|
|
|
|
|
352
|
$changed++; |
|
138
|
100
|
|
|
|
|
198
|
my $id = $_->value; |
|
139
|
100
|
|
|
|
|
175
|
unless (exists $blanks{$id}) { |
|
140
|
300
|
100
|
|
|
|
4193
|
$blanks{$id} = Attean::Variable->new(value => $self->new_temporary('blank')); |
|
141
|
6
|
|
|
|
|
91
|
} |
|
142
|
6
|
|
|
|
|
32
|
$_ = $blanks{$id}; |
|
143
|
6
|
50
|
|
|
|
23
|
} |
|
144
|
6
|
|
|
|
|
39
|
} |
|
145
|
|
|
|
|
|
|
|
|
146
|
6
|
|
|
|
|
318
|
if ($changed) { |
|
147
|
|
|
|
|
|
|
my $new = Attean::TriplePattern->new(@nodes); |
|
148
|
|
|
|
|
|
|
$triples[$i] = $new; |
|
149
|
|
|
|
|
|
|
} |
|
150
|
100
|
100
|
|
|
|
1596
|
} |
|
151
|
6
|
|
|
|
|
98
|
my $bgp = Attean::Algebra::BGP->new( triples => \@triples ); |
|
152
|
6
|
|
|
|
|
213
|
my @plans = $self->bgp_join_plans($bgp, $model, $active_graphs, $default_graphs, $interesting, map { |
|
153
|
|
|
|
|
|
|
[$self->access_plans($model, $active_graphs, $_)] |
|
154
|
|
|
|
|
|
|
} @triples); |
|
155
|
69
|
|
|
|
|
1345
|
return @plans; |
|
156
|
|
|
|
|
|
|
} elsif ($algebra->isa('Attean::Algebra::Join')) { |
|
157
|
69
|
|
|
|
|
225
|
return $self->group_join_plans($model, $active_graphs, $default_graphs, $interesting, map { |
|
|
100
|
|
|
|
|
356
|
|
|
158
|
|
|
|
|
|
|
[$self->plans_for_algebra($_, $model, $active_graphs, $default_graphs, %args)] |
|
159
|
68
|
|
|
|
|
541
|
} @children); |
|
160
|
|
|
|
|
|
|
} elsif ($algebra->isa('Attean::Algebra::Distinct') or $algebra->isa('Attean::Algebra::Reduced')) { |
|
161
|
|
|
|
|
|
|
my @plans = $self->plans_for_algebra($child, $model, $active_graphs, $default_graphs, %args); |
|
162
|
11
|
|
|
|
|
28
|
my @dist; |
|
|
22
|
|
|
|
|
177
|
|
|
163
|
|
|
|
|
|
|
foreach my $p (@plans) { |
|
164
|
|
|
|
|
|
|
if ($p->distinct) { |
|
165
|
6
|
|
|
|
|
144
|
push(@dist, $p); |
|
166
|
6
|
|
|
|
|
16
|
} else { |
|
167
|
6
|
|
|
|
|
15
|
my @vars = @{ $p->in_scope_variables }; |
|
168
|
38
|
100
|
|
|
|
4194
|
my $cmps = $p->ordered; |
|
169
|
12
|
|
|
|
|
65
|
if ($self->_comparators_are_stable_and_cover_vars($cmps, @vars)) { |
|
170
|
|
|
|
|
|
|
# the plan has a stable ordering which covers all the variables, so we can just uniq the iterator |
|
171
|
26
|
|
|
|
|
169
|
push(@dist, Attean::Plan::Unique->new(children => [$p], distinct => 1, ordered => $p->ordered)); |
|
|
26
|
|
|
|
|
81
|
|
|
172
|
26
|
|
|
|
|
55
|
} else { |
|
173
|
26
|
50
|
|
|
|
80
|
# TODO: if the plan isn't distinct, but is ordered, we can use a batched implementation |
|
174
|
|
|
|
|
|
|
push(@dist, Attean::Plan::HashDistinct->new(children => [$p], distinct => 1, ordered => $p->ordered)); |
|
175
|
0
|
|
|
|
|
0
|
} |
|
176
|
|
|
|
|
|
|
} |
|
177
|
|
|
|
|
|
|
} |
|
178
|
26
|
|
|
|
|
405
|
return @dist; |
|
179
|
|
|
|
|
|
|
} elsif ($algebra->isa('Attean::Algebra::Filter')) { |
|
180
|
|
|
|
|
|
|
# TODO: simple range relation filters can be handled differently if that filter operates on a variable that is part of the ordering |
|
181
|
|
|
|
|
|
|
my $expr = $algebra->expression; |
|
182
|
6
|
|
|
|
|
494
|
my $w = Attean::TreeRewriter->new(types => ['Attean::API::DirectedAcyclicGraph']); |
|
183
|
|
|
|
|
|
|
$w->register_pre_handler(sub { |
|
184
|
|
|
|
|
|
|
my ($t, $parent, $thunk) = @_; |
|
185
|
6
|
|
|
|
|
22
|
if ($t->isa('Attean::ExistsExpression')) { |
|
186
|
6
|
|
|
|
|
94
|
my $pattern = $t->pattern; |
|
187
|
|
|
|
|
|
|
my $plan = $self->plan_for_algebra($pattern, $model, $active_graphs, $default_graphs, @_); |
|
188
|
12
|
|
|
12
|
|
25
|
unless ($plan->does('Attean::API::BindingSubstitutionPlan')) { |
|
189
|
12
|
50
|
|
|
|
78
|
die 'Exists plan does not consume Attean::API::BindingSubstitutionPlan: ' . $plan->as_string; |
|
190
|
0
|
|
|
|
|
0
|
} |
|
191
|
0
|
|
|
|
|
0
|
my $new = Attean::ExistsPlanExpression->new( |
|
192
|
0
|
0
|
|
|
|
0
|
plan => $plan, |
|
193
|
0
|
|
|
|
|
0
|
); |
|
194
|
|
|
|
|
|
|
return (1, 0, $new); |
|
195
|
0
|
|
|
|
|
0
|
} |
|
196
|
|
|
|
|
|
|
return (0, 1, $t); |
|
197
|
|
|
|
|
|
|
}); |
|
198
|
0
|
|
|
|
|
0
|
my ($changed, $rewritten) = $w->rewrite($expr, {}); |
|
199
|
|
|
|
|
|
|
if ($changed) { |
|
200
|
12
|
|
|
|
|
34
|
$expr = $rewritten; |
|
201
|
6
|
|
|
|
|
388
|
} |
|
202
|
6
|
|
|
|
|
63
|
|
|
203
|
6
|
50
|
|
|
|
24
|
my $var = $self->new_temporary('filter'); |
|
204
|
0
|
|
|
|
|
0
|
my %exprs = ($var => $expr); |
|
205
|
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
my @plans; |
|
207
|
6
|
|
|
|
|
25
|
foreach my $plan ($self->plans_for_algebra($child, $model, $active_graphs, $default_graphs, %args)) { |
|
208
|
6
|
|
|
|
|
22
|
my $distinct = $plan->distinct; |
|
209
|
|
|
|
|
|
|
my $ordered = $plan->ordered; |
|
210
|
6
|
|
|
|
|
12
|
if ($expr->isa('Attean::ValueExpression') and $expr->value->does('Attean::API::Variable')) { |
|
211
|
6
|
|
|
|
|
26
|
my $filtered = Attean::Plan::EBVFilter->new(children => [$plan], variable => $expr->value->value, distinct => $distinct, ordered => $ordered); |
|
212
|
6
|
|
|
|
|
113
|
push(@plans, $filtered); |
|
213
|
6
|
|
|
|
|
45
|
} else { |
|
214
|
6
|
100
|
66
|
|
|
52
|
my @vars = ($var); |
|
215
|
3
|
|
|
|
|
94
|
my @inscope = ($var, @{ $plan->in_scope_variables }); |
|
216
|
3
|
|
|
|
|
628
|
my @pvars = map { Attean::Variable->new($_) } @{ $plan->in_scope_variables }; |
|
217
|
|
|
|
|
|
|
my $extend = Attean::Plan::Extend->new(children => [$plan], expressions => \%exprs, distinct => 0, ordered => $ordered); |
|
218
|
3
|
|
|
|
|
19
|
my $filtered = Attean::Plan::EBVFilter->new(children => [$extend], variable => $var, distinct => 0, ordered => $ordered); |
|
219
|
3
|
|
|
|
|
9
|
my $proj = $self->new_projection($filtered, $distinct, @{ $plan->in_scope_variables }); |
|
|
3
|
|
|
|
|
14
|
|
|
220
|
3
|
|
|
|
|
7
|
push(@plans, $proj); |
|
|
3
|
|
|
|
|
53
|
|
|
|
3
|
|
|
|
|
13
|
|
|
221
|
3
|
|
|
|
|
193
|
} |
|
222
|
3
|
|
|
|
|
839
|
} |
|
223
|
3
|
|
|
|
|
641
|
return @plans; |
|
|
3
|
|
|
|
|
39
|
|
|
224
|
3
|
|
|
|
|
795
|
} elsif ($algebra->isa('Attean::Algebra::OrderBy')) { |
|
225
|
|
|
|
|
|
|
# TODO: no-op if already ordered |
|
226
|
|
|
|
|
|
|
my @cmps = @{ $algebra->comparators }; |
|
227
|
6
|
|
|
|
|
112
|
my ($exprs, $ascending, $svars) = $self->_order_by($algebra); |
|
228
|
|
|
|
|
|
|
my @plans; |
|
229
|
|
|
|
|
|
|
foreach my $plan ($self->plans_for_algebra($child, $model, $active_graphs, $default_graphs, interesting_order => $algebra->comparators, %args)) { |
|
230
|
3
|
|
|
|
|
7
|
my $distinct = $plan->distinct; |
|
|
3
|
|
|
|
|
15
|
|
|
231
|
3
|
|
|
|
|
26
|
|
|
232
|
3
|
|
|
|
|
5
|
if (scalar(@cmps) == 1 and $cmps[0]->expression->isa('Attean::ValueExpression') and $cmps[0]->expression->value->does('Attean::API::Variable')) { |
|
233
|
3
|
|
|
|
|
18
|
# TODO: extend this to handle more than one comparator, so long as they are *all* just variables (and not complex expressions) |
|
234
|
3
|
|
|
|
|
48
|
# If we're sorting by just a variable name, don't bother creating new variables for the sort expressions, use the underlying variable directy |
|
235
|
|
|
|
|
|
|
my @vars = @{ $plan->in_scope_variables }; |
|
236
|
3
|
50
|
33
|
|
|
90
|
my @pvars = map { Attean::Variable->new($_) } @{ $plan->in_scope_variables }; |
|
|
|
|
33
|
|
|
|
|
|
237
|
|
|
|
|
|
|
my $var = $cmps[0]->expression->value->value; |
|
238
|
|
|
|
|
|
|
my $ascending = { $var => $cmps[0]->ascending }; |
|
239
|
3
|
|
|
|
|
54
|
my $ordered = Attean::Plan::OrderBy->new(children => [$plan], variables => [$var], ascending => $ascending, distinct => $distinct, ordered => \@cmps); |
|
|
3
|
|
|
|
|
14
|
|
|
240
|
3
|
|
|
|
|
7
|
push(@plans, $ordered); |
|
|
3
|
|
|
|
|
55
|
|
|
|
3
|
|
|
|
|
14
|
|
|
241
|
3
|
|
|
|
|
171
|
} else { |
|
242
|
3
|
|
|
|
|
13
|
my @vars = (@{ $plan->in_scope_variables }, keys %$exprs); |
|
243
|
3
|
|
|
|
|
33
|
my @pvars = map { Attean::Variable->new($_) } @{ $plan->in_scope_variables }; |
|
244
|
3
|
|
|
|
|
722
|
my $extend = Attean::Plan::Extend->new(children => [$plan], expressions => $exprs, distinct => 0, ordered => $plan->ordered); |
|
245
|
|
|
|
|
|
|
my $ordered = Attean::Plan::OrderBy->new(children => [$extend], variables => $svars, ascending => $ascending, distinct => 0, ordered => \@cmps); |
|
246
|
0
|
|
|
|
|
0
|
my $proj = $self->new_projection($ordered, $distinct, @{ $plan->in_scope_variables }); |
|
|
0
|
|
|
|
|
0
|
|
|
247
|
0
|
|
|
|
|
0
|
push(@plans, $proj); |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
248
|
0
|
|
|
|
|
0
|
} |
|
249
|
0
|
|
|
|
|
0
|
} |
|
250
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
251
|
0
|
|
|
|
|
0
|
return @plans; |
|
252
|
|
|
|
|
|
|
} elsif ($algebra->isa('Attean::Algebra::LeftJoin')) { |
|
253
|
|
|
|
|
|
|
my $l = [$self->plans_for_algebra($children[0], $model, $active_graphs, $default_graphs, %args)]; |
|
254
|
|
|
|
|
|
|
my $r = [$self->plans_for_algebra($children[1], $model, $active_graphs, $default_graphs, %args)]; |
|
255
|
3
|
|
|
|
|
26
|
return $self->join_plans($model, $active_graphs, $default_graphs, $l, $r, 'left', $algebra->expression); |
|
256
|
|
|
|
|
|
|
} elsif ($algebra->isa('Attean::Algebra::Minus')) { |
|
257
|
0
|
|
|
|
|
0
|
my $l = [$self->plans_for_algebra($children[0], $model, $active_graphs, $default_graphs, %args)]; |
|
258
|
0
|
|
|
|
|
0
|
my $r = [$self->plans_for_algebra($children[1], $model, $active_graphs, $default_graphs, %args)]; |
|
259
|
0
|
|
|
|
|
0
|
return $self->join_plans($model, $active_graphs, $default_graphs, $l, $r, 'minus'); |
|
260
|
|
|
|
|
|
|
} elsif ($algebra->isa('Attean::Algebra::Project')) { |
|
261
|
0
|
|
|
|
|
0
|
my $vars = $algebra->variables; |
|
262
|
0
|
|
|
|
|
0
|
my @vars = map { $_->value } @{ $vars }; |
|
263
|
0
|
|
|
|
|
0
|
my $vars_key = join(' ', sort @vars); |
|
264
|
|
|
|
|
|
|
my $distinct = 0; |
|
265
|
8
|
|
|
|
|
29
|
my @plans = map { |
|
266
|
8
|
|
|
|
|
16
|
($vars_key eq join(' ', sort @{ $_->in_scope_variables })) |
|
|
14
|
|
|
|
|
47
|
|
|
|
8
|
|
|
|
|
17
|
|
|
267
|
8
|
|
|
|
|
31
|
? $_ # no-op if plan is already properly-projected |
|
268
|
8
|
|
|
|
|
15
|
: $self->new_projection($_, $distinct, @vars) |
|
269
|
|
|
|
|
|
|
} $self->plans_for_algebra($child, $model, $active_graphs, $default_graphs, %args); |
|
270
|
8
|
100
|
|
|
|
217
|
return @plans; |
|
|
14
|
|
|
|
|
758
|
|
|
|
14
|
|
|
|
|
97
|
|
|
271
|
|
|
|
|
|
|
} elsif ($algebra->isa('Attean::Algebra::Graph')) { |
|
272
|
|
|
|
|
|
|
my $graph = $algebra->graph; |
|
273
|
|
|
|
|
|
|
if ($graph->does('Attean::API::Term')) { |
|
274
|
8
|
|
|
|
|
1153
|
if (my $available = $args{available_graphs}) { |
|
275
|
|
|
|
|
|
|
# the list of available graphs has been restricted, and this |
|
276
|
10
|
|
|
|
|
36
|
# graph is not available so return an empty table plan. |
|
277
|
10
|
100
|
|
|
|
31
|
unless (any { $_->equals($graph) } @$available) { |
|
278
|
5
|
100
|
|
|
|
90
|
my $plan = Attean::Plan::Table->new( variables => [], rows => [], distinct => 0, ordered => [] ); |
|
279
|
|
|
|
|
|
|
return $plan; |
|
280
|
|
|
|
|
|
|
} |
|
281
|
2
|
100
|
|
2
|
|
14
|
} |
|
|
2
|
|
|
|
|
7
|
|
|
282
|
1
|
|
|
|
|
26
|
return $self->plans_for_algebra($child, $model, [$graph], $default_graphs, %args); |
|
283
|
1
|
|
|
|
|
173
|
} else { |
|
284
|
|
|
|
|
|
|
my $gvar = $graph->value; |
|
285
|
|
|
|
|
|
|
my $graphs = $model->get_graphs; |
|
286
|
4
|
|
|
|
|
79
|
my @plans; |
|
287
|
|
|
|
|
|
|
my %vars = map { $_ => 1 } $child->in_scope_variables; |
|
288
|
5
|
|
|
|
|
118
|
$vars{ $gvar }++; |
|
289
|
5
|
|
|
|
|
98
|
my @vars = keys %vars; |
|
290
|
5
|
|
|
|
|
133
|
|
|
291
|
5
|
|
|
|
|
25
|
my %available; |
|
|
3
|
|
|
|
|
195
|
|
|
292
|
5
|
|
|
|
|
80
|
if (my $available = $args{available_graphs}) { |
|
293
|
5
|
|
|
|
|
17
|
foreach my $a (@$available) { |
|
294
|
|
|
|
|
|
|
$available{ $a->value }++; |
|
295
|
5
|
|
|
|
|
568
|
} |
|
296
|
5
|
100
|
|
|
|
21
|
$graphs = $graphs->grep(sub { $available{ $_->value } }); |
|
297
|
2
|
|
|
|
|
4
|
} |
|
298
|
4
|
|
|
|
|
14
|
|
|
299
|
|
|
|
|
|
|
my @branches; |
|
300
|
2
|
|
|
4
|
|
22
|
my %ignore = map { $_->value => 1 } @$default_graphs; |
|
|
4
|
|
|
|
|
21
|
|
|
301
|
|
|
|
|
|
|
while (my $graph = $graphs->next) { |
|
302
|
|
|
|
|
|
|
next if $ignore{ $graph->value }; |
|
303
|
5
|
|
|
|
|
68
|
my %exprs = ($gvar => Attean::ValueExpression->new(value => $graph)); |
|
304
|
5
|
|
|
|
|
13
|
# TODO: rewrite $child pattern here to replace any occurrences of the variable $gvar to $graph |
|
|
2
|
|
|
|
|
13
|
|
|
305
|
5
|
|
|
|
|
32
|
my @plans = map { |
|
306
|
3
|
50
|
|
|
|
8
|
Attean::Plan::Extend->new(children => [$_], expressions => \%exprs, distinct => 0, ordered => $_->ordered); |
|
307
|
3
|
|
|
|
|
59
|
} $self->plans_for_algebra($child, $model, [$graph], $default_graphs, %args); |
|
308
|
|
|
|
|
|
|
push(@branches, \@plans); |
|
309
|
|
|
|
|
|
|
} |
|
310
|
3
|
|
|
|
|
14
|
|
|
|
3
|
|
|
|
|
60
|
|
|
311
|
|
|
|
|
|
|
if (scalar(@branches) == 1) { |
|
312
|
3
|
|
|
|
|
721
|
@plans = @{ shift(@branches) }; |
|
313
|
|
|
|
|
|
|
} else { |
|
314
|
|
|
|
|
|
|
cartesian { push(@plans, Attean::Plan::Union->new(children => [@_], distinct => 0, ordered => [])) } @branches; |
|
315
|
5
|
100
|
|
|
|
21
|
} |
|
316
|
1
|
|
|
|
|
2
|
return @plans; |
|
|
1
|
|
|
|
|
3
|
|
|
317
|
|
|
|
|
|
|
} |
|
318
|
4
|
|
|
4
|
|
40
|
} elsif ($algebra->isa('Attean::Algebra::Table')) { |
|
|
4
|
|
|
|
|
207
|
|
|
319
|
|
|
|
|
|
|
my $rows = $algebra->rows; |
|
320
|
5
|
|
|
|
|
749
|
my $vars = $algebra->variables; |
|
321
|
|
|
|
|
|
|
my @vars = map { $_->value } @{ $vars }; |
|
322
|
|
|
|
|
|
|
|
|
323
|
0
|
|
|
|
|
0
|
if (scalar(@$rows) < $self->table_threshold) { |
|
324
|
0
|
|
|
|
|
0
|
return Attean::Plan::Table->new( variables => $vars, rows => $rows, distinct => 0, ordered => [] ); |
|
325
|
0
|
|
|
|
|
0
|
} else { |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
326
|
|
|
|
|
|
|
my $iter = Attean::ListIterator->new( |
|
327
|
0
|
0
|
|
|
|
0
|
item_type => 'Attean::API::Result', |
|
328
|
0
|
|
|
|
|
0
|
variables => \@vars, |
|
329
|
|
|
|
|
|
|
values => $rows |
|
330
|
0
|
|
|
|
|
0
|
); |
|
331
|
|
|
|
|
|
|
return Attean::Plan::Iterator->new( iterator => $iter, distinct => 0, ordered => [] ); |
|
332
|
|
|
|
|
|
|
} |
|
333
|
|
|
|
|
|
|
} elsif ($algebra->isa('Attean::Algebra::Service')) { |
|
334
|
|
|
|
|
|
|
my $endpoint = $algebra->endpoint; |
|
335
|
0
|
|
|
|
|
0
|
my $silent = $algebra->silent; |
|
336
|
|
|
|
|
|
|
my $sparql = sprintf('SELECT * WHERE { %s }', $child->as_sparql); |
|
337
|
|
|
|
|
|
|
my @vars = $child->in_scope_variables; |
|
338
|
4
|
|
|
|
|
16
|
my $plan = Attean::Plan::Service->new( endpoint => $endpoint, silent => $silent, sparql => $sparql, distinct => 0, in_scope_variables => \@vars, ordered => [] ); |
|
339
|
4
|
|
|
|
|
15
|
return $plan; |
|
340
|
4
|
|
|
|
|
22
|
} elsif ($algebra->isa('Attean::Algebra::Slice')) { |
|
341
|
4
|
|
|
|
|
113
|
my $limit = $algebra->limit; |
|
342
|
4
|
|
|
|
|
288
|
my $offset = $algebra->offset; |
|
343
|
4
|
|
|
|
|
999
|
my @plans; |
|
344
|
|
|
|
|
|
|
foreach my $plan ($self->plans_for_algebra($child, $model, $active_graphs, $default_graphs, %args)) { |
|
345
|
0
|
|
|
|
|
0
|
my $vars = $plan->in_scope_variables; |
|
346
|
0
|
|
|
|
|
0
|
push(@plans, Attean::Plan::Slice->new(children => [$plan], limit => $limit, offset => $offset, distinct => $plan->distinct, ordered => $plan->ordered)); |
|
347
|
0
|
|
|
|
|
0
|
} |
|
348
|
0
|
|
|
|
|
0
|
return @plans; |
|
349
|
0
|
|
|
|
|
0
|
} elsif ($algebra->isa('Attean::Algebra::Union')) { |
|
350
|
0
|
|
|
|
|
0
|
# TODO: if both branches are similarly ordered, we can use Attean::Plan::Merge to keep the resulting plan ordered |
|
351
|
|
|
|
|
|
|
my @vars = keys %{ { map { map { $_ => 1 } $_->in_scope_variables } @children } }; |
|
352
|
0
|
|
|
|
|
0
|
my @plansets = map { [$self->plans_for_algebra($_, $model, $active_graphs, $default_graphs, %args)] } @children; |
|
353
|
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
my @plans; |
|
355
|
0
|
|
|
|
|
0
|
cartesian { |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
356
|
0
|
|
|
|
|
0
|
push(@plans, Attean::Plan::Union->new(children => \@_, distinct => 0, ordered => [])) |
|
|
0
|
|
|
|
|
0
|
|
|
357
|
|
|
|
|
|
|
} @plansets; |
|
358
|
0
|
|
|
|
|
0
|
return @plans; |
|
359
|
|
|
|
|
|
|
} elsif ($algebra->isa('Attean::Algebra::Extend')) { |
|
360
|
0
|
|
|
0
|
|
0
|
my $var = $algebra->variable->value; |
|
361
|
0
|
|
|
|
|
0
|
my $expr = $algebra->expression; |
|
362
|
0
|
|
|
|
|
0
|
my %exprs = ($var => $expr); |
|
363
|
|
|
|
|
|
|
my @vars = $algebra->in_scope_variables; |
|
364
|
1
|
|
|
|
|
7
|
|
|
365
|
1
|
|
|
|
|
5
|
my @plans; |
|
366
|
1
|
|
|
|
|
3
|
foreach my $plan ($self->plans_for_algebra($child, $model, $active_graphs, $default_graphs, %args)) { |
|
367
|
1
|
|
|
|
|
7
|
my $extend = Attean::Plan::Extend->new(children => [$plan], expressions => \%exprs, distinct => 0, ordered => $plan->ordered); |
|
368
|
|
|
|
|
|
|
push(@plans, $extend); |
|
369
|
1
|
|
|
|
|
75
|
} |
|
370
|
1
|
|
|
|
|
97
|
return @plans; |
|
371
|
4
|
|
|
|
|
76
|
} elsif ($algebra->isa('Attean::Algebra::Group')) { |
|
372
|
4
|
|
|
|
|
890
|
my $aggs = $algebra->aggregates; |
|
373
|
|
|
|
|
|
|
my $groups = $algebra->groupby; |
|
374
|
1
|
|
|
|
|
12
|
my %exprs; |
|
375
|
|
|
|
|
|
|
foreach my $expr (@$aggs) { |
|
376
|
1
|
|
|
|
|
4
|
my $var = $expr->variable->value; |
|
377
|
1
|
|
|
|
|
3
|
$exprs{$var} = $expr; |
|
378
|
1
|
|
|
|
|
3
|
} |
|
379
|
1
|
|
|
|
|
2
|
my @plans; |
|
380
|
1
|
|
|
|
|
5
|
foreach my $plan ($self->plans_for_algebra($child, $model, $active_graphs, $default_graphs, %args)) { |
|
381
|
1
|
|
|
|
|
3
|
my $extend = Attean::Plan::Aggregate->new(children => [$plan], aggregates => \%exprs, groups => $groups, distinct => 0, ordered => []); |
|
382
|
|
|
|
|
|
|
push(@plans, $extend); |
|
383
|
1
|
|
|
|
|
2
|
} |
|
384
|
1
|
|
|
|
|
42
|
return @plans; |
|
385
|
4
|
|
|
|
|
60
|
} elsif ($algebra->isa('Attean::Algebra::Ask')) { |
|
386
|
4
|
|
|
|
|
1087
|
my @plans; |
|
387
|
|
|
|
|
|
|
foreach my $plan ($self->plans_for_algebra($child, $model, $active_graphs, $default_graphs, %args)) { |
|
388
|
1
|
|
|
|
|
5
|
return Attean::Plan::Exists->new(children => [$plan], distinct => 1, ordered => []); |
|
389
|
|
|
|
|
|
|
} |
|
390
|
4
|
|
|
|
|
22
|
return @plans; |
|
391
|
4
|
|
|
|
|
146
|
} elsif ($algebra->isa('Attean::Algebra::Path')) { |
|
392
|
4
|
|
|
|
|
74
|
my $s = $algebra->subject; |
|
393
|
|
|
|
|
|
|
my $path = $algebra->path; |
|
394
|
0
|
|
|
|
|
0
|
my $o = $algebra->object; |
|
395
|
|
|
|
|
|
|
|
|
396
|
0
|
|
|
|
|
0
|
my @algebra = $self->simplify_path($s, $path, $o); |
|
397
|
0
|
|
|
|
|
0
|
|
|
398
|
0
|
|
|
|
|
0
|
my @join; |
|
399
|
|
|
|
|
|
|
if (scalar(@algebra)) { |
|
400
|
0
|
|
|
|
|
0
|
my @triples; |
|
401
|
|
|
|
|
|
|
while (my $pa = shift(@algebra)) { |
|
402
|
0
|
|
|
|
|
0
|
if ($pa->isa('Attean::TriplePattern')) { |
|
403
|
0
|
0
|
0
|
|
|
0
|
push(@triples, $pa); |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
404
|
0
|
|
|
|
|
0
|
} else { |
|
405
|
0
|
|
|
|
|
0
|
if (scalar(@triples)) { |
|
406
|
0
|
0
|
|
|
|
0
|
push(@join, Attean::Algebra::BGP->new( triples => [@triples] )); |
|
407
|
0
|
|
|
|
|
0
|
@triples = (); |
|
408
|
|
|
|
|
|
|
} |
|
409
|
0
|
0
|
|
|
|
0
|
push(@join, $pa); |
|
410
|
0
|
|
|
|
|
0
|
} |
|
411
|
0
|
|
|
|
|
0
|
} |
|
412
|
|
|
|
|
|
|
if (scalar(@triples)) { |
|
413
|
0
|
|
|
|
|
0
|
push(@join, Attean::Algebra::BGP->new( triples => [@triples] )); |
|
414
|
|
|
|
|
|
|
} |
|
415
|
|
|
|
|
|
|
|
|
416
|
0
|
0
|
|
|
|
0
|
my @vars = $algebra->in_scope_variables; |
|
417
|
0
|
|
|
|
|
0
|
|
|
418
|
|
|
|
|
|
|
my @joins = $self->group_join_plans($model, $active_graphs, $default_graphs, $interesting, map { |
|
419
|
|
|
|
|
|
|
[$self->plans_for_algebra($_, $model, $active_graphs, $default_graphs, %args)] |
|
420
|
0
|
|
|
|
|
0
|
} @join); |
|
421
|
|
|
|
|
|
|
|
|
422
|
|
|
|
|
|
|
my @plans; |
|
423
|
0
|
|
|
|
|
0
|
foreach my $j (@joins) { |
|
|
0
|
|
|
|
|
0
|
|
|
424
|
|
|
|
|
|
|
push(@plans, Attean::Plan::Project->new(children => [$j], variables => [map { Attean::Variable->new($_) } @vars], distinct => 0, ordered => [])); |
|
425
|
|
|
|
|
|
|
} |
|
426
|
0
|
|
|
|
|
0
|
return @plans; |
|
427
|
0
|
|
|
|
|
0
|
|
|
428
|
0
|
|
|
|
|
0
|
} elsif ($path->isa('Attean::Algebra::ZeroOrMorePath') or $path->isa('Attean::Algebra::OneOrMorePath')) { |
|
|
0
|
|
|
|
|
0
|
|
|
429
|
|
|
|
|
|
|
my $skip = $path->isa('Attean::Algebra::OneOrMorePath') ? 1 : 0; |
|
430
|
0
|
|
|
|
|
0
|
my $begin = Attean::Variable->new(value => $self->new_temporary('pp')); |
|
431
|
|
|
|
|
|
|
my $end = Attean::Variable->new(value => $self->new_temporary('pp')); |
|
432
|
|
|
|
|
|
|
my $s_var = $s->does('Attean::API::Variable'); |
|
433
|
0
|
0
|
|
|
|
0
|
my $o_var = $o->does('Attean::API::Variable'); |
|
434
|
0
|
|
|
|
|
0
|
|
|
435
|
0
|
|
|
|
|
0
|
my $child = $path->children->[0]; |
|
436
|
0
|
|
|
|
|
0
|
my $a; |
|
437
|
0
|
|
|
|
|
0
|
if ($s_var and not($o_var)) { |
|
438
|
|
|
|
|
|
|
my $inv = Attean::Algebra::InversePath->new( children => [$child] ); |
|
439
|
0
|
|
|
|
|
0
|
$a = Attean::Algebra::Path->new( subject => $end, path => $inv, object => $begin ); |
|
440
|
0
|
|
|
|
|
0
|
} else { |
|
441
|
0
|
0
|
0
|
|
|
0
|
$a = Attean::Algebra::Path->new( subject => $begin, path => $child, object => $end ); |
|
442
|
0
|
|
|
|
|
0
|
} |
|
443
|
0
|
|
|
|
|
0
|
my @cplans = $self->plans_for_algebra($a, $model, $active_graphs, $default_graphs, %args); |
|
444
|
|
|
|
|
|
|
my @plans; |
|
445
|
0
|
|
|
|
|
0
|
foreach my $cplan (@cplans) { |
|
446
|
|
|
|
|
|
|
my $plan = Attean::Plan::ALPPath->new( |
|
447
|
0
|
|
|
|
|
0
|
subject => $s, |
|
448
|
0
|
|
|
|
|
0
|
children => [$cplan], |
|
449
|
0
|
|
|
|
|
0
|
object => $o, |
|
450
|
0
|
|
|
|
|
0
|
graph => $active_graphs, |
|
451
|
|
|
|
|
|
|
skip => $skip, |
|
452
|
|
|
|
|
|
|
step_begin => $begin, |
|
453
|
|
|
|
|
|
|
step_end => $end, |
|
454
|
|
|
|
|
|
|
distinct => 0, |
|
455
|
|
|
|
|
|
|
ordered => [] |
|
456
|
|
|
|
|
|
|
); |
|
457
|
|
|
|
|
|
|
push(@plans, $plan); |
|
458
|
|
|
|
|
|
|
} |
|
459
|
|
|
|
|
|
|
return @plans; |
|
460
|
|
|
|
|
|
|
} elsif ($path->isa('Attean::Algebra::ZeroOrOnePath')) { |
|
461
|
0
|
|
|
|
|
0
|
my $a = Attean::Algebra::Path->new( subject => $s, path => $path->children->[0], object => $o ); |
|
462
|
|
|
|
|
|
|
my @children = $self->plans_for_algebra($a, $model, $active_graphs, $default_graphs, %args); |
|
463
|
0
|
|
|
|
|
0
|
my @plans; |
|
464
|
|
|
|
|
|
|
foreach my $plan (@children) { |
|
465
|
0
|
|
|
|
|
0
|
push(@plans, Attean::Plan::ZeroOrOnePath->new( |
|
466
|
0
|
|
|
|
|
0
|
subject => $s, |
|
467
|
0
|
|
|
|
|
0
|
children => [$plan], |
|
468
|
0
|
|
|
|
|
0
|
object => $o, |
|
469
|
0
|
|
|
|
|
0
|
graph => $active_graphs, |
|
470
|
|
|
|
|
|
|
distinct => 0, |
|
471
|
|
|
|
|
|
|
ordered => [] |
|
472
|
|
|
|
|
|
|
)); |
|
473
|
|
|
|
|
|
|
} |
|
474
|
|
|
|
|
|
|
return @plans; |
|
475
|
|
|
|
|
|
|
} else { |
|
476
|
|
|
|
|
|
|
die "Cannot simplify property path $path: " . $algebra->as_string; |
|
477
|
|
|
|
|
|
|
} |
|
478
|
0
|
|
|
|
|
0
|
} elsif ($algebra->isa('Attean::Algebra::Construct')) { |
|
479
|
|
|
|
|
|
|
my @children = $self->plans_for_algebra($child, $model, $active_graphs, $default_graphs, %args); |
|
480
|
0
|
|
|
|
|
0
|
my @plans; |
|
481
|
|
|
|
|
|
|
foreach my $plan (@children) { |
|
482
|
|
|
|
|
|
|
push(@plans, Attean::Plan::Construct->new(triples => $algebra->triples, children => [$plan], distinct => 0, ordered => [])); |
|
483
|
0
|
|
|
|
|
0
|
} |
|
484
|
0
|
|
|
|
|
0
|
return @plans; |
|
485
|
0
|
|
|
|
|
0
|
} elsif ($algebra->isa('Attean::Algebra::Describe')) { |
|
486
|
0
|
|
|
|
|
0
|
my @children = $self->plans_for_algebra($child, $model, $active_graphs, $default_graphs, %args); |
|
487
|
|
|
|
|
|
|
my @plans; |
|
488
|
0
|
|
|
|
|
0
|
foreach my $plan (@children) { |
|
489
|
|
|
|
|
|
|
push(@plans, Attean::Plan::Describe->new(terms => $algebra->terms, graph => $active_graphs, children => [$plan], distinct => 0, ordered => [])); |
|
490
|
1
|
|
|
|
|
6
|
} |
|
491
|
1
|
|
|
|
|
2
|
return @plans; |
|
492
|
1
|
|
|
|
|
3
|
} elsif ($algebra->isa('Attean::Algebra::Clear')) { |
|
493
|
1
|
|
|
|
|
17
|
my $plan_class = $algebra->drop ? 'Attean::Plan::Drop' : 'Attean::Plan::Clear'; |
|
494
|
|
|
|
|
|
|
my $target = $algebra->target; |
|
495
|
1
|
|
|
|
|
312
|
if ($target eq 'GRAPH') { |
|
496
|
|
|
|
|
|
|
return Attean::Plan::Clear->new(graphs => [$algebra->graph]); |
|
497
|
0
|
0
|
|
|
|
0
|
} else { |
|
498
|
0
|
|
|
|
|
0
|
my %default = map { $_->value => 1 } @$active_graphs; |
|
499
|
0
|
0
|
|
|
|
0
|
my $graphs = $model->get_graphs; |
|
500
|
0
|
|
|
|
|
0
|
my @graphs; |
|
501
|
|
|
|
|
|
|
while (my $graph = $graphs->next) { |
|
502
|
0
|
|
|
|
|
0
|
if ($target eq 'ALL') { |
|
|
0
|
|
|
|
|
0
|
|
|
503
|
0
|
|
|
|
|
0
|
push(@graphs, $graph); |
|
504
|
0
|
|
|
|
|
0
|
} else { |
|
505
|
0
|
|
|
|
|
0
|
if ($target eq 'DEFAULT' and $default{ $graph->value }) { |
|
506
|
0
|
0
|
|
|
|
0
|
push(@graphs, $graph); |
|
507
|
0
|
|
|
|
|
0
|
} elsif ($target eq 'NAMED' and not $default{ $graph->value }) { |
|
508
|
|
|
|
|
|
|
push(@graphs, $graph); |
|
509
|
0
|
0
|
0
|
|
|
0
|
} |
|
|
|
0
|
0
|
|
|
|
|
|
510
|
0
|
|
|
|
|
0
|
} |
|
511
|
|
|
|
|
|
|
} |
|
512
|
0
|
|
|
|
|
0
|
return $plan_class->new(graphs => \@graphs); |
|
513
|
|
|
|
|
|
|
} |
|
514
|
|
|
|
|
|
|
} elsif ($algebra->isa('Attean::Algebra::Add')) { |
|
515
|
|
|
|
|
|
|
my $triple = triplepattern(variable('s'), variable('p'), variable('o')); |
|
516
|
0
|
|
|
|
|
0
|
my $child; |
|
517
|
|
|
|
|
|
|
my $default_source = 0; |
|
518
|
|
|
|
|
|
|
if (my $from = $algebra->source) { |
|
519
|
0
|
|
|
|
|
0
|
($child) = $self->access_plans( $model, $active_graphs, $triple->as_quad_pattern($from) ); |
|
520
|
0
|
|
|
|
|
0
|
} else { |
|
521
|
0
|
|
|
|
|
0
|
$default_source++; |
|
522
|
0
|
0
|
|
|
|
0
|
my $bgp = Attean::Algebra::BGP->new( triples => [$triple] ); |
|
523
|
0
|
|
|
|
|
0
|
($child) = $self->plans_for_algebra($bgp, $model, $active_graphs, $default_graphs, %args); |
|
524
|
|
|
|
|
|
|
} |
|
525
|
0
|
|
|
|
|
0
|
|
|
526
|
0
|
|
|
|
|
0
|
my $dest; |
|
527
|
0
|
|
|
|
|
0
|
my $default_dest = 0; |
|
528
|
|
|
|
|
|
|
if (my $g = $algebra->destination) { |
|
529
|
|
|
|
|
|
|
$dest = $triple->as_quad_pattern($g); |
|
530
|
0
|
|
|
|
|
0
|
} else { |
|
531
|
0
|
|
|
|
|
0
|
$default_dest++; |
|
532
|
0
|
0
|
|
|
|
0
|
$dest = $triple->as_quad_pattern($default_graphs->[0]); |
|
533
|
0
|
|
|
|
|
0
|
} |
|
534
|
|
|
|
|
|
|
|
|
535
|
0
|
|
|
|
|
0
|
|
|
536
|
0
|
|
|
|
|
0
|
my @plans; |
|
537
|
|
|
|
|
|
|
my $run_update = 1; |
|
538
|
|
|
|
|
|
|
if ($default_dest and $default_source) { |
|
539
|
|
|
|
|
|
|
$run_update = 0; |
|
540
|
0
|
|
|
|
|
0
|
} elsif ($default_dest or $default_source) { |
|
541
|
0
|
|
|
|
|
0
|
# |
|
542
|
0
|
0
|
0
|
|
|
0
|
} elsif ($algebra->source->equals($algebra->destination)) { |
|
|
|
0
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
543
|
0
|
|
|
|
|
0
|
$run_update = 0; |
|
544
|
|
|
|
|
|
|
} |
|
545
|
|
|
|
|
|
|
|
|
546
|
|
|
|
|
|
|
if ($run_update) { |
|
547
|
0
|
|
|
|
|
0
|
if ($algebra->drop_destination) { |
|
548
|
|
|
|
|
|
|
my @graphs = $algebra->has_destination ? $algebra->destination : @$default_graphs; |
|
549
|
|
|
|
|
|
|
unshift(@plans, Attean::Plan::Clear->new(graphs => [@graphs])); |
|
550
|
0
|
0
|
|
|
|
0
|
} |
|
551
|
0
|
0
|
|
|
|
0
|
|
|
552
|
0
|
0
|
|
|
|
0
|
push(@plans, Attean::Plan::TripleTemplateToModelQuadMethod->new( |
|
553
|
0
|
|
|
|
|
0
|
graph => $default_graphs->[0], |
|
554
|
|
|
|
|
|
|
order => ['add_quad'], |
|
555
|
|
|
|
|
|
|
patterns => {'add_quad' => [$dest]}, |
|
556
|
0
|
|
|
|
|
0
|
children => [$child], |
|
557
|
|
|
|
|
|
|
)); |
|
558
|
|
|
|
|
|
|
|
|
559
|
|
|
|
|
|
|
if ($algebra->drop_source) { |
|
560
|
|
|
|
|
|
|
my @graphs = $algebra->has_source ? $algebra->source : @$default_graphs; |
|
561
|
|
|
|
|
|
|
push(@plans, Attean::Plan::Clear->new(graphs => [@graphs])); |
|
562
|
|
|
|
|
|
|
} |
|
563
|
0
|
0
|
|
|
|
0
|
} |
|
564
|
0
|
0
|
|
|
|
0
|
my $plan = (scalar(@plans) == 1) ? shift(@plans) : Attean::Plan::Sequence->new( children => \@plans ); |
|
565
|
0
|
|
|
|
|
0
|
return $plan; |
|
566
|
|
|
|
|
|
|
} elsif ($algebra->isa('Attean::Algebra::Modify')) { |
|
567
|
|
|
|
|
|
|
unless ($child) { |
|
568
|
0
|
0
|
|
|
|
0
|
# This is an INSERT/DELETE DATA algebra with ground data and no pattern |
|
569
|
0
|
|
|
|
|
0
|
$child = Attean::Algebra::BGP->new( triples => [] ); |
|
570
|
|
|
|
|
|
|
} |
|
571
|
0
|
0
|
|
|
|
0
|
|
|
572
|
|
|
|
|
|
|
my $dataset = $algebra->dataset; |
|
573
|
0
|
|
|
|
|
0
|
my @default = @{ $dataset->{default} || [] }; |
|
574
|
|
|
|
|
|
|
my @named = values %{ $dataset->{named} || {} }; |
|
575
|
|
|
|
|
|
|
|
|
576
|
0
|
|
|
|
|
0
|
my @active_graphs = @$active_graphs; |
|
577
|
0
|
0
|
|
|
|
0
|
my @default_graphs = @$default_graphs; |
|
|
0
|
|
|
|
|
0
|
|
|
578
|
0
|
0
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
579
|
|
|
|
|
|
|
if (scalar(@default) or scalar(@named)) { |
|
580
|
0
|
|
|
|
|
0
|
# change the available named graphs |
|
581
|
0
|
|
|
|
|
0
|
# change the active graph(s) |
|
582
|
|
|
|
|
|
|
@active_graphs = @default; |
|
583
|
0
|
0
|
0
|
|
|
0
|
@default_graphs = @default; |
|
584
|
|
|
|
|
|
|
$args{ available_graphs } = [@named]; |
|
585
|
|
|
|
|
|
|
} else { |
|
586
|
0
|
|
|
|
|
0
|
# no custom dataset |
|
587
|
0
|
|
|
|
|
0
|
} |
|
588
|
0
|
|
|
|
|
0
|
|
|
589
|
|
|
|
|
|
|
my @children = $self->plans_for_algebra($child, $model, \@active_graphs, \@default_graphs, %args); |
|
590
|
|
|
|
|
|
|
my $i = $algebra->insert; |
|
591
|
|
|
|
|
|
|
my $d = $algebra->delete; |
|
592
|
|
|
|
|
|
|
my %patterns; |
|
593
|
0
|
|
|
|
|
0
|
my @order; |
|
594
|
0
|
|
|
|
|
0
|
if (scalar(@$d)) { |
|
595
|
0
|
|
|
|
|
0
|
push(@order, 'remove_quad'); |
|
596
|
0
|
|
|
|
|
0
|
$patterns{ 'remove_quad' } = $d; |
|
597
|
|
|
|
|
|
|
} |
|
598
|
0
|
0
|
|
|
|
0
|
if (scalar(@$i)) { |
|
599
|
0
|
|
|
|
|
0
|
push(@order, 'add_quad'); |
|
600
|
0
|
|
|
|
|
0
|
$patterns{ 'add_quad' } = $i; |
|
601
|
|
|
|
|
|
|
} |
|
602
|
0
|
0
|
|
|
|
0
|
return map { |
|
603
|
0
|
|
|
|
|
0
|
Attean::Plan::TripleTemplateToModelQuadMethod->new( |
|
604
|
0
|
|
|
|
|
0
|
graph => $default_graphs->[0], |
|
605
|
|
|
|
|
|
|
order => \@order, |
|
606
|
|
|
|
|
|
|
patterns => \%patterns, |
|
607
|
0
|
|
|
|
|
0
|
children => [$_], |
|
|
0
|
|
|
|
|
0
|
|
|
608
|
|
|
|
|
|
|
) |
|
609
|
|
|
|
|
|
|
} @children; |
|
610
|
|
|
|
|
|
|
} elsif ($algebra->isa('Attean::Algebra::Load')) { |
|
611
|
|
|
|
|
|
|
my $pattern = triplepattern(variable('subject'), variable('predicate'), variable('object')); |
|
612
|
|
|
|
|
|
|
my $load = Attean::Plan::Load->new( url => $algebra->url->value, silent => $algebra->silent ); |
|
613
|
|
|
|
|
|
|
my $graph = $algebra->has_graph ? $algebra->graph : $default_graphs->[0]; |
|
614
|
|
|
|
|
|
|
my $plan = Attean::Plan::TripleTemplateToModelQuadMethod->new( |
|
615
|
0
|
|
|
|
|
0
|
graph => $graph, |
|
616
|
0
|
|
|
|
|
0
|
order => ['add_quad'], |
|
617
|
0
|
0
|
|
|
|
0
|
patterns => {'add_quad' => [$pattern]}, |
|
618
|
0
|
|
|
|
|
0
|
children => [$load], |
|
619
|
|
|
|
|
|
|
); |
|
620
|
|
|
|
|
|
|
return $plan; |
|
621
|
|
|
|
|
|
|
} elsif ($algebra->isa('Attean::Algebra::Create')) { |
|
622
|
|
|
|
|
|
|
return Attean::Plan::Sequence->new( children => [] ); |
|
623
|
|
|
|
|
|
|
} elsif ($algebra->isa('Attean::Algebra::Sequence')) { |
|
624
|
0
|
|
|
|
|
0
|
my @plans; |
|
625
|
|
|
|
|
|
|
foreach my $child (@{ $algebra->children }) { |
|
626
|
0
|
|
|
|
|
0
|
my ($plan) = $self->plans_for_algebra($child, $model, $active_graphs, $default_graphs, %args); |
|
627
|
|
|
|
|
|
|
push(@plans, $plan); |
|
628
|
0
|
|
|
|
|
0
|
} |
|
629
|
0
|
|
|
|
|
0
|
return Attean::Plan::Sequence->new( children => \@plans ); |
|
|
0
|
|
|
|
|
0
|
|
|
630
|
0
|
|
|
|
|
0
|
} |
|
631
|
0
|
|
|
|
|
0
|
die "Unimplemented algebra evaluation for: " . $algebra->as_string; |
|
632
|
|
|
|
|
|
|
} |
|
633
|
0
|
|
|
|
|
0
|
|
|
634
|
|
|
|
|
|
|
# sub plans_for_unbounded_path { |
|
635
|
0
|
|
|
|
|
0
|
# my $self = shift; |
|
636
|
|
|
|
|
|
|
# my $algebra = shift; |
|
637
|
|
|
|
|
|
|
# my $model = shift; |
|
638
|
|
|
|
|
|
|
# my $active_graphs = shift; |
|
639
|
|
|
|
|
|
|
# my $default_graphs = shift; |
|
640
|
|
|
|
|
|
|
# my %args = @_; |
|
641
|
|
|
|
|
|
|
# |
|
642
|
|
|
|
|
|
|
# my $s = $algebra->subject; |
|
643
|
|
|
|
|
|
|
# my $path = $algebra->path; |
|
644
|
|
|
|
|
|
|
# my $o = $algebra->object; |
|
645
|
|
|
|
|
|
|
# |
|
646
|
|
|
|
|
|
|
# return Attean::Plan::ALPPath->new(distinct => 0, ordered => []); |
|
647
|
|
|
|
|
|
|
# } |
|
648
|
|
|
|
|
|
|
|
|
649
|
|
|
|
|
|
|
my $self = shift; |
|
650
|
|
|
|
|
|
|
my @args = @_; |
|
651
|
|
|
|
|
|
|
|
|
652
|
|
|
|
|
|
|
my @bgptriples = map { @{ $_->triples } } grep { $_->isa('Attean::Algebra::BGP') } @args; |
|
653
|
|
|
|
|
|
|
my @triples = grep { $_->isa('Attean::TriplePattern') } @args; |
|
654
|
0
|
|
|
0
|
|
0
|
my @rest = grep { not $_->isa('Attean::Algebra::BGP') and not $_->isa('Attean::TriplePattern') } @args; |
|
655
|
0
|
|
|
|
|
0
|
if (scalar(@rest) == 0) { |
|
656
|
|
|
|
|
|
|
return Attean::Algebra::BGP->new( triples => [@bgptriples, @triples] ); |
|
657
|
0
|
|
|
|
|
0
|
} else { |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
658
|
0
|
|
|
|
|
0
|
my $p = Attean::Algebra::BGP->new( triples => [@bgptriples, @triples] ); |
|
|
0
|
|
|
|
|
0
|
|
|
659
|
0
|
|
0
|
|
|
0
|
while (scalar(@rest) > 0) { |
|
|
0
|
|
|
|
|
0
|
|
|
660
|
0
|
0
|
|
|
|
0
|
$p = Attean::Algebra::Join->new( children => [$p, shift(@rest)] ); |
|
661
|
0
|
|
|
|
|
0
|
} |
|
662
|
|
|
|
|
|
|
return $p; |
|
663
|
0
|
|
|
|
|
0
|
} |
|
664
|
0
|
|
|
|
|
0
|
} |
|
665
|
0
|
|
|
|
|
0
|
|
|
666
|
|
|
|
|
|
|
=item C<< simplify_path( $subject, $path, $object ) >> |
|
667
|
0
|
|
|
|
|
0
|
|
|
668
|
|
|
|
|
|
|
Return a simplified L<Attean::API::Algebra> object corresponding to the given |
|
669
|
|
|
|
|
|
|
property path. |
|
670
|
|
|
|
|
|
|
|
|
671
|
|
|
|
|
|
|
=cut |
|
672
|
|
|
|
|
|
|
|
|
673
|
|
|
|
|
|
|
my $self = shift; |
|
674
|
|
|
|
|
|
|
my $s = shift; |
|
675
|
|
|
|
|
|
|
my $path = shift; |
|
676
|
|
|
|
|
|
|
my $o = shift; |
|
677
|
|
|
|
|
|
|
if ($path->isa('Attean::Algebra::SequencePath')) { |
|
678
|
|
|
|
|
|
|
my $jvar = Attean::Variable->new(value => $self->new_temporary('pp')); |
|
679
|
0
|
|
|
0
|
1
|
0
|
my ($lhs, $rhs) = @{ $path->children }; |
|
680
|
0
|
|
|
|
|
0
|
my @paths; |
|
681
|
0
|
|
|
|
|
0
|
push(@paths, $self->simplify_path($s, $lhs, $jvar)); |
|
682
|
0
|
|
|
|
|
0
|
push(@paths, $self->simplify_path($jvar, $rhs, $o)); |
|
683
|
0
|
0
|
|
|
|
0
|
return $self->_package(@paths); |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
684
|
0
|
|
|
|
|
0
|
} elsif ($path->isa('Attean::Algebra::InversePath')) { |
|
685
|
0
|
|
|
|
|
0
|
my ($ipath) = @{ $path->children }; |
|
|
0
|
|
|
|
|
0
|
|
|
686
|
0
|
|
|
|
|
0
|
return $self->simplify_path($o, $ipath, $s); |
|
687
|
0
|
|
|
|
|
0
|
} elsif ($path->isa('Attean::Algebra::PredicatePath')) { |
|
688
|
0
|
|
|
|
|
0
|
my $pred = $path->predicate; |
|
689
|
0
|
|
|
|
|
0
|
return Attean::TriplePattern->new($s, $pred, $o); |
|
690
|
|
|
|
|
|
|
} elsif ($path->isa('Attean::Algebra::AlternativePath')) { |
|
691
|
0
|
|
|
|
|
0
|
my ($l, $r) = @{ $path->children }; |
|
|
0
|
|
|
|
|
0
|
|
|
692
|
0
|
|
|
|
|
0
|
my $la = $self->_package($self->simplify_path($s, $l, $o)); |
|
693
|
|
|
|
|
|
|
my $ra = $self->_package($self->simplify_path($s, $r, $o)); |
|
694
|
0
|
|
|
|
|
0
|
return Attean::Algebra::Union->new( children => [$la, $ra] ); |
|
695
|
0
|
|
|
|
|
0
|
} elsif ($path->isa('Attean::Algebra::NegatedPropertySet')) { |
|
696
|
|
|
|
|
|
|
my @preds = @{ $path->predicates }; |
|
697
|
0
|
|
|
|
|
0
|
my $pvar = Attean::Variable->new(value => $self->new_temporary('nps')); |
|
|
0
|
|
|
|
|
0
|
|
|
698
|
0
|
|
|
|
|
0
|
my $pvar_e = Attean::ValueExpression->new( value => $pvar ); |
|
699
|
0
|
|
|
|
|
0
|
my $t = Attean::TriplePattern->new($s, $pvar, $o); |
|
700
|
0
|
|
|
|
|
0
|
my @vals = map { Attean::ValueExpression->new( value => $_ ) } @preds; |
|
701
|
|
|
|
|
|
|
my $expr = Attean::FunctionExpression->new( children => [$pvar_e, @vals], operator => 'notin' ); |
|
702
|
0
|
|
|
|
|
0
|
my $bgp = Attean::Algebra::BGP->new( triples => [$t] ); |
|
|
0
|
|
|
|
|
0
|
|
|
703
|
0
|
|
|
|
|
0
|
my $f = Attean::Algebra::Filter->new( children => [$bgp], expression => $expr ); |
|
704
|
0
|
|
|
|
|
0
|
return $f; |
|
705
|
0
|
|
|
|
|
0
|
} else { |
|
706
|
0
|
|
|
|
|
0
|
return; |
|
|
0
|
|
|
|
|
0
|
|
|
707
|
0
|
|
|
|
|
0
|
} |
|
708
|
0
|
|
|
|
|
0
|
} |
|
709
|
0
|
|
|
|
|
0
|
|
|
710
|
0
|
|
|
|
|
0
|
=item C<< new_projection( $plan, $distinct, @variable_names ) >> |
|
711
|
|
|
|
|
|
|
|
|
712
|
0
|
|
|
|
|
0
|
Return a new L<< Attean::Plan::Project >> plan over C<< $plan >>, projecting |
|
713
|
|
|
|
|
|
|
the named variables. C<< $disctinct >> should be true if the caller can |
|
714
|
|
|
|
|
|
|
guarantee that the resulting plan will produce distinct results, false otherwise. |
|
715
|
|
|
|
|
|
|
|
|
716
|
|
|
|
|
|
|
This method takes care of computing plan metadata such as the resulting ordering. |
|
717
|
|
|
|
|
|
|
|
|
718
|
|
|
|
|
|
|
=cut |
|
719
|
|
|
|
|
|
|
|
|
720
|
|
|
|
|
|
|
my $self = shift; |
|
721
|
|
|
|
|
|
|
my $plan = shift; |
|
722
|
|
|
|
|
|
|
my $distinct = shift; |
|
723
|
|
|
|
|
|
|
my @vars = @_; |
|
724
|
|
|
|
|
|
|
my $order = $plan->ordered; |
|
725
|
|
|
|
|
|
|
my @pvars = map { Attean::Variable->new($_) } @vars; |
|
726
|
|
|
|
|
|
|
|
|
727
|
10
|
|
|
10
|
1
|
22
|
my %pvars = map { $_ => 1 } @vars; |
|
728
|
10
|
|
|
|
|
20
|
my @porder; |
|
729
|
10
|
|
|
|
|
17
|
CMP: foreach my $cmp (@{ $order }) { |
|
730
|
10
|
|
|
|
|
27
|
my @cmpvars = $self->_comparator_referenced_variables($cmp); |
|
731
|
10
|
|
|
|
|
31
|
foreach my $v (@cmpvars) { |
|
732
|
10
|
|
|
|
|
21
|
unless ($pvars{ $v }) { |
|
|
16
|
|
|
|
|
553
|
|
|
733
|
|
|
|
|
|
|
# projection is dropping a variable used in this comparator |
|
734
|
10
|
|
|
|
|
473
|
# so we lose any remaining ordering that the sub-plan had. |
|
|
16
|
|
|
|
|
41
|
|
|
735
|
10
|
|
|
|
|
22
|
last CMP; |
|
736
|
10
|
|
|
|
|
14
|
} |
|
|
10
|
|
|
|
|
24
|
|
|
737
|
0
|
|
|
|
|
0
|
} |
|
738
|
0
|
|
|
|
|
0
|
|
|
739
|
0
|
0
|
|
|
|
0
|
# all the variables used by this comparator are available after |
|
740
|
|
|
|
|
|
|
# projection, so the resulting plan will continue to be ordered |
|
741
|
|
|
|
|
|
|
# by this comparator |
|
742
|
0
|
|
|
|
|
0
|
push(@porder, $cmp); |
|
743
|
|
|
|
|
|
|
} |
|
744
|
|
|
|
|
|
|
|
|
745
|
|
|
|
|
|
|
return Attean::Plan::Project->new(children => [$plan], variables => \@pvars, distinct => $distinct, ordered => \@porder); |
|
746
|
|
|
|
|
|
|
} |
|
747
|
|
|
|
|
|
|
|
|
748
|
|
|
|
|
|
|
=item C<< bgp_join_plans( $bgp, $model, \@active_graphs, \@default_graphs, \@interesting_order, \@plansA, \@plansB, ... ) >> |
|
749
|
0
|
|
|
|
|
0
|
|
|
750
|
|
|
|
|
|
|
Returns a list of alternative plans for the join of a set of triples. |
|
751
|
|
|
|
|
|
|
The arguments C<@plansA>, C<@plansB>, etc. represent alternative plans for each |
|
752
|
10
|
|
|
|
|
160
|
triple participating in the join. |
|
753
|
|
|
|
|
|
|
|
|
754
|
|
|
|
|
|
|
=cut |
|
755
|
|
|
|
|
|
|
|
|
756
|
|
|
|
|
|
|
my $self = shift; |
|
757
|
|
|
|
|
|
|
my $bgp = shift; |
|
758
|
|
|
|
|
|
|
my $model = shift; |
|
759
|
|
|
|
|
|
|
my $active = shift; |
|
760
|
|
|
|
|
|
|
my $default = shift; |
|
761
|
|
|
|
|
|
|
my $interesting = shift; |
|
762
|
|
|
|
|
|
|
my @triples = @_; |
|
763
|
|
|
|
|
|
|
|
|
764
|
69
|
|
|
69
|
1
|
142
|
if (scalar(@triples)) { |
|
765
|
69
|
|
|
|
|
106
|
my @plans = $self->joins_for_plan_alternatives($model, $active, $default, $interesting, @triples); |
|
766
|
69
|
|
|
|
|
99
|
my @triples = @{ $bgp->triples }; |
|
767
|
69
|
|
|
|
|
113
|
|
|
768
|
69
|
|
|
|
|
113
|
# If the BGP does not contain any blanks, then the results are |
|
769
|
69
|
|
|
|
|
105
|
# guaranteed to be distinct. Otherwise, we have to assume they're |
|
770
|
69
|
|
|
|
|
136
|
# not distinct. |
|
771
|
|
|
|
|
|
|
my $distinct = 1; |
|
772
|
69
|
100
|
|
|
|
155
|
LOOP: foreach my $t (@triples) { |
|
773
|
62
|
|
|
|
|
937
|
foreach my $b ($t->values_consuming_role('Attean::API::Blank')) { |
|
774
|
61
|
|
|
|
|
120
|
$distinct = 0; |
|
|
61
|
|
|
|
|
510
|
|
|
775
|
|
|
|
|
|
|
last LOOP; |
|
776
|
|
|
|
|
|
|
} |
|
777
|
|
|
|
|
|
|
foreach my $b ($t->values_consuming_role('Attean::API::Variable')) { |
|
778
|
|
|
|
|
|
|
if ($b->value =~ /^[.]/) { |
|
779
|
61
|
|
|
|
|
108
|
# variable names starting with a dot represent placeholders introduced during query planning (with C<new_temporary>) |
|
780
|
61
|
|
|
|
|
147
|
# they are not projectable, and so may cause an otherwise distinct result to become non-distinct |
|
781
|
97
|
|
|
|
|
301
|
$distinct = 0; |
|
782
|
0
|
|
|
|
|
0
|
last LOOP; |
|
783
|
0
|
|
|
|
|
0
|
} |
|
784
|
|
|
|
|
|
|
} |
|
785
|
97
|
|
|
|
|
1655
|
} |
|
786
|
141
|
100
|
|
|
|
1645
|
|
|
787
|
|
|
|
|
|
|
# Set the distinct flag on each of the top-level join plans that |
|
788
|
|
|
|
|
|
|
# represents the entire BGP. (Sub-plans won't ever be marked as |
|
789
|
6
|
|
|
|
|
17
|
# distinct, but that shouldn't matter to the rest of the planning |
|
790
|
6
|
|
|
|
|
20
|
# process.) |
|
791
|
|
|
|
|
|
|
if ($distinct) { |
|
792
|
|
|
|
|
|
|
foreach my $p (@plans) { |
|
793
|
|
|
|
|
|
|
$p->distinct(1); |
|
794
|
|
|
|
|
|
|
} |
|
795
|
|
|
|
|
|
|
} |
|
796
|
|
|
|
|
|
|
|
|
797
|
|
|
|
|
|
|
return @plans; |
|
798
|
|
|
|
|
|
|
} else { |
|
799
|
61
|
100
|
|
|
|
170
|
# The empty BGP is a special case -- it results in a single join-identity result |
|
800
|
55
|
|
|
|
|
132
|
my $r = Attean::Result->new( bindings => {} ); |
|
801
|
119
|
|
|
|
|
3524
|
my $plan = Attean::Plan::Table->new( rows => [$r], variables => [], distinct => 1, ordered => [] ); |
|
802
|
|
|
|
|
|
|
return $plan; |
|
803
|
|
|
|
|
|
|
} |
|
804
|
|
|
|
|
|
|
} |
|
805
|
61
|
|
|
|
|
1521
|
|
|
806
|
|
|
|
|
|
|
=item C<< group_join_plans( $model, \@active_graphs, \@default_graphs, \@interesting_order, \@plansA, \@plansB, ... ) >> |
|
807
|
|
|
|
|
|
|
|
|
808
|
7
|
|
|
|
|
97
|
Returns a list of alternative plans for the join of a set of sub-plans. |
|
809
|
7
|
|
|
|
|
280
|
The arguments C<@plansA>, C<@plansB>, etc. represent alternative plans for each |
|
810
|
7
|
|
|
|
|
1415
|
sub-plan participating in the join. |
|
811
|
|
|
|
|
|
|
|
|
812
|
|
|
|
|
|
|
=cut |
|
813
|
|
|
|
|
|
|
|
|
814
|
|
|
|
|
|
|
my $self = shift; |
|
815
|
|
|
|
|
|
|
return $self->joins_for_plan_alternatives(@_); |
|
816
|
|
|
|
|
|
|
} |
|
817
|
|
|
|
|
|
|
|
|
818
|
|
|
|
|
|
|
=item C<< joins_for_plan_alternatives( $model, \@active_graphs, \@default_graphs, $interesting, \@plan_A, \@plan_B, ... ) >> |
|
819
|
|
|
|
|
|
|
|
|
820
|
|
|
|
|
|
|
Returns a list of alternative plans that may all be used to produce results |
|
821
|
|
|
|
|
|
|
matching the join of C<< plan_A >>, C< plan_B >>, etc. Each plan array here |
|
822
|
|
|
|
|
|
|
(e.g. C<< @plan_A >>) should contain equivalent plans. |
|
823
|
11
|
|
|
11
|
1
|
26
|
|
|
824
|
11
|
|
|
|
|
200
|
=cut |
|
825
|
|
|
|
|
|
|
|
|
826
|
|
|
|
|
|
|
my $self = shift; |
|
827
|
|
|
|
|
|
|
my $model = shift; |
|
828
|
|
|
|
|
|
|
my $active_graphs = shift; |
|
829
|
|
|
|
|
|
|
my $default_graphs = shift; |
|
830
|
|
|
|
|
|
|
my $interesting = shift; |
|
831
|
|
|
|
|
|
|
my @args = @_; # each $args[$i] here is an array reference containing alternate plans for element $i |
|
832
|
|
|
|
|
|
|
die "This query planner does not seem to consume a Attean::API::JoinPlanner role (which is necessary for query planning)"; |
|
833
|
|
|
|
|
|
|
} |
|
834
|
|
|
|
|
|
|
|
|
835
|
|
|
|
|
|
|
=item C<< access_plans( $model, $active_graphs, $pattern ) >> |
|
836
|
1
|
|
|
1
|
1
|
2
|
|
|
837
|
1
|
|
|
|
|
3
|
Returns a list of alternative L<Attean::API::Plan> objects that may be used to |
|
838
|
1
|
|
|
|
|
1
|
produce results matching the L<Attean::API::TripleOrQuadPattern> $pattern in |
|
839
|
1
|
|
|
|
|
3
|
the context of C<< $active_graphs >>. |
|
840
|
1
|
|
|
|
|
1
|
|
|
841
|
1
|
|
|
|
|
2
|
=cut |
|
842
|
1
|
|
|
|
|
23
|
|
|
843
|
|
|
|
|
|
|
# $pattern is a Attean::API::TripleOrQuadPattern object |
|
844
|
|
|
|
|
|
|
# Return a Attean::API::Plan object that represents the evaluation of $pattern. |
|
845
|
|
|
|
|
|
|
# e.g. different plans might represent different ways of producing the matches (table scan, index match, etc.) |
|
846
|
|
|
|
|
|
|
my $self = shift; |
|
847
|
|
|
|
|
|
|
my $model = shift; |
|
848
|
|
|
|
|
|
|
my $active_graphs = shift; |
|
849
|
|
|
|
|
|
|
my $pattern = shift; |
|
850
|
|
|
|
|
|
|
my @vars = map { $_->value } $pattern->values_consuming_role('Attean::API::Variable'); |
|
851
|
|
|
|
|
|
|
my %vars; |
|
852
|
|
|
|
|
|
|
my $dup = 0; |
|
853
|
|
|
|
|
|
|
foreach my $v (@vars) { |
|
854
|
|
|
|
|
|
|
$dup++ if ($vars{$v}++); |
|
855
|
|
|
|
|
|
|
} |
|
856
|
|
|
|
|
|
|
|
|
857
|
100
|
|
|
100
|
1
|
227
|
my $distinct = 0; # TODO: is this pattern distinct? does it have blank nodes? |
|
858
|
100
|
|
|
|
|
152
|
|
|
859
|
100
|
|
|
|
|
146
|
my @nodes = $pattern->values; |
|
860
|
100
|
|
|
|
|
141
|
unless ($nodes[3]) { |
|
861
|
100
|
|
|
|
|
351
|
$nodes[3] = $active_graphs; |
|
|
144
|
|
|
|
|
1624
|
|
|
862
|
100
|
|
|
|
|
180
|
} |
|
863
|
100
|
|
|
|
|
135
|
my $plan = Attean::Plan::Quad->new( |
|
864
|
100
|
|
|
|
|
173
|
subject => $nodes[0], |
|
865
|
144
|
50
|
|
|
|
474
|
predicate => $nodes[1], |
|
866
|
|
|
|
|
|
|
object => $nodes[2], |
|
867
|
|
|
|
|
|
|
graph => $nodes[3], |
|
868
|
100
|
|
|
|
|
168
|
values => \@nodes, |
|
869
|
|
|
|
|
|
|
distinct => $distinct, |
|
870
|
100
|
|
|
|
|
246
|
ordered => [], |
|
871
|
100
|
50
|
|
|
|
275
|
); |
|
872
|
100
|
|
|
|
|
164
|
return $plan; |
|
873
|
|
|
|
|
|
|
} |
|
874
|
100
|
|
|
|
|
2031
|
|
|
875
|
|
|
|
|
|
|
=item C<< join_plans( $model, \@active_graphs, \@default_graphs, \@plan_left, \@plan_right, $type [, $expr] ) >> |
|
876
|
|
|
|
|
|
|
|
|
877
|
|
|
|
|
|
|
Returns a list of alternative plans for the join of one plan from C<< @plan_left >> |
|
878
|
|
|
|
|
|
|
and one plan from C<< @plan_right >>. The join C<< $type >> must be one of |
|
879
|
|
|
|
|
|
|
C<< 'inner' >>, C<< 'left' >>, or C<< 'minus' >>, indicating the join algorithm |
|
880
|
|
|
|
|
|
|
to be used. If C<< $type >> is C<< 'left' >>, then the optional C<< $expr >> |
|
881
|
|
|
|
|
|
|
may be used to supply a filter expression that should be used by the SPARQL |
|
882
|
|
|
|
|
|
|
left-join algorithm. |
|
883
|
100
|
|
|
|
|
10995
|
|
|
884
|
|
|
|
|
|
|
=cut |
|
885
|
|
|
|
|
|
|
|
|
886
|
|
|
|
|
|
|
# $lhs and $rhs are both Attean::API::Plan objects |
|
887
|
|
|
|
|
|
|
# Return a Attean::API::Plan object that represents the evaluation of $lhs ⋈ $rhs. |
|
888
|
|
|
|
|
|
|
# The $left and $minus flags indicate the type of the join to be performed (⟕ and ▷, respectively). |
|
889
|
|
|
|
|
|
|
# e.g. different plans might represent different join algorithms (nested loop join, hash join, etc.) or different orderings ($lhs ⋈ $rhs or $rhs ⋈ $lhs) |
|
890
|
|
|
|
|
|
|
my $self = shift; |
|
891
|
|
|
|
|
|
|
my $model = shift; |
|
892
|
|
|
|
|
|
|
my $active_graphs = shift; |
|
893
|
|
|
|
|
|
|
my $default_graphs = shift; |
|
894
|
|
|
|
|
|
|
my $lplans = shift; |
|
895
|
|
|
|
|
|
|
my $rplans = shift; |
|
896
|
|
|
|
|
|
|
my $type = shift; |
|
897
|
|
|
|
|
|
|
my $left = ($type eq 'left'); |
|
898
|
|
|
|
|
|
|
my $minus = ($type eq 'minus'); |
|
899
|
|
|
|
|
|
|
my $expr = shift; |
|
900
|
|
|
|
|
|
|
|
|
901
|
|
|
|
|
|
|
my @plans; |
|
902
|
263
|
|
|
263
|
1
|
1882
|
Carp::confess unless (reftype($lplans) eq 'ARRAY'); |
|
903
|
263
|
|
|
|
|
330
|
foreach my $lhs (@{ $lplans }) { |
|
904
|
263
|
|
|
|
|
328
|
foreach my $rhs (@{ $rplans }) { |
|
905
|
263
|
|
|
|
|
362
|
my @vars = (@{ $lhs->in_scope_variables }, @{ $rhs->in_scope_variables }); |
|
906
|
263
|
|
|
|
|
304
|
my %vars; |
|
907
|
263
|
|
|
|
|
322
|
my %join_vars; |
|
908
|
263
|
|
|
|
|
336
|
foreach my $v (@vars) { |
|
909
|
263
|
|
|
|
|
404
|
if ($vars{$v}++) { |
|
910
|
263
|
|
|
|
|
342
|
$join_vars{$v}++; |
|
911
|
263
|
|
|
|
|
351
|
} |
|
912
|
|
|
|
|
|
|
} |
|
913
|
263
|
|
|
|
|
307
|
my @join_vars = keys %join_vars; |
|
914
|
263
|
50
|
|
|
|
898
|
|
|
915
|
263
|
|
|
|
|
336
|
if ($left) { |
|
|
263
|
|
|
|
|
464
|
|
|
916
|
353
|
|
|
|
|
13296
|
if (scalar(@join_vars) > 0) { |
|
|
353
|
|
|
|
|
554
|
|
|
917
|
366
|
|
|
|
|
2485
|
push(@plans, Attean::Plan::HashJoin->new(children => [$lhs, $rhs], left => 1, expression => $expr, join_variables => \@join_vars, distinct => 0, ordered => [])); |
|
|
366
|
|
|
|
|
919
|
|
|
|
366
|
|
|
|
|
952
|
|
|
918
|
366
|
|
|
|
|
590
|
} |
|
919
|
|
|
|
|
|
|
push(@plans, Attean::Plan::NestedLoopJoin->new(children => [$lhs, $rhs], left => 1, expression => $expr, join_variables => \@join_vars, distinct => 0, ordered => $lhs->ordered)); |
|
920
|
366
|
|
|
|
|
523
|
} elsif ($minus) { |
|
921
|
1308
|
100
|
|
|
|
2408
|
# we can't use a hash join for MINUS queries, because of the definition of MINUS having a special case for compatible results that have disjoint domains |
|
922
|
363
|
|
|
|
|
574
|
push(@plans, Attean::Plan::NestedLoopJoin->new(children => [$lhs, $rhs], anti => 1, join_variables => \@join_vars, distinct => 0, ordered => $lhs->ordered)); |
|
923
|
|
|
|
|
|
|
} else { |
|
924
|
|
|
|
|
|
|
if (scalar(@join_vars) > 0) { |
|
925
|
366
|
|
|
|
|
855
|
# if there's shared variables (hopefully), we can also use a hash join |
|
926
|
|
|
|
|
|
|
push(@plans, Attean::Plan::HashJoin->new(children => [$lhs, $rhs], join_variables => \@join_vars, distinct => 0, ordered => [])); |
|
927
|
366
|
50
|
|
|
|
852
|
push(@plans, Attean::Plan::HashJoin->new(children => [$rhs, $lhs], join_variables => \@join_vars, distinct => 0, ordered => [])); |
|
|
|
50
|
|
|
|
|
|
|
928
|
0
|
0
|
|
|
|
0
|
# } else { |
|
929
|
0
|
|
|
|
|
0
|
# warn "No join vars for $lhs ⋈ $rhs"; |
|
930
|
|
|
|
|
|
|
} |
|
931
|
0
|
|
|
|
|
0
|
|
|
932
|
|
|
|
|
|
|
# nested loop joins work in all cases |
|
933
|
|
|
|
|
|
|
push(@plans, Attean::Plan::NestedLoopJoin->new(children => [$lhs, $rhs], join_variables => \@join_vars, distinct => 0, ordered => $lhs->ordered)); |
|
934
|
0
|
|
|
|
|
0
|
push(@plans, Attean::Plan::NestedLoopJoin->new(children => [$rhs, $lhs], join_variables => \@join_vars, distinct => 0, ordered => $rhs->ordered)); |
|
935
|
|
|
|
|
|
|
} |
|
936
|
366
|
100
|
|
|
|
751
|
} |
|
937
|
|
|
|
|
|
|
} |
|
938
|
311
|
|
|
|
|
5723
|
|
|
939
|
311
|
|
|
|
|
5480
|
return @plans; |
|
940
|
|
|
|
|
|
|
} |
|
941
|
|
|
|
|
|
|
|
|
942
|
|
|
|
|
|
|
|
|
943
|
|
|
|
|
|
|
my $self = shift; |
|
944
|
|
|
|
|
|
|
my %vars; |
|
945
|
366
|
|
|
|
|
7138
|
while (my $c = shift) { |
|
946
|
366
|
|
|
|
|
59758
|
my $expr = $c->expression; |
|
947
|
|
|
|
|
|
|
foreach my $v ($expr->in_scope_variables) { |
|
948
|
|
|
|
|
|
|
$vars{$v}++; |
|
949
|
|
|
|
|
|
|
} |
|
950
|
|
|
|
|
|
|
} |
|
951
|
263
|
|
|
|
|
37595
|
return keys %vars; |
|
952
|
|
|
|
|
|
|
} |
|
953
|
|
|
|
|
|
|
|
|
954
|
|
|
|
|
|
|
my $self = shift; |
|
955
|
|
|
|
|
|
|
my $cmps = shift; |
|
956
|
0
|
|
|
0
|
|
0
|
my @vars = @_; |
|
957
|
0
|
|
|
|
|
0
|
my %unseen = map { $_ => 1 } @vars; |
|
958
|
0
|
|
|
|
|
0
|
foreach my $c (@$cmps) { |
|
959
|
0
|
|
|
|
|
0
|
return 0 unless ($c->expression->is_stable); |
|
960
|
0
|
|
|
|
|
0
|
foreach my $v ($self->_comparator_referenced_variables($c)) { |
|
961
|
0
|
|
|
|
|
0
|
delete $unseen{$v}; |
|
962
|
|
|
|
|
|
|
} |
|
963
|
|
|
|
|
|
|
} |
|
964
|
0
|
|
|
|
|
0
|
my @keys = keys %unseen; |
|
965
|
|
|
|
|
|
|
return (scalar(@keys) == 0); |
|
966
|
|
|
|
|
|
|
} |
|
967
|
|
|
|
|
|
|
|
|
968
|
26
|
|
|
26
|
|
39
|
my $self = shift; |
|
969
|
26
|
|
|
|
|
31
|
my $algebra = shift; |
|
970
|
26
|
|
|
|
|
43
|
my ($exprs, $ascending, $svars); |
|
971
|
26
|
|
|
|
|
52
|
my @cmps = @{ $algebra->comparators }; |
|
|
78
|
|
|
|
|
129
|
|
|
972
|
26
|
|
|
|
|
53
|
my %ascending; |
|
973
|
0
|
0
|
|
|
|
0
|
my %exprs; |
|
974
|
0
|
|
|
|
|
0
|
my @svars; |
|
975
|
0
|
|
|
|
|
0
|
foreach my $i (0 .. $#cmps) { |
|
976
|
|
|
|
|
|
|
my $var = $self->new_temporary('order'); |
|
977
|
|
|
|
|
|
|
my $cmp = $cmps[$i]; |
|
978
|
26
|
|
|
|
|
57
|
push(@svars, $var); |
|
979
|
26
|
|
|
|
|
87
|
$ascending{$var} = $cmp->ascending; |
|
980
|
|
|
|
|
|
|
$exprs{$var} = $cmp->expression; |
|
981
|
|
|
|
|
|
|
} |
|
982
|
|
|
|
|
|
|
return (\%exprs, \%ascending, \@svars); |
|
983
|
3
|
|
|
3
|
|
8
|
} |
|
984
|
3
|
|
|
|
|
6
|
} |
|
985
|
3
|
|
|
|
|
7
|
|
|
986
|
3
|
|
|
|
|
7
|
1; |
|
|
3
|
|
|
|
|
13
|
|
|
987
|
3
|
|
|
|
|
11
|
|
|
988
|
|
|
|
|
|
|
|
|
989
|
3
|
|
|
|
|
0
|
=back |
|
990
|
3
|
|
|
|
|
11
|
|
|
991
|
3
|
|
|
|
|
11
|
=head1 BUGS |
|
992
|
3
|
|
|
|
|
11
|
|
|
993
|
3
|
|
|
|
|
9
|
Please report any bugs or feature requests to through the GitHub web interface |
|
994
|
3
|
|
|
|
|
13
|
at L<https://github.com/kasei/attean/issues>. |
|
995
|
3
|
|
|
|
|
13
|
|
|
996
|
|
|
|
|
|
|
=head1 SEE ALSO |
|
997
|
3
|
|
|
|
|
13
|
|
|
998
|
|
|
|
|
|
|
|
|
999
|
|
|
|
|
|
|
|
|
1000
|
|
|
|
|
|
|
=head1 AUTHOR |
|
1001
|
|
|
|
|
|
|
|
|
1002
|
|
|
|
|
|
|
Gregory Todd Williams C<< <gwilliams@cpan.org> >> |
|
1003
|
|
|
|
|
|
|
|
|
1004
|
|
|
|
|
|
|
=head1 COPYRIGHT |
|
1005
|
|
|
|
|
|
|
|
|
1006
|
|
|
|
|
|
|
Copyright (c) 2014--2022 Gregory Todd Williams. |
|
1007
|
|
|
|
|
|
|
This program is free software; you can redistribute it and/or modify it under |
|
1008
|
|
|
|
|
|
|
the same terms as Perl itself. |
|
1009
|
|
|
|
|
|
|
|
|
1010
|
|
|
|
|
|
|
=cut |