Commit | Line | Data |
4e9fc3f3 |
1 | package # hide from the pauses |
2 | DBIx::Class::ResultSource::RowParser; |
76031e14 |
3 | |
4 | use strict; |
5 | use warnings; |
6 | |
9f98c4b2 |
7 | use base 'DBIx::Class'; |
8 | |
9f98c4b2 |
9 | use DBIx::Class::ResultSource::RowParser::Util qw( |
10 | assemble_simple_parser |
11 | assemble_collapsing_parser |
12 | ); |
e5c63829 |
13 | use DBIx::Class::_Util qw( DUMMY_ALIASPAIR dbic_internal_try dbic_internal_catch ); |
76031e14 |
14 | |
47dba3e3 |
15 | use DBIx::Class::Carp; |
16 | |
e5c63829 |
17 | # FIXME - this should go away |
18 | # instead Carp::Skip should export usable keywords or something like that |
19 | my $unique_carper; |
20 | BEGIN { $unique_carper = \&carp_unique } |
21 | |
9f98c4b2 |
22 | use namespace::clean; |
76031e14 |
23 | |
47dba3e3 |
24 | # Accepts a prefetch map (one or more relationships for the current source), |
25 | # returns a set of select/as pairs for each of those relationships. Columns |
26 | # are fully qualified inflation_slot names |
27 | sub _resolve_selection_from_prefetch { |
28 | my ($self, $pre, $alias_map, $pref_path) = @_; |
29 | |
30 | # internal recursion marker |
76031e14 |
31 | $pref_path ||= []; |
32 | |
33 | if (not defined $pre or not length $pre) { |
34 | return (); |
35 | } |
36 | elsif( ref $pre eq 'ARRAY' ) { |
47dba3e3 |
37 | map { $self->_resolve_selection_from_prefetch( $_, $alias_map, [ @$pref_path ] ) } |
38 | @$pre; |
76031e14 |
39 | } |
40 | elsif( ref $pre eq 'HASH' ) { |
76031e14 |
41 | map { |
47dba3e3 |
42 | $self->_resolve_selection_from_prefetch($_, $alias_map, [ @$pref_path ] ), |
43 | $self->related_source($_)->_resolve_selection_from_prefetch( |
44 | $pre->{$_}, $alias_map, [ @$pref_path, $_] ) |
76031e14 |
45 | } keys %$pre; |
76031e14 |
46 | } |
47 | elsif( ref $pre ) { |
48 | $self->throw_exception( |
49 | "don't know how to resolve prefetch reftype ".ref($pre)); |
50 | } |
51 | else { |
52 | my $p = $alias_map; |
47dba3e3 |
53 | $p = $p->{$_} for @$pref_path, $pre; |
76031e14 |
54 | |
55 | $self->throw_exception ( |
56 | "Unable to resolve prefetch '$pre' - join alias map does not contain an entry for path: " |
57 | . join (' -> ', @$pref_path, $pre) |
58 | ) if (ref $p->{-join_aliases} ne 'ARRAY' or not @{$p->{-join_aliases}} ); |
59 | |
47dba3e3 |
60 | # this shift() is critical - it is what allows prefetch => [ (foo) x 2 ] to work |
61 | my $src_alias = shift @{$p->{-join_aliases}}; |
76031e14 |
62 | |
47dba3e3 |
63 | # ordered [select => as] pairs |
64 | map { [ |
65 | "${src_alias}.$_" => join ( '.', |
66 | @$pref_path, |
67 | $pre, |
68 | $_, |
69 | ) |
70 | ] } $self->related_source($pre)->columns; |
71 | } |
72 | } |
76031e14 |
73 | |
47dba3e3 |
74 | sub _resolve_prefetch { |
75 | carp_unique( |
76 | 'There is no good reason to call this internal deprecated method - ' |
77 | . 'please open a ticket detailing your usage, so that a better plan can ' |
78 | . 'be devised for your case. In either case _resolve_prefetch() is ' |
79 | . 'deprecated in favor of _resolve_selection_from_prefetch(), which has ' |
80 | . 'a greatly simplified arglist.' |
81 | ); |
76031e14 |
82 | |
47dba3e3 |
83 | $_[0]->_resolve_selection_from_prefetch( $_[1], $_[3] ); |
76031e14 |
84 | } |
85 | |
47dba3e3 |
86 | |
9f98c4b2 |
87 | # Takes an arrayref of {as} dbic column aliases and the collapse and select |
88 | # attributes from the same $rs (the selector requirement is a temporary |
89 | # workaround... I hope), and returns a coderef capable of: |
90 | # my $me_pref_clps = $coderef->([$rs->cursor->next/all]) |
91 | # Where the $me_pref_clps arrayref is the future argument to inflate_result() |
92 | # |
93 | # For an example of this coderef in action (and to see its guts) look at |
94 | # t/resultset/rowparser_internals.t |
95 | # |
96 | # This is a huge performance win, as we call the same code for every row |
97 | # returned from the db, thus avoiding repeated method lookups when traversing |
98 | # relationships |
99 | # |
100 | # Also since the coderef is completely stateless (the returned structure is |
101 | # always fresh on every new invocation) this is a very good opportunity for |
102 | # memoization if further speed improvements are needed |
103 | # |
104 | # The way we construct this coderef is somewhat fugly, although the result is |
105 | # really worth it. The final coderef does not perform any kind of recursion - |
106 | # the entire nested structure constructor is rolled out into a single scope. |
107 | # |
108 | # In any case - the output of this thing is meticulously micro-tested, so |
109 | # any sort of adjustment/rewrite should be relatively easy (fsvo relatively) |
110 | # |
111 | sub _mk_row_parser { |
4a0eed52 |
112 | # $args and $attrs are separated to delineate what is core collapser stuff and |
5b309063 |
113 | # what is dbic $rs specific |
114 | my ($self, $args, $attrs) = @_; |
9f98c4b2 |
115 | |
79adc44f |
116 | die "HRI without pruning makes zero sense" |
117 | if ( $args->{hri_style} && ! $args->{prune_null_branches} ); |
118 | |
119 | my %common = ( |
120 | hri_style => $args->{hri_style}, |
121 | prune_null_branches => $args->{prune_null_branches}, |
122 | val_index => { map |
123 | { $args->{inflate_map}[$_] => $_ } |
124 | ( 0 .. $#{$args->{inflate_map}} ) |
125 | }, |
126 | ); |
127 | |
02a73c96 |
128 | my $src = (! $args->{collapse} ) ? assemble_simple_parser(\%common) : do { |
129 | my $collapse_map = $self->_resolve_collapse ({ |
130 | # FIXME |
131 | # only consider real columns (not functions) during collapse resolution |
132 | # this check shouldn't really be here, as fucktards are not supposed to |
133 | # alias random crap to existing column names anyway, but still - just in |
134 | # case |
135 | # FIXME !!!! - this does not yet deal with unbalanced selectors correctly |
136 | # (it is now trivial as the attrs specify where things go out of sync |
137 | # needs MOAR tests) |
138 | as => { map |
139 | { ref $attrs->{select}[$common{val_index}{$_}] ? () : ( $_ => $common{val_index}{$_} ) } |
140 | keys %{$common{val_index}} |
141 | }, |
142 | premultiplied => $args->{premultiplied}, |
143 | }); |
144 | |
02a73c96 |
145 | assemble_collapsing_parser({ |
79adc44f |
146 | %common, |
02a73c96 |
147 | collapse_map => $collapse_map, |
148 | }); |
149 | }; |
150 | |
2fdeef65 |
151 | utf8::upgrade($src) |
152 | if DBIx::Class::_ENV_::STRESSTEST_UTF8_UPGRADE_GENERATED_COLLAPSER_SOURCE; |
153 | |
5bcb1673 |
154 | $src; |
9f98c4b2 |
155 | } |
156 | |
157 | |
2d0b795a |
158 | # Takes an arrayref selection list and generates a collapse-map representing |
76031e14 |
159 | # row-object fold-points. Every relationship is assigned a set of unique, |
160 | # non-nullable columns (which may *not even be* from the same resultset) |
161 | # and the collapser will use this information to correctly distinguish |
2d0b795a |
162 | # data of individual to-be-row-objects. See t/resultset/rowparser_internals.t |
163 | # for extensive RV examples |
76031e14 |
164 | sub _resolve_collapse { |
82f0e0aa |
165 | my ($self, $args, $common_args) = @_; |
76031e14 |
166 | |
167 | # for comprehensible error messages put ourselves at the head of the relationship chain |
82f0e0aa |
168 | $args->{_rel_chain} ||= [ $self->source_name ]; |
76031e14 |
169 | |
9f98c4b2 |
170 | # record top-level fully-qualified column index, signify toplevelness |
171 | unless ($common_args->{_as_fq_idx}) { |
172 | $common_args->{_as_fq_idx} = { %{$args->{as}} }; |
173 | $args->{_is_top_level} = 1; |
82f0e0aa |
174 | }; |
76031e14 |
175 | |
fd2d3c95 |
176 | my ($my_cols, $rel_cols, $native_cols); |
82f0e0aa |
177 | for (keys %{$args->{as}}) { |
76031e14 |
178 | if ($_ =~ /^ ([^\.]+) \. (.+) /x) { |
179 | $rel_cols->{$1}{$2} = 1; |
180 | } |
181 | else { |
fd2d3c95 |
182 | $native_cols->{$_} = $my_cols->{$_} = {}; # important for ||='s below |
76031e14 |
183 | } |
184 | } |
185 | |
186 | my $relinfo; |
fcf32d04 |
187 | # run through relationships, collect metadata |
76031e14 |
188 | for my $rel (keys %$rel_cols) { |
76031e14 |
189 | my $inf = $self->relationship_info ($rel); |
190 | |
95e41036 |
191 | $relinfo->{$rel} = { |
192 | is_single => ( $inf->{attrs}{accessor} && $inf->{attrs}{accessor} ne 'multi' ), |
193 | is_inner => ( ( $inf->{attrs}{join_type} || '' ) !~ /^left/i), |
194 | rsrc => $self->related_source($rel), |
e5c63829 |
195 | fk_map => ( |
196 | dbic_internal_try { |
7293955e |
197 | $self->resolve_relationship_condition( |
e5c63829 |
198 | rel_name => $rel, |
199 | |
200 | # an API where these are optional would be too cumbersome, |
201 | # instead always pass in some dummy values |
202 | DUMMY_ALIASPAIR, |
203 | )->{identity_map}, |
204 | } |
205 | dbic_internal_catch { |
206 | |
207 | $unique_carper->( |
208 | "Resolution of relationship '$rel' failed unexpectedly, " |
209 | . 'please relay the following error and seek assistance via ' |
210 | . DBIx::Class::_ENV_::HELP_URL . ". Encountered error: $_" |
211 | ); |
212 | |
213 | # RV |
214 | +{} |
215 | } |
216 | ), |
95e41036 |
217 | }; |
76031e14 |
218 | } |
219 | |
fcf32d04 |
220 | # inject non-left fk-bridges from *INNER-JOINED* children (if any) |
221 | for my $rel (grep { $relinfo->{$_}{is_inner} } keys %$relinfo) { |
222 | my $ri = $relinfo->{$rel}; |
223 | for (keys %{$ri->{fk_map}} ) { |
224 | # need to know source from *our* pov, hence $rel.col |
225 | $my_cols->{$_} ||= { via_fk => "$rel.$ri->{fk_map}{$_}" } |
226 | if defined $rel_cols->{$rel}{$ri->{fk_map}{$_}} # in fact selected |
227 | } |
228 | } |
229 | |
a0726a33 |
230 | # if the parent is already defined *AND* we have an inner reverse relationship |
231 | # (i.e. do not exist without it) , assume all of its related FKs are selected |
76031e14 |
232 | # (even if they in fact are NOT in the select list). Keep a record of what we |
233 | # assumed, and if any such phantom-column becomes part of our own collapser, |
234 | # throw everything assumed-from-parent away and replace with the collapser of |
235 | # the parent (whatever it may be) |
236 | my $assumed_from_parent; |
a0726a33 |
237 | if ( ! $args->{_parent_info}{underdefined} and ! $args->{_parent_info}{rev_rel_is_optional} ) { |
fcf32d04 |
238 | for my $col ( values %{$args->{_parent_info}{rel_condition} || {}} ) { |
239 | next if exists $my_cols->{$col}; |
27f3e97d |
240 | $my_cols->{$col} = {}; |
fcf32d04 |
241 | $assumed_from_parent->{columns}{$col}++; |
242 | } |
76031e14 |
243 | } |
244 | |
245 | # get colinfo for everything |
246 | if ($my_cols) { |
247 | my $ci = $self->columns_info; |
248 | $my_cols->{$_}{colinfo} = $ci->{$_} for keys %$my_cols; |
249 | } |
250 | |
251 | my $collapse_map; |
252 | |
3faac878 |
253 | # first try to reuse the parent's collapser (i.e. reuse collapser over 1:1) |
254 | # (makes for a leaner coderef later) |
fd2d3c95 |
255 | if( |
256 | ! $collapse_map->{-identifying_columns} |
257 | and |
258 | $args->{_parent_info}{collapser_reusable} |
259 | ) { |
9f98c4b2 |
260 | $collapse_map->{-identifying_columns} = $args->{_parent_info}{collapse_on_idcols} |
fd2d3c95 |
261 | } |
262 | |
263 | # Still don't know how to collapse - in case we are a *single* relationship |
264 | # AND our parent is defined AND we have any *native* non-nullable pieces: then |
265 | # we are still good to go |
266 | # NOTE: it doesn't matter if the nonnullable set is unique or not - it will be |
267 | # made unique by the parents identifying cols |
268 | if( |
269 | ! $collapse_map->{-identifying_columns} |
270 | and |
271 | $args->{_parent_info}{is_single} |
272 | and |
273 | @{ $args->{_parent_info}{collapse_on_idcols} } |
274 | and |
275 | ( my @native_nonnull_cols = grep { |
276 | $native_cols->{$_}{colinfo} |
277 | and |
278 | ! $native_cols->{$_}{colinfo}{is_nullable} |
279 | } keys %$native_cols ) |
280 | ) { |
281 | |
282 | $collapse_map->{-identifying_columns} = [ __unique_numlist( |
283 | @{ $args->{_parent_info}{collapse_on_idcols}||[] }, |
284 | |
285 | # FIXME - we don't really need *all* of the columns, $our_nonnull_cols[0] |
286 | # is sufficient. However map the entire thing to engage the extra nonnull |
287 | # explicit checks, just to be on the safe side |
288 | # Remove some day in the future |
289 | (map |
290 | { |
291 | $common_args->{_as_fq_idx}{join ('.', |
292 | @{$args->{_rel_chain}}[1 .. $#{$args->{_rel_chain}}], |
293 | $_, |
294 | )} |
295 | } |
296 | @native_nonnull_cols |
297 | ), |
298 | )]; |
3faac878 |
299 | } |
300 | |
4a0eed52 |
301 | # Still don't know how to collapse - try to resolve based on our columns (plus already inserted FK bridges) |
76031e14 |
302 | if ( |
9f98c4b2 |
303 | ! $collapse_map->{-identifying_columns} |
3faac878 |
304 | and |
76031e14 |
305 | $my_cols |
306 | and |
4e9fc3f3 |
307 | my $idset = $self->_identifying_column_set ({map { $_ => $my_cols->{$_}{colinfo} } keys %$my_cols}) |
76031e14 |
308 | ) { |
309 | # see if the resulting collapser relies on any implied columns, |
310 | # and fix stuff up if this is the case |
4e9fc3f3 |
311 | my @reduced_set = grep { ! $assumed_from_parent->{columns}{$_} } @$idset; |
76031e14 |
312 | |
9f98c4b2 |
313 | $collapse_map->{-identifying_columns} = [ __unique_numlist( |
3faac878 |
314 | @{ $args->{_parent_info}{collapse_on_idcols}||[] }, |
315 | |
76031e14 |
316 | (map |
317 | { |
318 | my $fqc = join ('.', |
82f0e0aa |
319 | @{$args->{_rel_chain}}[1 .. $#{$args->{_rel_chain}}], |
76031e14 |
320 | ( $my_cols->{$_}{via_fk} || $_ ), |
321 | ); |
322 | |
82f0e0aa |
323 | $common_args->{_as_fq_idx}->{$fqc}; |
76031e14 |
324 | } |
4e9fc3f3 |
325 | @reduced_set |
76031e14 |
326 | ), |
3faac878 |
327 | )]; |
76031e14 |
328 | } |
329 | |
330 | # Stil don't know how to collapse - keep descending down 1:1 chains - if |
331 | # a related non-LEFT 1:1 is resolvable - its condition will collapse us |
332 | # too |
9f98c4b2 |
333 | unless ($collapse_map->{-identifying_columns}) { |
76031e14 |
334 | my @candidates; |
335 | |
336 | for my $rel (keys %$relinfo) { |
337 | next unless ($relinfo->{$rel}{is_single} && $relinfo->{$rel}{is_inner}); |
338 | |
82f0e0aa |
339 | if ( my $rel_collapse = $relinfo->{$rel}{rsrc}->_resolve_collapse ({ |
340 | as => $rel_cols->{$rel}, |
341 | _rel_chain => [ @{$args->{_rel_chain}}, $rel ], |
342 | _parent_info => { underdefined => 1 }, |
343 | }, $common_args)) { |
9f98c4b2 |
344 | push @candidates, $rel_collapse->{-identifying_columns}; |
76031e14 |
345 | } |
346 | } |
347 | |
348 | # get the set with least amount of columns |
349 | # FIXME - maybe need to implement a data type order as well (i.e. prefer several ints |
350 | # to a single varchar) |
351 | if (@candidates) { |
9f98c4b2 |
352 | ($collapse_map->{-identifying_columns}) = sort { scalar @$a <=> scalar @$b } (@candidates); |
76031e14 |
353 | } |
354 | } |
355 | |
fcf32d04 |
356 | # Stil don't know how to collapse, and we are the root node. Last ditch |
357 | # effort in case we are *NOT* premultiplied. |
358 | # Run through *each multi* all the way down, left or not, and all |
359 | # *left* singles (a single may become a multi underneath) . When everything |
360 | # gets back see if all the rels link to us definitively. If this is the |
361 | # case we are good - either one of them will define us, or if all are NULLs |
362 | # we know we are "unique" due to the "non-premultiplied" check |
363 | if ( |
9f98c4b2 |
364 | ! $collapse_map->{-identifying_columns} |
fcf32d04 |
365 | and |
366 | ! $args->{premultiplied} |
367 | and |
9f98c4b2 |
368 | $args->{_is_top_level} |
fcf32d04 |
369 | ) { |
370 | my (@collapse_sets, $uncollapsible_chain); |
371 | |
372 | for my $rel (keys %$relinfo) { |
373 | |
374 | # we already looked at these higher up |
375 | next if ($relinfo->{$rel}{is_single} && $relinfo->{$rel}{is_inner}); |
376 | |
377 | if (my $clps = $relinfo->{$rel}{rsrc}->_resolve_collapse ({ |
378 | as => $rel_cols->{$rel}, |
379 | _rel_chain => [ @{$args->{_rel_chain}}, $rel ], |
380 | _parent_info => { underdefined => 1 }, |
381 | }, $common_args) ) { |
382 | |
383 | # for singles use the idcols wholesale (either there or not) |
384 | if ($relinfo->{$rel}{is_single}) { |
9f98c4b2 |
385 | push @collapse_sets, $clps->{-identifying_columns}; |
fcf32d04 |
386 | } |
387 | elsif (! $relinfo->{$rel}{fk_map}) { |
388 | $uncollapsible_chain = 1; |
389 | last; |
390 | } |
391 | else { |
392 | my $defined_cols_parent_side; |
393 | |
394 | for my $fq_col ( grep { /^$rel\.[^\.]+$/ } keys %{$args->{as}} ) { |
395 | my ($col) = $fq_col =~ /([^\.]+)$/; |
396 | |
397 | $defined_cols_parent_side->{$_} = $args->{as}{$fq_col} for grep |
398 | { $relinfo->{$rel}{fk_map}{$_} eq $col } |
399 | keys %{$relinfo->{$rel}{fk_map}} |
400 | ; |
401 | } |
402 | |
403 | if (my $set = $self->_identifying_column_set([ keys %$defined_cols_parent_side ]) ) { |
404 | push @collapse_sets, [ sort map { $defined_cols_parent_side->{$_} } @$set ]; |
405 | } |
406 | else { |
407 | $uncollapsible_chain = 1; |
408 | last; |
409 | } |
410 | } |
411 | } |
412 | else { |
413 | $uncollapsible_chain = 1; |
414 | last; |
415 | } |
416 | } |
417 | |
418 | unless ($uncollapsible_chain) { |
419 | # if we got here - we are good to go, but the construction is tricky |
420 | # since our children will want to include our collapse criteria - we |
421 | # don't give them anything (safe, since they are all collapsible on their own) |
4a0eed52 |
422 | # in addition we record the individual collapse possibilities |
fcf32d04 |
423 | # of all left children node collapsers, and merge them in the rowparser |
424 | # coderef later |
9f98c4b2 |
425 | $collapse_map->{-identifying_columns} = []; |
426 | $collapse_map->{-identifying_columns_variants} = [ sort { |
58b92e31 |
427 | (scalar @$a) <=> (scalar @$b) |
428 | or |
429 | ( |
430 | # Poor man's max() |
431 | ( sort { $b <=> $a } @$a )[0] |
432 | <=> |
433 | ( sort { $b <=> $a } @$b )[0] |
434 | ) |
fcf32d04 |
435 | } @collapse_sets ]; |
436 | } |
437 | } |
438 | |
76031e14 |
439 | # stop descending into children if we were called by a parent for first-pass |
440 | # and don't despair if nothing was found (there may be other parallel branches |
441 | # to dive into) |
82f0e0aa |
442 | if ($args->{_parent_info}{underdefined}) { |
9f98c4b2 |
443 | return $collapse_map->{-identifying_columns} ? $collapse_map : undef |
76031e14 |
444 | } |
445 | # nothing down the chain resolved - can't calculate a collapse-map |
9f98c4b2 |
446 | elsif (! $collapse_map->{-identifying_columns}) { |
76031e14 |
447 | $self->throw_exception ( sprintf |
448 | "Unable to calculate a definitive collapse column set for %s%s: fetch more unique non-nullable columns", |
449 | $self->source_name, |
82f0e0aa |
450 | @{$args->{_rel_chain}} > 1 |
451 | ? sprintf (' (last member of the %s chain)', join ' -> ', @{$args->{_rel_chain}} ) |
76031e14 |
452 | : '' |
453 | , |
454 | ); |
455 | } |
456 | |
457 | # If we got that far - we are collapsable - GREAT! Now go down all children |
458 | # a second time, and fill in the rest |
459 | |
a0726a33 |
460 | $collapse_map->{-identifying_columns} = [ __unique_numlist( |
461 | @{ $args->{_parent_info}{collapse_on_idcols}||[] }, |
462 | @{ $collapse_map->{-identifying_columns} }, |
463 | )]; |
3faac878 |
464 | |
76031e14 |
465 | for my $rel (sort keys %$relinfo) { |
466 | |
82f0e0aa |
467 | $collapse_map->{$rel} = $relinfo->{$rel}{rsrc}->_resolve_collapse ({ |
468 | as => { map { $_ => 1 } ( keys %{$rel_cols->{$rel}} ) }, |
469 | _rel_chain => [ @{$args->{_rel_chain}}, $rel], |
470 | _parent_info => { |
3faac878 |
471 | # shallow copy |
9f98c4b2 |
472 | collapse_on_idcols => [ @{$collapse_map->{-identifying_columns}} ], |
76031e14 |
473 | |
474 | rel_condition => $relinfo->{$rel}{fk_map}, |
475 | |
a0726a33 |
476 | is_optional => ! $relinfo->{$rel}{is_inner}, |
477 | |
fd2d3c95 |
478 | is_single => $relinfo->{$rel}{is_single}, |
479 | |
86be9bcb |
480 | # if there is at least one *inner* reverse relationship ( meaning identity-only ) |
a0726a33 |
481 | # we can safely assume that the child can not exist without us |
86be9bcb |
482 | rev_rel_is_optional => ( |
483 | ( grep { |
484 | ($_->{attrs}{join_type}||'') !~ /^left/i |
485 | } values %{ $self->reverse_relationship_info($rel) } ) |
486 | ? 0 |
487 | : 1 |
488 | ), |
76031e14 |
489 | |
490 | # if this is a 1:1 our own collapser can be used as a collapse-map |
491 | # (regardless of left or not) |
3d8caf63 |
492 | collapser_reusable => ( |
493 | $relinfo->{$rel}{is_single} |
494 | && |
495 | $relinfo->{$rel}{is_inner} |
496 | && |
497 | @{$collapse_map->{-identifying_columns}} |
498 | ) ? 1 : 0, |
76031e14 |
499 | }, |
82f0e0aa |
500 | }, $common_args ); |
76031e14 |
501 | |
502 | $collapse_map->{$rel}{-is_single} = 1 if $relinfo->{$rel}{is_single}; |
503 | $collapse_map->{$rel}{-is_optional} ||= 1 unless $relinfo->{$rel}{is_inner}; |
3faac878 |
504 | } |
76031e14 |
505 | |
506 | return $collapse_map; |
507 | } |
508 | |
76031e14 |
509 | # adding a dep on MoreUtils *just* for this is retarded |
510 | sub __unique_numlist { |
3faac878 |
511 | sort { $a <=> $b } keys %{ {map { $_ => 1 } @_ }} |
76031e14 |
512 | } |
513 | |
76031e14 |
514 | 1; |