# t/52cycle.t
'Test::Memory::Cycle' => 0,
+ 'Devel::Cycle' => 1.10,
# t/60core.t
'DateTime::Format::MySQL' => 0,
my $timezone;
if ( defined $info->{extra}{timezone} ) {
carp "Putting timezone into extra => { timezone => '...' } has been deprecated, ".
- "please put it directly into the columns definition.";
+ "please put it directly into the '$column' column definition.";
$timezone = $info->{extra}{timezone};
}
my $locale;
if ( defined $info->{extra}{locale} ) {
carp "Putting locale into extra => { locale => '...' } has been deprecated, ".
- "please put it directly into the columns definition.";
+ "please put it directly into the '$column' column definition.";
$locale = $info->{extra}{locale};
}
if (defined $info->{extra}{floating_tz_ok}) {
carp "Putting floating_tz_ok into extra => { floating_tz_ok => 1 } has been deprecated, ".
- "please put it directly into the columns definition.";
+ "please put it directly into the '$column' column definition.";
$info{floating_tz_ok} = $info->{extra}{floating_tz_ok};
}
=cut
-sub as_query { return shift->cursor->as_query(@_) }
+sub as_query {
+ my $self = shift;
+ return $self->result_source->storage->as_query($self->_resolved_attrs);
+}
=head2 find_or_new
my $attrs = $self->{attrs};
my $from = $attrs->{from}
- || [ { $attrs->{alias} => $source->from } ];
+ || [ {
+ -result_source => $source,
+ -alias => $attrs->{alias},
+ $attrs->{alias} => $source->from,
+ } ];
my $seen = { %{$attrs->{seen_join} || {} } };
push( @{ $attrs->{as} }, @$adds );
}
- $attrs->{from} ||= [ { $self->{attrs}{alias} => $source->from } ];
+ $attrs->{from} ||= [ {
+ -result_source => $source,
+ -alias => $self->{attrs}{alias},
+ $self->{attrs}{alias} => $source->from,
+ } ];
if ( exists $attrs->{join} || exists $attrs->{prefetch} ) {
my $join = delete $attrs->{join} || {};
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;
$type = $rel_info->{attrs}{join_type} || '';
$force_left->{force} = 1 if lc($type) eq 'left';
}
- return [ { $as => $self->related_source($join)->from,
+
+ my $rel_src = $self->related_source($join);
+ return [ { $as => $rel_src->from,
+ -result_source => $rel_src,
-join_type => $type,
-join_path => [@$jpath, $join],
- -join_alias => $as,
+ -alias => $as,
-relation_chain_depth => $seen->{-relation_chain_depth} || 0,
},
$self->_resolve_condition($rel_info->{cond}, $as, $alias) ];
use DBI;
use DBIx::Class::Storage::DBI::Cursor;
use DBIx::Class::Storage::Statistics;
-use Scalar::Util qw/blessed weaken/;
+use Scalar::Util();
use List::Util();
__PACKAGE__->mk_group_accessors('simple' =>
if($dbh && !$self->unsafe) {
my $weak_self = $self;
- weaken($weak_self);
+ Scalar::Util::weaken($weak_self);
$dbh->{HandleError} = sub {
if ($weak_self) {
$weak_self->throw_exception("DBI Exception: $_[0]");
sub _prep_for_execute {
my ($self, $op, $extra_bind, $ident, $args) = @_;
- if( blessed($ident) && $ident->isa("DBIx::Class::ResultSource") ) {
+ if( Scalar::Util::blessed($ident) && $ident->isa("DBIx::Class::ResultSource") ) {
$ident = $ident->from();
}
return ($sql, \@bind);
}
+=head2 as_query
+
+=over 4
+
+=item Arguments: $rs_attrs
+
+=item Return Value: \[ $sql, @bind ]
+
+=back
+
+Returns the SQL statement and bind vars that would result from the given
+ResultSet attributes (does not actually run a query)
+
+=cut
+
+sub as_query {
+ my ($self, $rs_attr) = @_;
+
+ my $sql_maker = $self->sql_maker;
+ local $sql_maker->{for};
+
+ # my ($op, $bind, $ident, $bind_attrs, $select, $cond, $order, $rows, $offset) = $self->_select_args(...);
+ my @args = $self->_select_args($rs_attr->{from}, $rs_attr->{select}, $rs_attr->{where}, $rs_attr);
+
+ # my ($sql, $bind) = $self->_prep_for_execute($op, $bind, $ident, [ $select, $cond, $order, $rows, $offset ]);
+ my ($sql, $bind) = $self->_prep_for_execute(
+ @args[0 .. 2],
+ [ @args[4 .. $#args] ],
+ );
+ return \[ "($sql)", @{ $bind || [] }];
+}
+
sub _fix_bind_params {
my ($self, @bind) = @_;
if ( $self->debug ) {
@bind = $self->_fix_bind_params(@bind);
-
+
$self->debugobj->query_start( $sql, @bind );
}
}
sub insert {
my ($self, $source, $to_insert) = @_;
-
+
my $ident = $source->from;
my $bind_attributes = $self->source_bind_attributes($source);
my $self = shift @_;
my $source = shift @_;
- my $bind_attrs = {}; ## If ever it's needed...
+ my $bind_attrs = $self->source_bind_attributes($source);
return $self->_execute('delete' => [], $source, $bind_attrs, @_);
}
(qw/order_by group_by having _virtual_order_by/ )
};
- my $bind_attrs = {}; ## Future support
+
+ my $bind_attrs = {};
+
+ my $alias2source = $self->_resolve_ident_sources ($ident);
+
+ for my $alias (keys %$alias2source) {
+ my $bindtypes = $self->source_bind_attributes ($alias2source->{$alias}) || {};
+ for my $col (keys %$bindtypes) {
+
+ my $fqcn = join ('.', $alias, $col);
+ $bind_attrs->{$fqcn} = $bindtypes->{$col} if $bindtypes->{$col};
+
+ # so that unqualified searches can be bound too
+ $bind_attrs->{$col} = $bind_attrs->{$fqcn} if $alias eq 'me';
+ }
+ }
+
+ # This would be the point to deflate anything found in $condition
+ # (and leave $attrs->{bind} intact). Problem is - inflators historically
+ # expect a row object. And all we have is a resultsource (it is trivial
+ # to extract deflator coderefs via $alias2source above).
+ #
+ # I don't see a way forward other than changing the way deflators are
+ # invoked, and that's just bad...
+
my @args = ('select', $attrs->{bind}, $ident, $bind_attrs, $select, $condition, $order);
if ($attrs->{software_limit} ||
$sql_maker->_default_limit_syntax eq "GenericSubQ") {
return @args;
}
+sub _resolve_ident_sources {
+ my ($self, $ident) = @_;
+
+ my $alias2source = {};
+
+ # the reason this is so contrived is that $ident may be a {from}
+ # structure, specifying multiple tables to join
+ if ( Scalar::Util::blessed($ident) && $ident->isa("DBIx::Class::ResultSource") ) {
+ $alias2source->{$ident->alias} = $ident;
+ }
+ elsif (ref $ident eq 'ARRAY') {
+
+ for (@$ident) {
+ my $tabinfo;
+ if (ref $_ eq 'HASH') {
+ $tabinfo = $_;
+ }
+ if (ref $_ eq 'ARRAY' and ref $_->[0] eq 'HASH') {
+ $tabinfo = $_->[0];
+ }
+
+ $alias2source->{$tabinfo->{-alias}} = $tabinfo->{-result_source}
+ if ($tabinfo->{-result_source});
+ }
+ }
+
+ return $alias2source;
+}
+
sub count {
my ($self, $source, $attrs) = @_;
return bless ($new, $class);
}
-=head2 as_query
-
-=over 4
-
-=item Arguments: none
-
-=item Return Value: \[ $sql, @bind ]
-
-=back
-
-Returns the SQL statement and bind vars associated with the invocant.
-
-=cut
-
-sub as_query {
- my $self = shift;
-
- my $storage = $self->{storage};
- my $sql_maker = $storage->sql_maker;
- local $sql_maker->{for};
-
- my @args = $storage->_select_args(@{$self->{args}});
- my ($sql, $bind) = $storage->_prep_for_execute(@args[0 .. 2], [@args[4 .. $#args]]);
- return \[ "($sql)", @{ $bind || [] }];
-}
-
=head2 next
=over 4
use lib qw(t/lib);
BEGIN {
- eval { require Test::Memory::Cycle };
- if ($@) {
- plan skip_all => "leak test needs Test::Memory::Cycle";
+ eval { require Test::Memory::Cycle; require Devel::Cycle };
+ if ($@ or Devel::Cycle->VERSION < 1.10) {
+ plan skip_all => "leak test needs Test::Memory::Cycle and Devel::Cycle >= 1.10";
} else {
plan tests => 1;
}
# (mysql doesn't seem to like subqueries with equally named columns)
#
-SKIP: {
+{
# try a ->has_many direction (due to a 'multi' accessor the select/group_by group is collapsed)
my $owners = $schema->resultset ('Owners')->search (
{ 'books.id' => { '!=', undef }},
is ($_->count, 2, 'Prefetched grouped search returns correct count');
}
- # try a ->prefetch direction (no select collapse)
+ # try a ->belongs_to direction (no select collapse)
my $books = $schema->resultset ('BooksInLibrary')->search (
{ 'owner.name' => 'wiggle' },
{ prefetch => 'owner', distinct => 1 }
my $schema = DBICTest->init_schema();
-plan tests => 53;
+plan tests => 57;
SKIP: {
eval { require DateTime::Format::MySQL };
my $starts = $event->starts_at;
is("$starts", '2006-04-25T22:24:33', 'Correct date/time');
+TODO: {
+ local $TODO = "We can't do this yet before 0.09" if DBIx::Class->VERSION < 0.09;
+
+ ok(my $row =
+ $schema->resultset('Event')->search({ starts_at => $starts })->single);
+ is(eval { $row->id }, 1, 'DT in search');
+
+ ok($row =
+ $schema->resultset('Event')->search({ starts_at => { '>=' => $starts } })->single);
+ is(eval { $row->id }, 1, 'DT in search with condition');
+}
+
# create using DateTime
my $created = $schema->resultset('Event')->create({
starts_at => DateTime->new(year=>2006, month=>6, day=>18),
is($row->get_column('bytea'), $big_long_string, "Created the blob correctly.");
}
-TODO: {
- local $TODO =
- 'Passing bind attributes to $sth->bind_param() should be implemented (it only works in $storage->insert ATM)';
-
+{
my $rs = $schema->resultset('BindType')->search({ bytea => $big_long_string });
# search on the bytea column (select)
sub _find_co_root {
my @mod_parts = split /::/, (__PACKAGE__ . '.pm');
- my $rel_path = file (@mod_parts);
+ my $rel_path = join ('/', @mod_parts); # %INC stores paths with / regardless of OS
return undef unless ($INC{$rel_path});
# - do 'cd ..' as many times as necessary to get to t/lib/../..
my $root = dir ($INC{$rel_path});
- for (0 .. @mod_parts + 1) {
+ for (1 .. @mod_parts + 2) {
$root = $root->parent;
}
use lib qw(t/lib);
use DBICTest;
-plan $@ ? (skip_all => 'needs DBD::SQLite for testing') : (tests => 2);
+plan skip_all => 'fix pending';
+#plan tests => 4;
my $schema = DBICTest->init_schema();
my $no_prefetch = $schema->resultset('Artist')->search(
}
);
-my $no_prefetch_count = 0;
-my $use_prefetch_count = 0;
-
is($no_prefetch->count, $use_prefetch->count, '$no_prefetch->count == $use_prefetch->count');
+is(
+ scalar ($no_prefetch->all),
+ scalar ($use_prefetch->all),
+ "Amount of returned rows is right"
+);
+
-TODO: {
- local $TODO = "This is a difficult bug to fix, workaround is not to use prefetch with rows";
- $no_prefetch_count++ while $no_prefetch->next;
- $use_prefetch_count++ while $use_prefetch->next;
- is(
- $no_prefetch_count,
- $use_prefetch_count,
- "manual row count confirms consistency"
- . " (\$no_prefetch_count == $no_prefetch_count, "
- . " \$use_prefetch_count == $use_prefetch_count)"
- );
-}
-__END__
-The fix is to, when using prefetch, take the query and put it into a subquery
-joined to the tables we're prefetching from. This might result in the same
-table being joined once in the main subquery and once in the main query. This
-may actually resolve other, unknown edgecase bugs. It is also the right way
-to do prefetching. Optimizations can come later.
+my $artist_many_cds = $schema->resultset('Artist')->search ( {}, {
+ join => 'cds',
+ group_by => 'me.artistid',
+ having => \ 'count(cds.cdid) > 1',
+})->first;
+
+
+$no_prefetch = $schema->resultset('Artist')->search(
+ { artistid => $artist_many_cds->id },
+ { rows => 1 }
+);
-This means that:
- $foo_rs->search(
- { ... },
- {
- prefetch => 'bar',
- ...
- },
- );
+$use_prefetch = $schema->resultset('Artist')->search(
+ { artistid => $artist_many_cds->id },
+ {
+ prefetch => 'cds',
+ rows => 1
+ }
+);
-becomes:
- my $temp = $foo_rs->search(
- { ... },
- {
- join => 'bar',
- ...
- },
- );
- $foo_rs->storage->schema->resultset('foo')->search(
- undef,
- {
- from => [
- { me => $temp->as_query },
- ],
- prefetch => 'bar',
- },
- );
+my $prefetch_artist = $use_prefetch->first;
+my $normal_artist = $no_prefetch->first;
-Problem:
- * The prefetch->join change needs to happen ONLY IF there are conditions
- that depend on bar being joined.
- * How will this work when the $rs is further searched on? Those clauses
- need to be added to the subquery, not the outer one. This is particularly
- true if rows is added in the attribute later per the Pager.
+is(
+ $prefetch_artist->cds->count,
+ $normal_artist->cds->count,
+ "Count of child rel with prefetch + rows => 1 is right"
+);
+is (
+ scalar ($prefetch_artist->cds->all),
+ scalar ($normal_artist->cds->all),
+ "Amount of child rel rows with prefetch + rows => 1 is right"
+);