|  line  | 
 stmt  | 
 bran  | 
 cond  | 
 sub  | 
 pod  | 
 time  | 
 code  | 
| 
1
 | 
  
50
  
 | 
 
 | 
 
 | 
  
50
  
 | 
 
 | 
657
 | 
 use v5.14;  | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
154
 | 
    | 
| 
2
 | 
50
 | 
 
 | 
 
 | 
  
50
  
 | 
 
 | 
242
 | 
 use warnings;  | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
112
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
2277
 | 
    | 
| 
3
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head1 NAME  | 
| 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
6
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 Attean::API::IDPJoinPlanner - Iterative dynamic programming query planning role  | 
| 
7
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
8
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head1 VERSION  | 
| 
9
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
10
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 This document describes Attean::API::IDPJoinPlanner version 0.033  | 
| 
11
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
12
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head1 SYNOPSIS  | 
| 
13
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
14
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   extends 'Attean::QueryPlanner';  | 
| 
15
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
   with 'Attean::API::IDPJoinPlanner';  | 
| 
16
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
17
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head1 DESCRIPTION  | 
| 
18
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
19
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 The Attean::API::IDPJoinPlanner role provides a query planner the  | 
| 
20
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 C<< joins_for_plan_alternatives >> method, as well as the cost estimation  | 
| 
21
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 methods that consume the L<Attean::API::CostPlanner> role.  | 
| 
22
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
23
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head1 ATTRIBUTES  | 
| 
24
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
25
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =over 4  | 
| 
26
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
27
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =back  | 
| 
28
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
29
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head1 METHODS  | 
| 
30
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
31
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =over 4  | 
| 
32
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
33
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =cut  | 
| 
34
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
35
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	use Types::Standard qw(CodeRef Object);  | 
| 
36
 | 
50
 | 
 
 | 
 
 | 
  
50
  
 | 
 
 | 
275
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
87
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
381
 | 
    | 
| 
37
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	use Moo::Role;  | 
| 
38
 | 
50
 | 
 
 | 
 
 | 
  
50
  
 | 
 
 | 
26767
 | 
 	  | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
107
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
363
 | 
    | 
| 
39
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	requires 'plan_for_algebra'; # plan_for_algebra($algebra, $model, \@default_graphs)  | 
| 
40
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	has 'request_signer' => (is => 'rw', isa => Object);  | 
| 
41
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 }  | 
| 
42
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
43
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	use Scalar::Util qw(refaddr);  | 
| 
44
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	use Types::Standard qw(CodeRef);  | 
| 
45
 | 
50
 | 
 
 | 
 
 | 
  
50
  
 | 
 
 | 
19702
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
133
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
2685
 | 
    | 
| 
46
 | 
50
 | 
 
 | 
 
 | 
  
50
  
 | 
 
 | 
344
 | 
 	use Moo::Role;  | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
117
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
270
 | 
    | 
| 
47
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	use namespace::clean;  | 
| 
48
 | 
50
 | 
 
 | 
 
 | 
  
50
  
 | 
 
 | 
19461
 | 
 	with 'Attean::API::QueryPlanner';  | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
101
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
193
 | 
    | 
| 
49
 | 
50
 | 
 
 | 
 
 | 
  
50
  
 | 
 
 | 
15103
 | 
 	  | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
112
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
437
 | 
    | 
| 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	requires 'plans_for_algebra'; # plans_for_algebra($algebra, $model, \@active_graphs, \@default_graphs)  | 
| 
51
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	requires 'cost_for_plan'; # cost_for_plan($plan, $model)  | 
| 
52
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	  | 
| 
53
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	before 'cost_for_plan' => sub {  | 
| 
54
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		my $self	= shift;  | 
| 
55
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		my $plan	= shift;  | 
| 
56
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		my $model	= shift;  | 
| 
57
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		  | 
| 
58
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		if (refaddr($self) == refaddr($model)) {  | 
| 
59
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			Carp::confess "Model and planner objects cannot be the same in call to cost_for_plan";  | 
| 
60
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		} elsif ($self->does('Attean::API::Model') and $model->does('Attean::API::Model')) {  | 
| 
61
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			Carp::confess "Model and planner objects cannot both consume Attean::API::Model in call to cost_for_plan";  | 
| 
62
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		}  | 
| 
63
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	};  | 
| 
64
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	  | 
| 
65
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		my $self			= shift;  | 
| 
66
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		my $algebra			= shift;  | 
| 
67
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		my $model			= shift;  | 
| 
68
 | 
41
 | 
 
 | 
 
 | 
  
41
  
 | 
  
0
  
 | 
23810
 | 
 		my $default_graphs	= shift;  | 
| 
69
 | 
41
 | 
 
 | 
 
 | 
 
 | 
 
 | 
63
 | 
 		my $active_graphs	= $default_graphs;  | 
| 
70
 | 
41
 | 
 
 | 
 
 | 
 
 | 
 
 | 
66
 | 
 		my @plans			= sort { $self->cost_for_plan($a, $model) <=> $self->cost_for_plan($b, $model) } $self->plans_for_algebra($algebra, $model, $active_graphs, $default_graphs);  | 
| 
71
 | 
41
 | 
 
 | 
 
 | 
 
 | 
 
 | 
58
 | 
 		my $plan			= shift(@plans);  | 
| 
72
 | 
41
 | 
 
 | 
 
 | 
 
 | 
 
 | 
58
 | 
 		return $plan;  | 
| 
73
 | 
41
 | 
 
 | 
 
 | 
 
 | 
 
 | 
177
 | 
 	}  | 
| 
 
 | 
93
 | 
 
 | 
 
 | 
 
 | 
 
 | 
1517
 | 
    | 
| 
74
 | 
41
 | 
 
 | 
 
 | 
 
 | 
 
 | 
884
 | 
 }  | 
| 
75
 | 
41
 | 
 
 | 
 
 | 
 
 | 
 
 | 
278
 | 
    | 
| 
76
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	use Moo::Role;  | 
| 
77
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	requires 'joins_for_plan_alternatives';  | 
| 
78
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 }  | 
| 
79
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
80
 | 
50
 | 
 
 | 
 
 | 
  
50
  
 | 
 
 | 
39963
 | 
 	use Math::Cartesian::Product;  | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
109
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
205
 | 
    | 
| 
81
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
82
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	use Moo::Role;  | 
| 
83
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
84
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	with 'Attean::API::JoinPlanner';  | 
| 
85
 | 
50
 | 
 
 | 
 
 | 
  
50
  
 | 
 
 | 
16960
 | 
 	with 'Attean::API::QueryPlanner';  | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
141
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
2428
 | 
    | 
| 
86
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
87
 | 
50
 | 
 
 | 
 
 | 
  
50
  
 | 
 
 | 
302
 | 
 		my $self			= shift;  | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
100
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
247
 | 
    | 
| 
88
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		my $model			= shift;  | 
| 
89
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		my $active_graphs	= shift;  | 
| 
90
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		my $default_graphs	= shift;  | 
| 
91
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		my $interesting		= shift;  | 
| 
92
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		my @args			= @_; # each $args[$i] here is an array reference containing alternate plans for element $i  | 
| 
93
 | 
47
 | 
 
 | 
 
 | 
  
47
  
 | 
  
