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 |