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