use DBIx::Class::ResultSetColumn;
use DBIx::Class::ResultClass::HashRefInflator;
use Scalar::Util qw( blessed reftype );
+use SQL::Abstract 'is_literal_value';
use DBIx::Class::_Util qw(
- dbic_internal_try dump_value
+ dbic_internal_try dbic_internal_catch dump_value emit_loud_diag
fail_on_internal_wantarray fail_on_internal_call UNRESOLVABLE_CONDITION
);
use DBIx::Class::SQLMaker::Util qw( normalize_sqla_condition extract_equality_conditions );
-use Try::Tiny;
+use DBIx::Class::ResultSource::FromSpec::Util 'find_join_path_to_alias';
BEGIN {
# De-duplication in _merge_attr() is disabled, but left in for reference
sub find {
my $self = shift;
- my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {});
+ my $attrs = (@_ > 1 && ref $_[-1] eq 'HASH' ? pop(@_) : {});
- my $rsrc = $self->result_source;
my $constraint_name;
if (exists $attrs->{key}) {
# Parse out the condition from input
my $call_cond;
+ my $rsrc = $self->result_source;
+
if (ref $_[0] eq 'HASH') {
$call_cond = { %{$_[0]} };
}
}
# process relationship data if any
+ my $rel_list;
+
for my $key (keys %$call_cond) {
if (
length ref($call_cond->{$key})
and
- my $relinfo = $rsrc->relationship_info($key)
+ ( $rel_list ||= { map { $_ => 1 } $rsrc->relationships } )
+ ->{$key}
+ and
+ ! is_literal_value( $call_cond->{$key} )
and
- # implicitly skip has_many's (likely MC)
- (ref (my $val = delete $call_cond->{$key}) ne 'ARRAY' )
+ # implicitly skip has_many's (likely MC), via the delete()
+ ( ref( my $val = delete $call_cond->{$key} ) ne 'ARRAY' )
) {
- my ($rel_cond, $crosstable) = $rsrc->_resolve_condition(
- $relinfo->{cond}, $val, $key, $key
- );
- $self->throw_exception("Complex condition via relationship '$key' is unsupported in find()")
- if $crosstable or ref($rel_cond) ne 'HASH';
+ # FIXME: it seems wrong that relationship conditions take precedence...?
+ $call_cond = {
+ %$call_cond,
- # supplement condition
- # relationship conditions take precedence (?)
- @{$call_cond}{keys %$rel_cond} = values %$rel_cond;
+ %{ $rsrc->_resolve_relationship_condition(
+ rel_name => $key,
+ foreign_values => $val,
+ infer_values_based_on => {},
+
+ self_alias => "\xFE", # irrelevant
+ foreign_alias => "\xFF", # irrelevant
+ )->{inferred_values} },
+ };
}
}
$alias
);
}
- catch {
+ dbic_internal_catch {
push @fc_exceptions, $_ if $_ =~ /\bFilterColumn\b/;
};
}
.' Instead use ->search({ x => { -like => "y%" } })'
.' (note the outer pair of {}s - they are important!)'
);
- my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {});
+ my $attrs = (@_ > 1 && ref $_[-1] eq 'HASH' ? pop(@_) : {});
my $query = ref $_[0] eq 'HASH' ? { %{shift()} }: {@_};
$query->{$_} = { 'like' => $query->{$_} } for keys %$query;
return $class->search($query, { %$attrs });
# At this point assume either hashes or arrays
my $rsrc = $self->result_source;
+ my $storage = $rsrc->schema->storage;
if(defined wantarray) {
my (@results, $guard);
# column names only, nothing to do
return if @$data == 1;
- $guard = $rsrc->schema->storage->txn_scope_guard
+ $guard = $storage->txn_scope_guard
if @$data > 2;
@results = map
}
else {
- $guard = $rsrc->schema->storage->txn_scope_guard
+ $guard = $storage->txn_scope_guard
if @$data > 1;
@results = map { $self->new_result($_)->insert } @$data;
or
ref $data->[$i][$_->{pos}] eq 'HASH'
or
- ( defined blessed $data->[$i][$_->{pos}] and $data->[$i][$_->{pos}]->isa('DBIx::Class::Row') )
+ (
+ defined blessed $data->[$i][$_->{pos}]
+ and
+ $data->[$i][$_->{pos}]->isa(
+ $DBIx::Class::ResultSource::__expected_result_class_isa
+ ||
+ emit_loud_diag(
+ confess => 1,
+ msg => 'Global $DBIx::Class::ResultSource::__expected_result_class_isa unexpectedly unset...'
+ )
+ )
+ )
)
and
1
# moar sanity check... sigh
for ( ref $data->[$i][$_->{pos}] eq 'ARRAY' ? @{$data->[$i][$_->{pos}]} : $data->[$i][$_->{pos}] ) {
- if ( defined blessed $_ and $_->isa('DBIx::Class::Row' ) ) {
+ if (
+ defined blessed $_
+ and
+ $_->isa(
+ $DBIx::Class::ResultSource::__expected_result_class_isa
+ ||
+ emit_loud_diag(
+ confess => 1,
+ msg => 'Global $DBIx::Class::ResultSource::__expected_result_class_isa unexpectedly unset...'
+ )
+ )
+ ) {
carp_unique("Fast-path populate() with supplied related objects is not possible - falling back to regular create()");
return my $throwaway = $self->populate(@_);
}
or
ref $data->[$i]{$_} eq 'HASH'
or
- ( defined blessed $data->[$i]{$_} and $data->[$i]{$_}->isa('DBIx::Class::Row') )
+ (
+ defined blessed $data->[$i]{$_}
+ and
+ $data->[$i]{$_}->isa(
+ $DBIx::Class::ResultSource::__expected_result_class_isa
+ ||
+ emit_loud_diag(
+ confess => 1,
+ msg => 'Global $DBIx::Class::ResultSource::__expected_result_class_isa unexpectedly unset...'
+ )
+ )
+ )
)
and
1
# moar sanity check... sigh
for ( ref $data->[$i]{$_} eq 'ARRAY' ? @{$data->[$i]{$_}} : $data->[$i]{$_} ) {
- if ( defined blessed $_ and $_->isa('DBIx::Class::Row' ) ) {
+ if (
+ defined blessed $_
+ and
+ $_->isa(
+ $DBIx::Class::ResultSource::__expected_result_class_isa
+ ||
+ emit_loud_diag(
+ confess => 1,
+ msg => 'Global $DBIx::Class::ResultSource::__expected_result_class_isa unexpectedly unset...'
+ )
+ )
+ ) {
carp_unique("Fast-path populate() with supplied related objects is not possible - falling back to regular create()");
return my $throwaway = $self->populate(@_);
}
### start work
my $guard;
- $guard = $rsrc->schema->storage->txn_scope_guard
+ $guard = $storage->txn_scope_guard
if $slices_with_rels;
### main source data
# FIXME - need to switch entirely to a coderef-based thing,
# so that large sets aren't copied several times... I think
- $rsrc->schema->storage->_insert_bulk(
+ $storage->_insert_bulk(
$rsrc,
[ @$colnames, sort keys %$rs_data ],
[ map {
sub find_or_new {
my $self = shift;
- my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {});
+ my $attrs = (@_ > 1 && ref $_[-1] eq 'HASH' ? pop(@_) : {});
my $hash = ref $_[0] eq 'HASH' ? shift : {@_};
if (keys %$hash and my $row = $self->find($hash, $attrs) ) {
return $row;
sub find_or_create {
my $self = shift;
- my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {});
+ my $attrs = (@_ > 1 && ref $_[-1] eq 'HASH' ? pop(@_) : {});
my $hash = ref $_[0] eq 'HASH' ? shift : {@_};
if (keys %$hash and my $row = $self->find($hash, $attrs) ) {
return $row;
sub update_or_create {
my $self = shift;
- my $attrs = (@_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {});
+ my $attrs = (@_ > 1 && ref $_[-1] eq 'HASH' ? pop(@_) : {});
my $cond = ref $_[0] eq 'HASH' ? shift : {@_};
my $row = $self->find($cond, $attrs);
sub update_or_new {
my $self = shift;
- my $attrs = ( @_ > 1 && ref $_[$#_] eq 'HASH' ? pop(@_) : {} );
+ my $attrs = ( @_ > 1 && ref $_[-1] eq 'HASH' ? pop(@_) : {} );
my $cond = ref $_[0] eq 'HASH' ? shift : {@_};
my $row = $self->find( $cond, $attrs );
my $attrs = $self->_chain_relationship($rel);
- my $storage = $rsrc->schema->storage;
-
# Previously this atribute was deleted (instead of being set as it is now)
# Doing so seems to be harmless in all available test permutations
# See also 01d59a6a6 and mst's comment below
#
- $attrs->{alias} = $storage->relname_to_table_alias(
+ $attrs->{alias} = $rsrc->schema->storage->relname_to_table_alias(
$rel,
$attrs->{seen_join}{$rel}
);
# since this is search_related, and we already slid the select window inwards
# (the select/as attrs were deleted in the beginning), we need to flip all
# left joins to inner, so we get the expected results
- # read the comment on top of the actual function to see what this does
- $attrs->{from} = $storage->_inner_join_to_node( $attrs->{from}, $attrs->{alias} );
+ #
+ # The DBIC relationship chaining implementation is pretty simple - every
+ # new related_relationship is pushed onto the {from} stack, and the {select}
+ # window simply slides further in. This means that when we count somewhere
+ # in the middle, we got to make sure that everything in the join chain is an
+ # actual inner join, otherwise the count will come back with unpredictable
+ # results (a resultset may be generated with _some_ rows regardless of if
+ # the relation which the $rs currently selects has rows or not). E.g.
+ # $artist_rs->cds->count - normally generates:
+ # SELECT COUNT( * ) FROM artist me LEFT JOIN cd cds ON cds.artist = me.artistid
+ # which actually returns the number of artists * (number of cds || 1)
+ #
+ # So what we do here is crawl {from}, determine if the current alias is at
+ # the top of the stack, and if not - make sure the chain is inner-joined down
+ # to the root.
+ #
+ my $switch_branch = find_join_path_to_alias(
+ $attrs->{from},
+ $attrs->{alias},
+ );
+
+ if ( @{ $switch_branch || [] } ) {
+
+ # So it looks like we will have to switch some stuff around.
+ # local() is useless here as we will be leaving the scope
+ # anyway, and deep cloning is just too fucking expensive
+ # So replace the first hashref in the node arrayref manually
+ my @new_from = $attrs->{from}[0];
+ my $sw_idx = { map { (values %$_), 1 } @$switch_branch }; #there's one k/v per join-path
+
+ for my $j ( @{$attrs->{from}}[ 1 .. $#{$attrs->{from}} ] ) {
+ my $jalias = $j->[0]{-alias};
+
+ if ($sw_idx->{$jalias}) {
+ my %attrs = %{$j->[0]};
+ delete $attrs{-join_type};
+ push @new_from, [
+ \%attrs,
+ @{$j}[ 1 .. $#$j ],
+ ];
+ }
+ else {
+ push @new_from, $j;
+ }
+ }
+
+ $attrs->{from} = \@new_from;
+ }
+
#XXX - temp fix for result_class bug. There likely is a more elegant fix -groditi
delete $attrs->{result_class};