Merge branch 0.08200_track into master
Peter Rabbitson [Thu, 7 Apr 2011 23:55:41 +0000 (01:55 +0200)]
1  2 
Changes
lib/DBIx/Class.pm
lib/DBIx/Class/Relationship/HasOne.pm
lib/DBIx/Class/Relationship/ManyToMany.pm
lib/DBIx/Class/ResultSet.pm
lib/DBIx/Class/ResultSource.pm
lib/DBIx/Class/Row.pm
lib/DBIx/Class/SQLMaker.pm
lib/DBIx/Class/SQLMaker/OracleJoins.pm
t/relationship/custom.t

diff --cc Changes
+++ b/Changes
@@@ -1,59 -1,11 +1,65 @@@
  Revision history for DBIx::Class
  
 +    * New Features / Changes
 +        - Add quote_names connection option. When set to true automatically
 +          sets quote_char and name_sep appropriate for your RDBMS
 +        - Support for MS Access databases via DBD::ODBC and DBD::ADO (only
 +          Win32 support currently tested)
 +        - IC::DateTime support for MSSQL over DBD::ADO
 +        - Both the ::ODBC and ::ADO dispatchers now warn if a rdbms-specific
 +          driver is not found for this connection before falling back to
 +          plain ::Storage::DBI
 +        - ::Storage::DBI::sth was mistakenly marked/documented as public,
 +          privatize and warn on deprecated use
 +        - Massive overhaul of bind values/attributes handling - slightly
 +          changes the output of as_query (should not cause compat issues)
 +        - Support ancient DB2 versions (5.4 and older), with proper limit
 +          dialect
 +
 +    * Fixes
 +        - Fix ::Storage::DBI::* MRO problems on 5.8.x perls
 +        - Disable mysql_auto_reconnect for MySQL - depending on the ENV
 +          it sometimes defaults to on and causes major borkage on older
 +          DBD::mysql versions
 +        - Fix dropped bind values in select/group_by on Oracle (omission
 +          from 0542ec57 and 4c2b30d6)
 +        - Fix remaining errors with Oracle and identifiers longer than the
 +          Oracle-imposed maximum of 30 characters (RT#66390)
 +        - Fix older oracle-specific "WhereJoins" to work properly with
 +          name quoting
 +        - Fix problems with M.A.D. under CGI::SpeedyCGI (RT#65131)
 +        - Better error handling when prepare() fails silently
 +        - Fixes skipped lines when a comment is followed by a statement
 +          when deploying a schema via sql file
 +        - Fix reverse_relationship_info on prototypical result sources
 +          (sources not yet registered with a schema)
 +        - Warn and skip relationships missing from a partial schema during
 +          dbic cascade_delete
 +        - Automatically require the requested cursor class before use
 +          (RT#64795)
 +        - Work around a Firebird ODBC driver bug exposed by DBD::ODBC 1.29
 +        - Fix exiting via next warnings in ResultSource::sequence()
 +        - Fix stripping of table qualifiers in update/delete in arrayref
 +          condition elements
 +        - Change SQLMaker carp-monkeypatch to be compatible with versions
 +          of SQL::Abstract >= 1.73
 +        - Fix using \[] literals in the from resultset attribute
 +        - Fix populate() with \[], arrays (datatype) and other exotic values
 +
 +    * Misc
 +        - Rewire all warnings to a new Carp-like implementation internal
 +          to DBIx::Class, and remove the Carp::Clan dependency
 +        - Only load Class::C3 and friends if necessary ($] < 5.010)
 +        - Greatly reduced loading of non-essential modules to aid startup
 +          time (mainly benefiting CGI users)
 +        - Make sure all namespaces are clean of rogue imports
 +
+ 0.08190-TRIAL 2011-01-24 15:35 (UTC)
+     * New Features / Changes
+         - Support for completely arbitrary SQL::Abstract-based conditions
+           in all types of relationships
  0.08127 2011-01-19 16:40 (UTC)
      * New Features / Changes
          - Schema/resultsource instances are now crossreferenced via a new
Simple merge
Simple merge
Simple merge
@@@ -7,10 -7,11 +7,10 @@@ use DBIx::Class::ResultSet
  use DBIx::Class::ResultSourceHandle;
  
  use DBIx::Class::Exception;
 -use Carp::Clan qw/^DBIx::Class/;
 +use DBIx::Class::Carp;
  use Try::Tiny;
  use List::Util 'first';
- use Scalar::Util qw/weaken isweak/;
+ use Scalar::Util qw/blessed weaken isweak/;
 -use Storable qw/nfreeze thaw/;
  use namespace::clean;
  
  use base qw/DBIx::Class/;
Simple merge
@@@ -35,14 -38,12 +38,13 @@@ Currently the enhancements to L<SQL::Ab
  use base qw/
    DBIx::Class::SQLMaker::LimitDialects
    SQL::Abstract
 -  Class::Accessor::Grouped
 +  DBIx::Class
  /;
  use mro 'c3';
- use strict;
- use warnings;
  use Sub::Name 'subname';
 -use Carp::Clan qw/^DBIx::Class|^SQL::Abstract|^Try::Tiny/;
 +use DBIx::Class::Carp;
 +use DBIx::Class::Exception;
  use namespace::clean;
  
  __PACKAGE__->mk_group_accessors (simple => qw/quote_char name_sep limit_dialect/);
@@@ -399,21 -394,22 +401,23 @@@ sub _gen_from_blocks 
        $join_type =~ s/^\s+ | \s+$//xg;
      }
  
 -    $join_type = $self->{_default_jointype} if not defined $join_type;
 -
 -    push @sqlf, $self->_generate_join_clause( $join_type );
 +    my @j = $self->_generate_join_clause( $join_type );
  
      if (ref $to eq 'ARRAY') {
 -      push(@sqlf, '(', $self->_recurse_from(@$to), ')');
 -    } else {
 -      push(@sqlf, $self->_from_chunk_to_sql($to));
 +      push(@j, '(', $self->_recurse_from(@$to), ')');
 +    }
 +    else {
 +      push(@j, $self->_from_chunk_to_sql($to));
      }
  
-     push(@j, ' ON ', $self->_join_condition($on));
+     my ($sql, @bind) = $self->_join_condition($on);
 -    push(@sqlf, ' ON ', $sql);
++    push(@j, ' ON ', $sql);
+     push @{$self->{from_bind}}, @bind;
 +
 +    push @fchunks, join '', @j;
    }
  
 -  return join('', @sqlf);
 +  return @fchunks;
  }
  
  sub _from_chunk_to_sql {
@@@ -71,25 -68,20 +71,26 @@@ sub _recurse_oracle_joins 
        #TODO: Support full outer joins -- this would happen much earlier in
        #the sequence since oracle 8's full outer join syntax is best
        #described as INSANE.
 -      croak "Can't handle full outer joins in Oracle 8 yet!\n"
 -        if $to_jt->{-join_type} =~ /full/i;
 +      $self->throw_exception("Can't handle full outer joins in Oracle 8 yet!\n")
 +        if $jt =~ /full/i;
  
 -      $left_join  = q{(+)} if $to_jt->{-join_type} =~ /left/i
 -        && $to_jt->{-join_type} !~ /inner/i;
 +      $left_join  = q{(+)} if $jt =~ /left/i
 +        && $jt !~ /inner/i;
  
 -      $right_join = q{(+)} if $to_jt->{-join_type} =~ /right/i
 -        && $to_jt->{-join_type} !~ /inner/i;
 +      $right_join = q{(+)} if $jt =~ /right/i
 +        && $jt !~ /inner/i;
      }
  
 -    foreach my $lhs (keys %{ $on }) {
 -      $where->{$lhs . $left_join} = \"= $on->{ $lhs }$right_join";
 -    }