0
  
 | 
76
 | 
 		  | 
| 
94
 | 
47
 | 
 
 | 
 
 | 
 
 | 
 
 | 
61
 | 
 		my $plans	= shift(@args);  | 
| 
95
 | 
47
 | 
 
 | 
 
 | 
 
 | 
 
 | 
58
 | 
 		while (scalar(@args)) {  | 
| 
96
 | 
47
 | 
 
 | 
 
 | 
 
 | 
 
 | 
66
 | 
 			my $next	= shift(@args);  | 
| 
97
 | 
47
 | 
 
 | 
 
 | 
 
 | 
 
 | 
72
 | 
 			my @plans	= $self->join_plans($model, $active_graphs, $default_graphs, $plans, $next, 'inner');  | 
| 
98
 | 
47
 | 
 
 | 
 
 | 
 
 | 
 
 | 
77
 | 
 			$plans		= \@plans;  | 
| 
99
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		}  | 
| 
100
 | 
47
 | 
 
 | 
 
 | 
 
 | 
 
 | 
73
 | 
 		  | 
| 
101
 | 
47
 | 
 
 | 
 
 | 
 
 | 
 
 | 
112
 | 
 		my @plans	= @$plans;  | 
| 
102
 | 
35
 | 
 
 | 
 
 | 
 
 | 
 
 | 
55
 | 
 		return @plans;  | 
| 
103
 | 
35
 | 
 
 | 
 
 | 
 
 | 
 
 | 
183
 | 
 	}  | 
| 
104
 | 
35
 | 
 
 | 
 
 | 
 
 | 
 
 | 
119
 | 
 }  | 
| 
105
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
106
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	use Types::Standard qw(Int);  | 
| 
107
 | 
47
 | 
 
 | 
 
 | 
 
 | 
 
 | 
122
 | 
 	use Scalar::Util qw(blessed);  | 
| 
108
 | 
47
 | 
 
 | 
 
 | 
 
 | 
 
 | 
178
 | 
    | 
| 
109
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	use Moo::Role;  | 
| 
110
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
111
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	with 'Attean::API::CostPlanner';  | 
| 
112
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	with 'MooX::Log::Any';  | 
| 
113
 | 
50
 | 
 
 | 
 
 | 
  
50
  
 | 
 
 | 
22925
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
122
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
309
 | 
    | 
| 
114
 | 
50
 | 
 
 | 
 
 | 
  
50
  
 | 
 
 | 
21082
 | 
 	has 'keep' => (is => 'ro', isa => Int, default => 5);  | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
106
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
2061
 | 
    | 
| 
115
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	  | 
| 
116
 | 
50
 | 
 
 | 
 
 | 
  
50
  
 | 
 
 | 
280
 | 
 	around 'joins_for_plan_alternatives' => sub {  | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
139
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
218
 | 
    | 
| 
117
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		my $orig	= shift;  | 
| 
118
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		my $self	= shift;  | 
| 
119
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		my $model			= shift;  | 
| 
120
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		my @plans	= $orig->($self, $model, @_);  | 
| 
121
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		return $self->prune_plans($model, [], \@plans);  | 
| 
122
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	};  | 
| 
123
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	  | 
| 
124
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		my $self		= shift;  | 
| 
125
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		my $model		= shift;  | 
| 
126
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		my $interesting	= shift;  | 
| 
127
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		my @plans		= @{ shift || [] };  | 
| 
128
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		no  sort 'stable';  | 
| 
129
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		my @sorted	= map { $_->[1] } sort { $a->[0] <=> $b->[0] } map { [$self->cost_for_plan($_, $model), $_] } @plans;  | 
| 
130
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		  | 
| 
131
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		return ($self->keep) ? splice(@sorted, 0, $self->keep) : @sorted;  | 
| 
132
 | 
26
 | 
 
 | 
 
 | 
  
26
  
 | 
  
0
  
 | 
36
 | 
 	}  | 
| 
133
 | 
26
 | 
 
 | 
 
 | 
 
 | 
 
 | 
27
 | 
 	  | 
| 
134
 | 
26
 | 
 
 | 
 
 | 
 
 | 
 
 | 
29
 | 
 		my $self	= shift;  | 
| 
135
 | 
26
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
32
 | 
 		my $plan	= shift;  | 
| 
 
 | 
26
 | 
 
 | 
 
 | 
 
 | 
 
 | 
69
 | 
    | 
| 
136
 | 
50
 | 
 
 | 
 
 | 
  
50
  
 | 
 
 | 
48127
 | 
 		my $model	= shift;  | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
25294
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
281
 | 
    | 
| 
137
 | 
26
 | 
 
 | 
 
 | 
 
 | 
 
 | 
52
 | 
 		Carp::confess "No model given" unless (blessed($model) and $model->does('Attean::API::Model'));  | 
| 
 
 | 
691
 | 
 
 | 
 
 | 
 
 | 
 
 | 
834
 | 
    | 
| 
 
 | 
3668
 | 
 
 | 
 
 | 
 
 | 
 
 | 
3532
 | 
    | 
| 
 
 | 
691
 | 
 
 | 
 
 | 
 
 | 
 
 | 
9474
 | 
    | 
| 
138
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		  | 
| 
139
 | 
