From: Peter Rabbitson Date: Thu, 7 Apr 2011 23:55:41 +0000 (+0200) Subject: Merge branch 0.08200_track into master X-Git-Tag: v0.08191~38 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=dbsrgits%2FDBIx-Class.git;a=commitdiff_plain;h=b4e9f590228d1d73d4089c2ec88372e683e17aeb Merge branch 0.08200_track into master --- b4e9f590228d1d73d4089c2ec88372e683e17aeb diff --cc Changes index fe69450,cb42f77..6a1c165 --- a/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 diff --cc lib/DBIx/Class/ResultSource.pm index dffe6ad,53e866d..0d8bbf5 --- a/lib/DBIx/Class/ResultSource.pm +++ b/lib/DBIx/Class/ResultSource.pm @@@ -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/; diff --cc lib/DBIx/Class/SQLMaker.pm index 89053e3,570af4d..1a30757 --- a/lib/DBIx/Class/SQLMaker.pm +++ b/lib/DBIx/Class/SQLMaker.pm @@@ -35,14 -38,12 +38,13 @@@ Currently the enhancements to Lmk_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 { diff --cc lib/DBIx/Class/SQLMaker/OracleJoins.pm index 0313a4f,3bc8ec9..d2bc160 --- a/lib/DBIx/Class/SQLMaker/OracleJoins.pm +++ b/lib/DBIx/Class/SQLMaker/OracleJoins.pm @@@ -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; diff --cc t/relationship/custom.t index 0000000,e60bad6..99a0786 mode 000000,100644..100644 --- a/t/relationship/custom.t +++ b/t/relationship/custom.t @@@ -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;