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