26
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
1428
 | 
 		if ($plan->has_cost) {  | 
| 
140
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			return $plan->cost;  | 
| 
141
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		} else {  | 
| 
142
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			if ($model->does('Attean::API::CostPlanner')) {  | 
| 
143
 | 
2082
 | 
 
 | 
 
 | 
  
2082
  
 | 
  
0
  
 | 
96453
 | 
 				if (defined(my $cost = $model->cost_for_plan($plan, $self))) {  | 
| 
144
 | 
2082
 | 
 
 | 
 
 | 
 
 | 
 
 | 
2096
 | 
 					$plan->cost($cost);  | 
| 
145
 | 
2082
 | 
 
 | 
 
 | 
 
 | 
 
 | 
2029
 | 
 					$self->log->info('Model \''.ref($model).'\' did cost planning for \''.ref($plan).'\' and got cost '.$cost);  | 
| 
146
 | 
2082
 | 
  
 50
  
 | 
  
 33
  
 | 
 
 | 
 
 | 
6619
 | 
 					return $cost;  | 
| 
147
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				}  | 
| 
148
 | 
2082
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
25749
 | 
 			}  | 
| 
149
 | 
1283
 | 
 
 | 
 
 | 
 
 | 
 
 | 
15814
 | 
    | 
| 
150
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			my $cost	= 1;  | 
| 
151
 | 
799
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
1161
 | 
 			my @children	= @{ $plan->children };  | 
| 
152
 | 
799
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
19151
 | 
 			if ($plan->isa('Attean::Plan::Quad')) {  | 
| 
153
 | 
27
 | 
 
 | 
 
 | 
 
 | 
 
 | 
419
 | 
 				my @vars	= map { $_->value } grep { blessed($_) and $_->does('Attean::API::Variable') } $plan->values;  | 
| 
154
 | 
27
 | 
 
 | 
 
 | 
 
 | 
 
 | 
905
 | 
 				return scalar(@vars);  | 
| 
155
 | 
27
 | 
 
 | 
 
 | 
 
 | 
 
 | 
328
 | 
 			} elsif ($plan->isa('Attean::Plan::Table')) {  | 
| 
156
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				my $rows	= $plan->rows;  | 
| 
157
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				$cost		= scalar(@$rows);  | 
| 
158
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			} elsif ($plan->isa('Attean::Plan::NestedLoopJoin')) {  | 
| 
159
 | 
772
 | 
 
 | 
 
 | 
 
 | 
 
 | 
4788
 | 
 				my $lcost		= $self->cost_for_plan($children[0], $model);  | 
| 
160
 | 
772
 | 
 
 | 
 
 | 
 
 | 
 
 | 
774
 | 
 				my $rcost		= $self->cost_for_plan($children[1], $model);  | 
| 
 
 | 
772
 | 
 
 | 
 
 | 
 
 | 
 
 | 
1862
 | 
    | 
| 
161
 | 
772
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
3515
 | 
 				if ($lcost == 0) {  | 
| 
 
 | 
 
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
 
 | 
 
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
 
 | 
 
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
 
 | 
 
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
 
 | 
 
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
162
 | 
  
0
  
 | 
  
  0
  
 | 
 
 | 
 
 | 
 
 | 
0
 | 
 					$cost	= $rcost;  | 
| 
 
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
    | 
| 
 
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
    | 
| 
163
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
 				} elsif ($rcost == 0) {  | 
| 
164
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 					$cost	= $lcost;  | 
| 
165
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
 				} else {  | 
| 
166
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
 					$cost	= $lcost * $rcost;  | 
| 
167
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				}  | 
| 
168
 | 
408
 | 
 
 | 
 
 | 
 
 | 
 
 | 
6196
 | 
 				  | 
| 
169
 | 
408
 | 
 
 | 
 
 | 
 
 | 
 
 | 
9720
 | 
 				# a cartesian nested loop join is bad, but the algorithm already  | 
| 
170
 | 
408
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
4862
 | 
 				# has to check for all possible joins, so it's not as bad as  | 
| 
 
 | 
 
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
171
 | 
26
 | 
 
 | 
 
 | 
 
 | 
 
 | 
30
 | 
 				# a cartesian hash join (below)  | 
| 
172
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				$cost	*= 10 unless ($plan->children_are_variable_connected);  | 
| 
173
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
 			} elsif ($plan->isa('Attean::Plan::HashJoin')) {  | 
| 
174
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				my $joined		= $plan->children_are_variable_connected;  | 
| 
175
 | 
382
 | 
 
 | 
 
 | 
 
 | 
 
 | 
811
 | 
 				my $lcost		= $self->cost_for_plan($children[0], $model);  | 
| 
176
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				my $rcost		= $self->cost_for_plan($children[1], $model);  | 
| 
177
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				$cost	= ($lcost + $rcost);  | 
| 
178
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				$cost += ($lcost < $rcost); # To let the plan with cheaper rhs win  | 
| 
179
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				$cost	*= 100 unless ($plan->children_are_variable_connected);  | 
| 
180
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			} elsif ($plan->isa('Attean::Plan::Service')) {  | 
| 
181
 | 
408
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
1005
 | 
 				my $scost	= 10;  | 
| 
182
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				foreach my $c (@{ $plan->children }) {  | 
| 
183
 | 
356
 | 
 
 | 
 
 | 
 
 | 
 
 | 
805
 | 
 					$scost	+= $self->cost_for_plan($c, $model);  | 
| 
184
 | 
356
 | 
 
 | 
 
 | 
 
 | 
 
 | 
6046
 | 
 				}  | 
| 
185
 | 
356
 | 
 
 | 
 
 | 
 
 | 
 
 | 
8555
 | 
 				$cost	= 5 * $scost;  | 
| 
186
 | 
356
 | 
 
 | 
 
 | 
 
 | 
 
 | 
4225
 | 
 			} elsif ($plan->isa('Attean::Plan::Unique')) {  | 
| 
187
 | 
356
 | 
 
 | 
 
 | 
 
 | 
 
 | 
386
 | 
 				$cost	= 0; # consider a filter on the iterator (like unique) to be essentially free  | 
| 
188
 | 
356
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
764
 | 
 				foreach my $c (@{ $plan->children }) {  | 
| 
189
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 					$cost	+= $self->cost_for_plan($c, $model);  | 
| 
190
 | 
3
 | 
 
 | 
 
 | 
 
 | 
 
 | 
6
 | 
 				}  | 
| 
191
 | 
3
 | 
 
 | 
 
 | 
 
 | 
 
 | 
4
 | 
 			} else {  | 
| 
 
 | 
3
 | 
 
 | 
 
 | 
 
 | 
 
 | 
8
 | 
    | 
| 
192
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
 				foreach my $c (@{ $plan->children }) {  | 
| 
193
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 					$cost	+= $self->cost_for_plan($c, $model);  | 
| 
194
 | 
3
 | 
 
 | 
 
 | 
 
 | 
 
 | 
6
 | 
 				}  | 
| 
195
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			}  | 
| 
196
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
 			  | 
| 
197
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
 			$plan->cost($cost);  | 
| 
 
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
    | 
| 
198
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
 			if ($self->log->is_trace) {  | 
| 
199
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				$self->log->trace("Cost $cost estimated for\n".$plan->as_string);  | 
| 
200
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			} else {  | 
| 
201
 | 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
6
 | 
 				$self->log->debug('Estimated cost for \''.ref($plan).'\' is '.$cost);  | 
| 
 
 | 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
9
 | 
    | 
| 
202
 | 
5
 | 
 
 | 
 
 | 
 
 | 
 
 | 
72
 | 
 			}  | 
| 
203
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			return $cost;  | 
| 
204
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		}  | 
| 
205
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	}  | 
| 
206
 | 
772
 | 
 
 | 
 
 | 
 
 | 
 
 | 
10501
 | 
 }  | 
| 
207
 | 
772
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
22746
 | 
    | 
| 
208
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
 	use Encode qw(encode);  | 
| 
209
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	use Attean::RDF;  | 
| 
210
 | 
772
 | 
 
 | 
 
 | 
 
 | 
 
 | 
15894
 | 
 	use LWP::UserAgent;  | 
| 
211
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	use Scalar::Util qw(blessed reftype);  | 
| 
212
 | 
772
 | 
 
 | 
 
 | 
 
 | 
 
 | 
7835
 | 
 	use List::Util qw(reduce);  | 
| 
213
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	use List::MoreUtils qw(all any);  | 
| 
214
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	use Types::Standard qw(Int ConsumerOf InstanceOf);  | 
| 
215
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	use URI::Escape;  | 
| 
216
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	use Algorithm::Combinatorics qw(subsets);  | 
| 
217
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	use List::Util qw(min);  | 
| 
218
 | 
50
 | 
 
 | 
 
 | 
  
50
  
 | 
 
 | 
28051
 | 
 	use Math::Cartesian::Product;  | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
117
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
2926
 | 
    | 
| 
219
 | 
50
 | 
 
 | 
 
 | 
  
50
  
 | 
 
 | 
329
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
110
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
449
 | 
    | 
| 
220
 | 
50
 | 
 
 | 
 
 | 
  
50
  
 | 
 
 | 
36200
 | 
 	use Moo::Role;  | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
99
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
1039
 | 
    | 