++    # sadly SQLA treats where($scalar) as literal, so we need to jump some hoops
 +    push @where, map { \sprintf ('%s%s = %s%s',
-       $self->_quote($_),
++      ref $_ ? $self->_recurse_where($_) : $self->_quote($_),
 +      $left_join,
-       $self->_quote($on->{$_}),
++      ref $on->{$_} ? $self->_recurse_where($on->{$_}) : $self->_quote($on->{$_}),
 +      $right_join,
 +    )} keys %$on;
    }
 +
 +  return { -and => \@where };
  }
  
  1;
index 0000000,e60bad6..99a0786
mode 000000,100644..100644
--- /dev/null
@@@ -1,0 -1,214 +1,231 @@@
+ use strict;
+ use warnings;
+ use Test::More;
+ use Test::Exception;
+ use lib qw(t/lib);
+ use DBICTest;
+ use DBIC::SqlMakerTest;
+ my $schema = DBICTest->init_schema();
+ $schema->resultset('Artist')->delete;
+ $schema->resultset('CD')->delete;
+ my $artist  = $schema->resultset("Artist")->create({ artistid => 21, name => 'Michael Jackson', rank => 20 });
+ my $artist2 = $schema->resultset("Artist")->create({ artistid => 22, name => 'Chico Buarque', rank => 1 }) ;
+ my $artist3 = $schema->resultset("Artist")->create({ artistid => 23, name => 'Ziraldo', rank => 1 });
+ my $artist4 = $schema->resultset("Artist")->create({ artistid => 24, name => 'Paulo Caruso', rank => 20 });
+ my @artworks;
+ foreach my $year (1975..1985) {
+   my $cd = $artist->create_related('cds', { year => $year, title => 'Compilation from ' . $year });
+   push @artworks, $cd->create_related('artwork', {});
+ }
+ foreach my $year (1975..1995) {
+   my $cd = $artist2->create_related('cds', { year => $year, title => 'Compilation from ' . $year });
+   push @artworks, $cd->create_related('artwork', {});
+ }
+ foreach my $artwork (@artworks) {
+   $artwork->create_related('artwork_to_artist', { artist => $_ }) for ($artist3, $artist4);
+ }
+ my $cds_80s_rs = $artist->cds_80s;
+ is_same_sql_bind(
+   $cds_80s_rs->as_query,
+   '(
+     SELECT me.cdid, me.artist, me.title, me.year, me.genreid, me.single_track
+       FROM cd me
+     WHERE ( ( me.artist = ? AND ( me.year < ? AND me.year > ? ) ) )
+   )',
+   [
 -    [ 'me.artist' => 21   ],
 -    [ 'me.year' => 1990 ],
 -    [ 'me.year' => 1979 ],
 -  ]
