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