use base qw/DBIx::Class/;
use DBIx::Class::Carp;
use DBIx::Class::ResultSetColumn;
-use Scalar::Util qw/blessed weaken/;
+use Scalar::Util qw/blessed weaken reftype/;
use Try::Tiny;
use Data::Compare (); # no imports!!! guard against insane architecture
'bool' => "_bool",
fallback => 1;
+# this is real - CDBICompat overrides it with insanity
+# yes, prototype won't matter, but that's for now ;)
+sub _bool () { 1 }
+
__PACKAGE__->mk_group_accessors('simple' => qw/_result_class result_source/);
=head1 NAME
my ($source, $attrs) = @_;
$source = $source->resolve
if $source->isa('DBIx::Class::ResultSourceHandle');
+
$attrs = { %{$attrs||{}} };
+ delete @{$attrs}{qw(_sqlmaker_select_args _related_results_construction)};
if ($attrs->{page}) {
$attrs->{rows} ||= 10;
my $cache;
my %safe = (alias => 1, cache => 1);
if ( ! List::Util::first { !$safe{$_} } keys %$call_attrs and (
- ! defined $_[0]
+ ! defined $call_cond
or
- ref $_[0] eq 'HASH' && ! keys %{$_[0]}
+ ref $call_cond eq 'HASH' && ! keys %$call_cond
or
- ref $_[0] eq 'ARRAY' && ! @{$_[0]}
+ ref $call_cond eq 'ARRAY' && ! @$call_cond
)) {
$cache = $self->get_cache;
}
my $old_attrs = { %{$self->{attrs}} };
- my $old_having = delete $old_attrs->{having};
- my $old_where = delete $old_attrs->{where};
+ my ($old_having, $old_where) = delete @{$old_attrs}{qw(having where)};
my $new_attrs = { %$old_attrs };
# older deprecated name, use only if {columns} is not there
if (my $c = delete $new_attrs->{cols}) {
+ carp_unique( "Resultset attribute 'cols' is deprecated, use 'columns' instead" );
if ($new_attrs->{columns}) {
carp "Resultset specifies both the 'columns' and the legacy 'cols' attributes - ignoring 'cols'";
}
my ($self, $attrs) = @_;
# legacy syntax
- $attrs->{'+columns'} = $self->_merge_attr($attrs->{'+columns'}, delete $attrs->{include_columns})
- if exists $attrs->{include_columns};
+ if ( exists $attrs->{include_columns} ) {
+ carp_unique( "Resultset attribute 'include_columns' is deprecated, use '+columns' instead" );
+ $attrs->{'+columns'} = $self->_merge_attr(
+ $attrs->{'+columns'}, delete $attrs->{include_columns}
+ );
+ }
# columns are always placed first, however
my $self = shift;
return $self->{cursor} ||= do {
- my $attrs = { %{$self->_resolved_attrs } };
+ my $attrs = $self->_resolved_attrs;
$self->result_source->storage->select(
$attrs->{from}, $attrs->{select}, $attrs->{where}, $attrs
);
my $attrs = { %{$self->_resolved_attrs} };
$self->throw_exception(
- 'single() can not be used on resultsets prefetching has_many. Use find( \%cond ) or next() instead'
+ 'single() can not be used on resultsets collapsing a has_many. Use find( \%cond ) or next() instead'
) if $attrs->{collapse};
if ($where) {
$attrs->{from}, $attrs->{select},
$attrs->{where}, $attrs
)];
+
return undef unless @$data;
- $self->{stashed_rows} = [ $data ];
- $self->_construct_objects->[0];
+ $self->{_stashed_rows} = [ $data ];
+ $self->_construct_results->[0];
}
return ($self->all)[0];
}
- return shift(@{$self->{stashed_objects}}) if @{ $self->{stashed_objects}||[] };
+ return shift(@{$self->{_stashed_results}}) if @{ $self->{_stashed_results}||[] };
- $self->{stashed_objects} = $self->_construct_objects
+ $self->{_stashed_results} = $self->_construct_results
or return undef;
- return shift @{$self->{stashed_objects}};
+ return shift @{$self->{_stashed_results}};
}
-# Constructs as many objects as it can in one pass while respecting
+# Constructs as many results as it can in one pass while respecting
# cursor laziness. Several modes of operation:
#
-# * Always builds everything present in @{$self->{stashed_rows}}
+# * Always builds everything present in @{$self->{_stashed_rows}}
# * If called with $fetch_all true - pulls everything off the cursor and
-# builds all objects in one pass
+# builds all result structures (or objects) in one pass
# * If $self->_resolved_attrs->{collapse} is true, checks the order_by
# and if the resultset is ordered properly by the left side:
# * Fetches stuff off the cursor until the "master object" changes,
-# and saves the last extra row (if any) in @{$self->{stashed_rows}}
+# and saves the last extra row (if any) in @{$self->{_stashed_rows}}
# OR
# * Just fetches, and collapses/constructs everything as if $fetch_all
# was requested (there is no other way to collapse except for an
# eager cursor)
# * If no collapse is requested - just get the next row, construct and
# return
-sub _construct_objects {
+sub _construct_results {
my ($self, $fetch_all) = @_;
my $rsrc = $self->result_source;
my $attrs = $self->_resolved_attrs;
- if (!$fetch_all and ! $attrs->{order_by} and $attrs->{collapse}) {
+ if (
+ ! $fetch_all
+ and
+ ! $attrs->{order_by}
+ and
+ $attrs->{collapse}
+ and
+ my @pcols = $rsrc->primary_columns
+ ) {
# default order for collapsing unless the user asked for something
- $attrs->{order_by} = [ map { join '.', $attrs->{alias}, $_} $rsrc->primary_columns ];
+ $attrs->{order_by} = [ map { join '.', $attrs->{alias}, $_} @pcols ];
$attrs->{_ordered_for_collapse} = 1;
$attrs->{_order_is_artificial} = 1;
}
- my $cursor = $self->cursor;
-
# this will be used as both initial raw-row collector AND as a RV of
- # _construct_objects. Not regrowing the array twice matters a lot...
- # a suprising amount actually
- my $rows = delete $self->{stashed_rows};
+ # _construct_results. Not regrowing the array twice matters a lot...
+ # a surprising amount actually
+ my $rows = delete $self->{_stashed_rows};
+
+ my $cursor; # we may not need one at all
+
+ my $did_fetch_all = $fetch_all;
if ($fetch_all) {
# FIXME SUBOPTIMAL - we can do better, cursor->next/all (well diff. methods) should return a ref
- $rows = [ ($rows ? @$rows : ()), $cursor->all ];
+ $rows = [ ($rows ? @$rows : ()), $self->cursor->all ];
}
elsif( $attrs->{collapse} ) {
- $attrs->{_ordered_for_collapse} = (!$attrs->{order_by}) ? 0 : do {
- my $st = $rsrc->schema->storage;
- my @ord_cols = map
- { $_->[0] }
- ( $st->_extract_order_criteria($attrs->{order_by}) )
- ;
-
- my $colinfos = $st->_resolve_column_info($attrs->{from}, \@ord_cols);
-
- for (0 .. $#ord_cols) {
- if (
- ! $colinfos->{$ord_cols[$_]}
- or
- $colinfos->{$ord_cols[$_]}{-result_source} != $rsrc
- ) {
- splice @ord_cols, $_;
- last;
- }
- }
+ # a cursor will need to be closed over in case of collapse
+ $cursor = $self->cursor;
- # since all we check here are the start of the order_by belonging to the
- # top level $rsrc, a present identifying set will mean that the resultset
- # is ordered by its leftmost table in a tsable manner
- (@ord_cols and $rsrc->_identifying_column_set({ map
- { $colinfos->{$_}{-colname} => $colinfos->{$_} }
- @ord_cols
- })) ? 1 : 0;
- } unless defined $attrs->{_ordered_for_collapse};
+ $attrs->{_ordered_for_collapse} = (
+ (
+ $attrs->{order_by}
+ and
+ $rsrc->schema
+ ->storage
+ ->_main_source_order_by_portion_is_stable($rsrc, $attrs->{order_by}, $attrs->{where})
+ ) ? 1 : 0
+ ) unless defined $attrs->{_ordered_for_collapse};
if (! $attrs->{_ordered_for_collapse}) {
- $fetch_all = 1;
+ $did_fetch_all = 1;
# instead of looping over ->next, use ->all in stealth mode
# *without* calling a ->reset afterwards
- # FIXME - encapsulation breach, got to be a better way
+ # FIXME ENCAPSULATION - encapsulation breach, cursor method additions pending
if (! $cursor->{_done}) {
$rows = [ ($rows ? @$rows : ()), $cursor->all ];
$cursor->{_done} = 1;
}
}
- if (! $fetch_all and ! @{$rows||[]} ) {
+ if (! $did_fetch_all and ! @{$rows||[]} ) {
# FIXME SUBOPTIMAL - we can do better, cursor->next/all (well diff. methods) should return a ref
+ $cursor ||= $self->cursor;
if (scalar (my @r = $cursor->next) ) {
$rows = [ \@r ];
}
return undef unless @{$rows||[]};
- my @extra_collapser_args;
- if ($attrs->{collapse} and ! $fetch_all ) {
+ # sanity check - people are too clever for their own good
+ if ($attrs->{collapse} and my $aliastypes = $attrs->{_sqlmaker_select_args}[3]{_aliastypes} ) {
- @extra_collapser_args = (
- # FIXME SUBOPTIMAL - we can do better, cursor->next/all (well diff. methods) should return a ref
- sub { my @r = $cursor->next or return; \@r }, # how the collapser gets more rows
- ($self->{stashed_rows} = []), # where does it stuff excess
- );
+ my $multiplied_selectors;
+ for my $sel_alias ( grep { $_ ne $attrs->{alias} } keys %{ $aliastypes->{selecting} } ) {
+ if (
+ $aliastypes->{multiplying}{$sel_alias}
+ or
+ scalar grep { $aliastypes->{multiplying}{(values %$_)[0]} } @{ $aliastypes->{selecting}{$sel_alias}{-parents} }
+ ) {
+ $multiplied_selectors->{$_} = 1 for values %{$aliastypes->{selecting}{$sel_alias}{-seen_columns}}
+ }
+ }
+
+ for my $i (0 .. $#{$attrs->{as}} ) {
+ my $sel = $attrs->{select}[$i];
+
+ if (ref $sel eq 'SCALAR') {
+ $sel = $$sel;
+ }
+ elsif( ref $sel eq 'REF' and ref $$sel eq 'ARRAY' ) {
+ $sel = $$sel->[0];
+ }
+
+ $self->throw_exception(
+ 'Result collapse not possible - selection from a has_many source redirected to the main object'
+ ) if ($multiplied_selectors->{$sel} and $attrs->{as}[$i] !~ /\./);
+ }
}
# hotspot - skip the setter
my $infmap = $attrs->{as};
- $self->{_result_inflator}{is_hri} = do { ( $inflator_cref == (
- require DBIx::Class::ResultClass::HashRefInflator
- &&
- DBIx::Class::ResultClass::HashRefInflator->can('inflate_result')
- ) ) ? 1 : 0
- } unless defined $self->{_result_inflator}{is_hri};
+ $self->{_result_inflator}{is_core_row} = ( (
+ $inflator_cref
+ ==
+ ( \&DBIx::Class::Row::inflate_result || die "No ::Row::inflate_result() - can't happen" )
+ ) ? 1 : 0 ) unless defined $self->{_result_inflator}{is_core_row};
+
+ $self->{_result_inflator}{is_hri} = ( (
+ ! $self->{_result_inflator}{is_core_row}
+ and
+ $inflator_cref == (
+ require DBIx::Class::ResultClass::HashRefInflator
+ &&
+ DBIx::Class::ResultClass::HashRefInflator->can('inflate_result')
+ )
+ ) ? 1 : 0 ) unless defined $self->{_result_inflator}{is_hri};
+
- if ($attrs->{_single_resultclass_inflation}) {
+ if (! $attrs->{_related_results_construction}) {
# construct a much simpler array->hash folder for the one-table cases right here
if ($self->{_result_inflator}{is_hri}) {
for my $r (@$rows) {
);
}
}
- # Special-case multi-object HRI (we always prune)
- elsif ($self->{_result_inflator}{is_hri}) {
- ( $self->{_row_parser}{hri} ||= $rsrc->_mk_row_parser({
- eval => 1,
- inflate_map => $infmap,
- selection => $attrs->{select},
- collapse => $attrs->{collapse},
- premultiplied => $attrs->{_main_source_premultiplied},
- hri_style => 1,
- }) )->($rows, @extra_collapser_args);
- }
- # Regular multi-object
else {
+ my $parser_type =
+ $self->{_result_inflator}{is_hri} ? 'hri'
+ : $self->{_result_inflator}{is_core_row} ? 'classic_pruning'
+ : 'classic_nonpruning'
+ ;
- ( $self->{_row_parser}{classic} ||= $rsrc->_mk_row_parser({
+ # $args and $attrs to _mk_row_parser are seperated to delineate what is
+ # core collapser stuff and what is dbic $rs specific
+ @{$self->{_row_parser}{$parser_type}}{qw(cref nullcheck)} = $rsrc->_mk_row_parser({
eval => 1,
inflate_map => $infmap,
- selection => $attrs->{select},
collapse => $attrs->{collapse},
premultiplied => $attrs->{_main_source_premultiplied},
- }) )->($rows, @extra_collapser_args);
+ hri_style => $self->{_result_inflator}{is_hri},
+ prune_null_branches => $self->{_result_inflator}{is_hri} || $self->{_result_inflator}{is_core_row},
+ }, $attrs) unless $self->{_row_parser}{$parser_type}{cref};
+
+ # column_info metadata historically hasn't been too reliable.
+ # We need to start fixing this somehow (the collapse resolver
+ # can't work without it). Add an explicit check for the *main*
+ # result, hopefully this will gradually weed out such errors
+ #
+ # FIXME - this is a temporary kludge that reduces perfromance
+ # It is however necessary for the time being
+ my ($unrolled_non_null_cols_to_check, $err);
+
+ if (my $check_non_null_cols = $self->{_row_parser}{$parser_type}{nullcheck} ) {
+
+ $err =
+ 'Collapse aborted due to invalid ResultSource metadata - the following '
+ . 'selections are declared non-nullable but NULLs were retrieved: '
+ ;
+
+ my @violating_idx;
+ COL: for my $i (@$check_non_null_cols) {
+ ! defined $_->[$i] and push @violating_idx, $i and next COL for @$rows;
+ }
+
+ $self->throw_exception( $err . join (', ', map { "'$infmap->[$_]'" } @violating_idx ) )
+ if @violating_idx;
+
+ $unrolled_non_null_cols_to_check = join (',', @$check_non_null_cols);
+ }
- $_ = $inflator_cref->($res_class, $rsrc, @$_) for @$rows;
+ my $next_cref =
+ ($did_fetch_all or ! $attrs->{collapse}) ? undef
+ : defined $unrolled_non_null_cols_to_check ? eval sprintf <<'EOS', $unrolled_non_null_cols_to_check
+sub {
+ # FIXME SUBOPTIMAL - we can do better, cursor->next/all (well diff. methods) should return a ref
+ my @r = $cursor->next or return;
+ if (my @violating_idx = grep { ! defined $r[$_] } (%s) ) {
+ $self->throw_exception( $err . join (', ', map { "'$infmap->[$_]'" } @violating_idx ) )
}
+ \@r
+}
+EOS
+ : sub {
+ # FIXME SUBOPTIMAL - we can do better, cursor->next/all (well diff. methods) should return a ref
+ my @r = $cursor->next or return;
+ \@r
+ }
+ ;
- # CDBI compat stuff
- if ($attrs->{record_filter}) {
- $_ = $attrs->{record_filter}->($_) for @$rows;
+ $self->{_row_parser}{$parser_type}{cref}->(
+ $rows,
+ $next_cref ? ( $next_cref, $self->{_stashed_rows} = [] ) : (),
+ );
+
+ # Special-case multi-object HRI - there is no $inflator_cref pass
+ unless ($self->{_result_inflator}{is_hri}) {
+ $_ = $inflator_cref->($res_class, $rsrc, @$_) for @$rows
+ }
}
+ # The @$rows check seems odd at first - why wouldn't we want to warn
+ # regardless? The issue is things like find() etc, where the user
+ # *knows* only one result will come back. In these cases the ->all
+ # is not a pessimization, but rather something we actually want
+ carp_unique(
+ 'Unable to properly collapse has_many results in iterator mode due '
+ . 'to order criteria - performed an eager cursor slurp underneath. '
+ . 'Consider using ->all() instead'
+ ) if ( ! $fetch_all and @$rows > 1 );
+
return $rows;
}
my ($self, $result_class) = @_;
if ($result_class) {
- unless (ref $result_class) { # don't fire this for an object
- $self->ensure_class_loaded($result_class);
+ # don't fire this for an object
+ $self->ensure_class_loaded($result_class)
+ unless ref($result_class);
+
+ if ($self->get_cache) {
+ carp_unique('Changing the result_class of a ResultSet instance with cached results is a noop - the cache contents will not be altered');
+ }
+ # FIXME ENCAPSULATION - encapsulation breach, cursor method additions pending
+ elsif ($self->{cursor} && $self->{cursor}{_pos}) {
+ $self->throw_exception('Changing the result_class of a ResultSet instance with an active cursor is not supported');
}
+
$self->_result_class($result_class);
- # THIS LINE WOULD BE A BUG - this accessor specifically exists to
- # permit the user to set result class on one result set only; it only
- # chains if provided to search()
- #$self->{attrs}{result_class} = $result_class if ref $self;
delete $self->{_result_inflator};
}
# software based limiting can not be ported if this $rs is to be used
# in a subquery itself (i.e. ->as_query)
if ($self->_has_resolved_attr (qw/collapse group_by offset rows/)) {
- return $self->_count_subq_rs;
+ return $self->_count_subq_rs($self->{_attrs});
}
else {
- return $self->_count_rs;
+ return $self->_count_rs($self->{_attrs});
}
}
my ($self, $attrs) = @_;
my $rsrc = $self->result_source;
- $attrs ||= $self->_resolved_attrs;
my $tmp_attrs = { %$attrs };
# take off any limits, record_filter is cdbi, and no point of ordering nor locking a count
delete @{$tmp_attrs}{qw/rows offset order_by record_filter for/};
# overwrite the selector (supplied by the storage)
- $tmp_attrs->{select} = $rsrc->storage->_count_select ($rsrc, $attrs);
- $tmp_attrs->{as} = 'count';
-
- my $tmp_rs = $rsrc->resultset_class->new($rsrc, $tmp_attrs)->get_column ('count');
-
- return $tmp_rs;
+ $rsrc->resultset_class->new($rsrc, {
+ %$tmp_attrs,
+ select => $rsrc->storage->_count_select ($rsrc, $attrs),
+ as => 'count',
+ })->get_column ('count');
}
#
my ($self, $attrs) = @_;
my $rsrc = $self->result_source;
- $attrs ||= $self->_resolved_attrs;
my $sub_attrs = { %$attrs };
# extra selectors do not go in the subquery and there is no point of ordering it, nor locking it
- delete @{$sub_attrs}{qw/collapse columns as select _prefetch_selector_range order_by for/};
+ delete @{$sub_attrs}{qw/collapse columns as select order_by for/};
# if we multi-prefetch we group_by something unique, as this is what we would
# get out of the rs via ->next/->all. We *DO WANT* to clobber old group_by regardless
->get_column ('count');
}
-sub _bool {
- return 1;
-}
=head2 count_literal
$self->throw_exception("all() doesn't take any arguments, you probably wanted ->search(...)->all()");
}
- delete @{$self}{qw/stashed_rows stashed_objects/};
+ delete @{$self}{qw/_stashed_rows _stashed_results/};
if (my $c = $self->get_cache) {
return @$c;
$self->cursor->reset;
- my $objs = $self->_construct_objects('fetch_all') || [];
+ my $objs = $self->_construct_results('fetch_all') || [];
$self->set_cache($objs) if $self->{attrs}{cache};
sub reset {
my ($self) = @_;
- delete @{$self}{qw/stashed_rows stashed_objects/};
+ delete @{$self}{qw/_stashed_rows _stashed_results/};
$self->{all_cache_position} = 0;
$self->cursor->reset;
return $self;
my $attrs = { %{$self->_resolved_attrs} };
my $join_classifications;
- my $existing_group_by = delete $attrs->{group_by};
+ my ($existing_group_by) = delete @{$attrs}{qw(group_by _grouped_by_distinct)};
# do we need a subquery for any reason?
my $needs_subq = (
# simplify the joinmap, so we can further decide if a subq is necessary
if (!$needs_subq and @{$attrs->{from}} > 1) {
- $attrs->{from} = $storage->_prune_unused_joins ($attrs->{from}, $attrs->{select}, $self->{cond}, $attrs);
-
- # check if there are any joins left after the prune
- if ( @{$attrs->{from}} > 1 ) {
- $join_classifications = $storage->_resolve_aliastypes_from_select_args (
- [ @{$attrs->{from}}[1 .. $#{$attrs->{from}}] ],
- $attrs->{select},
- $self->{cond},
- $attrs
- );
- # any non-pruneable joins imply subq
- $needs_subq = scalar keys %{ $join_classifications->{restricting} || {} };
- }
+ ($attrs->{from}, $join_classifications) =
+ $storage->_prune_unused_joins ($attrs->{from}, $attrs->{select}, $self->{cond}, $attrs);
+
+ # any non-pruneable non-local restricting joins imply subq
+ $needs_subq = defined List::Util::first { $_ ne $attrs->{alias} } keys %{ $join_classifications->{restricting} || {} };
}
# check if the head is composite (by now all joins are thrown out unless $needs_subq)
);
# make a new $rs selecting only the PKs (that's all we really need for the subq)
- delete $attrs->{$_} for qw/collapse select _prefetch_selector_range as/;
+ delete $attrs->{$_} for qw/select as collapse/;
$attrs->{columns} = [ map { "$attrs->{alias}.$_" } @$idcols ];
- $attrs->{group_by} = \ ''; # FIXME - this is an evil hack, it causes the optimiser to kick in and throw away the LEFT joins
+
+ # this will be consumed by the pruner waaaaay down the stack
+ $attrs->{_force_prune_multiplying_joins} = 1;
+
my $subrs = (ref $self)->new($rsrc, $attrs);
if (@$idcols == 1) {
my ($merged_cond, $cols_from_relations) = $self->_merge_with_rscond($values);
- my %new = (
+ my $new = $self->result_class->new({
%$merged_cond,
- @$cols_from_relations
+ ( @$cols_from_relations
? (-cols_from_relations => $cols_from_relations)
- : (),
+ : ()
+ ),
-result_source => $self->result_source, # DO NOT REMOVE THIS, REQUIRED
- );
+ });
+
+ if (
+ reftype($new) eq 'HASH'
+ and
+ ! keys %$new
+ and
+ blessed($new)
+ ) {
+ carp_unique (sprintf (
+ "%s->new returned a blessed empty hashref - a strong indicator something is wrong with its inheritance chain",
+ $self->result_class,
+ ));
+ }
- return $self->result_class->new(\%new);
+ $new;
}
# _merge_with_rscond
my $attrs = { %{ $self->_resolved_attrs } };
- # For future use:
- #
- # in list ctx:
- # my ($sql, \@bind, \%dbi_bind_attrs) = _select_args_to_query (...)
- # $sql also has no wrapping parenthesis in list ctx
- #
- my $sqlbind = $self->result_source->storage
- ->_select_args_to_query ($attrs->{from}, $attrs->{select}, $attrs->{where}, $attrs);
+ my $aq = $self->result_source->storage->_select_args_to_query (
+ $attrs->{from}, $attrs->{select}, $attrs->{where}, $attrs
+ );
- return $sqlbind;
+ $self->{_attrs}{_sqlmaker_select_args} = $attrs->{_sqlmaker_select_args};
+
+ $aq;
}
=head2 find_or_new
{ artist => 'fred' }, { key => 'artists' });
$cd->cd_to_producer->find_or_new({ producer => $producer },
- { key => 'primary });
+ { key => 'primary' });
Find an existing record from this resultset using L</find>. if none exists,
instantiate a new result object and return it. The object will not be saved
=cut
sub create {
- my ($self, $attrs) = @_;
+ my ($self, $col_data) = @_;
$self->throw_exception( "create needs a hashref" )
- unless ref $attrs eq 'HASH';
- return $self->new_result($attrs)->insert;
+ unless ref $col_data eq 'HASH';
+ return $self->new_result($col_data)->insert;
}
=head2 find_or_create
sub related_resultset {
my ($self, $rel) = @_;
- return $self->{related_resultsets}{$rel} ||= do {
+ return $self->{related_resultsets}{$rel}
+ if defined $self->{related_resultsets}{$rel};
+
+ return $self->{related_resultsets}{$rel} = do {
my $rsrc = $self->result_source;
my $rel_info = $rsrc->relationship_info($rel);
# ->_resolve_join as otherwise they get lost - captainL
my $join = $self->_merge_joinpref_attr( $attrs->{join}, $attrs->{prefetch} );
- delete @{$attrs}{qw/join prefetch collapse group_by distinct select as columns +select +as +columns/};
+ delete @{$attrs}{qw/join prefetch collapse group_by distinct _grouped_by_distinct select as columns +select +as +columns/};
my $seen = { %{ (delete $attrs->{seen_join}) || {} } };
return {%$attrs, from => $from, seen_join => $seen};
}
-# FIXME - this needs to go live in Schema with the tree walker... or
-# something
-my $inflatemap_checker;
-$inflatemap_checker = sub {
- my ($rsrc, $relpaths) = @_;
-
- my $rels;
-
- for (@$relpaths) {
- $_ =~ /^ ( [^\.]+ ) \. (.+) $/x
- or next;
-
- push @{$rels->{$1}}, $2;
- }
-
- for my $rel (keys %$rels) {
- my $rel_rsrc = try {
- $rsrc->related_source ($rel)
- } catch {
- $rsrc->throw_exception(sprintf(
- "Inflation into non-existent relationship '%s' of '%s' requested, "
- . "check the inflation specification (columns/as) ending in '...%s.%s'",
- $rel,
- $rsrc->source_name,
- $rel,
- ( sort { length($a) <=> length ($b) } @{$rels->{$rel}} )[0],
- ))};
-
- $inflatemap_checker->($rel_rsrc, $rels->{$rel});
- }
-
- return;
-};
-
sub _resolved_attrs {
my $self = shift;
return $self->{_attrs} if $self->{_attrs};
}
}
- # validate the user-supplied 'as' chain
- # folks get too confused by the (logical) exception message, need to
- # go to some lengths to clarify the text
- #
- # FIXME - this needs to go live in Schema with the tree walker... or
- # something
- $inflatemap_checker->($source, \@as);
-
$attrs->{select} = \@sel;
$attrs->{as} = \@as;
carp_unique ("Useless use of distinct on a grouped resultset ('distinct' is ignored when a 'group_by' is present)");
}
else {
+ $attrs->{_grouped_by_distinct} = 1;
# distinct affects only the main selection part, not what prefetch may
# add below.
- $attrs->{group_by} = $source->storage->_group_over_selection (
- $attrs->{from},
- $attrs->{select},
- $attrs->{order_by},
- );
+ $attrs->{group_by} = $source->storage->_group_over_selection($attrs);
}
}
my @prefetch = $source->_resolve_prefetch( $prefetch, $alias, $join_map );
- # we need to somehow mark which columns came from prefetch
- if (@prefetch) {
- my $sel_end = $#{$attrs->{select}};
- $attrs->{_prefetch_selector_range} = [ $sel_end + 1, $sel_end + @prefetch ];
- }
-
push @{ $attrs->{select} }, (map { $_->[0] } @prefetch);
push @{ $attrs->{as} }, (map { $_->[1] } @prefetch);
}
- if ( ! List::Util::first { $_ =~ /\./ } @{$attrs->{as}} ) {
- $attrs->{_single_resultclass_inflation} = 1;
- $attrs->{collapse} = 0;
+ if ( List::Util::first { $_ =~ /\./ } @{$attrs->{as}} ) {
+ $attrs->{_related_results_construction} = 1;
}
# run through the resulting joinstructure (starting from our current slot)
if (ref $attrs->{from} eq 'ARRAY') {
- if (@{$attrs->{from}} <= 1) {
+ if (@{$attrs->{from}} == 1) {
# no joins - no collapse
$attrs->{collapse} = 0;
}
expression). Adds C<me.> onto the start of any column without a C<.> in
it and sets C<select> from that, then auto-populates C<as> from
C<select> as normal. (You may also use the C<cols> attribute, as in
-earlier versions of DBIC.)
+earlier versions of DBIC, but this is deprecated.)
Essentially C<columns> does the same as L</select> and L</as>.
=back
-Indicates additional columns to be selected from storage. Works the same
-as L</columns> but adds columns to the selection. (You may also use the
-C<include_columns> attribute, as in earlier versions of DBIC). For
-example:-
+Indicates additional columns to be selected from storage. Works the same as
+L</columns> but adds columns to the selection. (You may also use the
+C<include_columns> attribute, as in earlier versions of DBIC, but this is
+deprecated). For example:-
$schema->resultset('CD')->search(undef, {
'+columns' => ['artist.name'],
cmp_ok( $count, '==', $prefetch_count, "Counts should be the same" );
-That cmp_ok() may or may not pass depending on the datasets involved. This
-behavior may or may not survive the 0.09 transition.
+That cmp_ok() may or may not pass depending on the datasets involved. In other
+words the C<WHERE> condition would apply to the entire dataset, just like
+it would in regular SQL. If you want to add a condition only to the "right side"
+of a C<LEFT JOIN> - consider declaring and using a L<relationship with a custom
+condition|DBIx::Class::Relationship::Base/condition>
=back
[ $name => $val ] === [ { dbic_colname => $name }, $val ]
[ \$dt => $val ] === [ { sqlt_datatype => $dt }, $val ]
[ undef, $val ] === [ {}, $val ]
+ $val === [ {}, $val ]
=head1 AUTHOR AND CONTRIBUTORS