| 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 |