8 |
8 |
|
9 |
9 |
use Carp;
|
10 |
10 |
use Sort::Naturally;
|
|
11 |
use SL::DBUtils;
|
11 |
12 |
|
12 |
13 |
use SL::DB::Helper::Mappings;
|
13 |
14 |
use SL::DB::RecordLink;
|
... | ... | |
88 |
89 |
};
|
89 |
90 |
|
90 |
91 |
# If no 'via' is given then use a simple(r) method for querying the wanted objects.
|
91 |
|
if (!$params{via}) {
|
|
92 |
if (!$params{via} && !$params{recursive}) {
|
92 |
93 |
my @query = ( "${myself}_table" => $my_table,
|
93 |
94 |
"${myself}_id" => $self->id );
|
94 |
95 |
push @query, ( "${wanted}_table" => $wanted_tables ) if $wanted_tables;
|
... | ... | |
97 |
98 |
}
|
98 |
99 |
|
99 |
100 |
# More complex handling for the 'via' case.
|
100 |
|
my @sources = ( $self );
|
101 |
|
my @targets = map { SL::DB::Helper::Mappings::get_table_for_package($_) } @{ ref($params{via}) ? $params{via} : [ $params{via} ] };
|
102 |
|
push @targets, @{ $wanted_tables } if $wanted_tables;
|
103 |
|
|
104 |
|
my %seen = map { ($_->meta->table . $_->id => 1) } @sources;
|
105 |
|
|
106 |
|
while (@targets) {
|
107 |
|
my @new_sources = @sources;
|
108 |
|
foreach my $src (@sources) {
|
109 |
|
my @query = ( "${myself}_table" => $src->meta->table,
|
110 |
|
"${myself}_id" => $src->id,
|
111 |
|
"${wanted}_table" => \@targets );
|
112 |
|
push @new_sources,
|
113 |
|
map { $get_objects->($_) }
|
114 |
|
grep { !$seen{$_->$sub_wanted_table . $_->$sub_wanted_id} }
|
115 |
|
@{ SL::DB::Manager::RecordLink->get_all(query => [ and => \@query ]) };
|
|
101 |
if ($params{via}) {
|
|
102 |
my @sources = ( $self );
|
|
103 |
my @targets = map { SL::DB::Helper::Mappings::get_table_for_package($_) } @{ ref($params{via}) ? $params{via} : [ $params{via} ] };
|
|
104 |
push @targets, @{ $wanted_tables } if $wanted_tables;
|
|
105 |
|
|
106 |
my %seen = map { ($_->meta->table . $_->id => 1) } @sources;
|
|
107 |
|
|
108 |
while (@targets) {
|
|
109 |
my @new_sources = @sources;
|
|
110 |
foreach my $src (@sources) {
|
|
111 |
my @query = ( "${myself}_table" => $src->meta->table,
|
|
112 |
"${myself}_id" => $src->id,
|
|
113 |
"${wanted}_table" => \@targets );
|
|
114 |
push @new_sources,
|
|
115 |
map { $get_objects->($_) }
|
|
116 |
grep { !$seen{$_->$sub_wanted_table . $_->$sub_wanted_id} }
|
|
117 |
@{ SL::DB::Manager::RecordLink->get_all(query => [ and => \@query ]) };
|
|
118 |
}
|
|
119 |
|
|
120 |
@sources = @new_sources;
|
|
121 |
%seen = map { ($_->meta->table . $_->id => 1) } @sources;
|
|
122 |
shift @targets;
|
116 |
123 |
}
|
117 |
124 |
|
118 |
|
@sources = @new_sources;
|
119 |
|
%seen = map { ($_->meta->table . $_->id => 1) } @sources;
|
120 |
|
shift @targets;
|
|
125 |
my %wanted_tables_map = map { ($_ => 1) } @{ $wanted_tables };
|
|
126 |
return [ grep { $wanted_tables_map{$_->meta->table} } @sources ];
|
121 |
127 |
}
|
122 |
128 |
|
123 |
|
my %wanted_tables_map = map { ($_ => 1) } @{ $wanted_tables };
|
124 |
|
return [ grep { $wanted_tables_map{$_->meta->table} } @sources ];
|
|
129 |
# And lastly recursive mode
|
|
130 |
if ($params{recursive}) {
|
|
131 |
# don't use rose retrieval here. too slow.
|
|
132 |
# instead use recursive sql to get all the linked record_links entrys, and retrieve the objects from there
|
|
133 |
my $query = <<"";
|
|
134 |
WITH RECURSIVE record_links_rec_${wanted}(id, from_table, from_id, to_table, to_id, depth, path, cycle) AS (
|
|
135 |
SELECT id, from_table, from_id, to_table, to_id,
|
|
136 |
1, ARRAY[id], false
|
|
137 |
FROM record_links
|
|
138 |
WHERE ${myself}_id = ? and ${myself}_table = ?
|
|
139 |
UNION ALL
|
|
140 |
SELECT rl.id, rl.from_table, rl.from_id, rl.to_table, rl.to_id,
|
|
141 |
rlr.depth + 1, path || rl.id, rl.id = ANY(path)
|
|
142 |
FROM record_links rl, record_links_rec_${wanted} rlr
|
|
143 |
WHERE rlr.${wanted}_id = rl.${myself}_id AND rlr.${wanted}_table = rl.${myself}_table AND NOT cycle
|
|
144 |
)
|
|
145 |
SELECT DISTINCT ON (${wanted}_table, ${wanted}_id)
|
|
146 |
id, from_table, from_id, to_table, to_id, path, depth FROM record_links_rec_${wanted}
|
|
147 |
WHERE NOT cycle
|
|
148 |
ORDER BY ${wanted}_table, ${wanted}_id, depth ASC;
|
|
149 |
|
|
150 |
my $links = selectall_hashref_query($::form, $::form->get_standard_dbh, $query, $self->id, $self->meta->table);
|
|
151 |
my $link_objs = SL::DB::Manager::RecordLink->get_all(query => [ id => [ map { $_->{id} } @$links ] ]);
|
|
152 |
my @objects = map { $get_objects->($_) } @$link_objs;
|
|
153 |
|
|
154 |
if ($params{save_path}) {
|
|
155 |
my %links_by_id = map { $_->{id} => $_ } @$links;
|
|
156 |
for (@objects) {
|
|
157 |
$_->{_record_link_path} = $links_by_id{$_->{_record_link}->id}->{path};
|
|
158 |
$_->{_record_link_depth} = $links_by_id{$_->{_record_link}->id}->{depth};
|
|
159 |
}
|
|
160 |
}
|
|
161 |
|
|
162 |
return \@objects;
|
|
163 |
}
|
125 |
164 |
}
|
126 |
165 |
|
127 |
166 |
sub link_to_record {
|
... | ... | |
276 |
315 |
from => 'Order',
|
277 |
316 |
);
|
278 |
317 |
|
279 |
|
# transitive over known classes
|
|
318 |
# via over known classes
|
280 |
319 |
my @linked_objects = $order->linked_records(
|
281 |
|
direction => 'to',
|
282 |
320 |
to => 'Invoice',
|
283 |
321 |
via => 'DeliveryOrder',
|
284 |
322 |
);
|
|
323 |
my @linked_objects = $order->linked_records(
|
|
324 |
to => 'Invoice',
|
|
325 |
via => [ 'Order', 'DeliveryOrder' ],
|
|
326 |
);
|
|
327 |
|
|
328 |
# recursive
|
|
329 |
my @linked_objects = $order->linked_records(
|
|
330 |
recursive => 1,
|
|
331 |
);
|
|
332 |
|
285 |
333 |
|
286 |
334 |
# limit direction when further params contain additional keys
|
287 |
335 |
my %params = (to => 'Invoice', from => 'Order');
|
... | ... | |
356 |
404 |
query => [ transdate => DateTime->today_local ],
|
357 |
405 |
);
|
358 |
406 |
|
|
407 |
In case you don't know or care which or how many objects are visited the flag
|
|
408 |
C<recursive> can be used. It searches all reachable objects in the given direction:
|
|
409 |
|
|
410 |
my $records = $order->linked_records(
|
|
411 |
direction => 'to',
|
|
412 |
recursive => 1,
|
|
413 |
);
|
|
414 |
|
|
415 |
Only link chains of the same type will be considered. So even with direction
|
|
416 |
both, this
|
|
417 |
|
|
418 |
order 1 ---> invoice <--- order 2
|
|
419 |
|
|
420 |
started from order 1 will only find invoice. If an object is found both in each
|
|
421 |
direction, only one copy will be returned. The recursion is cycle protected,
|
|
422 |
and will not recurse infinitely. Cycles are defined by the same link being
|
|
423 |
visited twice, so this
|
|
424 |
|
|
425 |
|
|
426 |
order 1 ---> order 2 <--> delivery order
|
|
427 |
|
|
|
428 |
`--------> invoice
|
|
429 |
|
|
430 |
will find the path o1 -> o2 -> do -> o2 -> i without considering it a cycle.
|
|
431 |
|
|
432 |
The optional extra flag C<save_path> will give you extra inforamtion saved in
|
|
433 |
the returned objects:
|
|
434 |
|
|
435 |
my $records = $order->linked_records(
|
|
436 |
direction => 'to',
|
|
437 |
recursive => 1,
|
|
438 |
save_path => 1,
|
|
439 |
);
|
|
440 |
|
|
441 |
Every record will have two fields set:
|
|
442 |
|
|
443 |
=over 2
|
|
444 |
|
|
445 |
=item C<_record_link_path>
|
|
446 |
|
|
447 |
And array with the ids of the visited links. The shortest paths will be
|
|
448 |
prefered, so in the previous example this would contain the ids of o1-o2 and
|
|
449 |
o2-i.
|
|
450 |
|
|
451 |
=item C<_record_link_depth>
|
|
452 |
|
|
453 |
Recursion depth when this object was found. Equal to the number of ids in
|
|
454 |
C<_record_link_path>
|
|
455 |
|
|
456 |
=back
|
|
457 |
|
|
458 |
|
359 |
459 |
The optional parameters C<$params{sort_by}> and C<$params{sort_dir}>
|
360 |
460 |
can be used in order to sort the result. If C<$params{sort_by}> is
|
361 |
461 |
trueish then the result is sorted by calling L</sort_linked_records>.
|
... | ... | |
450 |
550 |
|
451 |
551 |
Nothing here yet.
|
452 |
552 |
|
|
553 |
=head1 TODO
|
|
554 |
|
|
555 |
* C<recursive> should take a query param depth and cut off there
|
|
556 |
* C<recursive> uses partial distinct which is known to be not terribly fast on
|
|
557 |
a million entry table. replace with a better statement if this ever becomes
|
|
558 |
an issue.
|
|
559 |
|
453 |
560 |
=head1 AUTHOR
|
454 |
561 |
|
455 |
562 |
Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
|
|
563 |
Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>
|
456 |
564 |
|
457 |
565 |
=cut
|
SL::DB::Helper::LinkedRecords: rekursive Suche in linked_records