| 
221
 | 
50
 | 
 
 | 
 
 | 
  
50
  
 | 
 
 | 
228
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
97
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
2120
 | 
    | 
| 
222
 | 
50
 | 
 
 | 
 
 | 
  
50
  
 | 
 
 | 
295
 | 
 	with 'Attean::API::JoinPlanner';  | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
98
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
2691
 | 
    | 
| 
223
 | 
50
 | 
 
 | 
 
 | 
  
50
  
 | 
 
 | 
322
 | 
 	with 'Attean::API::SimpleCostPlanner';  | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
97
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
347
 | 
    | 
| 
224
 | 
50
 | 
 
 | 
 
 | 
  
50
  
 | 
 
 | 
47565
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
102
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
314
 | 
    | 
| 
225
 | 
50
 | 
 
 | 
 
 | 
  
50
  
 | 
 
 | 
30940
 | 
 		my $self			= shift;  | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
109
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
2446
 | 
    | 
| 
226
 | 
50
 | 
 
 | 
 
 | 
  
50
  
 | 
 
 | 
25536
 | 
 		my $model			= shift;  | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
157663
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
3298
 | 
    | 
| 
227
 | 
50
 | 
 
 | 
 
 | 
  
50
  
 | 
 
 | 
456
 | 
 		my $active_graphs	= shift;  | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
115
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
2073
 | 
    | 
| 
228
 | 
50
 | 
 
 | 
 
 | 
  
50
  
 | 
 
 | 
281
 | 
 		my $default_graphs	= shift;  | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
108
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
1610
 | 
    | 
| 
229
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		my $interesting		= shift;  | 
| 
230
 | 
50
 | 
 
 | 
 
 | 
  
50
  
 | 
 
 | 
268
 | 
 		my @args			= @_; # each $args[$i] here is an array reference containing alternate plans for element $i  | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
113
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
360
 | 
    | 
| 
231
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
232
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		my $k				= 3; # this is the batch size over which to do full dynamic programming  | 
| 
233
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		  | 
| 
234
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		# initialize $optPlan{$i} to be a set of alternate plans for evaluating element $i  | 
| 
235
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		my %optPlan;  | 
| 
236
 | 
25
 | 
 
 | 
 
 | 
  
25
  
 | 
  
0
  
 | 
32
 | 
 		foreach my $i (0 .. $#args) {  | 
| 
237
 | 
25
 | 
 
 | 
 
 | 
 
 | 
 
 | 
31
 | 
 			$optPlan{$i}	= [$self->prune_plans($model, $interesting, $args[$i])];  | 
| 
238
 | 
25
 | 
 
 | 
 
 | 
 
 | 
 
 | 
31
 | 
 		}  | 
| 
239
 | 
25
 | 
 
 | 
 
 | 
 
 | 
 
 | 
31
 | 
 		  | 
| 
240
 | 
25
 | 
 
 | 
 
 | 
 
 | 
 
 | 