++    [
++      { sqlt_datatype => 'integer', dbic_colname => 'me.artist' }
++        => 21
++    ],
++    [
++      { sqlt_datatype => 'varchar', sqlt_size => 100, dbic_colname => 'me.year' }
++        => 1990
++    ],
++    [
++      { sqlt_datatype => 'varchar', sqlt_size => 100, dbic_colname => 'me.year' }
++        => 1979
++    ],
++  ],
+ );
+ my @cds_80s = $cds_80s_rs->all;
+ is(@cds_80s, 6, '6 80s cds found (1980 - 1985)');
+ map { ok($_->year < 1990 && $_->year > 1979) } @cds_80s;
+ my $cds_90s_rs = $artist2->cds_90s;
+ is_same_sql_bind(
+   $cds_90s_rs->as_query,
+   '(
+     SELECT me.cdid, me.artist, me.title, me.year, me.genreid, me.single_track
+       FROM artist artist__row
+       JOIN cd me
+         ON ( me.artist = artist__row.artistid AND ( me.year < ? AND me.year > ? ) )
+       WHERE ( artist__row.artistid = ? )
+   )',
+   [
 -    [ 'me.year' => 2000 ],
 -    [ 'me.year' => 1989 ],
 -    [ 'artist__row.artistid' => 22 ],
++    [
++      { sqlt_datatype => 'varchar', sqlt_size => 100, dbic_colname => 'me.year' }
++        => 2000
++    ],
++    [
++      { sqlt_datatype => 'varchar', sqlt_size => 100, dbic_colname => 'me.year' }
++        => 1989
++    ],
++    [ { sqlt_datatype => 'integer', dbic_colname => 'artist__row.artistid' }
++        => 22
++    ],
+   ]
+ );
+ my @cds_90s = $cds_90s_rs->all;
+ is(@cds_90s, 6, '6 90s cds found (1990 - 1995) even with non-optimized search');
+ map { ok($_->year < 2000 && $_->year > 1989) } @cds_90s;
+ lives_ok {
+   my @cds_90s_95 = $artist2->cds_90s->search({ 'me.year' => 1995 });
+   is(@cds_90s_95, 1, '1 90s (95) cds found even with non-optimized search');
+   map { ok($_->year == 1995) } @cds_90s_95;
+ } 'should preserve chain-head "me" alias (API-consistency)';
+ # search for all artists prefetching published cds in the 80s...
+ my @all_artists_with_80_cds = $schema->resultset("Artist")->search
+   ({ 'cds_80s.cdid' => { '!=' => undef } }, { join => 'cds_80s', distinct => 1 });
+ is_deeply(
+   [ sort ( map { $_->year } map { $_->cds_80s->all } @all_artists_with_80_cds ) ],
+   [ sort (1980..1989, 1980..1985) ],
+   '16 correct cds found'
+ );
+ TODO: {
+ local $TODO = 'Prefetch on custom rels can not work until the collapse rewrite is finished '
+   . '(currently collapser requires a right-side (which is indeterministic) order-by)';
+ lives_ok {
+ my @all_artists_with_80_cds_pref = $schema->resultset("Artist")->search
+   ({ 'cds_80s.cdid' => { '!=' => undef } }, { prefetch => 'cds_80s' });
+ is_deeply(
+   [ sort ( map { $_->year } map { $_->cds_80s->all } @all_artists_with_80_cds_pref ) ],
+   [ sort (1980..1989, 1980..1985) ],
+   '16 correct cds found'
+ );
+ } 'prefetchy-fetchy-fetch';
+ } # end of TODO
+ # try to create_related a 80s cd
+ throws_ok {
+   $artist->create_related('cds_80s', { title => 'related creation 1' });
+ } qr/\QCustom relationship 'cds_80s' not definitive - returns conditions instead of values for column(s): 'year'/,
+ 'Create failed - complex cond';
+ # now supply an explicit arg overwriting the ambiguous cond
+ my $id_2020 = $artist->create_related('cds_80s', { title => 'related creation 2', year => '2020' })->id;
+ is(
+   $schema->resultset('CD')->find($id_2020)->title,
+   'related creation 2',
+   '2020 CD created correctly'
+ );
+ # try a default year from a specific rel
+ my $id_1984 = $artist->create_related('cds_84', { title => 'related creation 3' })->id;
+ is(
+   $schema->resultset('CD')->find($id_1984)->title,
+   'related creation 3',
+   '1984 CD created correctly'
+ );
+ # try a specific everything via a non-simplified rel
+ throws_ok {
+   $artist->create_related('cds_90s', { title => 'related_creation 4', year => '2038' });
+ } qr/\QCustom relationship 'cds_90s' does not resolve to a join-free condition fragment/,
+ 'Create failed - non-simplified rel';
+ # Do a self-join last-entry search
+ my @last_tracks;
+ for my $cd ($schema->resultset('CD')->search ({}, { order_by => 'cdid'})->all) {
+   push @last_tracks, $cd->tracks
+                          ->search ({}, { order_by => { -desc => 'position'} })
+                           ->next || ();
+ }
+ my $last_tracks_rs = $schema->resultset('Track')->search (
+   {'next_track.trackid' => undef},
+   { join => 'next_track', order_by => 'me.cd' },
+ );
+ is_deeply (
+   [$last_tracks_rs->get_column ('trackid')->all],
+   [ map { $_->trackid } @last_tracks ],
+   'last group-entry via self-join works',
+ );
+ my $artwork = $schema->resultset('Artwork')->search({},{ order_by => 'cd_id' })->first;
+ my @artists = $artwork->artists->all;
+ is(scalar @artists, 2, 'the two artists are associated');
+ my @artwork_artists = $artwork->artwork_to_artist->all;
+ foreach (@artwork_artists) {
+   lives_ok {
+     my $artista = $_->artist;
+     my $artistb = $_->artist_test_m2m;
+     ok($artista->rank < 10 ? $artistb : 1, 'belongs_to with custom rel works.');
+     my $artistc = $_->artist_test_m2m_noopt;
+     ok($artista->rank < 10 ? $artistc : 1, 'belongs_to with custom rel works even in non-simplified.');
+   } 'belongs_to works with custom rels';
+ }
+ @artists = ();
+ lives_ok {
+   @artists = $artwork->artists_test_m2m2->all;
+ } 'manytomany with extended rels in the has many works';
+ is(scalar @artists, 2, 'two artists');
+ @artists = ();
+ lives_ok {
+   @artists = $artwork->artists_test_m2m->all;
+ } 'can fetch many to many with optimized version';
+ is(scalar @artists, 1, 'only one artist is associated');
+ @artists = ();
+ lives_ok {
+   @artists = $artwork->artists_test_m2m_noopt->all;
+ } 'can fetch many to many with non-optimized version';
+ is(scalar @artists, 1, 'only one artist is associated');
+ # Make a single for each last_track
+ my @singles = map {
+   $_->create_related('cd_single', {
+     title => $_->title . ' (the single)',
+     artist => $artist,
+     year => 1999,
+   }) } @last_tracks
+ ;
+ # See if chaining works
+ is_deeply (
+   [ map { $_->title } $last_tracks_rs->search_related('cd_single')->all ],
+   [ map { $_->title } @singles ],
+   'Retrieved singles in proper order'
+ );
+ # See if prefetch works
+ is_deeply (
+   [ map { $_->cd_single->title } $last_tracks_rs->search({}, { prefetch => 'cd_single' })->all ],
+   [ map { $_->title } @singles ],
+   'Prefetched singles in proper order'
+ );
+ done_testing;