X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FResultSet.pm;h=574b2da18f3dea55c4e8ae5d5065e472921e3ce5;hb=c9733800;hp=6cd23b1cc2d2adf1622a8086d6c1d553269b4718;hpb=9361b05d319e60314bf2caff1e96ff3c388a50bb;p=dbsrgits%2FDBIx-Class.git
diff --git a/lib/DBIx/Class/ResultSet.pm b/lib/DBIx/Class/ResultSet.pm
index 6cd23b1..574b2da 100644
--- a/lib/DBIx/Class/ResultSet.pm
+++ b/lib/DBIx/Class/ResultSet.pm
@@ -927,7 +927,7 @@ sub _build_unique_cond {
=over 4
-=item Arguments: $rel, $cond, \%attrs?
+=item Arguments: $rel, $cond?, \%attrs?
=item Return Value: $new_resultset (scalar context) || @row_objs (list context)
@@ -1541,10 +1541,15 @@ sub _count_subq_rs {
# 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/};
- # if we multi-prefetch we group_by primary keys only as this is what we would
+ # 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 ( keys %{$attrs->{collapse}} ) {
- $sub_attrs->{group_by} = [ map { "$attrs->{alias}.$_" } ($rsrc->_pri_cols) ]
+ $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 '
+ . 'has_many prefetch before count()'
+ );
+ } ]
}
# Calculate subquery selector
@@ -1737,35 +1742,119 @@ sub first {
sub _rs_update_delete {
my ($self, $op, $values) = @_;
+ my $cond = $self->{cond};
my $rsrc = $self->result_source;
+ my $storage = $rsrc->schema->storage;
- my $needs_group_by_subq = $self->_has_resolved_attr (qw/collapse group_by -join/);
- my $needs_subq = $needs_group_by_subq || $self->_has_resolved_attr(qw/rows offset/);
+ my $attrs = { %{$self->_resolved_attrs} };
- if ($needs_group_by_subq or $needs_subq) {
+ # "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};
- # make a new $rs selecting only the PKs (that's all we really need)
- my $attrs = $self->_resolved_attrs_copy;
+ # simplify the joinmap and maybe decide if a grouping (and thus subquery) is necessary
+ my $relation_classifications;
+ if (ref($attrs->{from}) eq 'ARRAY') {
+ if (@{$attrs->{from}} == 1) {
+ # not a fucking JOIN at all, quit with the dickery
+ $relation_classifications = {};
+ } else {
+ $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
+ }
+ }
+ else {
+ $needs_group_by_subq ||= 1; # if {from} is unparseable assume the worst
+ }
+
+ $needs_group_by_subq ||= exists $relation_classifications->{multiplying};
- delete $attrs->{$_} for qw/collapse _collapse_order_by select _prefetch_selector_range as/;
- $attrs->{columns} = [ map { "$attrs->{alias}.$_" } ($self->result_source->_pri_cols) ];
+ # 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
+ ) {
+ # 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. Tell SQLMaker to dequalify idents via a gross hack.
+ my $sqla = $rsrc->storage->sql_maker;
+ local $sqla->{_dequalify_idents} = 1;
+ return $rsrc->storage->$op(
+ $rsrc,
+ $op eq 'update' ? $values : (),
+ $self->{cond},
+ );
+ }
+
+ # we got this far - means it is time to wrap a subquery
+ my $idcols = $rsrc->_identifying_column_set || $self->throw_exception(
+ sprintf(
+ "Unable to perform complex resultset %s() without an identifying set of columns on source '%s'",
+ $op,
+ $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 _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);
+
+ if (@$idcols == 1) {
+ return $storage->$op (
+ $rsrc,
+ $op eq 'update' ? $values : (),
+ { $idcols->[0] => { -in => $subrs->as_query } },
+ );
+ }
+ elsif ($storage->_use_multicolumn_in) {
+ # This is hideously ugly, but SQLA does not understand multicol IN expressions
+ my $sql_maker = $storage->sql_maker;
+ my ($sql, @bind) = @${$subrs->as_query};
+ $sql = sprintf ('(%s) IN %s', # the as_query already comes with a set of parenthesis
+ join (', ', map { $sql_maker->_quote ($_) } @$idcols),
+ $sql,
+ );
+
+ return $storage->$op (
+ $rsrc,
+ $op eq 'update' ? $values : (),
+ \[$sql, @bind],
+ );
+ }
+ 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) {
- # make sure no group_by was supplied, or if there is one - make sure it matches
- # the columns compiled above perfectly. Anything else can not be sanely executed
- # on most databases so croak right then and there
+ $subq_group_by = $attrs->{columns};
- if (my $g = $attrs->{group_by}) {
+ # 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 ($existing_group_by) {
my @current_group_by = map
{ $_ =~ /\./ ? $_ : "$attrs->{alias}.$_" }
- @$g
+ @$existing_group_by
;
if (
join ("\x00", sort @current_group_by)
ne
- join ("\x00", sort @{$attrs->{columns}} )
+ join ("\x00", sort @$subq_group_by )
) {
$self->throw_exception (
"You have just attempted a $op operation on a resultset which does group_by"
@@ -1776,33 +1865,27 @@ sub _rs_update_delete {
);
}
}
- else {
- $attrs->{group_by} = $attrs->{columns};
- }
}
- my $subrs = (ref $self)->new($rsrc, $attrs);
- return $self->result_source->storage->_subq_update_delete($subrs, $op, $values);
- }
- else {
- # 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 {
- my $sqla = $rsrc->storage->sql_maker;
- local $sqla->{_dequalify_idents} = 1;
- $sqla->_recurse_where($self->{cond});
- } if $self->{cond};
+ my $guard = $storage->txn_scope_guard;
- return $rsrc->storage->$op(
+ my @op_condition;
+ for my $row ($subrs->search({}, { group_by => $subq_group_by })->cursor->all) {
+ push @op_condition, { map
+ { $idcols->[$_] => $row->[$_] }
+ (0 .. $#$idcols)
+ };
+ }
+
+ my $res = $storage->$op (
$rsrc,
$op eq 'update' ? $values : (),
- $self->{cond} ? \[$sql, @bind] : (),
+ \@op_condition,
);
+
+ $guard->commit;
+
+ return $res;
}
}
@@ -2626,6 +2709,23 @@ all in the call to C, even when set to C.
See also L and L. For information on how to declare
unique constraints, see L.
+If you need to know if an existing row was found or a new one created use
+L and L instead. Don't forget
+to call L to save the newly created row to the
+database!
+
+ my $cd = $schema->resultset('CD')->find_or_new({
+ cdid => 5,
+ artist => 'Massive Attack',
+ title => 'Mezzanine',
+ year => 2005,
+ });
+
+ if( $cd->in_storage ) {
+ # do some stuff
+ $cd->insert;
+ }
+
=cut
sub find_or_create {
@@ -2687,6 +2787,25 @@ all in the call to C, even when set to C.
See also L and L. For information on how to declare
unique constraints, see L.
+If you need to know if an existing row was updated or a new one created use
+L and L instead. Don't forget
+to call L 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 {
@@ -2976,9 +3095,9 @@ source alias of the current result set:
my $me = $self->current_source_alias;
- return $self->search(
+ return $self->search({
"$me.modified" => $user->id,
- );
+ });
}
=cut
@@ -3360,7 +3479,6 @@ sub _resolved_attrs {
$attrs->{_collapse_order_by} = \@$prefetch_ordering;
}
-
# 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
@@ -4109,6 +4227,24 @@ rows per page if the page attribute or method is used.
Specifies the (zero-based) row number for the first row to be returned, or the
of the first row of the first page if paging is used.
+=head2 software_limit
+
+=over 4
+
+=item Value: (0 | 1)
+
+=back
+
+When combined with L and/or L the generated SQL will not
+include any limit dialect stanzas. Instead the entire result will be selected
+as if no limits were specified, and DBIC will perform the limit locally, by
+artificially advancing and finishing the resulting L.
+
+This is the recommended way of performing resultset limiting when no sane RDBMS
+implementation is available (e.g.
+L using the
+L hack)
+
=head2 group_by
=over 4
@@ -4162,6 +4298,8 @@ Adds to the WHERE clause.
Can be overridden by passing C<< { where => undef } >> as an attribute
to a resultset.
+For more complicated where clauses see L.
+
=back
=head2 cache