31
 | 
 		my @todo	= (0 .. $#args); # initialize the todo list to all elements  | 
| 
241
 | 
25
 | 
 
 | 
 
 | 
 
 | 
 
 | 
43
 | 
 		my $next_symbol	= 'a'; # when we start batching together sub-plans, we'll rename them with letters (e.g. elements 1, 2, and 4 might become 'a', and then 3, 5, and 'a' become 'b')  | 
| 
242
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		  | 
| 
243
 | 
25
 | 
 
 | 
 
 | 
 
 | 
 
 | 
35
 | 
 		# until we've joined all the elements in todo and are left with a set of plans for the join of all elements  | 
| 
244
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		while (scalar(@todo) > 1) {  | 
| 
245
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			$k	= ($k < scalar(@todo)) ? $k : scalar(@todo); # in case we're joining fewer than the batch size  | 
| 
246
 | 
25
 | 
 
 | 
 
 | 
 
 | 
 
 | 
29
 | 
 			foreach my $i (2 .. $k) { # we've already initialized plans for evaluating single elements; now consider plans for groups of elements (with group sizes 2, 3, ..., $k)  | 
| 
247
 | 
25
 | 
 
 | 
 
 | 
 
 | 
 
 | 
57
 | 
 				foreach my $s (subsets(\@todo, $i)) { # pick a subset of size $i of the elements that need to be planned  | 
| 
248
 | 
39
 | 
 
 | 
 
 | 
 
 | 
 
 | 
109
 | 
 					my $s_key	= join('.', sort @$s);  | 
| 
249
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 					$optPlan{$s_key}	= [];  | 
| 
250
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 					foreach my $o (subsets($s)) { # partition the subset s into two (o and not_o)  | 
| 
251
 | 
25
 | 
 
 | 
 
 | 
 
 | 
 
 | 
60
 | 
 						next if (scalar(@$o) == 0); # only consider proper, non-empty subsets  | 
| 
252
 | 
25
 | 
 
 | 
 
 | 
 
 | 
 
 | 
74
 | 
 						next if (scalar(@$o) == scalar(@$s)); # only consider proper, non-empty subsets  | 
| 
253
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 						my $o_key	= join('.', sort @$o);  | 
| 
254
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 						my %o		= map { $_ => 1 } @$o;  | 
| 
255
 | 
25
 | 
 
 | 
 
 | 
 
 | 
 
 | 
55
 | 
 						my $not_o_key	= join('.', sort grep { not exists $o{$_} } @$s);  | 
| 
256
 | 
13
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
32
 | 
 						  | 
| 
257
 | 
13
 | 
 
 | 
 
 | 
 
 | 
 
 | 
33
 | 
 						my $lhs		= $optPlan{$o_key}; # get the plans for evaluating o  | 
| 
258
 | 
14
 | 
 
 | 
 
 | 
 
 | 
 
 | 
66
 | 
 						my $rhs		= $optPlan{$not_o_key}; # get the plans for evaluating not_o  | 
| 
259
 | 
16
 | 
 
 | 
 
 | 
 
 | 
 
 | 
1148
 | 
 						  | 
| 
260
 | 
16
 | 
 
 | 
 
 | 
 
 | 
 
 | 
37
 | 
 						# compute and store all the possible ways to evaluate s (o ⋈ not_o)  | 
| 
261
 | 
16
 | 
 
 | 
 
 | 
 
 | 
 
 | 
46
 | 
 						push(@{ $optPlan{$s_key} }, $self->join_plans($model, $active_graphs, $default_graphs, $lhs, $rhs, 'inner'));  | 
| 
262
 | 
68
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
1298
 | 
 						$optPlan{$s_key}	= [$self->prune_plans($model, $interesting, $optPlan{$s_key})];  | 
| 
263
 | 
52
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
95
 | 
 					}  | 
| 
264
 | 
36
 | 
 
 | 
 
 | 
 
 | 
 
 | 
88
 | 
 				}  | 
| 
265
 | 
36
 | 
 
 | 
 
 | 
 
 | 
 
 | 
62
 | 
 			}  | 
| 
 
 | 
39
 | 
 
 | 
 
 | 
 
 | 
 
 | 
102
 | 
    | 
| 
266
 | 
36
 | 
 
 | 
 
 | 
 
 | 
 
 | 
66
 | 
 			  | 
| 
 
 | 
78
 | 
 
 | 
 
 | 
 
 | 
 
 | 
145
 | 
    | 
| 
267
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			# find the minimum cost plan $p that computes the join over $k elements (the elements end up in @v)  | 
| 
268
 | 
36
 | 
 
 | 
 
 | 
 
 | 
 
 | 
58
 | 
 			my %min_plans;  | 
| 
269
 | 
36
 | 
 
 | 
 
 | 
 
 | 
 
 | 
42
 | 
 			foreach my $w (subsets(\@todo, $k)) {  | 
| 
270
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				my $w_key		= join('.', sort @$w);  | 
| 
271
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				my $plans		= $optPlan{$w_key};  | 
| 
272
 | 
36
 | 
 
 | 
 
 | 
 
 | 
 
 | 
42
 | 
 				my @costs		= map { $self->cost_for_plan($_, $model) => [$_, $w] } @$plans;  | 
| 
 
 | 
36
 | 
 
 | 
 
 | 
 
 | 
 
 | 
152
 | 
    | 
| 
273
 | 
36
 | 
 
 | 
 
 | 
 
 | 
 
 | 
133
 | 
 				my %costs		= @costs;  | 
| 
274
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				my $min			= min keys %costs;  | 
| 
275
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				my @min_plans;  | 
| 
276
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				while (my ($cost, $data) = splice(@costs, 0, 2)) {  | 
| 
277
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 					if ($cost == $min) {  | 
| 
278
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 						push(@min_plans, $data);  | 
| 
279
 | 
13
 | 
 
 | 
 
 | 
 
 | 
 
 | 
28
 | 
 					}  | 
| 
280
 | 
13
 | 
 
 | 
 
 | 
 
 | 
 
 | 
43
 | 
 				}  | 
| 
281
 | 
13
 | 
 
 | 
 
 | 
 
 | 
 
 | 
830
 | 
 				$min_plans{ $min }	= \@min_plans;  | 
| 
282
 | 
13
 | 
 
 | 
 
 | 
 
 | 
 
 | 
24
 | 
 			}  | 
| 
283
 | 
13
 | 
 
 | 
 
 | 
 
 | 
 
 | 
25
 | 
 			my $min_cost	= min keys %min_plans;  | 
| 
 
 | 
60
 | 
 
 | 
 
 | 
 
 | 
 
 | 
987
 | 
    | 
| 
284
 | 
13
 | 
 
 | 
 
 | 
 
 | 
 
 | 
116
 | 
 			my $min_plans	= $min_plans{$min_cost};  | 
| 
285
 | 
13
 | 
 
 | 
 
 | 
 
 | 
 
 | 
91
 | 
 			my @min_plans;  | 
| 
286
 | 
13
 | 
 
 | 
 
 | 
 
 | 
 
 | 
27
 | 
 			my $min_key;  | 
| 
287
 | 
13
 | 
 
 | 
 
 | 
 
 | 
 
 | 
46
 | 
 			foreach my $d (@$min_plans) {  | 
| 
288
 | 
60
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
97
 | 
 				my ($p, $v)		= @$d;  | 
| 
289
 | 
51
 | 
 
 | 
 
 | 
 
 | 
 
 | 
102
 | 
 				my $v_key		= join('.', sort @$v);  | 
| 
290
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				if (not(defined($min_key)) or $min_key eq $v_key) {  | 
| 
291
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 					push(@min_plans, $p);  | 
| 
292
 | 
13
 | 
 
 | 
 
 | 
 
 | 
 
 | 
44
 | 
 					$min_key	= $v_key;  | 
| 
293
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				}  | 
| 
294
 | 
13
 | 
 
 | 
 
 | 
 
 | 
 
 | 
49
 | 
 			}  | 
| 
295
 | 
13
 | 
 
 | 
 
 | 
 
 | 
 
 | 
22
 | 
 # 			my ($p, $v)		= @$min_plan;  | 
| 
296
 | 
13
 | 
 
 | 
 
 | 
 
 | 
 
 | 
20
 | 
 # 			my $v_key		= join('.', sort @$v);  | 
| 
297
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # 			warn "Choosing join for $v_key\n";  | 
| 
298
 | 
13
 | 
 
 | 
 
 | 
 
 | 
 
 | 
45
 | 
 			  | 
| 
299
 | 
51
 | 
 
 | 
 
 | 
 
 | 
 
 | 
79
 | 
 			# generate a new symbol $t to stand in for $p, the join over the elements in @v  | 
| 
300
 | 
51
 | 
 
 | 
 
 | 
 
 | 
 
 | 
92
 | 
 			my $t	= $next_symbol++;  | 
| 
301
 | 
51
 | 
  
 50
  
 | 
  
 66
  
 | 
 
 | 
 
 | 
136
 | 
 			  | 
| 
302
 | 
51
 | 
 
 | 
 
 | 
 
 | 
 
 | 
59
 | 
 			# remove elements in @v from the todo list, and replace them by the new composite element $t  | 
| 
303
 | 
51
 | 
 
 | 
 
 | 
 
 | 
 
 | 
65
 | 
 			$optPlan{$t}	= [@min_plans];  | 
| 
304
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			my %v	= map { $_ => 1 } split(/[.]/, $min_key);  | 
| 
305
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			push(@todo, $t);  | 
| 
306
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			@todo	= grep { not exists $v{$_} } @todo;  | 
| 
307
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			  | 
| 
308
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			# also remove subsets of @v from the optPlan hash as they are now covered by $optPlan{$t}  | 
| 
309
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			foreach my $o (subsets([keys %v])) {  | 
| 
310
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				my $o_key	= join('.', sort @$o);  | 
| 
311
 | 
13
 | 
 
 | 
 
 | 
 
 | 
 
 | 
48
 | 
 # 				warn "deleting $o_key\n";  | 
| 
312
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				delete $optPlan{$o_key};  | 
| 
313
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			}  | 
| 
314
 | 
13
 | 
 
 | 
 
 | 
 
 | 
 
 | 
39
 | 
 		}  | 
| 
315
 | 
13
 | 
 
 | 
 
 | 
 
 | 
 
 | 
48
 | 
 		  | 
| 
 
 | 
27
 | 
 
 | 
 
 | 
 
 | 
 
 | 
99
 | 
    | 
| 
316
 | 
13
 | 
 
 | 
 
 | 
 
 | 
 
 | 
27
 | 
 		my $final_key	= join('.', sort @todo);  | 
| 
317
 | 
13
 | 
 
 | 
 
 | 
 
 | 
 
 | 
26
 | 
 # 		use Data::Dumper;  | 
| 
 
 | 
40
 | 
 
 | 
 
 | 
 
 | 
 
 | 
73
 | 
    | 
| 
318
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # 		warn Dumper($optPlan{$final_key});  | 
| 
319
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		return $self->prune_plans($model, $interesting, $optPlan{$final_key});  | 
| 
320
 | 
13
 | 
 
 | 
 
 | 
 
 | 
 
 | 
58
 | 
 	}  | 
| 
321
 | 
56
 | 
 
 | 
 
 | 
 
 | 
 
 | 
1019
 | 
 	  | 
| 
322
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		my $self		= shift;  | 
| 
323
 | 
56
 | 
 
 | 
 
 | 
 
 | 
 
 | 
174
 | 
 		my $model		= shift;  | 
