Commit | Line | Data |
4e9fc3f3 |
1 | package # hide from the pauses |
2 | DBIx::Class::ResultSource::RowParser; |
76031e14 |
3 | |
4 | use strict; |
5 | use warnings; |
6 | |
7 | use Try::Tiny; |
8 | use List::Util 'first'; |
9 | use B 'perlstring'; |
10 | |
11 | use namespace::clean; |
12 | |
13 | use base 'DBIx::Class'; |
14 | |
15 | # Accepts one or more relationships for the current source and returns an |
16 | # array of column names for each of those relationships. Column names are |
17 | # prefixed relative to the current source, in accordance with where they appear |
18 | # in the supplied relationships. |
19 | sub _resolve_prefetch { |
20 | my ($self, $pre, $alias, $alias_map, $order, $pref_path) = @_; |
21 | $pref_path ||= []; |
22 | |
23 | if (not defined $pre or not length $pre) { |
24 | return (); |
25 | } |
26 | elsif( ref $pre eq 'ARRAY' ) { |
27 | return |
28 | map { $self->_resolve_prefetch( $_, $alias, $alias_map, $order, [ @$pref_path ] ) } |
29 | @$pre; |
30 | } |
31 | elsif( ref $pre eq 'HASH' ) { |
32 | my @ret = |
33 | map { |
34 | $self->_resolve_prefetch($_, $alias, $alias_map, $order, [ @$pref_path ] ), |
35 | $self->related_source($_)->_resolve_prefetch( |
4e9fc3f3 |
36 | $pre->{$_}, "${alias}.$_", $alias_map, $order, [ @$pref_path, $_] ) |
76031e14 |
37 | } keys %$pre; |
38 | return @ret; |
39 | } |
40 | elsif( ref $pre ) { |
41 | $self->throw_exception( |
42 | "don't know how to resolve prefetch reftype ".ref($pre)); |
43 | } |
44 | else { |
45 | my $p = $alias_map; |
46 | $p = $p->{$_} for (@$pref_path, $pre); |
47 | |
48 | $self->throw_exception ( |
49 | "Unable to resolve prefetch '$pre' - join alias map does not contain an entry for path: " |
50 | . join (' -> ', @$pref_path, $pre) |
51 | ) if (ref $p->{-join_aliases} ne 'ARRAY' or not @{$p->{-join_aliases}} ); |
52 | |
53 | my $as = shift @{$p->{-join_aliases}}; |
54 | |
55 | my $rel_info = $self->relationship_info( $pre ); |
56 | $self->throw_exception( $self->source_name . " has no such relationship '$pre'" ) |
57 | unless $rel_info; |
58 | |
59 | my $as_prefix = ($alias =~ /^.*?\.(.+)$/ ? $1.'.' : ''); |
76031e14 |
60 | |
61 | return map { [ "${as}.$_", "${as_prefix}${pre}.$_", ] } |
4e9fc3f3 |
62 | $self->related_source($pre)->columns; |
76031e14 |
63 | } |
64 | } |
65 | |
2d0b795a |
66 | # Takes an arrayref selection list and generates a collapse-map representing |
76031e14 |
67 | # row-object fold-points. Every relationship is assigned a set of unique, |
68 | # non-nullable columns (which may *not even be* from the same resultset) |
69 | # and the collapser will use this information to correctly distinguish |
2d0b795a |
70 | # data of individual to-be-row-objects. See t/resultset/rowparser_internals.t |
71 | # for extensive RV examples |
76031e14 |
72 | sub _resolve_collapse { |
73 | my ($self, $as, $as_fq_idx, $rel_chain, $parent_info, $node_idx_ref) = @_; |
74 | |
75 | # for comprehensible error messages put ourselves at the head of the relationship chain |
76 | $rel_chain ||= [ $self->source_name ]; |
77 | |
78 | # record top-level fully-qualified column index |
79 | $as_fq_idx ||= { %$as }; |
80 | |
81 | my ($my_cols, $rel_cols); |
82 | for (keys %$as) { |
83 | if ($_ =~ /^ ([^\.]+) \. (.+) /x) { |
84 | $rel_cols->{$1}{$2} = 1; |
85 | } |
86 | else { |
87 | $my_cols->{$_} = {}; # important for ||= below |
88 | } |
89 | } |
90 | |
91 | my $relinfo; |
92 | # run through relationships, collect metadata, inject non-left fk-bridges from |
93 | # *INNER-JOINED* children (if any) |
94 | for my $rel (keys %$rel_cols) { |
95 | my $rel_src = __get_related_source($self, $rel, $rel_cols->{$rel}); |
96 | |
97 | my $inf = $self->relationship_info ($rel); |
98 | |
99 | $relinfo->{$rel}{is_single} = $inf->{attrs}{accessor} && $inf->{attrs}{accessor} ne 'multi'; |
100 | $relinfo->{$rel}{is_inner} = ( $inf->{attrs}{join_type} || '' ) !~ /^left/i; |
101 | $relinfo->{$rel}{rsrc} = $rel_src; |
102 | |
103 | my $cond = $inf->{cond}; |
104 | |
105 | if ( |
106 | ref $cond eq 'HASH' |
107 | and |
108 | keys %$cond |
109 | and |
110 | ! first { $_ !~ /^foreign\./ } (keys %$cond) |
111 | and |
112 | ! first { $_ !~ /^self\./ } (values %$cond) |
113 | ) { |
114 | for my $f (keys %$cond) { |
115 | my $s = $cond->{$f}; |
116 | $_ =~ s/^ (?: foreign | self ) \.//x for ($f, $s); |
117 | $relinfo->{$rel}{fk_map}{$s} = $f; |
118 | |
4e9fc3f3 |
119 | # need to know source from *our* pov, hence $rel. |
76031e14 |
120 | $my_cols->{$s} ||= { via_fk => "$rel.$f" } if ( |
121 | defined $rel_cols->{$rel}{$f} # in fact selected |
122 | and |
4e9fc3f3 |
123 | $relinfo->{$rel}{is_inner} |
76031e14 |
124 | ); |
125 | } |
126 | } |
127 | } |
128 | |
129 | # if the parent is already defined, assume all of its related FKs are selected |
130 | # (even if they in fact are NOT in the select list). Keep a record of what we |
131 | # assumed, and if any such phantom-column becomes part of our own collapser, |
132 | # throw everything assumed-from-parent away and replace with the collapser of |
133 | # the parent (whatever it may be) |
134 | my $assumed_from_parent; |
135 | unless ($parent_info->{underdefined}) { |
136 | $assumed_from_parent->{columns} = { map |
137 | # only add to the list if we do not already select said columns |
138 | { ! exists $my_cols->{$_} ? ( $_ => 1 ) : () } |
139 | values %{$parent_info->{rel_condition} || {}} |
140 | }; |
141 | |
142 | $my_cols->{$_} = { via_collapse => $parent_info->{collapse_on} } |
143 | for keys %{$assumed_from_parent->{columns}}; |
144 | } |
145 | |
146 | # get colinfo for everything |
147 | if ($my_cols) { |
148 | my $ci = $self->columns_info; |
149 | $my_cols->{$_}{colinfo} = $ci->{$_} for keys %$my_cols; |
150 | } |
151 | |
152 | my $collapse_map; |
153 | |
154 | # try to resolve based on our columns (plus already inserted FK bridges) |
155 | if ( |
156 | $my_cols |
157 | and |
4e9fc3f3 |
158 | my $idset = $self->_identifying_column_set ({map { $_ => $my_cols->{$_}{colinfo} } keys %$my_cols}) |
76031e14 |
159 | ) { |
160 | # see if the resulting collapser relies on any implied columns, |
161 | # and fix stuff up if this is the case |
4e9fc3f3 |
162 | my @reduced_set = grep { ! $assumed_from_parent->{columns}{$_} } @$idset; |
76031e14 |
163 | |
76031e14 |
164 | $collapse_map->{-node_id} = __unique_numlist( |
4e9fc3f3 |
165 | (@reduced_set != @$idset) ? @{$parent_info->{collapse_on}} : (), |
76031e14 |
166 | (map |
167 | { |
168 | my $fqc = join ('.', |
169 | @{$rel_chain}[1 .. $#$rel_chain], |
170 | ( $my_cols->{$_}{via_fk} || $_ ), |
171 | ); |
172 | |
173 | $as_fq_idx->{$fqc}; |
174 | } |
4e9fc3f3 |
175 | @reduced_set |
76031e14 |
176 | ), |
177 | ); |
178 | } |
179 | |
180 | # Stil don't know how to collapse - keep descending down 1:1 chains - if |
181 | # a related non-LEFT 1:1 is resolvable - its condition will collapse us |
182 | # too |
183 | unless ($collapse_map->{-node_id}) { |
184 | my @candidates; |
185 | |
186 | for my $rel (keys %$relinfo) { |
187 | next unless ($relinfo->{$rel}{is_single} && $relinfo->{$rel}{is_inner}); |
188 | |
189 | if ( my $rel_collapse = $relinfo->{$rel}{rsrc}->_resolve_collapse ( |
190 | $rel_cols->{$rel}, |
191 | $as_fq_idx, |
192 | [ @$rel_chain, $rel ], |
193 | { underdefined => 1 } |
194 | )) { |
195 | push @candidates, $rel_collapse->{-node_id}; |
196 | } |
197 | } |
198 | |
199 | # get the set with least amount of columns |
200 | # FIXME - maybe need to implement a data type order as well (i.e. prefer several ints |
201 | # to a single varchar) |
202 | if (@candidates) { |
203 | ($collapse_map->{-node_id}) = sort { scalar @$a <=> scalar @$b } (@candidates); |
204 | } |
205 | } |
206 | |
207 | # Still dont know how to collapse - see if the parent passed us anything |
208 | # (i.e. reuse collapser over 1:1) |
209 | unless ($collapse_map->{-node_id}) { |
210 | $collapse_map->{-node_id} = $parent_info->{collapse_on} |
211 | if $parent_info->{collapser_reusable}; |
212 | } |
213 | |
214 | # stop descending into children if we were called by a parent for first-pass |
215 | # and don't despair if nothing was found (there may be other parallel branches |
216 | # to dive into) |
217 | if ($parent_info->{underdefined}) { |
218 | return $collapse_map->{-node_id} ? $collapse_map : undef |
219 | } |
220 | # nothing down the chain resolved - can't calculate a collapse-map |
221 | elsif (! $collapse_map->{-node_id}) { |
222 | $self->throw_exception ( sprintf |
223 | "Unable to calculate a definitive collapse column set for %s%s: fetch more unique non-nullable columns", |
224 | $self->source_name, |
225 | @$rel_chain > 1 |
226 | ? sprintf (' (last member of the %s chain)', join ' -> ', @$rel_chain ) |
227 | : '' |
228 | , |
229 | ); |
230 | } |
231 | |
232 | # If we got that far - we are collapsable - GREAT! Now go down all children |
233 | # a second time, and fill in the rest |
234 | |
235 | $collapse_map->{-is_optional} = 1 if $parent_info->{is_optional}; |
236 | $collapse_map->{-node_index} = ${ $node_idx_ref ||= \do { my $x = 1 } }++; # this is *deliberately* not 0-based |
237 | |
238 | my (@id_sets, $multis_in_chain); |
239 | for my $rel (sort keys %$relinfo) { |
240 | |
241 | $collapse_map->{$rel} = $relinfo->{$rel}{rsrc}->_resolve_collapse ( |
242 | { map { $_ => 1 } ( keys %{$rel_cols->{$rel}} ) }, |
243 | |
244 | $as_fq_idx, |
245 | |
246 | [ @$rel_chain, $rel], |
247 | |
248 | { |
249 | collapse_on => [ @{$collapse_map->{-node_id}} ], |
250 | |
251 | rel_condition => $relinfo->{$rel}{fk_map}, |
252 | |
253 | is_optional => $collapse_map->{-is_optional}, |
254 | |
255 | # if this is a 1:1 our own collapser can be used as a collapse-map |
256 | # (regardless of left or not) |
257 | collapser_reusable => $relinfo->{$rel}{is_single}, |
258 | }, |
259 | |
260 | $node_idx_ref, |
261 | ); |
262 | |
263 | $collapse_map->{$rel}{-is_single} = 1 if $relinfo->{$rel}{is_single}; |
264 | $collapse_map->{$rel}{-is_optional} ||= 1 unless $relinfo->{$rel}{is_inner}; |
265 | push @id_sets, @{ $collapse_map->{$rel}{-branch_id} }; |
266 | } |
267 | |
268 | $collapse_map->{-branch_id} = __unique_numlist( @id_sets, @{$collapse_map->{-node_id}} ); |
269 | |
270 | return $collapse_map; |
271 | } |
272 | |
76031e14 |
273 | # Takes an arrayref of {as} dbic column aliases and the collapse and select |
2d0b795a |
274 | # attributes from the same $rs (the selector requirement is a temporary |
275 | # workaround... I hope), and returns a coderef capable of: |
276 | # my $me_pref_clps = $coderef->([$rs->cursor->next/all]) |
277 | # Where the $me_pref_clps arrayref is the future argument to inflate_result() |
76031e14 |
278 | # |
279 | # For an example of this coderef in action (and to see its guts) look at |
2d0b795a |
280 | # t/resultset/rowparser_internals.t |
76031e14 |
281 | # |
2d0b795a |
282 | # This is a huge performance win, as we call the same code for # every row |
283 | # returned from the db, thus avoiding repeated method lookups when traversing |
284 | # relationships |
76031e14 |
285 | # |
286 | # Also since the coderef is completely stateless (the returned structure is |
287 | # always fresh on every new invocation) this is a very good opportunity for |
288 | # memoization if further speed improvements are needed |
289 | # |
2d0b795a |
290 | # The way we construct this coderef is somewhat fugly, although the result is |
291 | # really worth it. The final coderef does not perform any kind of recursion - |
292 | # the entire nested structure constructor is rolled out into a single scope. |
293 | # |
76031e14 |
294 | # In any case - the output of this thing is meticulously micro-tested, so |
2d0b795a |
295 | # any sort of adjustment/rewrite should be relatively easy (fsvo relatively) |
76031e14 |
296 | # |
297 | sub _mk_row_parser { |
298 | my ($self, $args) = @_; |
299 | |
300 | my $inflate_index = { map |
301 | { $args->{inflate_map}[$_] => $_ } |
302 | ( 0 .. $#{$args->{inflate_map}} ) |
303 | }; |
304 | |
2d0b795a |
305 | my $parser_src; |
306 | |
307 | # the non-collapsing assembler is easy |
308 | # FIXME SUBOPTIMAL there could be a yet faster way to do things here, but |
309 | # need to try an actual implementation and benchmark it: |
310 | # |
311 | # <timbunce_> First setup the nested data structure you want for each row |
312 | # Then call bind_col() to alias the row fields into the right place in |
313 | # the data structure, then to fetch the data do: |
314 | # push @rows, dclone($row_data_struct) while ($sth->fetchrow); |
315 | # |
316 | if (!$args->{collapse}) { |
317 | $parser_src = sprintf('$_ = %s for @{$_[0]}', __visit_infmap_simple( |
318 | $inflate_index, |
319 | { rsrc => $self }, # need the $rsrc to sanity-check inflation map once |
320 | )); |
321 | |
322 | # change the quoted placeholders to unquoted alias-references |
323 | $parser_src =~ s/ \' \xFF__VALPOS__(\d+)__\xFF \' /"\$_->[$1]"/gex; |
324 | } |
325 | |
326 | # the collapsing parser is more complicated - it needs to keep a lot of state |
327 | # |
328 | else { |
76031e14 |
329 | |
330 | my $collapse_map = $self->_resolve_collapse ( |
331 | # FIXME |
332 | # only consider real columns (not functions) during collapse resolution |
333 | # this check shouldn't really be here, as fucktards are not supposed to |
334 | # alias random crap to existing column names anyway, but still - just in |
335 | # case |
336 | # FIXME !!!! - this does not yet deal with unbalanced selectors correctly |
2d0b795a |
337 | # (it is now trivial as the attrs specify where things go out of sync |
338 | # needs MOAR tests) |
76031e14 |
339 | { map |
340 | { ref $args->{selection}[$inflate_index->{$_}] ? () : ( $_ => $inflate_index->{$_} ) } |
341 | keys %$inflate_index |
342 | } |
343 | ); |
344 | |
4e9fc3f3 |
345 | my $top_branch_idx_list = join (', ', @{$collapse_map->{-branch_id}}); |
76031e14 |
346 | |
4e9fc3f3 |
347 | my $top_node_id_path = join ('', map |
348 | { "{'\xFF__IDVALPOS__${_}__\xFF'}" } |
349 | @{$collapse_map->{-node_id}} |
350 | ); |
76031e14 |
351 | |
4e9fc3f3 |
352 | my $rel_assemblers = __visit_infmap_collapse ( |
76031e14 |
353 | $inflate_index, $collapse_map |
354 | ); |
76031e14 |
355 | |
4e9fc3f3 |
356 | $parser_src = sprintf (<<'EOS', $top_branch_idx_list, $top_node_id_path, $rel_assemblers); |
76031e14 |
357 | ### BEGIN STRING EVAL |
4e9fc3f3 |
358 | |
359 | my ($rows_pos, $result_pos, $cur_row, @cur_row_ids, @collapse_idx, $is_new_res) = (0,0); |
76031e14 |
360 | |
361 | # this loop is a bit arcane - the rationale is that the passed in |
362 | # $_[0] will either have only one row (->next) or will have all |
363 | # rows already pulled in (->all and/or unordered). Given that the |
364 | # result can be rather large - we reuse the same already allocated |
365 | # array, since the collapsed prefetch is smaller by definition. |
366 | # At the end we cut the leftovers away and move on. |
367 | while ($cur_row = |
4e9fc3f3 |
368 | ( ( $rows_pos >= 0 and $_[0][$rows_pos++] ) or do { $rows_pos = -1; undef } ) |
76031e14 |
369 | || |
370 | ($_[1] and $_[1]->()) |
371 | ) { |
372 | |
4e9fc3f3 |
373 | $cur_row_ids[$_] = defined $cur_row->[$_] ? $cur_row->[$_] : "\xFF\xFFN\xFFU\xFFL\xFFL\xFF\xFF" |
76031e14 |
374 | for (%1$s); # the top branch_id includes all id values |
375 | |
4e9fc3f3 |
376 | $is_new_res = ! $collapse_idx[1]%2$s and ( |
377 | $_[1] and $result_pos and (unshift @{$_[2]}, $cur_row) and last |
378 | ); |
76031e14 |
379 | |
4e9fc3f3 |
380 | %3$s |
76031e14 |
381 | |
4e9fc3f3 |
382 | $_[0][$result_pos++] = $collapse_idx[1]%2$s |
76031e14 |
383 | if $is_new_res; |
384 | } |
385 | |
386 | splice @{$_[0]}, $result_pos; # truncate the passed in array for cases of collapsing ->all() |
387 | ### END STRING EVAL |
388 | EOS |
389 | |
2d0b795a |
390 | # !!! note - different var than the one above |
76031e14 |
391 | # change the quoted placeholders to unquoted alias-references |
392 | $parser_src =~ s/ \' \xFF__VALPOS__(\d+)__\xFF \' /"\$cur_row->[$1]"/gex; |
4e9fc3f3 |
393 | $parser_src =~ s/ \' \xFF__IDVALPOS__(\d+)__\xFF \' /"\$cur_row_ids[$1]"/gex; |
76031e14 |
394 | } |
395 | |
76031e14 |
396 | $parser_src; |
397 | } |
398 | |
2d0b795a |
399 | # the simple non-collapsing nested structure recursor |
76031e14 |
400 | sub __visit_infmap_simple { |
401 | my ($val_idx, $args) = @_; |
402 | |
403 | my $my_cols = {}; |
404 | my $rel_cols; |
405 | for (keys %$val_idx) { |
406 | if ($_ =~ /^ ([^\.]+) \. (.+) /x) { |
407 | $rel_cols->{$1}{$2} = $val_idx->{$_}; |
408 | } |
409 | else { |
410 | $my_cols->{$_} = $val_idx->{$_}; |
411 | } |
412 | } |
413 | my @relperl; |
414 | for my $rel (sort keys %$rel_cols) { |
415 | |
2d0b795a |
416 | # DISABLEPRUNE |
76031e14 |
417 | #my $optional = $args->{is_optional}; |
418 | #$optional ||= ($args->{rsrc}->relationship_info($rel)->{attrs}{join_type} || '') =~ /^left/i; |
419 | |
420 | push @relperl, join ' => ', perlstring($rel), __visit_infmap_simple($rel_cols->{$rel}, { |
2d0b795a |
421 | rsrc => __get_related_source($args->{rsrc}, $rel, $rel_cols->{$rel}), |
422 | # DISABLEPRUNE |
423 | #non_top => 1, |
76031e14 |
424 | #is_optional => $optional, |
76031e14 |
425 | }); |
426 | |
2d0b795a |
427 | # FIXME SUBOPTIMAL DISABLEPRUNE - disabled to satisfy t/resultset/inflate_result_api.t |
76031e14 |
428 | #if ($optional and my @branch_null_checks = map |
429 | # { "(! defined '\xFF__VALPOS__${_}__\xFF')" } |
430 | # sort { $a <=> $b } values %{$rel_cols->{$rel}} |
431 | #) { |
432 | # $relperl[-1] = sprintf ( '(%s) ? ( %s => [] ) : ( %s )', |
433 | # join (' && ', @branch_null_checks ), |
434 | # perlstring($rel), |
435 | # $relperl[-1], |
436 | # ); |
437 | #} |
438 | } |
439 | |
440 | my $me_struct = keys %$my_cols |
441 | ? __visit_dump({ map { $_ => "\xFF__VALPOS__$my_cols->{$_}__\xFF" } (keys %$my_cols) }) |
442 | : 'undef' |
443 | ; |
444 | |
445 | return sprintf '[%s]', join (',', |
446 | $me_struct, |
447 | @relperl ? sprintf ('{ %s }', join (',', @relperl)) : (), |
448 | ); |
449 | } |
450 | |
2d0b795a |
451 | # the collapsing nested structure recursor |
76031e14 |
452 | sub __visit_infmap_collapse { |
453 | |
454 | my ($val_idx, $collapse_map, $parent_info) = @_; |
455 | |
456 | my $my_cols = {}; |
457 | my $rel_cols; |
458 | for (keys %$val_idx) { |
459 | if ($_ =~ /^ ([^\.]+) \. (.+) /x) { |
460 | $rel_cols->{$1}{$2} = $val_idx->{$_}; |
461 | } |
462 | else { |
463 | $my_cols->{$_} = $val_idx->{$_}; |
464 | } |
465 | } |
466 | |
467 | my $sequenced_node_id = join ('', map |
468 | { "{'\xFF__IDVALPOS__${_}__\xFF'}" } |
469 | @{$collapse_map->{-node_id}} |
470 | ); |
471 | |
472 | my $me_struct = keys %$my_cols |
473 | ? __visit_dump([{ map { $_ => "\xFF__VALPOS__$my_cols->{$_}__\xFF" } (keys %$my_cols) }]) |
4e9fc3f3 |
474 | : undef |
76031e14 |
475 | ; |
476 | my $node_idx_ref = sprintf '$collapse_idx[%d]%s', $collapse_map->{-node_index}, $sequenced_node_id; |
477 | |
478 | my $parent_idx_ref = sprintf( '$collapse_idx[%d]%s[1]{%s}', |
479 | @{$parent_info}{qw/node_idx sequenced_node_id/}, |
480 | perlstring($parent_info->{relname}), |
481 | ) if $parent_info; |
482 | |
483 | my @src; |
484 | if ($collapse_map->{-node_index} == 1) { |
485 | push @src, sprintf( '%s ||= %s;', |
486 | $node_idx_ref, |
487 | $me_struct, |
4e9fc3f3 |
488 | ) if $me_struct; |
76031e14 |
489 | } |
490 | elsif ($collapse_map->{-is_single}) { |
4e9fc3f3 |
491 | push @src, sprintf ( '%s ||= %s%s;', |
76031e14 |
492 | $parent_idx_ref, |
493 | $node_idx_ref, |
4e9fc3f3 |
494 | $me_struct ? " ||= $me_struct" : '', |
76031e14 |
495 | ); |
496 | } |
497 | else { |
4e9fc3f3 |
498 | push @src, sprintf('push @{%s}, %s%s unless %s;', |
76031e14 |
499 | $parent_idx_ref, |
500 | $node_idx_ref, |
4e9fc3f3 |
501 | $me_struct ? " ||= $me_struct" : '', |
76031e14 |
502 | $node_idx_ref, |
503 | ); |
504 | } |
505 | |
2d0b795a |
506 | # DISABLEPRUNE |
76031e14 |
507 | #my $known_defined = { %{ $parent_info->{known_defined} || {} } }; |
508 | #$known_defined->{$_}++ for @{$collapse_map->{-node_id}}; |
509 | |
510 | for my $rel (sort keys %$rel_cols) { |
511 | |
4e9fc3f3 |
512 | push @src, sprintf( '%s[1]{%s} ||= [];', $node_idx_ref, perlstring($rel) ) |
513 | unless $collapse_map->{$rel}{-is_single}; |
76031e14 |
514 | |
515 | push @src, __visit_infmap_collapse($rel_cols->{$rel}, $collapse_map->{$rel}, { |
516 | node_idx => $collapse_map->{-node_index}, |
517 | sequenced_node_id => $sequenced_node_id, |
518 | relname => $rel, |
2d0b795a |
519 | # DISABLEPRUNE |
76031e14 |
520 | #known_defined => $known_defined, |
521 | }); |
522 | |
2d0b795a |
523 | # FIXME SUBOPTIMAL DISABLEPRUNE - disabled to satisfy t/resultset/inflate_result_api.t |
76031e14 |
524 | #if ($collapse_map->{$rel}{-is_optional} and my @null_checks = map |
4e9fc3f3 |
525 | # { "(! defined '\xFF__IDVALPOS__${_}__\xFF')" } |
76031e14 |
526 | # sort { $a <=> $b } grep |
527 | # { ! $known_defined->{$_} } |
528 | # @{$collapse_map->{$rel}{-node_id}} |
529 | #) { |
530 | # $src[-1] = sprintf( '(%s) or %s', |
531 | # join (' || ', @null_checks ), |
532 | # $src[-1], |
533 | # ); |
534 | #} |
535 | } |
536 | |
537 | join "\n", @src; |
538 | } |
539 | |
540 | # adding a dep on MoreUtils *just* for this is retarded |
541 | sub __unique_numlist { |
542 | [ sort { $a <=> $b } keys %{ {map { $_ => 1 } @_ }} ] |
543 | } |
544 | |
545 | # This error must be thrown from two distinct codepaths, joining them is |
546 | # rather hard. Go for this hack instead. |
547 | sub __get_related_source { |
548 | my ($rsrc, $rel, $relcols) = @_; |
549 | try { |
550 | $rsrc->related_source ($rel) |
551 | } catch { |
552 | $rsrc->throw_exception(sprintf( |
553 | "Can't inflate prefetch into non-existent relationship '%s' from '%s', " |
554 | . "check the inflation specification (columns/as) ending in '...%s.%s'.", |
555 | $rel, |
556 | $rsrc->source_name, |
557 | $rel, |
558 | (sort { length($a) <=> length ($b) } keys %$relcols)[0], |
559 | ))}; |
560 | } |
561 | |
562 | # keep our own DD object around so we don't have to fitz with quoting |
563 | my $dumper_obj; |
564 | sub __visit_dump { |
565 | # we actually will be producing functional perl code here, |
566 | # thus no second-guessing of what these globals might have |
567 | # been set to. DO NOT CHANGE! |
568 | ($dumper_obj ||= do { |
569 | require Data::Dumper; |
570 | Data::Dumper->new([]) |
2d0b795a |
571 | ->Useperl (0) |
76031e14 |
572 | ->Purity (1) |
573 | ->Pad ('') |
574 | ->Useqq (0) |
575 | ->Terse (1) |
576 | ->Quotekeys (1) |
577 | ->Deepcopy (0) |
578 | ->Deparse (0) |
579 | ->Maxdepth (0) |
580 | ->Indent (0) # faster but harder to read, perhaps leave at 1 ? |
581 | })->Values ([$_[0]])->Dump; |
582 | } |
583 | |
584 | 1; |