my $rs = $schema->resultset('CD')->search({ title => '100th Window' });
-IMPORTANT: If called on an object, proxies to new_result instead so
+=over
+
+=item WARNING
+
+If called on an object, proxies to L</new_result> instead, so
my $cd = $schema->resultset('CD')->new({ title => 'Spoon' });
-will return a CD object, not a ResultSet.
+will return a CD object, not a ResultSet, and is equivalent to:
+
+ my $cd = $schema->resultset('CD')->new_result({ title => 'Spoon' });
+
+Please also keep in mind that many internals call C<new_result> directly,
+so overloading this method with the idea of intercepting new result object
+creation B<will not work>. See also warning pertaining to L</create>.
+
+=back
=cut
=head2 search_literal
+B<CAVEAT>: C<search_literal> is provided for Class::DBI compatibility and
+should only be used in that context. C<search_literal> is a convenience
+method. It is equivalent to calling C<< $schema->search(\[]) >>, but if you
+want to ensure columns are bound correctly, use L</search>.
+
+See L<DBIx::Class::Manual::Cookbook/Searching> and
+L<DBIx::Class::Manual::FAQ/Searching> for searching techniques that do not
+require C<search_literal>.
+
=over 4
-=item Arguments: $sql_fragment, @bind_values
+=item Arguments: $sql_fragment, @standalone_bind_values
=item Return Value: $resultset (scalar context) || @row_objs (list context)
Pass a literal chunk of SQL to be added to the conditional part of the
resultset query.
-CAVEAT: C<search_literal> is provided for Class::DBI compatibility and should
-only be used in that context. C<search_literal> is a convenience method.
-It is equivalent to calling $schema->search(\[]), but if you want to ensure
-columns are bound correctly, use C<search>.
-
Example of how to use C<search> instead of C<search_literal>
my @cds = $cd_rs->search_literal('cdid = ? AND (artist = ? OR artist = ?)', (2, 1, 2));
my @cds = $cd_rs->search(\[ 'cdid = ? AND (artist = ? OR artist = ?)', [ 'cdid', 2 ], [ 'artist', 1 ], [ 'artist', 2 ] ]);
-
-See L<DBIx::Class::Manual::Cookbook/Searching> and
-L<DBIx::Class::Manual::FAQ/Searching> for searching techniques that do not
-require C<search_literal>.
-
=cut
sub search_literal {
if ( @bind && ref($bind[-1]) eq 'HASH' ) {
$attr = pop @bind;
}
- return $self->search(\[ $sql, map [ __DUMMY__ => $_ ], @bind ], ($attr || () ));
+ return $self->search(\[ $sql, map [ {} => $_ ], @bind ], ($attr || () ));
}
=head2 find
# Run the query, passing the result_class since it should propagate for find
my $rs = $self->search ($final_cond, {result_class => $self->result_class, %$attrs});
- if ($rs->_resolved_attrs->{collapse}) {
+ if (keys %{$rs->_resolved_attrs->{collapse}}) {
my $row = $rs->next;
carp "Query returned more than one row" if $rs->next;
return $row;
and
!$ENV{DBIC_NULLABLE_KEY_NOWARN}
and
- my @undefs = grep { ! defined $final_cond->{$_} } (keys %$final_cond)
+ my @undefs = sort grep { ! defined $final_cond->{$_} } (keys %$final_cond)
) {
carp_unique ( sprintf (
"NULL/undef values supplied for requested unique constraint '%s' (NULL "
my $attrs = $self->_resolved_attrs_copy;
- $self->throw_exception(
- 'single() can not be used on resultsets prefetching has_many. Use find( \%cond ) or next() instead'
- ) if $attrs->{collapse};
+ if (keys %{$attrs->{collapse}}) {
+ $self->throw_exception(
+ 'single() can not be used on resultsets prefetching has_many. Use find( \%cond ) or next() instead'
+ );
+ }
if ($where) {
if (defined $attrs->{where}) {
}
}
- my $data = [ $self->result_source->storage->select_single(
+ my @data = $self->result_source->storage->select_single(
$attrs->{from}, $attrs->{select},
$attrs->{where}, $attrs
- )];
+ );
- return @$data ? $self->_construct_objects($data)->[0] : undef;
+ return (@data ? ($self->_construct_object(@data))[0] : undef);
}
sub next {
my ($self) = @_;
-
if (my $cache = $self->get_cache) {
$self->{all_cache_position} ||= 0;
return $cache->[$self->{all_cache_position}++];
}
-
if ($self->{attrs}{cache}) {
delete $self->{pager};
$self->{all_cache_position} = 1;
return ($self->all)[0];
}
+ if ($self->{stashed_objects}) {
+ my $obj = shift(@{$self->{stashed_objects}});
+ delete $self->{stashed_objects} unless @{$self->{stashed_objects}};
+ return $obj;
+ }
+ my @row = (
+ exists $self->{stashed_row}
+ ? @{delete $self->{stashed_row}}
+ : $self->cursor->next
+ );
+ return undef unless (@row);
+ my ($row, @more) = $self->_construct_object(@row);
+ $self->{stashed_objects} = \@more if @more;
+ return $row;
+}
- return shift(@{$self->{stashed_objects}}) if @{ $self->{stashed_objects}||[] };
-
- $self->{stashed_objects} = $self->_construct_objects
- or return undef;
+sub _construct_object {
+ my ($self, @row) = @_;
- return shift @{$self->{stashed_objects}};
+ my $info = $self->_collapse_result($self->{_attrs}{as}, \@row)
+ or return ();
+ my @new = $self->result_class->inflate_result($self->result_source, @$info);
+ @new = $self->{_attrs}{record_filter}->(@new)
+ if exists $self->{_attrs}{record_filter};
+ return @new;
}
-# takes a single DBI-row of data and coinstructs as many objects
-# as the resultset attributes call for.
-# This can be a bit of an action at a distance - it takes as an argument
-# the *current* cursor-row (already taken off the $sth), but if
-# collapsing is requested it will keep advancing the cursor either
-# until the current row-object is assembled (the collapser was able to
-# order the result sensibly) OR until the cursor is exhausted (an
-# unordered collapsing resultset effectively triggers ->all)
-sub _construct_objects {
- my ($self, $fetched_row, $fetch_all) = @_;
+sub _collapse_result {
+ my ($self, $as_proto, $row) = @_;
- my $attrs = $self->_resolved_attrs;
- my $unordered = 0; # will deal with this later
-
- # 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;
-
- # $fetch_all implies all() which means all stashes have been cleared
- # and the cursor reset
- if ($fetch_all) {
- # FIXME - we can do better, cursor->all (well a diff. method) should return a ref
- $rows = [ $self->cursor->all ];
- }
- elsif ($unordered) {
- $rows = [
- $fetched_row||(),
- @{ delete $self->{stashed_rows} || []},
- $self->cursor->all,
- ];
- }
- else { # simple single object
- $rows = [ $fetched_row || ( @{$self->{stashed_rows}||[]} ? shift @{$self->{stashed_rows}} : [$self->cursor->next] ) ];
+ my @copy = @$row;
+
+ # 'foo' => [ undef, 'foo' ]
+ # 'foo.bar' => [ 'foo', 'bar' ]
+ # 'foo.bar.baz' => [ 'foo.bar', 'baz' ]
+
+ my @construct_as = map { [ (/^(?:(.*)\.)?([^.]+)$/) ] } @$as_proto;
+
+ my %collapse = %{$self->{_attrs}{collapse}||{}};
+
+ my @pri_index;
+
+ # if we're doing collapsing (has_many prefetch) we need to grab records
+ # until the PK changes, so fill @pri_index. if not, we leave it empty so
+ # we know we don't have to bother.
+
+ # the reason for not using the collapse stuff directly is because if you
+ # had for e.g. two artists in a row with no cds, the collapse info for
+ # both would be NULL (undef) so you'd lose the second artist
+
+ # store just the index so we can check the array positions from the row
+ # without having to contruct the full hash
+
+ if (keys %collapse) {
+ my %pri = map { ($_ => 1) } $self->result_source->_pri_cols;
+ foreach my $i (0 .. $#construct_as) {
+ next if defined($construct_as[$i][0]); # only self table
+ if (delete $pri{$construct_as[$i][1]}) {
+ push(@pri_index, $i);
+ }
+ last unless keys %pri; # short circuit (Johnny Five Is Alive!)
+ }
}
- return undef unless @{$rows->[0]||[]};
+ # no need to do an if, it'll be empty if @pri_index is empty anyway
- my $rsrc = $self->result_source;
- my $res_class = $self->result_class;
- my $inflator = $res_class->can ('inflate_result')
- or $self->throw_exception("Inflator $res_class does not provide an inflate_result() method");
-
- # construct a much simpler array->hash folder for the one-table cases right here
- if ($attrs->{_single_object_inflation} and ! $attrs->{collapse}) {
- # FIXME this is a very very very hot spot
- # while rather optimal we can *still* do much better, by
- # building a smarter [Row|HRI]::inflate_result(), and
- # switch to feeding it data via some leaner interface
- #
- my $infmap = $attrs->{as};
- my @as_idx = 0..$#$infmap;
- for my $r (@$rows) {
- $r = [{ map { $infmap->[$_] => $r->[$_] } @as_idx }]
+ my %pri_vals = map { ($_ => $copy[$_]) } @pri_index;
+
+ my @const_rows;
+
+ do { # no need to check anything at the front, we always want the first row
+
+ my %const;
+
+ foreach my $this_as (@construct_as) {
+ $const{$this_as->[0]||''}{$this_as->[1]} = shift(@copy);
}
- # FIXME - this seems to be faster than the hashmapper aove, especially
- # on more rows, but need a better bench-environment to confirm
- #eval sprintf (
- # '$_ = [{ %s }] for @$rows',
- # join (', ', map { "\$infmap->[$_] => \$_->[$_]" } 0..$#$infmap )
- #);
- }
- else {
- push @$rows, @{$self->{stashed_rows}||[]};
-
- $rsrc->_mk_row_parser({
- inflate_map => $attrs->{as},
- selection => $attrs->{select},
- collapse => $attrs->{collapse},
- unordered => $unordered,
- })->(
- $rows, # modify in-place, shrinking/extending as necessary
- ($attrs->{collapse} and ! $fetch_all and ! $unordered)
- ? (
- sub { my @r = $self->cursor->next or return undef; \@r },
- ($self->{stashed_rows} = []), # this is where we empty things and prepare for leftovers
- )
- : ()
- ,
- );
- }
+ push(@const_rows, \%const);
+
+ } until ( # no pri_index => no collapse => drop straight out
+ !@pri_index
+ or
+ do { # get another row, stash it, drop out if different PK
- $_ = $res_class->$inflator($rsrc, @$_) for @$rows;
+ @copy = $self->cursor->next;
+ $self->{stashed_row} = \@copy;
- # CDBI compat stuff
- if ($attrs->{record_filter}) {
- $_ = $attrs->{record_filter}->($_) for @$rows;
+ # last thing in do block, counts as true if anything doesn't match
+
+ # check xor defined first for NULL vs. NOT NULL then if one is
+ # defined the other must be so check string equality
+
+ grep {
+ (defined $pri_vals{$_} ^ defined $copy[$_])
+ || (defined $pri_vals{$_} && ($pri_vals{$_} ne $copy[$_]))
+ } @pri_index;
+ }
+ );
+
+ my $alias = $self->{attrs}{alias};
+ my $info = [];
+
+ my %collapse_pos;
+
+ my @const_keys;
+
+ foreach my $const (@const_rows) {
+ scalar @const_keys or do {
+ @const_keys = sort { length($a) <=> length($b) } keys %$const;
+ };
+ foreach my $key (@const_keys) {
+ if (length $key) {
+ my $target = $info;
+ my @parts = split(/\./, $key);
+ my $cur = '';
+ my $data = $const->{$key};
+ foreach my $p (@parts) {
+ $target = $target->[1]->{$p} ||= [];
+ $cur .= ".${p}";
+ if ($cur eq ".${key}" && (my @ckey = @{$collapse{$cur}||[]})) {
+ # collapsing at this point and on final part
+ my $pos = $collapse_pos{$cur};
+ CK: foreach my $ck (@ckey) {
+ if (!defined $pos->{$ck} || $pos->{$ck} ne $data->{$ck}) {
+ $collapse_pos{$cur} = $data;
+ delete @collapse_pos{ # clear all positioning for sub-entries
+ grep { m/^\Q${cur}.\E/ } keys %collapse_pos
+ };
+ push(@$target, []);
+ last CK;
+ }
+ }
+ }
+ if (exists $collapse{$cur}) {
+ $target = $target->[-1];
+ }
+ }
+ $target->[0] = $data;
+ } else {
+ $info->[0] = $const->{$key};
+ }
+ }
}
- return $rows;
+ return $info;
}
=head2 result_source
# 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
- if ( $attrs->{collapse} ) {
+ if ( keys %{$attrs->{collapse}} ) {
$sub_attrs->{group_by} = [ map { "$attrs->{alias}.$_" } @{
$rsrc->_identifying_column_set || $self->throw_exception(
'Unable to construct a unique group_by criteria properly collapsing the '
=head2 count_literal
+B<CAVEAT>: C<count_literal> is provided for Class::DBI compatibility and
+should only be used in that context. See L</search_literal> for further info.
+
=over 4
-=item Arguments: $sql_fragment, @bind_values
+=item Arguments: $sql_fragment, @standalone_bind_values
=item Return Value: $count
sub all {
my $self = shift;
if(@_) {
- $self->throw_exception("all() doesn't take any arguments, you probably wanted ->search(...)->all()");
+ $self->throw_exception("all() doesn't take any arguments, you probably wanted ->search(...)->all()");
}
- delete $self->{stashed_rows};
- delete $self->{stashed_objects};
-
- if (my $c = $self->get_cache) {
- return @$c;
+ return @{ $self->get_cache } if $self->get_cache;
+
+ my @obj;
+
+ if (keys %{$self->_resolved_attrs->{collapse}}) {
+ # Using $self->cursor->all is really just an optimisation.
+ # If we're collapsing has_many prefetches it probably makes
+ # very little difference, and this is cleaner than hacking
+ # _construct_object to survive the approach
+ $self->cursor->reset;
+ my @row = $self->cursor->next;
+ while (@row) {
+ push(@obj, $self->_construct_object(@row));
+ @row = (exists $self->{stashed_row}
+ ? @{delete $self->{stashed_row}}
+ : $self->cursor->next);
+ }
+ } else {
+ @obj = map { $self->_construct_object(@$_) } $self->cursor->all;
}
- $self->cursor->reset;
-
- my $objs = $self->_construct_objects(undef, 'fetch_all') || [];
+ $self->set_cache(\@obj) if $self->{attrs}{cache};
- $self->set_cache($objs) if $self->{attrs}{cache};
-
- return @$objs;
+ return @obj;
}
=head2 reset
sub reset {
my ($self) = @_;
- delete $self->{_attrs};
- delete $self->{stashed_rows};
- delete $self->{stashed_objects};
-
+ delete $self->{_attrs} if exists $self->{_attrs};
$self->{all_cache_position} = 0;
$self->cursor->reset;
return $self;
my $attrs = { %{$self->_resolved_attrs} };
- # "needs" is a strong word here - if the subquery is part of an IN clause - no point of
- # even adding the group_by. It will really be used only when composing a poor-man's
- # multicolumn-IN equivalent OR set
- my $needs_group_by_subq = defined $attrs->{group_by};
+ my $existing_group_by = delete $attrs->{group_by};
+ my $needs_subq = defined $existing_group_by;
+
+ # simplify the joinmap and maybe decide if a subquery is necessary
+ my $relation_classifications = {};
- # simplify the joinmap and maybe decide if a grouping (and thus subquery) is necessary
- my $relation_classifications;
if (ref($attrs->{from}) eq 'ARRAY') {
- $attrs->{from} = $storage->_prune_unused_joins ($attrs->{from}, $attrs->{select}, $cond, $attrs);
+ # if we already know we need a subq, no point of classifying relations
+ if (!$needs_subq and @{$attrs->{from}} > 1) {
+ $attrs->{from} = $storage->_prune_unused_joins ($attrs->{from}, $attrs->{select}, $cond, $attrs);
- $relation_classifications = $storage->_resolve_aliastypes_from_select_args (
- [ @{$attrs->{from}}[1 .. $#{$attrs->{from}}] ],
- $attrs->{select},
- $cond,
- $attrs
- ) unless $needs_group_by_subq; # we already know we need a group, no point of resolving them
+ $relation_classifications = $storage->_resolve_aliastypes_from_select_args (
+ [ @{$attrs->{from}}[1 .. $#{$attrs->{from}}] ],
+ $attrs->{select},
+ $cond,
+ $attrs
+ );
+ }
}
else {
- $needs_group_by_subq ||= 1; # if {from} is unparseable assume the worst
+ $needs_subq ||= 1; # if {from} is unparseable assume the worst
}
- $needs_group_by_subq ||= exists $relation_classifications->{multiplying};
-
- # if no subquery - life is easy-ish
- unless (
- $needs_group_by_subq
- or
- keys %$relation_classifications # if any joins at all - need to wrap a subq
- or
- $self->_has_resolved_attr(qw/rows offset/) # limits call for a subq
+ # do we need anything like a subquery?
+ if (
+ ! $needs_subq
+ and
+ ! keys %{ $relation_classifications->{restricting} || {} }
+ and
+ ! $self->_has_resolved_attr(qw/rows offset/) # limits call for a subq
) {
# Most databases do not allow aliasing of tables in UPDATE/DELETE. Thus
# a condition containing 'me' or other table prefixes will not work
- # at all. What this code tries to do (badly) is to generate a condition
- # with the qualifiers removed, by exploiting the quote mechanism of sqla
- #
- # this is atrocious and should be replaced by normal sqla introspection
- # one sunny day
- my ($sql, @bind) = do {
+ # at all. Tell SQLMaker to dequalify idents via a gross hack.
+ my $cond = do {
my $sqla = $rsrc->storage->sql_maker;
local $sqla->{_dequalify_idents} = 1;
- $sqla->_recurse_where($self->{cond});
- } if $self->{cond};
-
+ \[ $sqla->_recurse_where($self->{cond}) ];
+ };
return $rsrc->storage->$op(
$rsrc,
$op eq 'update' ? $values : (),
- $self->{cond} ? \[$sql, @bind] : (),
+ $cond,
);
}
$rsrc->source_name,
)
);
- my $existing_group_by = delete $attrs->{group_by};
# 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/collapse _collapse_order_by select _prefetch_selector_range as/;
$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
my $subrs = (ref $self)->new($rsrc, $attrs);
);
}
else {
+
# if all else fails - get all primary keys and operate over a ORed set
# wrap in a transaction for consistency
# this is where the group_by starts to matter
- my $subq_group_by;
- if ($needs_group_by_subq) {
- $subq_group_by = $attrs->{columns};
-
+ if (
+ $existing_group_by
+ or
+ keys %{ $relation_classifications->{multiplying} || {} }
+ ) {
# make sure if there is a supplied group_by it matches the columns compiled above
# perfectly. Anything else can not be sanely executed on most databases so croak
# right then and there
if (
join ("\x00", sort @current_group_by)
ne
- join ("\x00", sort @$subq_group_by )
+ join ("\x00", sort @{$attrs->{columns}} )
) {
$self->throw_exception (
"You have just attempted a $op operation on a resultset which does group_by"
);
}
}
+
+ $subrs = $subrs->search({}, { group_by => $attrs->{columns} });
}
my $guard = $storage->txn_scope_guard;
my @op_condition;
- for my $row ($subrs->search({}, { group_by => $subq_group_by })->cursor->all) {
+ for my $row ($subrs->cursor->all) {
push @op_condition, { map
{ $idcols->[$_] => $row->[$_] }
(0 .. $#$idcols)
sub new_result {
my ($self, $values) = @_;
- $self->throw_exception( "new_result needs a hash" )
+
+ $self->throw_exception( "new_result takes only one argument - a hashref of values" )
+ if @_ > 2;
+
+ $self->throw_exception( "new_result expects a hashref" )
unless (ref $values eq 'HASH');
my ($merged_cond, $cols_from_relations) = $self->_merge_with_rscond($values);
=item Arguments: none
-=item Return Value: \[ $sql, @bind ]
+=item Return Value: \[ $sql, L<@bind_values|/DBIC BIND VALUES> ]
=back
with an arbitrary depth and width, as long as the relationships actually
exists and the correct column data has been supplied.
-
Instead of hashrefs of plain related data (key/value pairs), you may
also pass new or inserted objects. New objects (not inserted yet, see
L</new>), will be inserted into their appropriate tables.
lot of the internals simply never call it, so your override will be
bypassed more often than not. Override either L<new|DBIx::Class::Row/new>
or L<insert|DBIx::Class::Row/insert> depending on how early in the
-L</create> process you need to intervene.
+L</create> process you need to intervene. See also warning pertaining to
+L</new>.
=back
year => 2005,
});
- if( $cd->in_storage ) {
+ if( !$cd->in_storage ) {
# do some stuff
$cd->insert;
}
to call L<DBIx::Class::Row/insert> to save the newly created row to the
database!
- my $cd = $schema->resultset('CD')->update_or_new(
- {
- artist => 'Massive Attack',
- title => 'Mezzanine',
- year => 1998,
- },
- { key => 'cd_artist_title' }
- );
-
- if( $cd->in_storage ) {
- # do some stuff
- $cd->insert;
- }
-
=cut
sub update_or_create {
if (my $cache = $self->get_cache) {
if ($cache->[0] && $cache->[0]->related_resultset($rel)->get_cache) {
- $new_cache = [ map { @{$_->related_resultset($rel)->get_cache||[]} }
+ $new_cache = [ map { @{$_->related_resultset($rel)->get_cache} }
@$cache ];
}
}
if (my $cols = delete $attrs->{columns}) {
for my $c (ref $cols eq 'ARRAY' ? @$cols : $cols) {
if (ref $c eq 'HASH') {
- for my $as (keys %$c) {
+ for my $as (sort keys %$c) {
push @sel, $c->{$as};
push @as, $as;
}
if $attrs->{select};
# assume all unqualified selectors to apply to the current alias (legacy stuff)
- $_ = (ref $_ or $_ =~ /\./) ? $_ : "$alias.$_" for @sel;
+ for (@sel) {
+ $_ = (ref $_ or $_ =~ /\./) ? $_ : "$alias.$_";
+ }
- # disqualify all $alias.col as-bits (inflate-map mandated)
- $_ = ($_ =~ /^\Q$alias.\E(.+)$/) ? $1 : $_ for @as;
+ # disqualify all $alias.col as-bits (collapser mandated)
+ for (@as) {
+ $_ = ($_ =~ /^\Q$alias.\E(.+)$/) ? $1 : $_;
+ }
# de-duplicate the result (remove *identical* select/as pairs)
# and also die on duplicate {as} pointing to different {select}s
}
}
- # generate selections based on the prefetch helper
- my $prefetch;
- $prefetch = $self->_merge_joinpref_attr( {}, delete $attrs->{prefetch} )
- if defined $attrs->{prefetch};
-
- if ($prefetch) {
+ $attrs->{collapse} ||= {};
+ if ($attrs->{prefetch}) {
$self->throw_exception("Unable to prefetch, resultset contains an unnamed selector $attrs->{_dark_selector}{string}")
if $attrs->{_dark_selector};
- $attrs->{collapse} = 1;
+ my $prefetch = $self->_merge_joinpref_attr( {}, delete $attrs->{prefetch} );
+
+ my $prefetch_ordering = [];
# this is a separate structure (we don't look in {from} directly)
# as the resolver needs to shift things off the lists to work
}
}
- my @prefetch = $source->_resolve_prefetch( $prefetch, $alias, $join_map );
+ my @prefetch =
+ $source->_resolve_prefetch( $prefetch, $alias, $join_map, $prefetch_ordering, $attrs->{collapse} );
# we need to somehow mark which columns came from prefetch
if (@prefetch) {
push @{ $attrs->{select} }, (map { $_->[0] } @prefetch);
push @{ $attrs->{as} }, (map { $_->[1] } @prefetch);
- }
-
- # run through the resulting joinstructure (starting from our current slot)
- # and unset collapse if proven unnesessary
- if ($attrs->{collapse} && ref $attrs->{from} eq 'ARRAY') {
-
- if (@{$attrs->{from}} > 1) {
-
- # find where our table-spec starts and consider only things after us
- my @fromlist = @{$attrs->{from}};
- while (@fromlist) {
- my $t = shift @fromlist;
- $t = $t->[0] if ref $t eq 'ARRAY'; #me vs join from-spec mismatch
- last if ($t->{-alias} && $t->{-alias} eq $alias);
- }
- for (@fromlist) {
- $attrs->{collapse} = ! $_->[0]{-is_single}
- and last;
- }
- }
- else {
- # no joins - no collapse
- $attrs->{collapse} = 0;
- }
+ push( @{$attrs->{order_by}}, @$prefetch_ordering );
+ $attrs->{_collapse_order_by} = \@$prefetch_ordering;
}
- $attrs->{_single_object_inflation} = ! List::Util::first { $_ =~ /\./ } @{$attrs->{as}};
-
# if both page and offset are specified, produce a combined offset
# even though it doesn't make much sense, this is what pre 081xx has
# been doing
C<\%attrs> argument. See L</search>, L</search_rs>, L</find>,
L</count>.
+Default attributes can be set on the result class using
+L<DBIx::Class::ResultSource/resultset_attributes>. (Please read
+the CAVEATS on that feature before using it!)
+
These are in no particular order:
=head2 order_by
If you want to fetch related objects from other tables as well, see C<prefetch>
below.
+ NOTE: An internal join-chain pruner will discard certain joins while
+ constructing the actual SQL query, as long as the joins in question do not
+ affect the retrieved result. This for example includes 1:1 left joins
+ that are not part of the restriction specification (WHERE/HAVING) nor are
+ a part of the query selection.
+
For more help on using joins with search, see L<DBIx::Class::Manual::Joining>.
=head2 prefetch
=back
+=head2 alias
+
+=over 4
+
+=item Value: $source_alias
+
+=back
+
+Sets the source alias for the query. Normally, this defaults to C<me>, but
+nested search queries (sub-SELECTs) might need specific aliases set to
+reference inner queries. For example:
+
+ my $q = $rs
+ ->related_resultset('CDs')
+ ->related_resultset('Tracks')
+ ->search({
+ 'track.id' => { -ident => 'none_search.id' },
+ })
+ ->as_query;
+
+ my $ids = $self->search({
+ -not_exists => $q,
+ }, {
+ alias => 'none_search',
+ group_by => 'none_search.id',
+ })->get_column('id')->as_query;
+
+ $self->search({ id => { -in => $ids } })
+
+This attribute is directly tied to L</current_source_alias>.
+
=head2 page
=over 4
Adds to the WHERE clause.
# only return rows WHERE deleted IS NULL for all searches
- __PACKAGE__->resultset_attributes({ where => { deleted => undef } }); )
+ __PACKAGE__->resultset_attributes({ where => { deleted => undef } });
Can be overridden by passing C<< { where => undef } >> as an attribute
to a resultset.
=over 4
-=item Value: ( 'update' | 'shared' )
+=item Value: ( 'update' | 'shared' | \$scalar )
=back
Set to 'update' for a SELECT ... FOR UPDATE or 'shared' for a SELECT
-... FOR SHARED.
+... FOR SHARED. If \$scalar is passed, this is taken directly and embedded in the
+query.
+
+=head1 DBIC BIND VALUES
+
+Because DBIC may need more information to bind values than just the column name
+and value itself, it uses a special format for both passing and receiving bind
+values. Each bind value should be composed of an arrayref of
+C<< [ \%args => $val ] >>. The format of C<< \%args >> is currently:
+
+=over 4
+
+=item dbd_attrs
+
+If present (in any form), this is what is being passed directly to bind_param.
+Note that different DBD's expect different bind args. (e.g. DBD::SQLite takes
+a single numerical type, while DBD::Pg takes a hashref if bind options.)
+
+If this is specified, all other bind options described below are ignored.
+
+=item sqlt_datatype
+
+If present, this is used to infer the actual bind attribute by passing to
+C<< $resolved_storage->bind_attribute_by_data_type() >>. Defaults to the
+"data_type" from the L<add_columns column info|DBIx::Class::ResultSource/add_columns>.
+
+Note that the data type is somewhat freeform (hence the sqlt_ prefix);
+currently drivers are expected to "Do the Right Thing" when given a common
+datatype name. (Not ideal, but that's what we got at this point.)
+
+=item sqlt_size
+
+Currently used to correctly allocate buffers for bind_param_inout().
+Defaults to "size" from the L<add_columns column info|DBIx::Class::ResultSource/add_columns>,
+or to a sensible value based on the "data_type".
+
+=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,
+where the column name should be available at bind_param time (e.g. Oracle).
+
+=back
+
+For backwards compatibility and convenience, the following shortcuts are
+supported:
+
+ [ $name => $val ] === [ { dbic_colname => $name }, $val ]
+ [ \$dt => $val ] === [ { sqlt_datatype => $dt }, $val ]
+ [ undef, $val ] === [ {}, $val ]
+
+=head1 AUTHOR AND CONTRIBUTORS
+
+See L<AUTHOR|DBIx::Class/AUTHOR> and L<CONTRIBUTORS|DBIx::Class/CONTRIBUTORS> in DBIx::Class
+
+=head1 LICENSE
+
+You may distribute this code under the same terms as Perl itself.
=cut