| 
324
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		my $interesting	= shift;  | 
| 
325
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		my @plans		= @{ shift || [] };  | 
| 
326
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		no  sort 'stable';  | 
| 
327
 | 
25
 | 
 
 | 
 
 | 
 
 | 
 
 | 
63
 | 
 		my @sorted	= map { $_->[1] } sort { $a->[0] <=> $b->[0] } map { [$self->cost_for_plan($_, $model), $_] } @plans;  | 
| 
328
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		if ($self->log->is_trace) {  | 
| 
329
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			$self->log->trace('============= Plan iteration separator ==============');  | 
| 
330
 | 
25
 | 
 
 | 
 
 | 
 
 | 
 
 | 
74
 | 
 			foreach my $plan (@sorted){  | 
| 
331
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				$self->log->trace("Cost: " . $self->cost_for_plan($plan, $model) . " for plan:\n". $plan->as_string);  | 
| 
332
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			}  | 
| 
333
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		}  | 
| 
334
 | 
125
 | 
 
 | 
 
 | 
  
125
  
 | 
  
0
  
 | 
171
 | 
 		return splice(@sorted, 0, 5);  | 
| 
335
 | 
125
 | 
 
 | 
 
 | 
 
 | 
 
 | 
131
 | 
 	}  | 
| 
336
 | 
125
 | 
 
 | 
 
 | 
 
 | 
 
 | 
140
 | 
 	  | 
| 
337
 | 
125
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
154
 | 
 	# Return a cost value for $plan. This value is basically opaque, except  | 
| 
 
 | 
125
 | 
 
 | 
 
 | 
 
 | 
 
 | 
279
 | 
    | 
| 
338
 | 
50
 | 
 
 | 
 
 | 
  
50
  
 | 
 
 | 
53625
 | 
 	# that it will be used to sort plans by cost when determining which is the  | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
126
 | 
    | 
| 
 
 | 
50
 | 
 
 | 
 
 | 
 
 | 
 
 | 
356
 | 
    | 
| 
339
 | 
125
 | 
 
 | 
 
 | 
 
 | 
 
 | 
193
 | 
 	# cheapest plan to evaluate.  | 
| 
 
 | 
470
 | 
 
 | 
 
 | 
 
 | 
 
 | 
749
 | 
    | 
| 
 
 | 
810
 | 
 
 | 
 
 | 
 
 | 
 
 | 
1008
 | 
    | 
| 
 
 | 
470
 | 
 
 | 
 
 | 
 
 | 
 
 | 
7056
 | 
    | 
| 
340
 | 
125
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
1652
 | 
 		my $self	= shift;  | 
| 
341
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
 		my $plan	= shift;  | 
| 
342
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
 		my $model	= shift;  | 
| 
343
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
 		Carp::confess "No model given" unless (blessed($model) and $model->does('Attean::API::Model'));  | 
| 
344
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		  | 
| 
345
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		if ($plan->has_cost) {  | 
| 
346
 | 
125
 | 
 
 | 
 
 | 
 
 | 
 
 | 
2054
 | 
 			return $plan->cost;  | 
| 
347
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		} else {  | 
| 
348
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			if ($model->does('Attean::API::CostPlanner')) {  | 
| 
349
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				if (defined(my $cost = $model->cost_for_plan($plan, $self))) {  | 
| 
350
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 					$plan->cost($cost);  | 
| 
351
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 					$self->log->info('Model \''.ref($model).'\' did cost planning for \''.ref($plan).'\' and got cost '.$cost);  | 
| 
352
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 					return $cost;  | 
| 
353
 | 
1075
 | 
 
 | 
 
 | 
  
1075
  
 | 
  
0
  
 | 
33637
 | 
 				}  | 
| 
354
 | 
1075
 | 
 
 | 
 
 | 
 
 | 
 
 | 
1185
 | 
 			}  | 
| 
355
 | 
1075
 | 
 
 | 
 
 | 
 
 | 
 
 | 
1037
 | 
    | 
| 
356
 | 
1075
 | 
  
 50
  
 | 
  
 33
  
 | 
 
 | 
 
 | 
3261
 | 
 			my $cost	= 1;  | 
| 
357
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			my @children	= @{ $plan->children };  | 
| 
358
 | 
1075
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
12717
 | 
 			if ($plan->isa('Attean::Plan::Quad')) {  | 
| 
359
 | 
796
 | 
 
 | 
 
 | 
 
 | 
 
 | 
9668
 | 
 				my @vars	= map { $_->value } grep { blessed($_) and $_->does('Attean::API::Variable') } $plan->values;  | 
| 
360
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				# This gives a cost increasing at a reasonable pace  | 
| 
361
 | 
279
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
482
 | 
 				$cost	= $self->_hsp_heuristic_triple_sum($plan) * scalar(@vars);  | 
| 
362
 | 
279
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
6419
 | 
 			} elsif ($plan->isa('Attean::Plan::Table')) {  | 
| 
363
 | 
29
 | 
 
 | 
 
 | 
 
 | 
 
 | 
557
 | 
 				my $rows	= $plan->rows;  | 
| 
364
 | 
29
 | 
 
 | 
 
 | 
 
 | 
 
 | 
986
 | 
 				$cost		= scalar(@$rows);  | 
| 
365
 | 
29
 | 
 
 | 
 
 | 
 
 | 
 
 | 
387
 | 
 			} elsif ($plan->isa('Attean::Plan::NestedLoopJoin')) {  | 
| 
366
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				my $lcost		= $self->cost_for_plan($children[0], $model);  | 
| 
367
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				my $rcost		= $self->cost_for_plan($children[1], $model);  | 
| 
368
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				if ($lcost == 0) {  | 
| 
369
 | 
250
 | 
 
 | 
 
 | 
 
 | 
 
 | 
1595
 | 
 					$cost	= $rcost;  | 
| 
370
 | 
250
 | 
 
 | 
 
 | 
 
 | 
 
 | 
257
 | 
 				} elsif ($rcost == 0) {  | 
| 
 
 | 
250
 | 
 
 | 
 
 | 
 
 | 
 
 | 
712
 | 
    | 
| 
371
 | 
250
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
1200
 | 
 					$cost	= $lcost;  | 
| 
 
 | 
 
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
 
 | 
 
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
 
 | 
 
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
 
 | 
 
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
 
 | 
 
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
372
 | 
4
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
12
 | 
 				} else {  | 
| 
 
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
14
 | 
    | 
| 
 
 | 
16
 | 
 
 | 
 
 | 
 
 | 
 
 | 
215
 | 
    | 
| 
373
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 					my $mult = $self->_penalize_joins($plan);  | 
| 
374
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
26
 | 
 # 					warn "$mult * ($lcost * $rcost) [$children[0] $children[1]]";  | 
| 
375
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 					$cost	= $mult * $lcost * $rcost;  | 
| 
376
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
 				}  | 
| 
377
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
 			} elsif ($plan->isa('Attean::Plan::HashJoin')) {  | 
| 
378
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				my $lcost		= $self->cost_for_plan($children[0], $model);  | 
| 
379
 | 
124
 | 
 
 | 
 
 | 
 
 | 
 
 | 
1827
 | 
 				my $rcost		= $self->cost_for_plan($children[1], $model);  | 
| 
380
 | 
124
 | 
 
 | 
 
 | 
 
 | 
 
 | 
1975
 | 
 				if ($lcost == 0) {  | 
| 
381
 | 
124
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
634
 | 
 					$cost	= $rcost;  | 
| 
 
 | 
 
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
382
 | 
2
 | 
 
 | 
 
 | 
 
 | 
 
 | 
9
 | 
 				} elsif ($rcost == 0) {  | 
| 
383
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 					$cost	= $lcost;  | 
| 
384
 | 
2
 | 
 
 | 
 
 | 
 
 | 
 
 | 
3
 | 
 				} else {  | 
| 
385
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 					my $mult = $self->_penalize_joins($plan);  | 
| 
386
 | 
120
 | 
 
 | 
 
 | 
 
 | 
 
 | 
209
 | 
 # 					warn "$mult * ($lcost + $rcost)";  | 
| 
387
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 					$cost	= $mult * ($lcost + $rcost);  | 
| 
388
 | 
120
 | 
 
 | 
 
 | 
 
 | 
 
 | 
175
 | 
 					$cost += ($lcost < $rcost); # To let the plan with cheaper rhs win  | 
| 
389
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				}  | 
| 
390
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			} elsif ($plan->isa('Attean::Plan::Service')) {  | 
| 
391
 | 
104
 | 
 
 | 
 
 | 
 
 | 
 
 | 
1494
 | 
 				my $scost	= 10;  | 
| 
392
 | 
104
 | 
 
 | 
 
 | 
 
 | 
 
 | 
1661
 | 
 				foreach my $c (@{ $plan->children }) {  | 
| 
393
 | 
104
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
532
 | 
 					$scost	+= $self->cost_for_plan($c, $model);  | 
| 
 
 | 
 
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
394
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
 				}  | 
| 
395
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				$cost	= 5 * $scost;  | 
| 
396
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
 			} elsif ($plan->isa('Attean::Plan::Unique')) {  | 
| 
397
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				$cost	= 0; # consider a filter on the iterator (like unique) to be essentially free  | 
| 
398
 | 
104
 | 
 
 | 
 
 | 
 
 | 
 
 | 
196
 | 
 				foreach my $c (@{ $plan->children }) {  | 
| 
399
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 					$cost	+= $self->cost_for_plan($c, $model);  | 
| 
400
 | 
104
 | 
 
 | 
 
 | 
 
 | 
 
 | 
155
 | 
 				}  | 
| 
401
 | 
104
 | 
 
 | 
 
 | 
 
 | 
 
 | 
149
 | 
 			} else {  | 
| 
402
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				foreach my $c (@{ $plan->children }) {  | 
| 
403
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 					$cost	+= $self->cost_for_plan($c, $model);  | 
| 
404
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
1
 | 
 				}  | 
| 
405
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
2
 | 
 			}  | 
| 
 
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
3
 | 
    | 
| 
406
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
 			  | 
| 
407
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			# Costs must be integers for comparisons to work in the IDP planning algorithm  | 
| 
408
 | 
1
 | 
 
 | 
 
 | 
 
 | 
 
 | 
4
 | 
 			$cost	= int($cost);  | 
| 
409
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			$plan->cost($cost);  | 
| 
410
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
 			return $cost;  | 
| 
411
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
 		}  | 
