use strict;
use warnings;
-use base qw/DBIx::Class/;
+
+use base 'DBIx::Class';
+
use DBIx::Class::Carp;
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
- fail_on_internal_wantarray fail_on_internal_call UNRESOLVABLE_CONDITION
+ dbic_internal_try dbic_internal_catch dump_value emit_loud_diag
+ fail_on_internal_wantarray fail_on_internal_call
+ UNRESOLVABLE_CONDITION DUMMY_ALIASPAIR
);
-use Try::Tiny;
+use DBIx::Class::SQLMaker::Util qw( normalize_sqla_condition extract_equality_conditions );
+use DBIx::Class::ResultSource::FromSpec::Util 'find_join_path_to_alias';
BEGIN {
# De-duplication in _merge_attr() is disabled, but left in for reference
(ref $_ eq 'HASH' and ! keys %$_)
) and $_ = undef for ($left, $right);
- # either one of the two undef
- if ( (defined $left) xor (defined $right) ) {
- return defined $left ? $left : $right;
- }
- # both undef
- elsif ( ! defined $left ) {
- return undef
- }
- else {
- return $self->result_source->schema->storage->_collapse_cond({ -and => [$left, $right] });
- }
+ return(
+ # either one of the two undef
+ ( (defined $left) xor (defined $right) ) ? ( defined $left ? $left : $right )
+
+ # both undef
+ : ( ! defined $left ) ? undef
+
+ : { -and => [$left, $right] }
+ );
}
=head2 search_literal
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 (
+ # either a structure or a result-ish object
length ref($call_cond->{$key})
and
- my $relinfo = $rsrc->relationship_info($key)
+ ( $rel_list ||= { map { $_ => 1 } $rsrc->relationships } )
+ ->{$key}
and
- # implicitly skip has_many's (likely MC)
- (ref (my $val = delete $call_cond->{$key}) ne 'ARRAY' )
+ ! is_literal_value( $call_cond->{$key} )
+ and
+ # implicitly skip has_many's (likely MC), via the delete()
+ ( ref( my $foreign_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,
+
+ %{ $rsrc->_resolve_relationship_condition(
+ require_join_free_values => 1,
+ rel_name => $key,
+ foreign_values => (
+ (! defined blessed $foreign_val) ? $foreign_val : do {
+
+ my $f_result_class = $rsrc->related_source($key)->result_class;
- # supplement condition
- # relationship conditions take precedence (?)
- @{$call_cond}{keys %$rel_cond} = values %$rel_cond;
+ unless( $foreign_val->isa($f_result_class) ) {
+
+ $self->throw_exception(
+ 'Objects supplied to find() must inherit from '
+ . "'$DBIx::Class::ResultSource::__expected_result_class_isa'"
+ ) unless $foreign_val->isa(
+ $DBIx::Class::ResultSource::__expected_result_class_isa
+ );
+
+ carp_unique(
+ "Objects supplied to find() via '$key' usually should inherit from "
+ . "the related ResultClass ('$f_result_class'), perhaps you've made "
+ . 'a mistake?'
+ );
+ }
+
+ +{ $foreign_val->get_columns };
+ }
+ ),
+
+ # an API where these are optional would be too cumbersome,
+ # instead always pass in some dummy values
+ DUMMY_ALIASPAIR,
+ )->{join_free_values} },
+ };
}
}
if (defined $constraint_name) {
$final_cond = $self->_qualify_cond_columns (
- $self->result_source->_minimal_valueset_satisfying_constraint(
+ $rsrc->_minimal_valueset_satisfying_constraint(
constraint_name => $constraint_name,
values => ($self->_merge_with_rscond($call_cond))[0],
carp_on_nulls => 1,
dbic_internal_try {
push @unique_queries, $self->_qualify_cond_columns(
- $self->result_source->_minimal_valueset_satisfying_constraint(
+ $rsrc->_minimal_valueset_satisfying_constraint(
constraint_name => $c_name,
values => ($self->_merge_with_rscond($call_cond))[0],
- columns_info => ($ci ||= $self->result_source->columns_info),
+ columns_info => ($ci ||= $rsrc->columns_info),
),
$alias
);
}
- catch {
+ dbic_internal_catch {
push @fc_exceptions, $_ if $_ =~ /\bFilterColumn\b/;
};
}
=cut
-sub search_related {
+sub search_related :DBIC_method_is_indirect_sugar {
+ DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
return shift->related_resultset(shift)->search(@_);
}
=cut
-sub search_related_rs {
+sub search_related_rs :DBIC_method_is_indirect_sugar {
+ DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
return shift->related_resultset(shift)->search_rs(@_);
}
return $self->{cursor} ||= do {
my $attrs = $self->_resolved_attrs;
- $self->result_source->storage->select(
+ $self->result_source->schema->storage->select(
$attrs->{from}, $attrs->{select}, $attrs->{where}, $attrs
);
};
}
}
- my $data = [ $self->result_source->storage->select_single(
+ my $data = [ $self->result_source->schema->storage->select_single(
$attrs->{from}, $attrs->{select},
$attrs->{where}, $attrs
)];
=cut
sub get_column {
- my ($self, $column) = @_;
- my $new = DBIx::Class::ResultSetColumn->new($self, $column);
- return $new;
+ DBIx::Class::ResultSetColumn->new(@_);
}
=head2 search_like
.' 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 });
# overwrite the selector (supplied by the storage)
$rsrc->resultset_class->new($rsrc, {
%$tmp_attrs,
- select => $rsrc->storage->_count_select ($rsrc, $attrs),
+ select => $rsrc->schema->storage->_count_select ($rsrc, $attrs),
as => 'count',
})->get_column ('count');
}
# Calculate subquery selector
if (my $g = $sub_attrs->{group_by}) {
- my $sql_maker = $rsrc->storage->sql_maker;
+ my $sql_maker = $rsrc->schema->storage->sql_maker;
# necessary as the group_by may refer to aliased functions
my $sel_index;
return $rsrc->resultset_class
->new ($rsrc, $sub_attrs)
->as_subselect_rs
- ->search ({}, { columns => { count => $rsrc->storage->_count_select ($rsrc, $attrs) } })
+ ->search ({}, { columns => { count => $rsrc->schema->storage->_count_select ($rsrc, $attrs) } })
->get_column ('count');
}
=cut
-sub count_literal { shift->search_literal(@_)->count; }
+sub count_literal :DBIC_method_is_indirect_sugar {
+ DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
+ shift->search_literal(@_)->count
+}
=head2 all
=cut
-sub first {
+sub first :DBIC_method_is_indirect_sugar {
+ DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
return $_[0]->reset->next;
}
# a condition containing 'me' or other table prefixes will not work
# at all. Tell SQLMaker to dequalify idents via a gross hack.
$cond = do {
- my $sqla = $rsrc->storage->sql_maker;
+ my $sqla = $rsrc->schema->storage->sql_maker;
local $sqla->{_dequalify_idents} = 1;
\[ $sqla->_recurse_where($self->{cond}) ];
};
# FIXME - no cref handling
# 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 = $self->result_source->schema->storage->txn_scope_guard
+ $guard = $storage->txn_scope_guard
if @$data > 2;
@results = map
}
else {
- $guard = $self->result_source->schema->storage->txn_scope_guard
+ $guard = $storage->txn_scope_guard
if @$data > 1;
@results = map { $self->new_result($_)->insert } @$data;
# this means we have to walk the data structure twice
# whether we want this or not
# jnap, I hate you ;)
- my $rsrc = $self->result_source;
my $rel_info = { map { $_ => $rsrc->relationship_info($_) } $rsrc->relationships };
my ($colinfo, $colnames, $slices_with_rels);
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->storage->_insert_bulk(
+ $storage->_insert_bulk(
$rsrc,
[ @$colnames, sort keys %$rs_data ],
[ map {
$colinfo->{$rel}{fk_map} = { reverse %{ $rsrc->_resolve_relationship_condition(
rel_name => $rel,
- self_alias => "\xFE", # irrelevant
- foreign_alias => "\xFF", # irrelevant
+
+ # an API where these are optional would be too cumbersome,
+ # instead always pass in some dummy values
+ DUMMY_ALIASPAIR,
)->{identity_map} || {} } };
}
sub new_result {
my ($self, $values) = @_;
- $self->throw_exception( "new_result takes only one argument - a hashref of values" )
- if @_ > 2;
-
- $self->throw_exception( "Result object instantiation requires a hashref as argument" )
- unless (ref $values eq 'HASH');
+ $self->throw_exception( "Result object instantiation requires a single hashref argument" )
+ if @_ > 2 or ref $values ne 'HASH';
my ($merged_cond, $cols_from_relations) = $self->_merge_with_rscond($values);
@cols_from_relations = keys %{ $implied_data || {} };
}
else {
- my $eqs = $self->result_source->schema->storage->_extract_fixed_condition_columns($self->{cond}, 'consider_nulls');
+ my $eqs = extract_equality_conditions( $self->{cond}, 'consider_nulls' );
$implied_data = { map {
( ($eqs->{$_}||'') eq UNRESOLVABLE_CONDITION ) ? () : ( $_ => $eqs->{$_} )
} keys %$eqs };
my $attrs = { %{ $self->_resolved_attrs } };
- my $aq = $self->result_source->storage->_select_args_to_query (
+ my $aq = $self->result_source->schema->storage->_select_args_to_query (
$attrs->{from}, $attrs->{select}, $attrs->{where}, $attrs
);
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;
=cut
-sub create {
+sub create :DBIC_method_is_indirect_sugar {
#my ($self, $col_data) = @_;
DBIx::Class::_ENV_::ASSERT_NO_INTERNAL_INDIRECT_CALLS and fail_on_internal_call;
return shift->new_result(shift)->insert;
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 );
sub is_ordered {
my ($self) = @_;
- return scalar $self->result_source->storage->_extract_order_criteria($self->{attrs}{order_by});
+ return scalar $self->result_source->schema->storage->_extract_order_criteria($self->{attrs}{order_by});
}
=head2 related_resultset
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};
if ( $attrs->{rows} =~ /[^0-9]/ or $attrs->{rows} <= 0 );
}
+ # normalize where condition
+ $attrs->{where} = normalize_sqla_condition( $attrs->{where} )
+ if $attrs->{where};
# default selection list
$attrs->{columns} = [ $source->columns ]
else {
$attrs->{_grouped_by_distinct} = 1;
# distinct affects only the main selection part, not what prefetch may add below
- ($attrs->{group_by}, my $new_order) = $source->storage->_group_over_selection($attrs);
+ ($attrs->{group_by}, my $new_order) = $source->schema->storage->_group_over_selection($attrs);
# FIXME possibly ignore a rewritten order_by (may turn out to be an issue)
# The thinking is: if we are collapsing the subquerying prefetch engine will