| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Class::Inflate; |
|
2
|
|
|
|
|
|
|
|
|
3
|
2
|
|
|
2
|
|
2443
|
use 5.006; |
|
|
2
|
|
|
|
|
7
|
|
|
|
2
|
|
|
|
|
248
|
|
|
4
|
2
|
|
|
2
|
|
12
|
use strict; |
|
|
2
|
|
|
|
|
4
|
|
|
|
2
|
|
|
|
|
72
|
|
|
5
|
2
|
|
|
2
|
|
11
|
use warnings; |
|
|
2
|
|
|
|
|
8
|
|
|
|
2
|
|
|
|
|
210
|
|
|
6
|
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
require Exporter; |
|
8
|
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
our @ISA = qw(Exporter); |
|
10
|
|
|
|
|
|
|
our @EXPORT_OK = qw(inflate commit obliterate); |
|
11
|
|
|
|
|
|
|
our @EXPORT = ('inflate'); # @EXPORT_OK; |
|
12
|
|
|
|
|
|
|
our $VERSION = '0.07'; |
|
13
|
|
|
|
|
|
|
$::OBJECT = undef; |
|
14
|
|
|
|
|
|
|
|
|
15
|
2
|
|
|
2
|
|
3030
|
use Devel::Messenger qw(note); |
|
|
2
|
|
|
|
|
5683
|
|
|
|
2
|
|
|
|
|
890
|
|
|
16
|
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
# Preloaded methods go here. |
|
18
|
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
sub import { |
|
20
|
|
|
|
|
|
|
# allows 'use' syntax |
|
21
|
2
|
|
|
2
|
|
162
|
my ($class, @args) = @_; |
|
22
|
2
|
50
|
|
|
|
18
|
@args and $class->make(@args); |
|
23
|
|
|
|
|
|
|
} |
|
24
|
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
sub make { |
|
26
|
|
|
|
|
|
|
# sets up glue to database based on 'use' statement |
|
27
|
2
|
|
|
2
|
0
|
7
|
my ($generator_class, @args) = @_; |
|
28
|
2
|
|
|
|
|
85
|
my $target_class = $generator_class->find_target_class; |
|
29
|
2
|
|
|
|
|
8
|
my %persist = (); |
|
30
|
2
|
|
|
|
|
9
|
while (@args) { |
|
31
|
6
|
|
|
|
|
13
|
my ($table, $config) = splice(@args, 0, 2); |
|
32
|
6
|
|
|
|
|
21
|
$persist{$table} = $config; |
|
33
|
|
|
|
|
|
|
} |
|
34
|
|
|
|
|
|
|
# $generator_class->_install_method($target_class, $sub_name, $code); |
|
35
|
2
|
|
|
|
|
6
|
foreach my $export (@EXPORT) { |
|
36
|
2
|
|
|
|
|
12
|
my $generate_export = '_' . $export; |
|
37
|
2
|
|
|
|
|
13
|
$generator_class->$generate_export($target_class, \%persist, $export); |
|
38
|
|
|
|
|
|
|
} |
|
39
|
|
|
|
|
|
|
} |
|
40
|
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
sub find_target_class { |
|
42
|
|
|
|
|
|
|
# determines where to export generated methods |
|
43
|
2
|
|
|
2
|
0
|
7
|
my $class; |
|
44
|
2
|
|
|
|
|
13
|
my $c = 0; |
|
45
|
2
|
|
|
|
|
5
|
while (1) { |
|
46
|
6
|
|
|
|
|
46
|
$class = (caller($c++))[0]; |
|
47
|
4
|
|
|
|
|
34
|
last unless ($class->isa('Class::Inflate') and |
|
48
|
6
|
100
|
66
|
|
|
78
|
&{$class->can('ima_generator')}); |
|
49
|
|
|
|
|
|
|
} |
|
50
|
2
|
|
|
|
|
8
|
return $class; |
|
51
|
|
|
|
|
|
|
} |
|
52
|
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
sub ima_generator { |
|
54
|
|
|
|
|
|
|
# a subclass may redefine this to return 0, if it wishes |
|
55
|
|
|
|
|
|
|
# to allow methods added to itself |
|
56
|
4
|
|
|
4
|
0
|
28
|
1; |
|
57
|
|
|
|
|
|
|
} |
|
58
|
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
sub _install_method { |
|
60
|
|
|
|
|
|
|
# exports method to target class |
|
61
|
2
|
|
|
2
|
|
6
|
my $generator_class = shift; |
|
62
|
2
|
|
|
|
|
4
|
my $target_class = shift; |
|
63
|
2
|
|
|
|
|
4
|
my $accessor = shift; |
|
64
|
2
|
|
|
|
|
4
|
my $code = shift; |
|
65
|
2
|
|
|
2
|
|
14
|
no strict 'refs'; |
|
|
2
|
|
|
|
|
5
|
|
|
|
2
|
|
|
|
|
21406
|
|
|
66
|
2
|
50
|
|
|
|
4
|
unless (defined *{"$target_class\::$accessor"}{CODE}) { |
|
|
2
|
|
|
|
|
18
|
|
|
67
|
2
|
|
|
|
|
18
|
note \7, "building accessor '$target_class\::$accessor'\n"; |
|
68
|
2
|
|
|
|
|
12
|
return *{"$target_class\::$accessor"} = $code; |
|
|
2
|
|
|
|
|
561
|
|
|
69
|
|
|
|
|
|
|
} |
|
70
|
0
|
|
|
|
|
0
|
return; |
|
71
|
|
|
|
|
|
|
} |
|
72
|
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
sub _inflate { |
|
74
|
2
|
|
|
2
|
|
5
|
my $generator_class = shift; |
|
75
|
2
|
|
|
|
|
161
|
my $target_class = shift; |
|
76
|
2
|
|
|
|
|
5
|
my $persist = shift; |
|
77
|
2
|
|
|
|
|
3
|
my $sub_name = shift; |
|
78
|
|
|
|
|
|
|
$generator_class->_install_method($target_class, $sub_name, sub { |
|
79
|
3
|
|
|
3
|
|
136957
|
my $self = shift; |
|
80
|
3
|
50
|
|
|
|
27
|
my $class = ref($self) ? ref($self) : $self; |
|
81
|
3
|
|
|
|
|
9
|
my $dbh = shift; |
|
82
|
3
|
|
33
|
|
|
38
|
my $filter = shift || $self; |
|
83
|
3
|
|
|
|
|
35
|
my @sql = inflation_sql($class, $filter, $persist); |
|
84
|
3
|
|
|
|
|
9
|
my @records = (); # in object format |
|
85
|
3
|
|
|
|
|
6
|
my @data = (); # in table/field format |
|
86
|
3
|
|
|
|
|
7
|
my %rows_fetched = (); |
|
87
|
3
|
|
|
|
|
8
|
my %awaiting_join = (); |
|
88
|
3
|
|
|
|
|
9
|
foreach my $sql (@sql) { |
|
89
|
9
|
|
|
|
|
37
|
my @r = fetchrows($class, $dbh, $sql->{query}, $sql->{bind}); |
|
90
|
9
|
|
|
|
|
32
|
$rows_fetched{$sql->{table}} = \@r; |
|
91
|
9
|
100
|
100
|
|
|
47
|
if (exists($persist->{$sql->{table}}{join}) and keys %{$persist->{$sql->{table}}{join}}) { |
|
|
7
|
|
|
|
|
38
|
|
|
92
|
6
|
|
|
|
|
9
|
foreach my $table (keys %{$persist->{$sql->{table}}{join}}) { |
|
|
6
|
|
|
|
|
23
|
|
|
93
|
6
|
|
|
|
|
29
|
note \3, "want to join $sql->{table} to $table\n"; |
|
94
|
6
|
100
|
66
|
|
|
435
|
if (@data and $rows_fetched{$table}) { |
|
95
|
2
|
|
|
|
|
7
|
join_records($class, $persist, $table, \@data, $sql->{table}, \@r); |
|
96
|
|
|
|
|
|
|
} else { |
|
97
|
4
|
|
100
|
|
|
25
|
$awaiting_join{$table} ||= []; |
|
98
|
4
|
|
|
|
|
7
|
push @{$awaiting_join{$table}}, $sql->{table}; |
|
|
4
|
|
|
|
|
11
|
|
|
99
|
4
|
|
|
|
|
19
|
note \3, " will wait till we have $table data\n"; |
|
100
|
|
|
|
|
|
|
} |
|
101
|
|
|
|
|
|
|
} |
|
102
|
|
|
|
|
|
|
} else { |
|
103
|
|
|
|
|
|
|
# this is the master/parent table |
|
104
|
3
|
|
|
|
|
8429
|
note \3, "populating dataset from $sql->{table}\n"; |
|
105
|
3
|
|
|
|
|
256
|
$rows_fetched{$sql->{table}} = @r; |
|
106
|
3
|
|
|
|
|
10
|
@data = map { { $sql->{table} => [$_] } } splice @r; |
|
|
8
|
|
|
|
|
30
|
|
|
107
|
3
|
|
|
|
|
22
|
note \3, "placed " . scalar(@data) . ' of ' . scalar(@r) . " records in dataset\n"; |
|
108
|
|
|
|
|
|
|
} |
|
109
|
9
|
100
|
100
|
|
|
522
|
if (@data and $awaiting_join{$sql->{table}}) { |
|
110
|
3
|
|
|
|
|
6
|
my $join_awaiting_records; |
|
111
|
|
|
|
|
|
|
# need to recursively call this foreach loop |
|
112
|
|
|
|
|
|
|
$join_awaiting_records = sub { |
|
113
|
7
|
|
|
7
|
|
14
|
my $waiting = shift; |
|
114
|
7
|
100
|
|
|
|
25
|
return unless exists($awaiting_join{$waiting}); |
|
115
|
3
|
|
|
|
|
15
|
note \3, "now we have $waiting data\n"; |
|
116
|
3
|
|
|
|
|
201
|
while (@{$awaiting_join{$waiting}}) { |
|
|
7
|
|
|
|
|
29
|
|
|
117
|
4
|
|
|
|
|
7
|
my $table = shift @{$awaiting_join{$waiting}}; |
|
|
4
|
|
|
|
|
10
|
|
|
118
|
4
|
|
|
|
|
16
|
join_records($class, $persist, $waiting, \@data, $table, $rows_fetched{$table}); |
|
119
|
4
|
|
|
|
|
19
|
$join_awaiting_records->($table); |
|
120
|
|
|
|
|
|
|
} |
|
121
|
3
|
|
|
|
|
25
|
}; |
|
122
|
3
|
|
|
|
|
11
|
$join_awaiting_records->($sql->{table}); |
|
123
|
|
|
|
|
|
|
} |
|
124
|
|
|
|
|
|
|
} |
|
125
|
|
|
|
|
|
|
# TODO remove Dumper |
|
126
|
|
|
|
|
|
|
#use Data::Dumper; |
|
127
|
|
|
|
|
|
|
#note "dataset:\n" . Dumper(\@data) . "\n"; |
|
128
|
3
|
50
|
|
|
|
25
|
my $inflated_object = inflated_object($class, $persist, \@data, [ref($self) ? $self : ()]); |
|
129
|
3
|
|
|
|
|
13
|
while (my $record = $inflated_object->($dbh)) { |
|
130
|
8
|
|
|
|
|
93
|
push @records, $record; |
|
131
|
|
|
|
|
|
|
} |
|
132
|
3
|
50
|
|
|
|
161
|
return @records if wantarray; |
|
133
|
0
|
|
|
|
|
0
|
return \@records; |
|
134
|
2
|
|
|
|
|
28
|
}); |
|
135
|
|
|
|
|
|
|
} |
|
136
|
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
sub inflation_sql { |
|
138
|
3
|
|
|
3
|
0
|
9
|
my $class = shift; |
|
139
|
3
|
|
|
|
|
46
|
my $filter = shift; |
|
140
|
3
|
|
|
|
|
10
|
my $persist = shift; |
|
141
|
3
|
50
|
|
|
|
20
|
die "inflate filter must be a HASH\n" unless (UNIVERSAL::isa($filter, 'HASH')); |
|
142
|
3
|
|
|
|
|
8
|
my @sql = (); |
|
143
|
3
|
|
|
|
|
7
|
my $inflate = []; |
|
144
|
3
|
|
|
|
|
27
|
foreach my $table (keys %$persist) { |
|
145
|
9
|
|
|
|
|
18
|
push @$inflate, keys %{$persist->{$table}{methods}}; |
|
|
9
|
|
|
|
|
60
|
|
|
146
|
|
|
|
|
|
|
} |
|
147
|
3
|
|
|
|
|
19
|
my $method_tables = method_tables($class, $persist); |
|
148
|
3
|
|
|
|
|
24
|
my $inflation_fields = inflation_fields($class, $inflate, $persist, $method_tables); |
|
149
|
3
|
|
|
|
|
17
|
my $filter_values = filter_values($class, $filter, $persist, $method_tables); |
|
150
|
3
|
|
|
|
|
12
|
my %join_fields = (); # fields we must select because we will use them to match records |
|
151
|
|
|
|
|
|
|
# newer code |
|
152
|
3
|
|
|
|
|
13
|
foreach my $table (keys %$inflation_fields) { |
|
153
|
9
|
|
|
|
|
38
|
note \5, "building query for table $table\n"; |
|
154
|
9
|
|
|
|
|
745
|
my $table_filter = add_filter_defaults($class, $filter_values, $persist, $table); |
|
155
|
9
|
|
|
|
|
20
|
my @tables = (); |
|
156
|
9
|
|
|
|
|
15
|
my @fields = (); |
|
157
|
9
|
|
|
|
|
13
|
my @filter = (); |
|
158
|
9
|
|
|
|
|
11
|
my @bind = (); |
|
159
|
9
|
|
|
|
|
17
|
my $alias = ''; |
|
160
|
9
|
100
|
|
|
|
24
|
my $has_filter = exists($filter_values->{$table}) ? 1 : 0; |
|
161
|
9
|
|
|
|
|
20
|
my $has_external_filter = scalar(keys %$filter_values) > $has_filter; |
|
162
|
9
|
100
|
|
|
|
22
|
if ($has_external_filter) { |
|
163
|
|
|
|
|
|
|
# see if we can join to the external tables |
|
164
|
6
|
|
|
|
|
8
|
my $matched = 0; |
|
165
|
6
|
|
|
|
|
8
|
my $need_join = 0; |
|
166
|
|
|
|
|
|
|
# add current table and fields |
|
167
|
6
|
|
|
|
|
14
|
$alias = 'a'; |
|
168
|
6
|
|
|
|
|
16
|
my %table_alias = ( $table => $alias ); |
|
169
|
6
|
|
|
|
|
20
|
push @tables, $table . ' ' . $table_alias{$table}; |
|
170
|
6
|
|
|
|
|
9
|
push @fields, map { $table_alias{$table} . '.' . $_ } @{$inflation_fields->{$table}}; |
|
|
13
|
|
|
|
|
40
|
|
|
|
6
|
|
|
|
|
14
|
|
|
171
|
6
|
|
|
|
|
21
|
foreach my $external (keys %$filter_values) { |
|
172
|
6
|
50
|
|
|
|
18
|
next if ($table eq $external); |
|
173
|
|
|
|
|
|
|
# add external table |
|
174
|
6
|
|
|
|
|
21
|
$table_alias{$external} = ++$alias; |
|
175
|
|
|
|
|
|
|
# see if all the fields in the filter join to fields in our table |
|
176
|
|
|
|
|
|
|
#foreach my $field (@{$filter_values->{$external}{fields}}) { |
|
177
|
|
|
|
|
|
|
# if (exists($persist->{$table}{join}{$external}) and exists($persist->{$table}{join}{$external}{$field})) { |
|
178
|
|
|
|
|
|
|
# my $my_field_name = $persist->{$table}{join}{$external}{$field}; |
|
179
|
|
|
|
|
|
|
# # TODO get value right now? |
|
180
|
|
|
|
|
|
|
# $matched++; |
|
181
|
|
|
|
|
|
|
# } else { |
|
182
|
|
|
|
|
|
|
# $need_join++; |
|
183
|
|
|
|
|
|
|
# } |
|
184
|
|
|
|
|
|
|
#} |
|
185
|
|
|
|
|
|
|
} |
|
186
|
|
|
|
|
|
|
# add join (field = field) to filter |
|
187
|
6
|
|
|
|
|
20
|
my @aliases = keys %table_alias; |
|
188
|
6
|
|
|
|
|
14
|
foreach my $external (@aliases) { |
|
189
|
12
|
100
|
|
|
|
39
|
next if ($external eq $table); |
|
190
|
6
|
|
|
|
|
40
|
note \6, "will need to join from $table to $external\n"; |
|
191
|
6
|
50
|
|
|
|
476
|
my @pkey = exists($persist->{$table}{key}) ? @{$persist->{$table}{key}} : (); |
|
|
6
|
|
|
|
|
58
|
|
|
192
|
|
|
|
|
|
|
# iterate through tables we know how to join to |
|
193
|
6
|
|
|
|
|
16
|
my @partial_forward_path = (); |
|
194
|
6
|
100
|
|
|
|
25
|
my $next_path = join_path($persist, $table, $external, \@partial_forward_path, exists($persist->{$table}{join}) ? keys %{$persist->{$table}{join}} : ()); |
|
|
5
|
|
|
|
|
22
|
|
|
195
|
6
|
|
|
|
|
13
|
my @paths = (); |
|
196
|
6
|
|
|
|
|
21
|
my ($path, $join) = run_path_iterator($next_path, $persist->{$table}, 0); |
|
197
|
6
|
100
|
|
|
|
28
|
push @paths, $path if @$path; |
|
198
|
|
|
|
|
|
|
# if @path, but not all primary key fields are used in join, check for additional paths and possibly use multiple paths |
|
199
|
6
|
|
100
|
|
|
29
|
while (@$path and grep { !$join->{$_} } @pkey) { |
|
|
6
|
|
|
|
|
32
|
|
|
200
|
2
|
|
|
|
|
9
|
note \6, "checking for additional join paths\n"; |
|
201
|
2
|
|
|
|
|
169
|
($path, $join) = run_path_iterator($next_path, $persist->{$table}, 0); |
|
202
|
2
|
50
|
|
|
|
14
|
push @paths, $path if @$path; |
|
203
|
|
|
|
|
|
|
} |
|
204
|
6
|
|
|
|
|
10
|
my $static_from = $table; |
|
205
|
6
|
|
|
|
|
9
|
my $middle_join = 0; |
|
206
|
6
|
100
|
66
|
|
|
42
|
if (!@paths and exists($persist->{$external}{join})) { |
|
207
|
2
|
|
|
|
|
7
|
note \6, "checking for reverse join definition\n"; |
|
208
|
2
|
|
|
|
|
138
|
my @partial_reverse_path = (); |
|
209
|
2
|
|
|
|
|
4
|
my $reverse_path = join_path($persist, $external, $table, \@partial_reverse_path, keys %{$persist->{$external}{join}}); |
|
|
2
|
|
|
|
|
10
|
|
|
210
|
2
|
|
|
|
|
8
|
($path, $join) = run_path_iterator($reverse_path, $persist->{$table}, -1); |
|
211
|
2
|
100
|
|
|
|
10
|
push @paths, $path if @$path; |
|
212
|
|
|
|
|
|
|
# if @path, but not all primary key fields are used in join, check for additional paths and possibly use multiple paths |
|
213
|
2
|
|
66
|
|
|
14
|
while (@$path and grep { !$join->{$_} } @pkey) { |
|
|
1
|
|
|
|
|
7
|
|
|
214
|
1
|
|
|
|
|
4
|
note \6, "checking for additional join paths\n"; |
|
215
|
1
|
|
|
|
|
103
|
($path, $join) = run_path_iterator($reverse_path, $persist->{$table}, -1); |
|
216
|
1
|
50
|
|
|
|
8
|
push @paths, $path if @$path; |
|
217
|
|
|
|
|
|
|
} |
|
218
|
2
|
|
|
|
|
4
|
$static_from = $external; |
|
219
|
2
|
100
|
|
|
|
26
|
if (!@paths) { |
|
220
|
|
|
|
|
|
|
# check for a common table both ends know how to join to, if there is no direct join path |
|
221
|
1
|
|
|
|
|
5
|
my $next_path = meet_in_the_middle($table, $external, \@partial_forward_path, \@partial_reverse_path); |
|
222
|
1
|
|
|
|
|
6
|
my ($path, $join) = run_path_iterator($next_path, $persist->{$table}, 0); |
|
223
|
1
|
50
|
|
|
|
6
|
push @paths, $path if @$path; |
|
224
|
|
|
|
|
|
|
# if @path, but not all primary key fields are used in join, check for additional paths and possibly use multiple paths |
|
225
|
1
|
|
66
|
|
|
10
|
while (@$path and grep { !$join->{$_} } @pkey) { |
|
|
2
|
|
|
|
|
10
|
|
|
226
|
1
|
|
|
|
|
8
|
note \6, "checking for additional join paths\n"; |
|
227
|
1
|
|
|
|
|
88
|
($path, $join) = run_path_iterator($next_path, $persist->{$table}, 0); |
|
228
|
1
|
50
|
|
|
|
8
|
push @paths, $path if @$path; |
|
229
|
|
|
|
|
|
|
} |
|
230
|
1
|
50
|
|
|
|
5
|
if (@paths) { |
|
231
|
1
|
|
|
|
|
2
|
$static_from = $table; |
|
232
|
1
|
|
|
|
|
28
|
$middle_join = 1; |
|
233
|
|
|
|
|
|
|
} |
|
234
|
|
|
|
|
|
|
} |
|
235
|
|
|
|
|
|
|
} |
|
236
|
6
|
|
|
|
|
15
|
foreach my $path (@paths) { |
|
237
|
|
|
|
|
|
|
#my $from = $static_from; |
|
238
|
6
|
|
|
|
|
15
|
my @from_table = $static_from; |
|
239
|
6
|
100
|
|
|
|
15
|
if ($middle_join) { |
|
240
|
1
|
|
|
|
|
4
|
@from_table = ($table, $external); |
|
241
|
1
|
|
|
|
|
6
|
note \7, "will look for join columns between $table and $external, forwards and backwards\n"; |
|
242
|
|
|
|
|
|
|
} else { |
|
243
|
5
|
|
|
|
|
28
|
note \7, "will look for join columns between $table and $external, forwards only\n"; |
|
244
|
|
|
|
|
|
|
} |
|
245
|
6
|
|
|
|
|
505
|
foreach my $from (@from_table) { |
|
246
|
7
|
|
|
|
|
21
|
my @path = @$path; |
|
247
|
7
|
100
|
100
|
|
|
34
|
if ($middle_join and $from eq $external) { |
|
248
|
1
|
|
|
|
|
6
|
note \6, "looking for reverse join columns ($external to $table)\n"; |
|
249
|
1
|
|
|
|
|
79
|
@path = reverse @path; |
|
250
|
1
|
|
|
|
|
2
|
shift @path; # remove $external from list |
|
251
|
|
|
|
|
|
|
} else { |
|
252
|
6
|
|
|
|
|
29
|
note \6, "looking for join columns ($table to $external)\n"; |
|
253
|
|
|
|
|
|
|
} |
|
254
|
7
|
|
|
|
|
469
|
note \6, "path: " . join(' -> ', $from, @path) . "\n"; |
|
255
|
7
|
|
|
|
|
514
|
foreach my $step (@path) { |
|
256
|
|
|
|
|
|
|
# add external table |
|
257
|
8
|
100
|
|
|
|
23
|
my $t = ($step eq $table) ? $external : $step; |
|
258
|
8
|
100
|
|
|
|
28
|
$table_alias{$t} = ++$alias unless (exists($table_alias{$t})); |
|
259
|
8
|
100
|
|
|
|
15
|
push @tables, $t . ' ' . $table_alias{$t} unless (grep { $_ eq ($t . ' ' . $table_alias{$t}) } @tables); |
|
|
11
|
|
|
|
|
64
|
|
|
260
|
8
|
|
100
|
|
|
38
|
$join_fields{$step} ||= []; |
|
261
|
8
|
100
|
|
|
|
33
|
foreach my $field (exists($persist->{$from}{join}{$step}) ? keys %{$persist->{$from}{join}{$step}} : ()) { |
|
|
7
|
|
|
|
|
28
|
|
|
262
|
7
|
|
|
|
|
23
|
my $join_from = $table_alias{$from} . '.' . $persist->{$from}{join}{$step}{$field}; |
|
263
|
7
|
|
|
|
|
18
|
my $join_to = $table_alias{$step} . '.' . $field; |
|
264
|
7
|
|
|
|
|
9
|
my $seen = 0; |
|
265
|
7
|
|
|
|
|
15
|
foreach my $filter_item (@filter) { |
|
266
|
1
|
50
|
33
|
|
|
18
|
if ($filter_item eq ($join_from . ' = ' . $join_to) or $filter_item eq ($join_to . ' = ' . $join_from)) { |
|
267
|
0
|
|
|
|
|
0
|
$seen = 1; |
|
268
|
0
|
|
|
|
|
0
|
last; |
|
269
|
|
|
|
|
|
|
} |
|
270
|
|
|
|
|
|
|
} |
|
271
|
7
|
50
|
|
|
|
21
|
if ($seen) { |
|
272
|
0
|
|
|
|
|
0
|
note \6, ' skipping ' . $from . '.' . $persist->{$from}{join}{$step}{$field} . ' = ' . $step . '.' . $field . "\n"; |
|
273
|
0
|
|
|
|
|
0
|
next; |
|
274
|
|
|
|
|
|
|
} |
|
275
|
7
|
|
|
|
|
53
|
note \6, ' joining ' . $from . '.' . $persist->{$from}{join}{$step}{$field} . ' = ' . $step . '.' . $field . "\n"; |
|
276
|
7
|
|
|
|
|
665
|
push @filter, $table_alias{$from} . '.' . $persist->{$from}{join}{$step}{$field} . ' = ' . $table_alias{$step} . '.' . $field; |
|
277
|
7
|
|
|
|
|
12
|
push @{$join_fields{$from}}, $persist->{$from}{join}{$step}{$field}; |
|
|
7
|
|
|
|
|
25
|
|
|
278
|
7
|
|
|
|
|
14
|
push @{$join_fields{$step}}, $field; |
|
|
7
|
|
|
|
|
25
|
|
|
279
|
|
|
|
|
|
|
} |
|
280
|
8
|
|
|
|
|
40
|
$from = $step; |
|
281
|
|
|
|
|
|
|
} |
|
282
|
|
|
|
|
|
|
} |
|
283
|
|
|
|
|
|
|
} |
|
284
|
6
|
50
|
|
|
|
123
|
unless (@paths) { |
|
285
|
0
|
|
|
|
|
0
|
note \6, "no path from $table to $external found\n"; |
|
286
|
0
|
|
|
|
|
0
|
delete $table_alias{$external}; |
|
287
|
|
|
|
|
|
|
} |
|
288
|
|
|
|
|
|
|
} |
|
289
|
6
|
|
|
|
|
19
|
foreach my $external (keys %table_alias) { |
|
290
|
|
|
|
|
|
|
# add external table conditions to filter |
|
291
|
13
|
100
|
|
|
|
42
|
if (exists($filter_values->{$external})) { |
|
292
|
6
|
|
|
6
|
|
47
|
my $next_filter = expand_bind(sub { $table_alias{$external} . '.' . shift }, $filter_values->{$external}{fields}, $filter_values->{$external}{values}); |
|
|
6
|
|
|
|
|
20
|
|
|
293
|
6
|
|
|
|
|
17
|
while (my ($f, $b) = $next_filter->()) { |
|
294
|
6
|
|
|
|
|
9
|
push @filter, $f; |
|
295
|
6
|
|
|
|
|
25
|
push @bind, @$b; |
|
296
|
|
|
|
|
|
|
} |
|
297
|
|
|
|
|
|
|
} |
|
298
|
|
|
|
|
|
|
} |
|
299
|
|
|
|
|
|
|
#if ($need_join) { |
|
300
|
|
|
|
|
|
|
# # TODO join in SQL, select from both, remove other single table SQL |
|
301
|
|
|
|
|
|
|
#} else { |
|
302
|
|
|
|
|
|
|
# # TODO build SQL using our fieldnames for filter parameters |
|
303
|
|
|
|
|
|
|
#} |
|
304
|
|
|
|
|
|
|
} else { |
|
305
|
|
|
|
|
|
|
# build SQL statement for this table - no joins necessary |
|
306
|
3
|
|
|
|
|
6
|
push @tables, $table; |
|
307
|
3
|
|
|
|
|
5
|
push @fields, @{$inflation_fields->{$table}}; |
|
|
3
|
|
|
|
|
9
|
|
|
308
|
3
|
50
|
|
|
|
13
|
if (exists($filter_values->{$table})) { |
|
309
|
3
|
|
|
3
|
|
22
|
my $next_filter = expand_bind(sub { shift }, $filter_values->{$table}{fields}, $filter_values->{$table}{values}); |
|
|
3
|
|
|
|
|
8
|
|
|
310
|
3
|
|
|
|
|
14
|
while (my ($f, $b) = $next_filter->()) { |
|
311
|
3
|
|
|
|
|
6
|
push @filter, $f; |
|
312
|
3
|
|
|
|
|
12
|
push @bind, @$b; |
|
313
|
|
|
|
|
|
|
} |
|
314
|
|
|
|
|
|
|
} |
|
315
|
|
|
|
|
|
|
} |
|
316
|
9
|
50
|
|
|
|
38
|
if (keys %$table_filter) { |
|
317
|
0
|
0
|
|
|
|
0
|
my $prefix = $alias ? 'a.' : ''; |
|
318
|
0
|
|
|
0
|
|
0
|
my $next_filter = expand_bind(sub { $prefix . shift }, $table_filter->{fields}, $table_filter->{values}); |
|
|
0
|
|
|
|
|
0
|
|
|
319
|
0
|
|
|
|
|
0
|
while (my ($f, $b) = $next_filter->()) { |
|
320
|
0
|
|
|
|
|
0
|
push @filter, $f; |
|
321
|
0
|
|
|
|
|
0
|
push @bind, @$b; |
|
322
|
|
|
|
|
|
|
} |
|
323
|
|
|
|
|
|
|
} |
|
324
|
9
|
50
|
|
|
|
21
|
next unless @filter; |
|
325
|
9
|
|
|
|
|
175
|
push @sql, { |
|
326
|
|
|
|
|
|
|
'bind' => \@bind, |
|
327
|
|
|
|
|
|
|
'table' => $table, |
|
328
|
|
|
|
|
|
|
'tables' => \@tables, |
|
329
|
|
|
|
|
|
|
'fields' => \@fields, |
|
330
|
|
|
|
|
|
|
'filter' => \@filter, |
|
331
|
|
|
|
|
|
|
}; |
|
332
|
|
|
|
|
|
|
} |
|
333
|
3
|
|
|
|
|
13
|
foreach my $sql (@sql) { |
|
334
|
9
|
|
|
|
|
578
|
my ($table, $alias) = split(/\s+/, $sql->{tables}[0]); |
|
335
|
9
|
100
|
|
|
|
26
|
$alias .= $alias ? '.' : ''; |
|
336
|
9
|
50
|
|
|
|
35
|
if (exists($join_fields{$table})) { |
|
337
|
9
|
|
|
|
|
13
|
my %current = map { $_ => 1 } @{$sql->{fields}}; |
|
|
21
|
|
|
|
|
78
|
|
|
|
9
|
|
|
|
|
21
|
|
|
338
|
9
|
|
|
|
|
17
|
foreach my $field (@{$join_fields{$table}}) { |
|
|
9
|
|
|
|
|
22
|
|
|
339
|
14
|
50
|
|
|
|
59
|
unless (exists($current{$alias.$field})) { |
|
340
|
0
|
|
|
|
|
0
|
push @{$sql->{fields}}, $alias.$field; |
|
|
0
|
|
|
|
|
0
|
|
|
341
|
0
|
|
|
|
|
0
|
$current{$alias.$field}++; |
|
342
|
0
|
|
|
|
|
0
|
note \6, "adding $table.$field to selection\n"; |
|
343
|
|
|
|
|
|
|
} |
|
344
|
|
|
|
|
|
|
} |
|
345
|
|
|
|
|
|
|
} |
|
346
|
9
|
|
|
|
|
14
|
my $query = 'SELECT ' . join(', ', @{$sql->{fields}}); |
|
|
9
|
|
|
|
|
34
|
|
|
347
|
9
|
|
|
|
|
16
|
$query .= ' FROM ' . join(', ', @{$sql->{tables}}); |
|
|
9
|
|
|
|
|
28
|
|
|
348
|
9
|
|
|
|
|
13
|
$query .= ' WHERE ' . join(' AND ', @{$sql->{filter}}); |
|
|
9
|
|
|
|
|
27
|
|
|
349
|
9
|
|
|
|
|
24
|
$sql->{query} = $query; |
|
350
|
9
|
50
|
|
|
|
21
|
note \5, 'built query: ' . $query . " -> " . join(', ', map { defined($_) ? $_ : '' } @{$sql->{bind}}) . "\n"; |
|
|
9
|
|
|
|
|
63
|
|
|
|
9
|
|
|
|
|
18
|
|
|
351
|
|
|
|
|
|
|
} |
|
352
|
3
|
50
|
|
|
|
302
|
return @sql if wantarray; |
|
353
|
0
|
|
|
|
|
0
|
return \@sql; |
|
354
|
|
|
|
|
|
|
} |
|
355
|
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
sub expand_bind { |
|
357
|
|
|
|
|
|
|
# returns an iterator which returns a filter "$field = ?" and bind value |
|
358
|
9
|
|
|
9
|
0
|
15
|
my $transform = shift; |
|
359
|
9
|
|
|
|
|
13
|
my $fields = shift; |
|
360
|
9
|
|
|
|
|
12
|
my $values = shift; |
|
361
|
9
|
|
|
|
|
11
|
my $c = 0; |
|
362
|
|
|
|
|
|
|
return sub { |
|
363
|
18
|
100
|
|
18
|
|
138
|
return if ($c >= @$fields); |
|
364
|
9
|
|
|
|
|
22
|
my $field = $transform->($fields->[$c]); |
|
365
|
9
|
|
|
|
|
23
|
my $value = $values->[$c++]; |
|
366
|
9
|
|
|
|
|
18
|
my $operator = '='; |
|
367
|
9
|
|
|
|
|
13
|
my $placeholder = '?'; |
|
368
|
9
|
50
|
|
|
|
83
|
if (UNIVERSAL::isa($value, 'ARRAY')) { |
|
369
|
0
|
0
|
|
|
|
0
|
if (@$value == 0) { |
|
|
|
0
|
|
|
|
|
|
|
370
|
0
|
|
|
|
|
0
|
push @$value, undef; |
|
371
|
|
|
|
|
|
|
} elsif (@$value > 1) { |
|
372
|
0
|
|
|
|
|
0
|
$operator = 'IN'; |
|
373
|
0
|
|
|
|
|
0
|
$placeholder = '(' . join(', ', map { '?' } @$value) . ')'; |
|
|
0
|
|
|
|
|
0
|
|
|
374
|
|
|
|
|
|
|
} |
|
375
|
|
|
|
|
|
|
} else { |
|
376
|
9
|
|
|
|
|
22
|
$value = [$value]; |
|
377
|
|
|
|
|
|
|
} |
|
378
|
9
|
|
|
|
|
73
|
return ($field . ' ' . $operator . ' ' . $placeholder, $value); |
|
379
|
|
|
|
|
|
|
} |
|
380
|
9
|
|
|
|
|
56
|
} |
|
381
|
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
sub join_path { |
|
383
|
|
|
|
|
|
|
# determine possible paths to join two tables together |
|
384
|
8
|
|
|
8
|
0
|
11
|
my $persist = shift; |
|
385
|
8
|
|
|
|
|
13
|
my $launch = shift; |
|
386
|
8
|
|
|
|
|
11
|
my $target = shift; |
|
387
|
8
|
|
50
|
|
|
23
|
my $iterators = shift || []; |
|
388
|
8
|
|
|
|
|
17
|
my $spacing = ''; |
|
389
|
8
|
|
|
|
|
36
|
note \7, "creating iterator from $launch to $target\n"; |
|
390
|
8
|
|
|
|
|
597
|
push @$iterators, [member_of($spacing, $target, @_), []]; |
|
391
|
8
|
|
|
|
|
16
|
my $c = 0; |
|
392
|
|
|
|
|
|
|
return sub { |
|
393
|
11
|
|
|
11
|
|
64
|
while ($c < @$iterators) { |
|
394
|
15
|
|
|
|
|
36
|
my ($element, $match) = $iterators->[$c][0]->(); |
|
395
|
15
|
|
|
|
|
21
|
$spacing = ' ' x @{$iterators->[$c][1]}; |
|
|
15
|
|
|
|
|
47
|
|
|
396
|
15
|
100
|
|
|
|
39
|
unless (defined($element)) { |
|
397
|
8
|
|
|
|
|
48
|
note \7, $spacing . "iterator $c is exhausted\n"; |
|
398
|
8
|
|
|
|
|
627
|
$c++; |
|
399
|
8
|
|
|
|
|
26
|
next; |
|
400
|
|
|
|
|
|
|
} |
|
401
|
7
|
100
|
|
|
|
17
|
if ($match) { |
|
402
|
5
|
|
|
|
|
19
|
note \7, $spacing . "iterator $c found a join path: " . join(' -> ', @{$iterators->[$c][1]}, $element) . "\n"; |
|
|
5
|
|
|
|
|
29
|
|
|
403
|
5
|
|
|
|
|
390
|
return @{$iterators->[$c][1]}, $element; |
|
|
5
|
|
|
|
|
25
|
|
|
404
|
|
|
|
|
|
|
} else { |
|
405
|
2
|
50
|
|
|
|
3
|
if (grep { /^$element$/ } @{$iterators->[$c][1]}) { |
|
|
0
|
|
|
|
|
0
|
|
|
|
2
|
|
|
|
|
8
|
|
|
406
|
0
|
|
|
|
|
0
|
note \7, $spacing . "search loop detected: " . join(' -> ', @{$iterators->[$c][1]}, $element) . "\n"; |
|
|
0
|
|
|
|
|
0
|
|
|
407
|
|
|
|
|
|
|
} else { |
|
408
|
2
|
|
|
|
|
4
|
$spacing .= ' '; |
|
409
|
2
|
|
|
|
|
10
|
note \7, $spacing . "creating iterator from $element to $target\n"; |
|
410
|
2
|
|
|
|
|
135
|
push @$iterators, [member_of($spacing, $target, keys %{$persist->{$element}{join}}), [@{$iterators->[$c][1]}, $element]]; |
|
|
2
|
|
|
|
|
9
|
|
|
|
2
|
|
|
|
|
11
|
|
|
411
|
|
|
|
|
|
|
} |
|
412
|
|
|
|
|
|
|
} |
|
413
|
|
|
|
|
|
|
} |
|
414
|
6
|
|
|
|
|
21
|
note \7, "all iterators exhausted\n"; |
|
415
|
6
|
|
|
|
|
439
|
return; |
|
416
|
8
|
|
|
|
|
68
|
}; |
|
417
|
|
|
|
|
|
|
} |
|
418
|
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
sub member_of { |
|
420
|
|
|
|
|
|
|
# iterator to say if an element matches the target |
|
421
|
10
|
|
100
|
10
|
0
|
51
|
my $spacing = shift || ''; |
|
422
|
10
|
|
|
|
|
36
|
my $target = shift; |
|
423
|
10
|
|
|
|
|
21
|
my @possible = @_; |
|
424
|
10
|
100
|
|
|
|
37
|
note \7, $spacing . " determining if " . join(' or ', map { "'$_'" } @possible) . " knows how to join to '$target'\n" if @possible; |
|
|
7
|
|
|
|
|
43
|
|
|
425
|
|
|
|
|
|
|
return sub { |
|
426
|
15
|
|
|
15
|
|
43
|
while (my $element = shift(@possible)) { |
|
427
|
7
|
|
|
|
|
29
|
return ($element, ($element eq $target)); |
|
428
|
|
|
|
|
|
|
} |
|
429
|
8
|
|
|
|
|
17
|
return; |
|
430
|
|
|
|
|
|
|
} |
|
431
|
10
|
|
|
|
|
612
|
} |
|
432
|
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
sub run_path_iterator { |
|
434
|
|
|
|
|
|
|
# kicks the iterator to find the next path. Returns the path, and fields it will use for the join |
|
435
|
13
|
|
|
13
|
0
|
20
|
my $iterator = shift; |
|
436
|
13
|
|
|
|
|
16
|
my $table_instructions = shift; |
|
437
|
13
|
|
100
|
|
|
60
|
my $element = shift || 0; # expects 0 for forward, -1 for reverse path |
|
438
|
13
|
|
|
|
|
28
|
my @path = $iterator->(); |
|
439
|
13
|
100
|
|
|
|
52
|
return (\@path, {}) unless @path; |
|
440
|
6
|
100
|
66
|
|
|
134
|
my %join = map { $_ => 1 } (exists($table_instructions->{join}) and exists($table_instructions->{join}{$path[$element]})) ? values %{$table_instructions->{join}{$path[$element]}} : (); |
|
|
5
|
|
|
|
|
21
|
|
|
|
5
|
|
|
|
|
17
|
|
|
441
|
6
|
|
|
|
|
28
|
return (\@path, \%join); |
|
442
|
|
|
|
|
|
|
} |
|
443
|
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
sub meet_in_the_middle { |
|
445
|
1
|
|
|
1
|
0
|
3
|
my $launch = shift; |
|
446
|
1
|
|
|
|
|
2
|
my $target = shift; |
|
447
|
1
|
|
|
|
|
2
|
my $forward = shift; |
|
448
|
1
|
|
|
|
|
3
|
my $reverse = shift; |
|
449
|
1
|
|
|
|
|
3
|
my @queue = (); |
|
450
|
1
|
|
|
|
|
6
|
foreach my $fpath (@$forward) { |
|
451
|
2
|
100
|
|
|
|
4
|
next unless @{$fpath->[1]}; |
|
|
2
|
|
|
|
|
9
|
|
|
452
|
1
|
|
|
|
|
4
|
foreach my $rpath (@$reverse) { |
|
453
|
2
|
100
|
|
|
|
2
|
next unless @{$rpath->[1]}; |
|
|
2
|
|
|
|
|
9
|
|
|
454
|
1
|
|
|
|
|
2
|
push @queue, [[@{$fpath->[1]}], [reverse @{$rpath->[1]}]]; |
|
|
1
|
|
|
|
|
5
|
|
|
|
1
|
|
|
|
|
7
|
|
|
455
|
|
|
|
|
|
|
} |
|
456
|
|
|
|
|
|
|
} |
|
457
|
1
|
|
|
|
|
2
|
my %returned = (); |
|
458
|
|
|
|
|
|
|
return sub { |
|
459
|
2
|
|
|
2
|
|
8
|
while (my $queue = shift(@queue)) { |
|
460
|
1
|
|
|
|
|
3
|
my ($fqueue, $rqueue) = @$queue; |
|
461
|
1
|
|
|
|
|
1
|
my $c = 0; |
|
462
|
1
|
|
|
|
|
3
|
foreach my $element (@$rqueue) { |
|
463
|
1
|
|
|
|
|
2
|
$c++; |
|
464
|
1
|
50
|
|
|
|
6
|
if ($element eq $fqueue->[-1]) { |
|
465
|
1
|
|
|
|
|
10
|
note \7, "found a meet-in-the-middle join at '$element': " . join(' -> ', $launch, @$fqueue) . ' | ' . join(' <- ', @$rqueue, $target) . "\n"; |
|
466
|
1
|
50
|
|
|
|
100
|
if ($returned{$element}) { |
|
467
|
0
|
|
|
|
|
0
|
note \7, " (ignorning because we have already found a join through '$element')\n"; |
|
468
|
0
|
|
|
|
|
0
|
next; |
|
469
|
|
|
|
|
|
|
} |
|
470
|
1
|
|
|
|
|
5
|
my @path = (@$fqueue, splice(@$rqueue, $c), $target); |
|
471
|
1
|
|
|
|
|
8
|
note \7, " which makes a join of: " . join(' -> ', $launch, @path) . "\n"; |
|
472
|
1
|
|
|
|
|
76
|
my %seen = (); |
|
473
|
1
|
|
|
|
|
3
|
my @multiple = grep { $seen{$_}++ } @path; |
|
|
2
|
|
|
|
|
7
|
|
|
474
|
1
|
50
|
|
|
|
5
|
if (@multiple) { |
|
475
|
0
|
|
|
|
|
0
|
note \7, " (ignoring because join goes through " . join(' and ', @multiple) . " more than once)\n"; |
|
476
|
0
|
|
|
|
|
0
|
next; |
|
477
|
|
|
|
|
|
|
} |
|
478
|
1
|
|
|
|
|
3
|
$returned{$element}++; |
|
479
|
1
|
|
|
|
|
6
|
return @path; |
|
480
|
|
|
|
|
|
|
} |
|
481
|
|
|
|
|
|
|
} |
|
482
|
|
|
|
|
|
|
} |
|
483
|
1
|
|
|
|
|
3
|
return; |
|
484
|
|
|
|
|
|
|
} |
|
485
|
1
|
|
|
|
|
12
|
} |
|
486
|
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
sub inflation_fields { |
|
488
|
3
|
|
|
3
|
0
|
7
|
my $class = shift; |
|
489
|
3
|
|
|
|
|
6
|
my $inflate = shift; |
|
490
|
3
|
|
|
|
|
5
|
my $persist = shift; |
|
491
|
3
|
|
33
|
|
|
30
|
my $method_tables = shift || method_tables($class, $persist); |
|
492
|
3
|
|
|
|
|
7
|
my %fields = (); |
|
493
|
3
|
|
|
|
|
12
|
foreach my $method (@$inflate) { |
|
494
|
15
|
50
|
|
|
|
43
|
unless (exists($method_tables->{$method})) { |
|
495
|
0
|
|
|
|
|
0
|
warn "ignoring unknown method '$method' for inflation\n"; |
|
496
|
0
|
|
|
|
|
0
|
next; |
|
497
|
|
|
|
|
|
|
} |
|
498
|
15
|
|
|
|
|
23
|
my $table = $method_tables->{$method}; |
|
499
|
15
|
100
|
|
|
|
39
|
next if (exists($fields{$table})); # already did this table |
|
500
|
9
|
|
|
|
|
18
|
my $methods = $persist->{$table}{methods}; |
|
501
|
9
|
|
|
|
|
14
|
my @fields = (); |
|
502
|
|
|
|
|
|
|
# figure out which fields to select based on method names |
|
503
|
9
|
|
|
|
|
22
|
foreach my $field (values %$methods) { |
|
504
|
15
|
100
|
|
|
|
30
|
if (ref($field)) { |
|
505
|
3
|
50
|
33
|
|
|
31
|
if (ref($field) eq 'HASH' and exists($field->{fields})) { |
|
506
|
3
|
50
|
|
|
|
15
|
push @fields, ref($field->{fields}) eq 'ARRAY' ? @{$field->{fields}} : $field->{fields}; |
|
|
3
|
|
|
|
|
20
|
|
|
507
|
|
|
|
|
|
|
} |
|
508
|
|
|
|
|
|
|
} else { |
|
509
|
12
|
|
|
|
|
41
|
push @fields, $field; |
|
510
|
|
|
|
|
|
|
} |
|
511
|
|
|
|
|
|
|
} |
|
512
|
9
|
50
|
|
|
|
73
|
next unless @fields; |
|
513
|
|
|
|
|
|
|
# select any fields needed for joins |
|
514
|
9
|
100
|
|
|
|
27
|
if (exists($persist->{$table}{join})) { |
|
515
|
6
|
|
|
|
|
14
|
my %selected = map { $_ => 1 } @fields; |
|
|
6
|
|
|
|
|
22
|
|
|
516
|
6
|
|
|
|
|
10
|
foreach my $parent (keys %{$persist->{$table}{join}}) { |
|
|
6
|
|
|
|
|
21
|
|
|
517
|
6
|
|
|
|
|
11
|
foreach my $field (values %{$persist->{$table}{join}{$parent}}) { |
|
|
6
|
|
|
|
|
20
|
|
|
518
|
6
|
50
|
|
|
|
18
|
unless (exists($selected{$field})) { |
|
519
|
6
|
|
|
|
|
13
|
push @fields, $field; |
|
520
|
6
|
|
|
|
|
35
|
$selected{$field}++; |
|
521
|
|
|
|
|
|
|
} |
|
522
|
|
|
|
|
|
|
} |
|
523
|
|
|
|
|
|
|
} |
|
524
|
|
|
|
|
|
|
} |
|
525
|
9
|
|
|
|
|
40
|
note \6, "will select from table $table\n"; |
|
526
|
9
|
|
|
|
|
739
|
note \6, "will select fields " . join(', ', @fields) . "\n"; |
|
527
|
9
|
|
|
|
|
613
|
$fields{$table} = \@fields; |
|
528
|
|
|
|
|
|
|
} |
|
529
|
3
|
|
|
|
|
9
|
return \%fields; |
|
530
|
|
|
|
|
|
|
} |
|
531
|
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
sub filter_values { |
|
533
|
|
|
|
|
|
|
# returns the table name, field names and bind values for any method name |
|
534
|
3
|
|
|
3
|
0
|
8
|
my $class = shift; |
|
535
|
3
|
|
|
|
|
4
|
my $filter = shift; |
|
536
|
3
|
|
|
|
|
9
|
my $persist = shift; |
|
537
|
3
|
|
33
|
|
|
12
|
my $method_tables = shift || method_tables($class, $persist); |
|
538
|
3
|
|
|
|
|
9
|
my %values = (); |
|
539
|
3
|
|
|
|
|
12
|
foreach my $method (keys %$filter) { |
|
540
|
3
|
50
|
|
|
|
24
|
if (UNIVERSAL::can($filter, $method)) { |
|
541
|
3
|
|
|
|
|
11
|
my $value = $filter->$method(); |
|
542
|
3
|
50
|
33
|
|
|
48
|
if (!defined($value) or !length($value)) { |
|
543
|
|
|
|
|
|
|
# undefined values of an object do not count as filter parameters |
|
544
|
0
|
|
|
|
|
0
|
next; |
|
545
|
|
|
|
|
|
|
} |
|
546
|
|
|
|
|
|
|
} |
|
547
|
|
|
|
|
|
|
# TODO skip warning if filter is an object, rather than a HASH |
|
548
|
3
|
50
|
|
|
|
11
|
unless (exists($method_tables->{$method})) { |
|
549
|
0
|
|
|
|
|
0
|
warn "ignoring unknown filter field '$method'\n"; |
|
550
|
0
|
|
|
|
|
0
|
next; |
|
551
|
|
|
|
|
|
|
} |
|
552
|
3
|
|
|
|
|
8
|
my $table = $method_tables->{$method}; |
|
553
|
3
|
|
|
|
|
10
|
my $methods = $persist->{$table}{methods}; |
|
554
|
3
|
|
|
|
|
28
|
note \6, "filtering on $method\n"; |
|
555
|
3
|
|
|
|
|
315
|
my $field = $methods->{$method}; |
|
556
|
3
|
|
|
|
|
7
|
my @field = (); |
|
557
|
3
|
|
|
|
|
6
|
my @value = (); |
|
558
|
3
|
|
|
|
|
12
|
local $::OBJECT = $filter; |
|
559
|
3
|
50
|
|
|
|
11
|
if (ref($field)) { |
|
560
|
0
|
0
|
0
|
|
|
0
|
if (ref($field) eq 'HASH' and exists($field->{fields})) { |
|
561
|
0
|
0
|
|
|
|
0
|
push @field, ref($field->{fields}) eq 'ARRAY' ? @{$field->{fields}} : $field->{fields}; |
|
|
0
|
|
|
|
|
0
|
|
|
562
|
0
|
0
|
|
0
|
|
0
|
my $deflate = exists($field->{deflate}) ? $field->{deflate} : sub { @_ }; |
|
|
0
|
|
|
|
|
0
|
|
|
563
|
0
|
0
|
|
|
|
0
|
if (UNIVERSAL::can($filter, $method)) { |
|
564
|
0
|
|
|
|
|
0
|
push @value, $deflate->($filter->$method()); |
|
565
|
|
|
|
|
|
|
} else { |
|
566
|
0
|
|
|
|
|
0
|
push @value, $deflate->($filter->{$method}); |
|
567
|
|
|
|
|
|
|
} |
|
568
|
|
|
|
|
|
|
} |
|
569
|
|
|
|
|
|
|
} else { |
|
570
|
3
|
|
|
|
|
5
|
push @field, $field; |
|
571
|
3
|
50
|
|
|
|
15
|
if (UNIVERSAL::can($filter, $method)) { |
|
572
|
3
|
|
|
|
|
11
|
push @value, $filter->$method(); |
|
573
|
|
|
|
|
|
|
} else { |
|
574
|
0
|
|
|
|
|
0
|
push @value, $filter->{$method}; |
|
575
|
|
|
|
|
|
|
} |
|
576
|
|
|
|
|
|
|
} |
|
577
|
3
|
50
|
|
|
|
24
|
unless (@field == @value) { |
|
578
|
0
|
|
|
|
|
0
|
warn "filter for $method specified " . scalar(@field) . " fields, but " . scalar(@value) . " values\n"; |
|
579
|
|
|
|
|
|
|
} |
|
580
|
3
|
|
|
|
|
13
|
for (my $i = 0; $i < @field; $i++) { |
|
581
|
3
|
|
|
|
|
19
|
note \6, " ($table.$field[$i] = $value[$i])\n"; |
|
582
|
|
|
|
|
|
|
} |
|
583
|
3
|
|
50
|
|
|
253
|
$values{$table} ||= { 'fields' => [], 'values' => [] }; |
|
584
|
3
|
|
|
|
|
5
|
push @{$values{$table}{fields}}, @field; |
|
|
3
|
|
|
|
|
10
|
|
|
585
|
3
|
|
|
|
|
6
|
push @{$values{$table}{values}}, @value; |
|
|
3
|
|
|
|
|
15
|
|
|
586
|
|
|
|
|
|
|
} |
|
587
|
3
|
|
|
|
|
12
|
return \%values; |
|
588
|
|
|
|
|
|
|
} |
|
589
|
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
sub add_filter_defaults { |
|
591
|
|
|
|
|
|
|
# add values from table filter hash, for fields which have not been set |
|
592
|
9
|
|
|
9
|
0
|
15
|
my $class = shift; |
|
593
|
9
|
|
|
|
|
16
|
my $filter_values = shift; |
|
594
|
9
|
|
|
|
|
13
|
my $persist = shift; |
|
595
|
9
|
|
|
|
|
12
|
my $table = shift; |
|
596
|
9
|
|
|
|
|
15
|
my %new = (); |
|
597
|
9
|
50
|
33
|
|
|
40
|
if (exists($persist->{$table}{filter}) and keys(%{$persist->{$table}{filter}})) { |
|
|
0
|
|
|
|
|
0
|
|
|
598
|
0
|
|
|
|
|
0
|
note \6, "adding default filter values\n"; |
|
599
|
0
|
|
|
|
|
0
|
my %seen = (); |
|
600
|
0
|
0
|
|
|
|
0
|
%seen = map { $_ => 1 } @{$filter_values->{$table}{fields}} if (exists($filter_values->{$table})); |
|
|
0
|
|
|
|
|
0
|
|
|
|
0
|
|
|
|
|
0
|
|
|
601
|
0
|
|
|
|
|
0
|
my $default = $persist->{$table}{filter}; |
|
602
|
0
|
|
|
|
|
0
|
foreach my $field (keys %$default) { |
|
603
|
0
|
0
|
|
|
|
0
|
unless (exists($seen{$field})) { |
|
604
|
0
|
|
|
|
|
0
|
push @{$new{fields}}, $field; |
|
|
0
|
|
|
|
|
0
|
|
|
605
|
0
|
|
|
|
|
0
|
push @{$new{values}}, $default->{$field}; |
|
|
0
|
|
|
|
|
0
|
|
|
606
|
0
|
|
|
|
|
0
|
note \6, " ($table.$field = $default->{$field})\n"; |
|
607
|
|
|
|
|
|
|
} |
|
608
|
|
|
|
|
|
|
} |
|
609
|
|
|
|
|
|
|
} |
|
610
|
9
|
|
|
|
|
27
|
return \%new; |
|
611
|
|
|
|
|
|
|
} |
|
612
|
|
|
|
|
|
|
|
|
613
|
|
|
|
|
|
|
sub fetchrows { |
|
614
|
9
|
|
|
9
|
0
|
15
|
my $class = shift; |
|
615
|
9
|
|
|
|
|
163
|
my $dbh = shift; |
|
616
|
9
|
|
|
|
|
13
|
my $query = shift; |
|
617
|
9
|
|
|
|
|
12
|
my $bind = shift; |
|
618
|
9
|
|
|
|
|
14
|
my @records = (); |
|
619
|
9
|
50
|
33
|
|
|
103
|
if ($dbh and my $sth = $dbh->prepare($query)) { |
|
620
|
9
|
50
|
|
|
|
1765
|
note \2, $sth->{Statement} . ' -> ' . join(', ', map { defined($_) ? $_ : '' } @$bind) . "\n"; |
|
|
9
|
|
|
|
|
58
|
|
|
621
|
9
|
50
|
|
|
|
1622
|
if ($sth->execute(@$bind)) { |
|
622
|
9
|
|
|
|
|
276
|
while (my $record = $sth->fetchrow_hashref('NAME_lc')) { |
|
623
|
50
|
|
|
|
|
734
|
push @records, $record; |
|
624
|
|
|
|
|
|
|
} |
|
625
|
|
|
|
|
|
|
} |
|
626
|
|
|
|
|
|
|
} |
|
627
|
9
|
|
|
|
|
49
|
note \2, "fetched " . scalar(@records) . " records\n"; |
|
628
|
9
|
50
|
|
|
|
889
|
return @records if wantarray; |
|
629
|
0
|
|
|
|
|
0
|
return \@records; |
|
630
|
|
|
|
|
|
|
} |
|
631
|
|
|
|
|
|
|
|
|
632
|
|
|
|
|
|
|
sub join_records { |
|
633
|
6
|
|
|
6
|
0
|
10
|
my $class = shift; |
|
634
|
6
|
|
|
|
|
6
|
my $persist = shift; |
|
635
|
6
|
|
|
|
|
10
|
my $parent = shift; # table name |
|
636
|
6
|
|
|
|
|
9
|
my $data = shift; # master dataset |
|
637
|
6
|
|
|
|
|
8
|
my $child = shift; # table name |
|
638
|
6
|
|
|
|
|
7
|
my $records = shift; # records |
|
639
|
6
|
|
50
|
|
|
24
|
my $join = $persist->{$child}{join}{$parent} || return; # can't join without instructions |
|
640
|
6
|
|
|
|
|
15
|
my %parent = (); # keyed off join identifier |
|
641
|
6
|
|
|
|
|
11
|
my $children = 0; # count matches |
|
642
|
6
|
|
|
|
|
9
|
my %joined = (); |
|
643
|
6
|
|
|
|
|
34
|
note \5, "joining " . scalar(@$records) . " $child to matching $parent records\n"; |
|
644
|
6
|
|
|
|
|
571
|
foreach my $d (@$data) { |
|
645
|
16
|
|
|
|
|
28
|
my @identifier = (); |
|
646
|
16
|
|
|
|
|
40
|
foreach my $field (sort keys %$join) { |
|
647
|
16
|
|
|
|
|
60
|
push @identifier, $field, $d->{$parent}[0]->{lc($field)}; |
|
648
|
|
|
|
|
|
|
} |
|
649
|
16
|
|
|
|
|
43
|
my $identifier = join(':', @identifier); |
|
650
|
|
|
|
|
|
|
#note \7, " building parent identifier $identifier\n"; |
|
651
|
16
|
|
|
|
|
17
|
push @{$parent{$identifier}}, $d; |
|
|
16
|
|
|
|
|
53
|
|
|
652
|
|
|
|
|
|
|
} |
|
653
|
6
|
|
|
|
|
12
|
foreach my $r (@$records) { |
|
654
|
42
|
|
|
|
|
56
|
my @identifier = (); |
|
655
|
42
|
|
|
|
|
76
|
foreach my $field (sort keys %$join) { |
|
656
|
42
|
|
|
|
|
113
|
push @identifier, $field, $r->{lc($join->{$field})}; |
|
657
|
|
|
|
|
|
|
} |
|
658
|
42
|
50
|
|
|
|
71
|
my $identifier = join(':', map { defined($_) ? $_ : '' } @identifier); |
|
|
84
|
|
|
|
|
209
|
|
|
659
|
|
|
|
|
|
|
#note \7, " building child identifier $identifier\n"; |
|
660
|
42
|
50
|
|
|
|
98
|
if (exists($parent{$identifier})) { |
|
661
|
|
|
|
|
|
|
#note \7, " joining on $identifier\n"; |
|
662
|
42
|
|
|
|
|
40
|
foreach my $d (@{$parent{$identifier}}) { |
|
|
42
|
|
|
|
|
69
|
|
|
663
|
|
|
|
|
|
|
# TODO do not push child record onto data record more than once (in multiple join scenario) |
|
664
|
120
|
|
|
|
|
119
|
push @{$d->{$child}}, $r; |
|
|
120
|
|
|
|
|
185
|
|
|
665
|
120
|
|
|
|
|
126
|
$children++; |
|
666
|
120
|
|
|
|
|
220
|
$joined{$identifier}++; |
|
667
|
|
|
|
|
|
|
} |
|
668
|
|
|
|
|
|
|
} |
|
669
|
|
|
|
|
|
|
} |
|
670
|
6
|
|
|
|
|
50
|
note \5, "joined $children $child to " . scalar(keys %joined) . " $parent records\n"; |
|
671
|
6
|
|
|
|
|
589
|
return $children; |
|
672
|
|
|
|
|
|
|
} |
|
673
|
|
|
|
|
|
|
|
|
674
|
|
|
|
|
|
|
sub inflated_object { |
|
675
|
|
|
|
|
|
|
# returns iterator to turn table/field data into an object |
|
676
|
3
|
|
|
3
|
0
|
6
|
my $class = shift; |
|
677
|
3
|
|
|
|
|
7
|
my $persist = shift; |
|
678
|
3
|
|
|
|
|
5
|
my $data = shift; # ARRAY ref we shift from |
|
679
|
3
|
|
|
|
|
8
|
my $objects = shift; # should only ever contain zero or one objects |
|
680
|
3
|
|
|
|
|
4
|
my $c = 0; |
|
681
|
|
|
|
|
|
|
return sub { |
|
682
|
11
|
100
|
|
11
|
|
35
|
return unless @$data; |
|
683
|
8
|
|
|
|
|
12
|
my ($dbh) = @_; |
|
684
|
8
|
|
|
|
|
12
|
my $d = shift(@$data); |
|
685
|
8
|
|
66
|
|
|
55
|
my $object = $objects->[$c++] ||= $class->new(); |
|
686
|
8
|
|
|
|
|
51
|
local $::OBJECT = $object; |
|
687
|
8
|
|
|
|
|
14
|
my @postinflate = (); |
|
688
|
8
|
|
|
|
|
26
|
foreach my $table (keys %$d) { |
|
689
|
24
|
|
|
|
|
113
|
my $records = $d->{$table}; |
|
690
|
24
|
|
|
|
|
153
|
note "[$c] inflating object $class with " . scalar(@$records) . " records from $table\n"; |
|
691
|
24
|
|
|
|
|
1721
|
my $methods = $persist->{$table}{methods}; |
|
692
|
24
|
|
|
|
|
60
|
foreach my $method (keys %$methods) { |
|
693
|
40
|
|
|
|
|
219
|
note \6, "inflating method $method\n"; |
|
694
|
40
|
|
|
|
|
2894
|
my $field = $methods->{$method}; |
|
695
|
40
|
100
|
|
|
|
87
|
if (ref($field)) { |
|
696
|
8
|
50
|
33
|
|
|
60
|
if (ref($field) eq 'HASH' and exists($field->{fields})) { |
|
|
|
0
|
|
|
|
|
|
|
697
|
8
|
|
|
|
|
14
|
my @field = (); |
|
698
|
8
|
50
|
|
|
|
25
|
push @field, ref($field->{fields}) eq 'ARRAY' ? @{$field->{fields}} : $field->{fields}; |
|
|
8
|
|
|
|
|
21
|
|
|
699
|
8
|
50
|
|
|
|
33
|
if (UNIVERSAL::can($object, $method)) { |
|
700
|
8
|
|
|
|
|
15
|
my @values = (); |
|
701
|
8
|
|
|
|
|
16
|
foreach my $record (@$records) { |
|
702
|
96
|
50
|
|
|
|
6264
|
if (exists($field->{inflate})) { |
|
703
|
0
|
|
|
|
|
0
|
push @values, $field->{inflate}->(@{$record}{@field}); |
|
|
0
|
|
|
|
|
0
|
|
|
704
|
|
|
|
|
|
|
} else { |
|
705
|
96
|
|
|
|
|
119
|
push @values, @{$record}{@field}; |
|
|
96
|
|
|
|
|
486
|
|
|
706
|
|
|
|
|
|
|
} |
|
707
|
96
|
|
|
|
|
144
|
foreach my $field (@field) { |
|
708
|
96
|
50
|
|
|
|
492
|
note \6, " ($field = " . (defined($record->{$field}) ? $record->{$field} : '') . ")\n"; |
|
709
|
|
|
|
|
|
|
} |
|
710
|
|
|
|
|
|
|
} |
|
711
|
8
|
50
|
33
|
|
|
658
|
if ($field->{forceref} or ($field->{wantref} and @values > 1)) { |
|
|
|
|
33
|
|
|
|
|
|
712
|
8
|
|
|
|
|
105
|
$object->$method(\@values); |
|
713
|
|
|
|
|
|
|
} else { |
|
714
|
0
|
|
|
|
|
0
|
$object->$method(@values); |
|
715
|
|
|
|
|
|
|
} |
|
716
|
8
|
50
|
|
|
|
91
|
if (exists($field->{postinflate})) { |
|
717
|
0
|
|
|
|
|
0
|
push @postinflate, $field->{postinflate}; |
|
718
|
|
|
|
|
|
|
} |
|
719
|
|
|
|
|
|
|
} else { |
|
720
|
|
|
|
|
|
|
# TODO some warning - can't run method on object |
|
721
|
|
|
|
|
|
|
} |
|
722
|
|
|
|
|
|
|
} elsif (ref($field) eq 'HASH') { |
|
723
|
0
|
0
|
|
|
|
0
|
if (exists($field->{inflate})) { |
|
724
|
0
|
|
|
|
|
0
|
push my @values, $field->{inflate}->(); |
|
725
|
0
|
|
|
|
|
0
|
foreach my $value (@values) { |
|
726
|
0
|
|
|
|
|
0
|
note \6, " ( = $value)\n"; |
|
727
|
|
|
|
|
|
|
} |
|
728
|
0
|
|
|
|
|
0
|
$object->$method(@values); |
|
729
|
|
|
|
|
|
|
} |
|
730
|
0
|
0
|
|
|
|
0
|
if (exists($field->{postinflate})) { |
|
731
|
0
|
|
|
|
|
0
|
push @postinflate, $field->{postinflate}; |
|
732
|
|
|
|
|
|
|
} |
|
733
|
|
|
|
|
|
|
} |
|
734
|
|
|
|
|
|
|
} else { |
|
735
|
32
|
50
|
|
|
|
138
|
if (UNIVERSAL::can($object, $method)) { |
|
736
|
32
|
|
|
|
|
46
|
my @values = (); |
|
737
|
32
|
|
|
|
|
57
|
foreach my $record (@$records) { |
|
738
|
48
|
|
|
|
|
1169
|
push @values, $record->{$field}; |
|
739
|
48
|
50
|
|
|
|
230
|
note \6, " ($field = " . (defined($record->{$field}) ? $record->{$field} : 'undef') . ")\n"; |
|
740
|
|
|
|
|
|
|
} |
|
741
|
32
|
|
|
|
|
2328
|
$object->$method(@values); |
|
742
|
|
|
|
|
|
|
} else { |
|
743
|
|
|
|
|
|
|
# TODO some warning - can't run method on object |
|
744
|
|
|
|
|
|
|
} |
|
745
|
|
|
|
|
|
|
} |
|
746
|
|
|
|
|
|
|
} |
|
747
|
|
|
|
|
|
|
} |
|
748
|
8
|
50
|
|
|
|
59
|
note \6, "running postinflate hooks\n" if @postinflate; |
|
749
|
8
|
|
|
|
|
19
|
foreach my $code (@postinflate) { |
|
750
|
0
|
|
|
|
|
0
|
$code->($dbh); |
|
751
|
|
|
|
|
|
|
} |
|
752
|
8
|
|
|
|
|
63
|
return $object; |
|
753
|
3
|
|
|
|
|
40
|
}; |
|
754
|
|
|
|
|
|
|
} |
|
755
|
|
|
|
|
|
|
|
|
756
|
|
|
|
|
|
|
sub method_tables { |
|
757
|
3
|
|
|
3
|
0
|
8
|
my $class = shift; |
|
758
|
3
|
|
|
|
|
11
|
my $persist = shift; |
|
759
|
3
|
|
|
|
|
9
|
my %table = (); |
|
760
|
3
|
|
|
|
|
11
|
foreach my $table (keys %$persist) { |
|
761
|
9
|
|
|
|
|
13
|
foreach my $method (keys %{$persist->{$table}{methods}}) { |
|
|
9
|
|
|
|
|
26
|
|
|
762
|
15
|
|
|
|
|
44
|
$table{$method} = $table; |
|
763
|
|
|
|
|
|
|
} |
|
764
|
|
|
|
|
|
|
} |
|
765
|
3
|
|
|
|
|
91
|
return \%table; |
|
766
|
|
|
|
|
|
|
} |
|
767
|
|
|
|
|
|
|
|
|
768
|
|
|
|
|
|
|
1; |
|
769
|
|
|
|
|
|
|
__END__ |