7 |
7 |
our @EXPORT = qw(linked_records link_to_record);
|
8 |
8 |
|
9 |
9 |
use Carp;
|
|
10 |
use List::MoreUtils qw(any);
|
|
11 |
use List::UtilsBy qw(uniq_by);
|
10 |
12 |
use Sort::Naturally;
|
11 |
13 |
use SL::DBUtils;
|
12 |
14 |
|
... | ... | |
50 |
52 |
my %from_to = ( from => delete($params{from}) || $both,
|
51 |
53 |
to => delete($params{to}) || $both);
|
52 |
54 |
|
53 |
|
my @records = (@{ _linked_records_implementation($self, %params, direction => 'from', from => $from_to{from}) },
|
54 |
|
@{ _linked_records_implementation($self, %params, direction => 'to', to => $from_to{to} ) });
|
|
55 |
if ($params{batch} && $params{by_id}) {
|
|
56 |
my %results;
|
|
57 |
my @links = (
|
|
58 |
_linked_records_implementation($self, %params, direction => 'from', from => $from_to{from}),
|
|
59 |
_linked_records_implementation($self, %params, direction => 'to', to => $from_to{to} ),
|
|
60 |
);
|
|
61 |
|
|
62 |
for my $by_id (@links) {
|
|
63 |
for (keys %$by_id) {
|
|
64 |
$results{$_} = defined $results{$_}
|
|
65 |
? [ uniq_by { $_->id } @{ $results{$_} }, @{ $by_id->{$_} } ]
|
|
66 |
: $by_id->{$_};
|
|
67 |
}
|
|
68 |
}
|
|
69 |
|
|
70 |
return \%results;
|
|
71 |
} else {
|
|
72 |
my @records = (@{ _linked_records_implementation($self, %params, direction => 'from', from => $from_to{from}) },
|
|
73 |
@{ _linked_records_implementation($self, %params, direction => 'to', to => $from_to{to} ) });
|
55 |
74 |
|
56 |
|
my %record_map = map { ( ref($_) . $_->id => $_ ) } @records;
|
|
75 |
my %record_map = map { ( ref($_) . $_->id => $_ ) } @records;
|
57 |
76 |
|
58 |
|
return [ values %record_map ];
|
|
77 |
return [ values %record_map ];
|
|
78 |
}
|
59 |
79 |
}
|
60 |
80 |
|
61 |
81 |
if ($params{via}) {
|
... | ... | |
68 |
88 |
|
69 |
89 |
my $sub_wanted_table = "${wanted}_table";
|
70 |
90 |
my $sub_wanted_id = "${wanted}_id";
|
|
91 |
my $sub_myself_id = "${myself}_id";
|
71 |
92 |
|
72 |
93 |
my ($wanted_classes, $wanted_tables);
|
73 |
94 |
if ($params{$wanted}) {
|
... | ... | |
77 |
98 |
|
78 |
99 |
my @get_objects_query = ref($params{query}) eq 'ARRAY' ? @{ $params{query} } : ();
|
79 |
100 |
my $get_objects = sub {
|
80 |
|
my ($link) = @_;
|
81 |
|
my $manager_class = SL::DB::Helper::Mappings::get_manager_package_for_table($link->$sub_wanted_table);
|
82 |
|
my $object_class = SL::DB::Helper::Mappings::get_package_for_table($link->$sub_wanted_table);
|
83 |
|
eval "require " . $object_class . "; 1;";
|
84 |
|
return map {
|
85 |
|
$_->{_record_link_direction} = $wanted;
|
86 |
|
$_->{_record_link} = $link;
|
87 |
|
$_
|
88 |
|
} @{
|
89 |
|
$manager_class->get_all(
|
90 |
|
query => [ id => $link->$sub_wanted_id, @get_objects_query ],
|
|
101 |
my ($links) = @_;
|
|
102 |
return [] unless @$links;
|
|
103 |
|
|
104 |
my %classes;
|
|
105 |
push @{ $classes{ $_->$sub_wanted_table } //= [] }, $_->$sub_wanted_id for @$links;
|
|
106 |
|
|
107 |
my @objs;
|
|
108 |
for (keys %classes) {
|
|
109 |
my $manager_class = SL::DB::Helper::Mappings::get_manager_package_for_table($_);
|
|
110 |
my $object_class = SL::DB::Helper::Mappings::get_package_for_table($_);
|
|
111 |
eval "require " . $object_class . "; 1;";
|
|
112 |
|
|
113 |
push @objs, @{ $manager_class->get_all(
|
|
114 |
query => [ id => $classes{$_}, @get_objects_query ],
|
91 |
115 |
(with_objects => $params{with_objects}) x !!$params{with_objects},
|
92 |
116 |
inject_results => 1,
|
93 |
|
)
|
94 |
|
};
|
|
117 |
) };
|
|
118 |
}
|
|
119 |
|
|
120 |
my %objs_by_id = map { $_->id => $_ } @objs;
|
|
121 |
|
|
122 |
for (@$links) {
|
|
123 |
if ('ARRAY' eq ref $objs_by_id{$_->$sub_wanted_id}->{_record_link}) {
|
|
124 |
push @{ $objs_by_id{$_->$sub_wanted_id}->{_record_link_direction} }, $wanted;
|
|
125 |
push @{ $objs_by_id{$_->$sub_wanted_id}->{_record_link } }, $_;
|
|
126 |
} elsif ($objs_by_id{$_->$sub_wanted_id}->{_record_link}) {
|
|
127 |
$objs_by_id{$_->$sub_wanted_id}->{_record_link_direction} = [
|
|
128 |
$objs_by_id{$_->$sub_wanted_id}->{_record_link_direction},
|
|
129 |
$wanted,
|
|
130 |
];
|
|
131 |
$objs_by_id{$_->$sub_wanted_id}->{_record_link} = [
|
|
132 |
$objs_by_id{$_->$sub_wanted_id}->{_record_link},
|
|
133 |
$_,
|
|
134 |
];
|
|
135 |
} else {
|
|
136 |
$objs_by_id{$_->$sub_wanted_id}->{_record_link_direction} = $wanted;
|
|
137 |
$objs_by_id{$_->$sub_wanted_id}->{_record_link} = $_;
|
|
138 |
}
|
|
139 |
}
|
|
140 |
|
|
141 |
return \@objs;
|
95 |
142 |
};
|
96 |
143 |
|
97 |
144 |
# If no 'via' is given then use a simple(r) method for querying the wanted objects.
|
98 |
145 |
if (!$params{via} && !$params{recursive}) {
|
99 |
146 |
my @query = ( "${myself}_table" => $my_table,
|
100 |
|
"${myself}_id" => $self->id );
|
|
147 |
"${myself}_id" => $params{batch} ? $params{batch} : $self->id );
|
101 |
148 |
push @query, ( "${wanted}_table" => $wanted_tables ) if $wanted_tables;
|
102 |
149 |
|
103 |
|
return [ map { $get_objects->($_) } @{ SL::DB::Manager::RecordLink->get_all(query => [ and => \@query ]) } ];
|
|
150 |
my $links = SL::DB::Manager::RecordLink->get_all(query => [ and => \@query ]);
|
|
151 |
my $objs = $get_objects->($links);
|
|
152 |
|
|
153 |
if ($params{batch} && $params{by_id}) {
|
|
154 |
return {
|
|
155 |
map {
|
|
156 |
my $id = $_;
|
|
157 |
$_ => [
|
|
158 |
grep {
|
|
159 |
$_->{_record_link}->$sub_myself_id == $id
|
|
160 |
} @$objs
|
|
161 |
]
|
|
162 |
} @{ $params{batch} }
|
|
163 |
}
|
|
164 |
} else {
|
|
165 |
return $objs;
|
|
166 |
}
|
104 |
167 |
}
|
105 |
168 |
|
106 |
169 |
# More complex handling for the 'via' case.
|
107 |
170 |
if ($params{via}) {
|
|
171 |
die 'batch mode is not supported with via' if $params{batch};
|
|
172 |
|
108 |
173 |
my @sources = ( $self );
|
109 |
174 |
my @targets = map { SL::DB::Helper::Mappings::get_table_for_package($_) } @{ ref($params{via}) ? $params{via} : [ $params{via} ] };
|
110 |
175 |
push @targets, @{ $wanted_tables } if $wanted_tables;
|
... | ... | |
118 |
183 |
"${myself}_id" => $src->id,
|
119 |
184 |
"${wanted}_table" => \@targets );
|
120 |
185 |
push @new_sources,
|
121 |
|
map { $get_objects->($_) }
|
122 |
|
grep { !$seen{$_->$sub_wanted_table . $_->$sub_wanted_id} }
|
123 |
|
@{ SL::DB::Manager::RecordLink->get_all(query => [ and => \@query ]) };
|
|
186 |
@{ $get_objects->([
|
|
187 |
grep { !$seen{$_->$sub_wanted_table . $_->$sub_wanted_id} }
|
|
188 |
@{ SL::DB::Manager::RecordLink->get_all(query => [ and => \@query ]) }
|
|
189 |
]) };
|
124 |
190 |
}
|
125 |
191 |
|
126 |
192 |
@sources = @new_sources;
|
... | ... | |
134 |
200 |
|
135 |
201 |
# And lastly recursive mode
|
136 |
202 |
if ($params{recursive}) {
|
|
203 |
my ($id_token, @ids);
|
|
204 |
if ($params{batch}) {
|
|
205 |
$id_token = sprintf 'IN (%s)', join ', ', ('?') x @{ $params{batch} };
|
|
206 |
@ids = @{ $params{batch} };
|
|
207 |
} else {
|
|
208 |
$id_token = '= ?';
|
|
209 |
@ids = ($self->id);
|
|
210 |
}
|
|
211 |
|
137 |
212 |
# don't use rose retrieval here. too slow.
|
138 |
213 |
# instead use recursive sql to get all the linked record_links entrys, and retrieve the objects from there
|
139 |
214 |
my $query = <<"";
|
... | ... | |
141 |
216 |
SELECT id, from_table, from_id, to_table, to_id,
|
142 |
217 |
1, ARRAY[id], false
|
143 |
218 |
FROM record_links
|
144 |
|
WHERE ${myself}_id = ? and ${myself}_table = ?
|
|
219 |
WHERE ${myself}_id $id_token and ${myself}_table = ?
|
145 |
220 |
UNION ALL
|
146 |
221 |
SELECT rl.id, rl.from_table, rl.from_id, rl.to_table, rl.to_id,
|
147 |
222 |
rlr.depth + 1, path || rl.id, rl.id = ANY(path)
|
... | ... | |
153 |
228 |
WHERE NOT cycle
|
154 |
229 |
ORDER BY ${wanted}_table, ${wanted}_id, depth ASC;
|
155 |
230 |
|
156 |
|
my $links = selectall_hashref_query($::form, $::form->get_standard_dbh, $query, $self->id, $self->meta->table);
|
|
231 |
my $links = selectall_hashref_query($::form, $::form->get_standard_dbh, $query, @ids, $self->meta->table);
|
157 |
232 |
|
158 |
|
return [] unless @$links;
|
|
233 |
if (!@$links) {
|
|
234 |
return $params{by_id} ? {} : [];
|
|
235 |
}
|
159 |
236 |
|
160 |
237 |
my $link_objs = SL::DB::Manager::RecordLink->get_all(query => [ id => [ map { $_->{id} } @$links ] ]);
|
161 |
|
my @objects = map { $get_objects->($_) } @$link_objs;
|
|
238 |
my $objects = $get_objects->($link_objs);
|
|
239 |
|
|
240 |
my %links_by_id = map { $_->{id} => $_ } @$links;
|
162 |
241 |
|
163 |
242 |
if ($params{save_path}) {
|
164 |
|
my %links_by_id = map { $_->{id} => $_ } @$links;
|
165 |
|
for (@objects) {
|
166 |
|
my $link = $links_by_id{$_->{_record_link}->id};
|
167 |
|
my $intermediate_links = SL::DB::Manager::RecordLink->get_all(query => [ id => $link->{path} ]);
|
168 |
|
$_->{_record_link_path} = $link->{path};
|
169 |
|
$_->{_record_link_obj_path} = [ map { $get_objects->($_) } @$intermediate_links ];
|
170 |
|
$_->{_record_link_depth} = $link->{depth};
|
|
243 |
for (@$objects) {
|
|
244 |
for my $record_link ('ARRAY' eq ref $_->{_record_link} ? @{ $_->{_record_link} } : $_->{_record_link}) {
|
|
245 |
my $link = $links_by_id{$record_link->id};
|
|
246 |
my $intermediate_links = SL::DB::Manager::RecordLink->get_all(query => [ id => $link->{path} ]);
|
|
247 |
$_->{_record_link_path} = $link->{path};
|
|
248 |
$_->{_record_link_obj_path} = $get_objects->($intermediate_links);
|
|
249 |
$_->{_record_link_depth} = $link->{depth};
|
|
250 |
}
|
171 |
251 |
}
|
172 |
252 |
}
|
173 |
253 |
|
174 |
|
return \@objects;
|
|
254 |
if ($params{batch} && $params{by_id}) {
|
|
255 |
my %link_obj_by_id = map { $_->id => $_ } @$link_objs;
|
|
256 |
return +{
|
|
257 |
map {
|
|
258 |
my $id = $_;
|
|
259 |
$id => [
|
|
260 |
grep {
|
|
261 |
any {
|
|
262 |
$link_obj_by_id{
|
|
263 |
$links_by_id{$_->id}->{path}->[0]
|
|
264 |
}->$sub_myself_id == $id
|
|
265 |
} 'ARRAY' eq $_->{_record_link} ? @{ $_->{_record_link} } : $_->{_record_link}
|
|
266 |
} @$objects
|
|
267 |
]
|
|
268 |
} @{ $params{batch} }
|
|
269 |
};
|
|
270 |
} else {
|
|
271 |
return $objects;
|
|
272 |
}
|
175 |
273 |
}
|
176 |
274 |
}
|
177 |
275 |
|
... | ... | |
470 |
568 |
|
471 |
569 |
=back
|
472 |
570 |
|
|
571 |
Since record_links is comparatively expensive to call, you will want to cache
|
|
572 |
the results for multiple objects if you know in advance you'll need them.
|
|
573 |
|
|
574 |
You can pass the optional argument C<batch> with an array ref of ids which will
|
|
575 |
be used instead of the id of the invocant. You still need to call it as a
|
|
576 |
method on a valid object, because table information is inferred from there.
|
|
577 |
|
|
578 |
C<batch> mode will currenty not work with C<via>.
|
|
579 |
|
|
580 |
The optional flag C<by_id> will return the objects sorted into a hash instead
|
|
581 |
of a plain array. Calling C<<recursive => 1, batch => [1,2], by_id => 1>> on
|
|
582 |
order 1:
|
|
583 |
|
|
584 |
order 1 --> delivery order 1 --> invoice 1
|
|
585 |
order 2 --> delivery order 2 --> invoice 2
|
|
586 |
|
|
587 |
will give you:
|
|
588 |
|
|
589 |
{ 1 => [ delivery order 1, invoice 1 ],
|
|
590 |
2 => [ delivery order 2, invoice 1 ], }
|
|
591 |
|
|
592 |
you may then cache these as you see fit.
|
|
593 |
|
473 |
594 |
|
474 |
595 |
The optional parameters C<$params{sort_by}> and C<$params{sort_dir}>
|
475 |
596 |
can be used in order to sort the result. If C<$params{sort_by}> is
|
LinkedRecord: Batch mode