| 
 
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
    | 
| 
412
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
 	}  | 
| 
413
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	  | 
| 
414
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	  | 
| 
415
 | 
17
 | 
 
 | 
 
 | 
 
 | 
 
 | 
31
 | 
 # The below function finds a number to aid sorting  | 
| 
 
 | 
17
 | 
 
 | 
 
 | 
 
 | 
 
 | 
35
 | 
    | 
| 
416
 | 
17
 | 
 
 | 
 
 | 
 
 | 
 
 | 
258
 | 
 # It takes into account Heuristic 1 and 4 of the HSP paper, see REFERENCES  | 
| 
417
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # as well as that it was noted in the text that rdf:type is usually less selective.  | 
| 
418
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
419
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # By assigning the integers to nodes, depending on whether they are in  | 
| 
420
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # triple (subject, predicate, object), variables, rdf:type and  | 
| 
421
 | 
250
 | 
 
 | 
 
 | 
 
 | 
 
 | 
370
 | 
 # literals, and sum them, they may be sorted. See code for the actual  | 
| 
422
 | 
250
 | 
 
 | 
 
 | 
 
 | 
 
 | 
3445
 | 
 # values used.  | 
| 
423
 | 
250
 | 
 
 | 
 
 | 
 
 | 
 
 | 
5090
 | 
    | 
| 
424
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # Denoting s for bound subject, p for bound predicate, a for rdf:type  | 
| 
425
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # as predicate, o for bound object and l for literal object and ? for  | 
| 
426
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # variable, we get the following order, most of which are identical to  | 
| 
427
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # the HSP:  | 
| 
428
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
429
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # spl: 6  | 
| 
430
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # spo: 8  | 
| 
431
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # sao: 10  | 
| 
432
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # s?l: 14  | 
| 
433
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # s?o: 16  | 
| 
434
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # ?pl: 25  | 
| 
435
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # ?po: 27  | 
| 
436
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # ?ao: 29  | 
| 
437
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # sp?: 30  | 
| 
438
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # sa?: 32  | 
| 
439
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # ??l: 33  | 
| 
440
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # ??o: 35  | 
| 
441
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # s??: 38  | 
| 
442
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # ?p?: 49  | 
| 
443
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # ?a?: 51  | 
| 
444
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # ???: 57  | 
| 
445
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
446
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # Note that this number is not intended as an estimate of selectivity,  | 
| 
447
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # merely a sorting key, but further research may possibly create such  | 
| 
448
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 # numbers.  | 
| 
449
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
450
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		my ($self, $t) = @_;  | 
| 
451
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		my $sum = 0;  | 
| 
452
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		if ($t->subject->does('Attean::API::Variable')) {  | 
| 
453
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			$sum = 20;  | 
| 
454
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		} else {  | 
| 
455
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			$sum = 1;  | 
| 
456
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		}  | 
| 
457
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		if ($t->predicate->does('Attean::API::Variable')) {  | 
| 
458
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			$sum += 10;  | 
| 
459
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		} else {  | 
| 
460
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			if ($t->predicate->equals(iri('http://www.w3.org/1999/02/22-rdf-syntax-ns#type'))) {  | 
| 
461
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				$sum += 4;  | 
| 
462
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 			} else {  | 
| 
463
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				$sum += 2;  | 
| 
464
 | 
4
 | 
 
 | 
 
 | 
  
4
  
 | 
 
 | 
13
 | 
 			}  | 
| 
465
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
8
 | 
 		}  | 
| 
466
 | 
4
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
13
 | 
 		if ($t->object->does('Attean::API::Variable')) {  | 
| 
467
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
 			$sum += 27;  | 
| 
468
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		} elsif ($t->object->does('Attean::API::Literal')) {  | 
| 
469
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
70
 | 
 			$sum += 3;  | 
| 
470
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		} else {  | 
| 
471
 | 
4
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
12
 | 
 			$sum += 5;  | 
| 
472
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
 		}  | 
| 
473
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		return $sum;  | 
| 
474
 | 
