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;
For a list of attributes that can be passed to C<search>, see
L</ATTRIBUTES>. For more examples of using this function, see
L<Searching|DBIx::Class::Manual::Cookbook/Searching>. For a complete
-documentation for the first argument, see L<SQL::Abstract>
+documentation for the first argument, see L<SQL::Abstract/"WHERE CLAUSES">
and its extension L<DBIx::Class::SQLMaker>.
For more help on using joins with search, see L<DBIx::Class::Manual::Joining>.
}
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 };
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_results->[0];
$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_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} ) {
+ # a cursor will need to be closed over in case of collapse
+ $cursor = $self->cursor;
+
$attrs->{_ordered_for_collapse} = (
(
$attrs->{order_by}
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 ! $did_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_core_row} = ( (
$inflator_cref
==
);
}
}
- # Special-case multi-object HRI (we always prune, and there is no $inflator_cref pass)
- 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,
- prune_null_branches => 1,
- }) )->($rows, @extra_collapser_args);
- }
- # Regular multi-object
else {
- my $parser_type = $self->{_result_inflator}{is_core_row} ? 'classic_pruning' : 'classic_nonpruning';
+ my $parser_type =
+ $self->{_result_inflator}{is_hri} ? 'hri'
+ : $self->{_result_inflator}{is_core_row} ? 'classic_pruning'
+ : 'classic_nonpruning'
+ ;
- ( $self->{_row_parser}{$parser_type} ||= $rsrc->_mk_row_parser({
+ # $args and $attrs to _mk_row_parser are separated 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},
- prune_null_branches => $self->{_result_inflator}{is_core_row},
- }) )->($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 performance
+ # 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
+ }
+ ;
+
+ $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
# 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 _related_results_construction record_filter for/};
+ 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 _related_results_construction 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
# 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/select as collapse _related_results_construction/;
+ delete $attrs->{$_} for qw/select as collapse/;
$attrs->{columns} = [ map { "$attrs->{alias}.$_" } @$idcols ];
# this will be consumed by the pruner waaaaay down the stack
element should be a data value in the earlier specified column order.
For example:
- $Arstist_rs->populate([
+ $schema->resultset("Artist")->populate([
[ qw( artistid name ) ],
[ 100, 'A Formally Unknown Singer' ],
[ 101, 'A singer that jumped the shark two albums ago' ],
}
-# populate() argumnets went over several incarnations
+# populate() arguments went over several incarnations
# What we ultimately support is AoH
sub _normalize_populate_args {
my ($self, $arg) = @_;
);
}
else {
- # precendence must be given to passed values over values inherited from
+ # precedence must be given to passed values over values inherited from
# the cond, so the order here is important.
my $collapsed_cond = $self->_collapse_cond($self->{cond});
my %implied = %{$self->_remove_alias($collapsed_cond, $alias)};
# determines if the resultset defines at least one
# of the attributes supplied
#
-# used to determine if a subquery is neccessary
+# used to determine if a subquery is necessary
#
# supports some virtual attributes:
# -join
my $attrs = { %{ $self->_resolved_attrs } };
- $self->result_source->storage->_select_args_to_query (
+ my $aq = $self->result_source->storage->_select_args_to_query (
$attrs->{from}, $attrs->{select}, $attrs->{where}, $attrs
);
+
+ $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
#XXX - temp fix for result_class bug. There likely is a more elegant fix -groditi
delete @{$attrs}{qw(result_class alias)};
- my $related_cache;
-
- if (my $cache = $self->get_cache) {
- $related_cache = [ map
- { @{$_->related_resultset($rel)->get_cache||[]} }
- @$cache
- ];
- }
-
my $rel_source = $rsrc->related_source($rel);
my $new = do {
where => $attrs->{where},
});
};
- $new->set_cache($related_cache) if $related_cache;
+
+ if (my $cache = $self->get_cache) {
+ my @related_cache = map
+ { @{$_->related_resultset($rel)->get_cache||[]} }
+ @$cache
+ ;
+
+ $new->set_cache(\@related_cache) if @related_cache;
+ }
+
$new;
};
}
if ( List::Util::first { $_ =~ /\./ } @{$attrs->{as}} ) {
$attrs->{_related_results_construction} = 1;
}
- else {
- $attrs->{collapse} = 0;
- }
# run through the resulting joinstructure (starting from our current slot)
- # and unset collapse if proven unnesessary
+ # and unset collapse if proven unnecessary
#
# also while we are at it find out if the current root source has
# been premultiplied by previous related_source chaining
if (ref $attrs->{from} eq 'ARRAY') {
- if (@{$attrs->{from}} <= 1) {
+ if (@{$attrs->{from}} == 1) {
# no joins - no collapse
$attrs->{collapse} = 0;
}
If an L</order_by> is already declared, and orders the resultset in a way that
makes collapsing as described above impossible (e.g. C<< ORDER BY
has_many_rel.column >> or C<ORDER BY RANDOM()>), DBIC will automatically
-switch to "eager" mode and slurp the entire resultset before consturcting the
+switch to "eager" mode and slurp the entire resultset before constructing the
first object returned by L</next>.
Setting this attribute on a resultset that does not join any has_many
=item dbic_colname
Used to fill in missing sqlt_datatype and sqlt_size attributes (if they are
-explicitly specified they are never overriden). Also used by some weird DBDs,
+explicitly specified they are never overridden). Also used by some weird DBDs,
where the column name should be available at bind_param time (e.g. Oracle).
=back
[ $name => $val ] === [ { dbic_colname => $name }, $val ]
[ \$dt => $val ] === [ { sqlt_datatype => $dt }, $val ]
[ undef, $val ] === [ {}, $val ]
+ $val === [ {}, $val ]
=head1 AUTHOR AND CONTRIBUTORS