my ($self) = @_;
my $attrs = $self->_resolved_attrs_copy;
- $attrs->{_virtual_order_by} = $self->_gen_virtual_order;
return $self->{cursor}
||= $self->result_source->storage->select($attrs->{from}, $attrs->{select},
}
my $attrs = $self->_resolved_attrs_copy;
- $attrs->{_virtual_order_by} = $self->_gen_virtual_order;
if ($where) {
if (defined $attrs->{where}) {
return (@data ? ($self->_construct_object(@data))[0] : undef);
}
-# _gen_virtual_order
-#
-# This is a horrble hack, but seems like the best we can do at this point
-# Some limit emulations (Top) require an ordered resultset in order to
-# function at all. So supply a PK order if such a condition is detected
-
-sub _gen_virtual_order {
- my $self = shift;
- my $attrs = $self->_resolved_attrs_copy;
-
- if ($attrs->{rows} or $attrs->{offset} ) {
-
-# This check requires ensure_connected, so probably cheaper to just calculate all the time
-
-# my $sm = $self->result_source->storage->_sql_maker;
-#
-# if ($sm->_default_limit_syntax eq 'Top' and not @{$sm->_resolve_order ($attrs->{order_by}) }) {
-
- return [ $self->result_source->primary_columns ];
-
-# }
- }
-
- return undef;
-}
# _is_unique_query
#
return $self->search(@_)->count if @_ and defined $_[0];
return scalar @{ $self->get_cache } if $self->get_cache;
- my @grouped_subq_attrs = qw/prefetch collapse distinct group_by having/;
- my @subq_attrs = ();
-
- my $attrs = $self->_resolved_attrs;
- # if we are not paged - we are simply asking for a limit
- if (not $attrs->{page} and not $attrs->{software_limit}) {
- push @subq_attrs, qw/rows offset/;
- }
-
- my $need_subq = $self->_has_attr (@subq_attrs);
- my $need_group_subq = $self->_has_attr (@grouped_subq_attrs);
-
- return ($need_subq || $need_group_subq)
- ? $self->_count_subq ($need_group_subq)
- : $self->_count_simple
-}
-
-sub _count_subq {
- my ($self, $add_group_by) = @_;
+ my $meth = $self->_has_resolved_attr (qw/collapse group_by/)
+ ? 'count_grouped'
+ : 'count'
+ ;
my $attrs = $self->_resolved_attrs_copy;
+ my $rsrc = $self->result_source;
- # copy for the subquery, we need to do some adjustments to it too
- my $sub_attrs = { %$attrs };
-
- # these can not go in the subquery, and there is no point of ordering it
- delete $sub_attrs->{$_} for qw/prefetch collapse select +select as +as columns +columns order_by/;
-
- # if needed force a group_by and the same set of columns (most databases require this)
- if ($add_group_by) {
-
- # if we prefetch, we group_by primary keys only as this is what we would get out of the rs via ->next/->all
- # simply deleting group_by suffices, as the code below will re-fill it
- # Note: we check $attrs, as $sub_attrs has collapse deleted
- if (ref $attrs->{collapse} and keys %{$attrs->{collapse}} ) {
- delete $sub_attrs->{group_by};
- }
-
- $sub_attrs->{columns} = $sub_attrs->{group_by} ||= [ map { "$attrs->{alias}.$_" } ($self->result_source->primary_columns) ];
- }
-
- $attrs->{from} = [{
- count_subq => (ref $self)->new ($self->result_source, $sub_attrs )->as_query
- }];
-
- # the subquery replaces this
- delete $attrs->{$_} for qw/where bind prefetch collapse distinct group_by having having_bind/;
-
- return $self->__count ($attrs);
-}
-
-sub _count_simple {
- my $self = shift;
-
- my $count = $self->__count;
- return 0 unless $count;
-
- # need to take offset from resolved attrs
-
- my $attrs = $self->_resolved_attrs;
-
- $count -= $attrs->{offset} if $attrs->{offset};
- $count = $attrs->{rows} if $attrs->{rows} and $attrs->{rows} < $count;
- $count = 0 if ($count < 0);
- return $count;
-}
-
-sub __count {
- my ($self, $attrs) = @_;
-
- $attrs ||= $self->_resolved_attrs_copy;
-
- # take off any column specs, any pagers, record_filter is cdbi, and no point of ordering a count
- delete $attrs->{$_} for (qw/columns +columns select +select as +as rows offset page pager order_by record_filter/);
-
- $attrs->{select} = { count => '*' };
- $attrs->{as} = [qw/count/];
-
- my $tmp_rs = (ref $self)->new($self->result_source, $attrs);
- my ($count) = $tmp_rs->cursor->next;
-
- return $count;
+ return $rsrc->storage->$meth ($rsrc, $attrs);
}
sub _bool {
my $rsrc = $self->result_source;
- my $needs_group_by_subq = $self->_has_attr (qw/prefetch distinct join seen_join group_by/);
- my $needs_subq = $self->_has_attr (qw/row offset page/);
+ my $needs_group_by_subq = $self->_has_resolved_attr (qw/collapse group_by -join/);
+ my $needs_subq = $self->_has_resolved_attr (qw/row offset/);
if ($needs_group_by_subq or $needs_subq) {
# make a new $rs selecting only the PKs (that's all we really need)
my $attrs = $self->_resolved_attrs_copy;
- delete $attrs->{$_} for qw/prefetch collapse select +select as +as columns +columns/;
+ delete $attrs->{$_} for qw/collapse select as/;
$attrs->{columns} = [ map { "$attrs->{alias}.$_" } ($self->result_source->primary_columns) ];
if ($needs_group_by_subq) {
my $subrs = (ref $self)->new($rsrc, $attrs);
- return $self->result_source->storage->subq_update_delete($subrs, $op, $values);
+ return $self->result_source->storage->_subq_update_delete($subrs, $op, $values);
}
else {
return $rsrc->storage->$op(
=item Arguments: none
-=item Return Value: 1
+=item Return Value: $storage_rv
=back
will not run DBIC cascade triggers. See L</delete_all> if you need triggers
to run. See also L<DBIx::Class::Row/delete>.
-delete may not generate correct SQL for a query with joins or a resultset
-chained from a related resultset. In this case it will generate a warning:-
-
-In these cases you may find that delete_all is more appropriate, or you
-need to respecify your query in a way that can be expressed without a join.
+Return value will be the amount of rows deleted; exact type of return value
+is storage-dependent.
=cut
## do the belongs_to relationships
foreach my $index (0..$#$data) {
- if( grep { !defined $data->[$index]->{$_} } @pks ) {
- my @ret = $self->populate($data);
- return;
+
+ # delegate to create() for any dataset without primary keys with specified relationships
+ if (grep { !defined $data->[$index]->{$_} } @pks ) {
+ for my $r (@rels) {
+ if (grep { ref $data->[$index]{$r} eq $_ } qw/HASH ARRAY/) { # a related set must be a HASH or AoH
+ my @ret = $self->populate($data);
+ return;
+ }
+ }
}
foreach my $rel (@rels) {
- next unless $data->[$index]->{$rel} && ref $data->[$index]->{$rel} eq "HASH";
+ next unless ref $data->[$index]->{$rel} eq "HASH";
my $result = $self->related_resultset($rel)->create($data->[$index]->{$rel});
my ($reverse) = keys %{$self->result_source->reverse_relationship_info($rel)};
my $related = $result->result_source->_resolve_condition(
return 0;
}
-# _has_attr
+# _has_resolved_attr
#
# determines if the resultset defines at least one
# of the attributes supplied
#
# used to determine if a subquery is neccessary
-sub _has_attr {
+sub _has_resolved_attr {
my ($self, @attr_names) = @_;
my $attrs = $self->_resolved_attrs;
my $join_check_req;
for my $n (@attr_names) {
- ++$join_check_req if $n =~ /join/;
+ ++$join_check_req if $n eq '-join';
my $attr = $attrs->{$n};
}
}
- # a join can be expressed as a multi-level from
+ # a resolved join is expressed as a multi-level from
return 1 if (
$join_check_req
and
=cut
-sub as_query { return shift->cursor->as_query(@_) }
+sub as_query {
+ my $self = shift;
+
+ my $attrs = $self->_resolved_attrs_copy;
+
+ # 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);
+
+ return $sqlbind;
+}
=head2 find_or_new
my $self = shift;
my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {});
my $hash = ref $_[0] eq 'HASH' ? shift : {@_};
- my $exists = $self->find($hash, $attrs);
- return defined $exists ? $exists : $self->new_result($hash);
+ if (keys %$hash and my $row = $self->find($hash, $attrs) ) {
+ return $row;
+ }
+ return $self->new_result($hash);
}
=head2 create
my $self = shift;
my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {});
my $hash = ref $_[0] eq 'HASH' ? shift : {@_};
- my $exists = $self->find($hash, $attrs);
- return defined $exists ? $exists : $self->create($hash);
+ if (keys %$hash and my $row = $self->find($hash, $attrs) ) {
+ return $row;
+ }
+ return $self->create($hash);
}
=head2 update_or_create
my $source = $self->result_source;
my $attrs = $self->{attrs};
- my $from = $attrs->{from}
- || [ { $attrs->{alias} => $source->from } ];
+ my $from = [ @{
+ $attrs->{from}
+ ||
+ [{
+ -source_handle => $source->handle,
+ -alias => $attrs->{alias},
+ $attrs->{alias} => $source->from,
+ }]
+ }];
- my $seen = { %{$attrs->{seen_join}||{}} };
+ my $seen = { %{$attrs->{seen_join} || {} } };
# we need to take the prefetch the attrs into account before we
# ->_resolve_join as otherwise they get lost - captainL
push( @{ $attrs->{as} }, @$adds );
}
- $attrs->{from} ||= [ { $self->{attrs}{alias} => $source->from } ];
+ $attrs->{from} ||= [ {
+ -source_handle => $source->handle,
+ -alias => $self->{attrs}{alias},
+ $self->{attrs}{alias} => $source->from,
+ } ];
if ( exists $attrs->{join} || exists $attrs->{prefetch} ) {
my $join = delete $attrs->{join} || {};
$attrs->{order_by} = [];
}
- my $collapse = $attrs->{collapse} || {};
+ # If the order_by is otherwise empty - we will use this for TOP limit
+ # emulation and the like.
+ # Although this is needed only if the order_by is not defined, it is
+ # actually cheaper to just populate this rather than properly examining
+ # order_by (stuf like [ {} ] and the like)
+ $attrs->{_virtual_order_by} = [ $self->result_source->primary_columns ];
+
+
+ $attrs->{collapse} ||= {};
if ( my $prefetch = delete $attrs->{prefetch} ) {
$prefetch = $self->_merge_attr( {}, $prefetch );
- my @pre_order;
- foreach my $p ( ref $prefetch eq 'ARRAY' ? @$prefetch : ($prefetch) ) {
-
- # bring joins back to level of current class
- my $join_map = $self->_joinpath_aliases ($attrs->{from}, $attrs->{seen_join});
- my @prefetch =
- $source->_resolve_prefetch( $p, $alias, $join_map, \@pre_order, $collapse );
- push( @{ $attrs->{select} }, map { $_->[0] } @prefetch );
- push( @{ $attrs->{as} }, map { $_->[1] } @prefetch );
- }
- push( @{ $attrs->{order_by} }, @pre_order );
+
+ my $prefetch_ordering = [];
+
+ my $join_map = $self->_joinpath_aliases ($attrs->{from}, $attrs->{seen_join});
+
+ my @prefetch =
+ $source->_resolve_prefetch( $prefetch, $alias, $join_map, $prefetch_ordering, $attrs->{collapse} );
+
+ push( @{ $attrs->{select} }, map { $_->[0] } @prefetch );
+ push( @{ $attrs->{as} }, map { $_->[1] } @prefetch );
+
+ push( @{ $attrs->{order_by} }, @$prefetch_ordering );
+ $attrs->{_collapse_order_by} = \@$prefetch_ordering;
}
+
if (delete $attrs->{distinct}) {
$attrs->{group_by} ||= [ grep { !ref($_) || (ref($_) ne 'HASH') } @{$attrs->{select}} ];
}
- $attrs->{collapse} = $collapse;
-
- if ( $attrs->{page} and not defined $attrs->{offset} ) {
- $attrs->{offset} = ( $attrs->{rows} * ( $attrs->{page} - 1 ) );
+ # 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
+ if (my $page = delete $attrs->{page}) {
+ $attrs->{offset} = ($attrs->{rows} * ($page - 1)) +
+ ($attrs->{offset} || 0);
}
return $self->{_attrs} = $attrs;
my $p = $paths;
$p = $p->{$_} ||= {} for @{$j->[0]{-join_path}};
- push @{$p->{-join_aliases} }, $j->[0]{-join_alias};
+ push @{$p->{-join_aliases} }, $j->[0]{-alias};
}
return $paths;
# SELECT child.* FROM person child
# INNER JOIN person father ON child.father_id = father.id
-If you need to express really complex joins or you need a subselect, you
+You can select from a subquery by passing a resultset to from as follows.
+
+ $schema->resultset('Artist')->search(
+ undef,
+ { alias => 'artist2',
+ from => [ { artist2 => $artist_rs->as_query } ],
+ } );
+
+ # and you'll get sql like this..
+ # SELECT artist2.artistid, artist2.name, artist2.rank, artist2.charfield FROM
+ # ( SELECT me.artistid, me.name, me.rank, me.charfield FROM artists me ) artist2
+
+If you need to express really complex joins, you
can supply literal SQL to C<from> via a scalar reference. In this case
-the contents of the scalar will replace the table name asscoiated with the
+the contents of the scalar will replace the table name associated with the
resultsource.
WARNING: This technique might very well not work as expected on chained