4
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
70
 | 
 	}  | 
| 
475
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
    | 
| 
476
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 	# The following method returns a factor used to penalize certain types of joins.  | 
| 
477
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
9
 | 
 	# It penalizes cartesian joins heavily, but also uses HSP Heuristic 2 (see REFERENCES)  | 
| 
478
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		my ($self, $plan) = @_;  | 
| 
479
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		my $jv			= $plan->join_variables;  | 
| 
480
 | 
4
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
21
 | 
 		my @children	= @{ $plan->children };  | 
| 
 
 | 
 
 | 
  
  0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
481
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
64
 | 
 		my $mult		   = 1;  | 
| 
482
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		if (scalar(@$jv)) {  | 
| 
483
 | 
  
0
  
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
 			if ( all { $_->isa('Attean::Plan::Quad') } @children[0..1]) {  | 
| 
484
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				my $var = ${$jv}[0]; # We will join on this  | 
| 
485
 | 
0
 | 
 
 | 
 
 | 
 
 | 
 
 | 
0
 | 
 				my @lnodes = $children[0]->values;  | 
| 
486
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				my @rnodes = $children[1]->values;  | 
| 
487
 | 
4
 | 
 
 | 
 
 | 
 
 | 
 
 | 
22
 | 
 				# Now, find where the join variables are in the triple patterns  | 
| 
488
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				my %joinpos;  | 
| 
489
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				for (my $i = 0; $i <= 2; $i++) {  | 
| 
490
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 					if ($lnodes[$i]->does('Attean::API::Variable') && $lnodes[$i]->value eq $var) {  | 
| 
491
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 						$joinpos{l} = $i;  | 
| 
492
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 					}  | 
| 
493
 | 
224
 | 
 
 | 
 
 | 
  
224
  
 | 
 
 | 
278
 | 
 					if ($rnodes[$i]->does('Attean::API::Variable') && $rnodes[$i]->value eq $var) {  | 
| 
494
 | 
224
 | 
 
 | 
 
 | 
 
 | 
 
 | 
374
 | 
 						$joinpos{r} = $i;  | 
| 
495
 | 
224
 | 
 
 | 
 
 | 
 
 | 
 
 | 
240
 | 
 					}  | 
| 
 
 | 
224
 | 
 
 | 
 
 | 
 
 | 
 
 | 
380
 | 
    | 
| 
496
 | 
224
 | 
 
 | 
 
 | 
 
 | 
 
 | 
285
 | 
 					last if scalar keys(%joinpos) >= 2; # Perhaps a bit premature optimization  | 
| 
497
 | 
224
 | 
  
100
  
 | 
 
 | 
 
 | 
 
 | 
341
 | 
 				}  | 
| 
498
 | 
208
 | 
  
100
  
 | 
 
 | 
  
344
  
 | 
 
 | 
822
 | 
 				my $joinpos = join("", sort values(%joinpos)); # We can now match on this string  | 
| 
 
 | 
344
 | 
 
 | 
 
 | 
 
 | 
 
 | 
830
 | 
    | 
| 
499
 | 
64
 | 
 
 | 
 
 | 
 
 | 
 
 | 
69
 | 
 				my %costs = ('12' => 1.1, # The penalty numbers come mostly out from thin air  | 
| 
 
 | 
64
 | 
 
 | 
 
 | 
 
 | 
 
 | 
97
 | 
    | 
| 
500
 | 
64
 | 
 
 | 
 
 | 
 
 | 
 
 | 
139
 | 
 								 '01' => 1.2,  | 
| 
501
 | 
64
 | 
 
 | 
 
 | 
 
 | 
 
 | 
137
 | 
 								 '02' => 1.5,  | 
| 
502
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 								 '22' => 1.6,  | 
| 
503
 | 
64
 | 
 
 | 
 
 | 
 
 | 
 
 | 
89
 | 
 								 '00' => 1.8,  | 
| 
504
 | 
64
 | 
 
 | 
 
 | 
 
 | 
 
 | 
115
 | 
 								 '11' => 2);  | 
| 
505
 | 
64
 | 
  
 50
  
 | 
  
 33
  
 | 
 
 | 
 
 | 
179
 | 
 				if (exists $costs{$joinpos}) {  | 
| 
506
 | 
64
 | 
 
 | 
 
 | 
 
 | 
 
 | 
887
 | 
 					$mult = $costs{$joinpos};  | 
| 
507
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 				}  | 
| 
508
 | 
64
 | 
  
 50
  
 | 
  
 33
  
 | 
 
 | 
 
 | 
111
 | 
 				#warn "Penalty: $mult for quads:\n" . $children[0]->as_string . $children[1]->as_string  | 
| 
509
 | 
64
 | 
 
 | 
 
 | 
 
 | 
 
 | 
691
 | 
 			}  | 
| 
510
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		} else {  | 
| 
511
 | 
64
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
124
 | 
 			$mult = 5; # penalize cartesian joins  | 
| 
512
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 		}  | 
| 
513
 | 
64
 | 
 
 | 
 
 | 
 
 | 
 
 | 
198
 | 
 		return $mult;  | 
| 
514
 | 
64
 | 
 
 | 
 
 | 
 
 | 
 
 | 
188
 | 
 	}  | 
| 
515
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 }  | 
| 
516
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
517
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
518
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 1;  | 
| 
519
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
520
 | 
64
 | 
  
 50
  
 | 
 
 | 
 
 | 
 
 | 
108
 | 
    | 
| 
521
 | 
64
 | 
 
 | 
 
 | 
 
 | 
 
 | 
199
 | 
 =back  | 
| 
522
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
523
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head1 BUGS  | 
| 
524
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
525
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 Please report any bugs or feature requests to through the GitHub web interface  | 
| 
526
 | 
16
 | 
 
 | 
 
 | 
 
 | 
 
 | 
22
 | 
 at L<https://github.com/kasei/attean/issues>.  | 
| 
527
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
528
 | 
224
 | 
 
 | 
 
 | 
 
 | 
 
 | 
553
 | 
 =head1 REFERENCES  | 
| 
529
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
530
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 The seminal reference for Iterative Dynamic Programming is "Iterative  | 
| 
531
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 dynamic programming: a new class of query optimization algorithms" by  | 
| 
532
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 D. Kossmann and K. Stocker, ACM Transactions on Database Systems  | 
| 
533
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 (2000).  | 
| 
534
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
535
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 The heuristics to order triple patterns in this module is  | 
| 
536
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 influenced by L<The ICS-FORTH Heuristics-based SPARQL Planner  | 
| 
537
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 (HSP)|http://www.ics.forth.gr/isl/index_main.php?l=e&c=645>.  | 
| 
538
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
539
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
540
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head1 SEE ALSO  | 
| 
541
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
542
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
543
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
544
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head1 AUTHOR  | 
| 
545
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
546
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 Gregory Todd Williams  C<< <gwilliams@cpan.org> >>  | 
| 
547
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
548
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =head1 COPYRIGHT  | 
| 
549
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
550
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 Copyright (c) 2014--2022 Gregory Todd Williams.  | 
| 
551
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 This program is free software; you can redistribute it and/or modify it under  | 
| 
552
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 the same terms as Perl itself.  | 
| 
553
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
    | 
| 
554
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 
 | 
 =cut  |