Merge 'filter_column' into 'trunk'
Peter Rabbitson [Tue, 11 May 2010 14:46:46 +0000 (14:46 +0000)]
r9147@Thesaurus (orig r9134):  frew | 2010-04-13 16:54:24 +0200
branch for FilterColumn
r9148@Thesaurus (orig r9135):  frew | 2010-04-13 18:09:57 +0200
change names wrap accessors
r9158@Thesaurus (orig r9145):  frew | 2010-04-14 17:55:14 +0200
basic tests and a tiny fix
r9159@Thesaurus (orig r9146):  frew | 2010-04-14 19:30:46 +0200
working filter column impl
r9160@Thesaurus (orig r9147):  frew | 2010-04-14 19:31:18 +0200
useless var
r9161@Thesaurus (orig r9148):  frew | 2010-04-14 20:10:57 +0200
MultiCreate test
r9163@Thesaurus (orig r9150):  frew | 2010-04-14 20:22:10 +0200
test db in MC
r9178@Thesaurus (orig r9165):  rabbit | 2010-04-14 23:35:00 +0200
Not sure how this was never noticed, but it definitely doesn't seem right and all tests pass...
r9191@Thesaurus (orig r9178):  frew | 2010-04-15 06:34:16 +0200
better namiology
r9193@Thesaurus (orig r9180):  frew | 2010-04-15 16:14:28 +0200
method and arg rename
r9194@Thesaurus (orig r9181):  frew | 2010-04-15 16:35:25 +0200
use result source for filtering instead of result
r9195@Thesaurus (orig r9182):  frew | 2010-04-15 17:04:38 +0200
initial stab at incomplete docs
r9278@Thesaurus (orig r9265):  frew | 2010-04-28 22:05:36 +0200
doc, removal of source stuff, and Changes
r9324@Thesaurus (orig r9311):  frew | 2010-05-06 01:49:25 +0200
test caching
r9327@Thesaurus (orig r9314):  rabbit | 2010-05-06 16:30:36 +0200
Play nicer with lower-level methods
r9328@Thesaurus (orig r9315):  frew | 2010-05-07 04:27:18 +0200
no filter and inflate column
r9352@Thesaurus (orig r9339):  rabbit | 2010-05-10 13:40:00 +0200
Maintain full coherence between filtered cache and unfiltered results, including store_column
r9353@Thesaurus (orig r9340):  rabbit | 2010-05-10 13:40:48 +0200
Fix typo
r9357@Thesaurus (orig r9343):  rabbit | 2010-05-11 16:45:50 +0200
Comment weird looking code

Changes
lib/DBIx/Class/Optional/Dependencies.pm
lib/DBIx/Class/Relationship/Base.pm
lib/DBIx/Class/ResultSet.pm
lib/DBIx/Class/Row.pm
lib/DBIx/Class/SQLAHacks.pm
lib/DBIx/Class/Storage/DBI.pm
lib/DBIx/Class/Storage/DBI/MSSQL.pm
t/60core.t
t/74mssql.t
t/resultset/as_subselect_rs.t

diff --git a/Changes b/Changes
index 1058ac5..94576fa 100644 (file)
--- a/Changes
+++ b/Changes
@@ -18,7 +18,12 @@ Revision history for DBIx::Class
           resultsets
         - MSSQL limits now don't require nearly as many applications of
           the unsafe_subselect_ok attribute, due to optimized queries
+        - Fix as_subselect_rs to not inject resultset class-wide where
+          conditions outside of the resulting subquery
         - Depend on optimized SQL::Abstract (faster SQL generation)
+        - update on row not in database now OK if no changes - 
+          fixes problems with cascaded unnecessary updates
+
 
 0.08121 2010-04-11 18:43:00 (UTC)
         - Support for Firebird RDBMS with DBD::InterBase and ODBC
index bc262eb..310c389 100644 (file)
@@ -283,12 +283,29 @@ sub req_group_list {
 
 # This is to be called by the author only (automatically in Makefile.PL)
 sub _gen_pod {
+
   my $class = shift;
   my $modfn = __PACKAGE__ . '.pm';
   $modfn =~ s/\:\:/\//g;
 
-  require DBIx::Class;
-  my $distver = DBIx::Class->VERSION;
+  my $podfn = __FILE__;
+  $podfn =~ s/\.pm$/\.pod/;
+
+  my $distver =
+    eval { require DBIx::Class; DBIx::Class->VERSION; }
+      ||
+    do {
+      warn
+"\n\n---------------------------------------------------------------------\n" .
+'Unable to load core DBIx::Class module to determine current version, '.
+'possibly due to missing dependencies. Author-mode autodocumentation ' .
+"halted\n\n" . $@ .
+"\n\n---------------------------------------------------------------------\n"
+      ;
+      '*UNKNOWN*';  # rv
+    }
+  ;
+
   my $sqltver = $class->req_list_for ('deploy')->{'SQL::Translator'}
     or die "Hrmm? No sqlt dep?";
 
@@ -431,10 +448,7 @@ EOD
     'You may distribute this code under the same terms as Perl itself',
   );
 
-  my $fn = __FILE__;
-  $fn =~ s/\.pm$/\.pod/;
-
-  open (my $fh, '>', $fn) or croak "Unable to write to $fn: $!";
+  open (my $fh, '>', $podfn) or croak "Unable to write to $podfn: $!";
   print $fh join ("\n\n", @chunks);
   close ($fh);
 }
index 62133a8..35ae568 100644 (file)
@@ -122,6 +122,40 @@ is creating constraints where it shouldn't, or not creating them where it
 should, set this attribute to a true or false value to override the detection
 of when to create constraints.
 
+=item cascade_copy
+
+If C<cascade_copy> is true on a C<has_many> relationship for an
+object, then when you copy the object all the related objects will
+be copied too. To turn this behaviour off, pass C<< cascade_copy => 0 >> 
+in the C<$attr> hashref. 
+
+The behaviour defaults to C<< cascade_copy => 1 >> for C<has_many>
+relationships.
+
+=item cascade_delete
+
+By default, DBIx::Class cascades deletes across C<has_many>,
+C<has_one> and C<might_have> relationships. You can disable this
+behaviour on a per-relationship basis by supplying 
+C<< cascade_delete => 0 >> in the relationship attributes.
+
+The cascaded operations are performed after the requested delete,
+so if your database has a constraint on the relationship, it will
+have deleted/updated the related records or raised an exception
+before DBIx::Class gets to perform the cascaded operation.
+
+=item cascade_update
+
+By default, DBIx::Class cascades updates across C<has_one> and
+C<might_have> relationships. You can disable this behaviour on a
+per-relationship basis by supplying C<< cascade_update => 0 >> in
+the relationship attributes.
+
+This is not a RDMS style cascade update - it purely means that when
+an object has update called on it, all the related objects also
+have update called. It will not change foreign keys automatically -
+you must arrange to do this yourself.
+
 =item on_delete / on_update
 
 If you are using L<SQL::Translator> to create SQL for you, you can use these
index 3805812..7ac07ca 100644 (file)
@@ -1289,11 +1289,6 @@ sub _count_subq_rs {
     $sub_attrs->{select} = @pcols ? \@pcols : [ 1 ];
   }
 
-
-  # this is so that the query can be simplified e.g.
-  # * ordering can be thrown away in things like Top limit
-  $sub_attrs->{-for_count_only} = 1;
-
   return $rsrc->resultset_class
                ->new ($rsrc, $sub_attrs)
                 ->as_subselect_rs
@@ -2684,15 +2679,22 @@ sub as_subselect_rs {
 
   my $attrs = $self->_resolved_attrs;
 
-  return $self->result_source->resultset->search( undef, {
+  my $fresh_rs = (ref $self)->new (
+    $self->result_source
+  );
+
+  # these pieces will be locked in the subquery
+  delete $fresh_rs->{cond};
+  delete @{$fresh_rs->{attrs}}{qw/where bind/};
+
+  return $fresh_rs->search( {}, {
     from => [{
       $attrs->{alias} => $self->as_query,
       -alias         => $attrs->{alias},
       -source_handle => $self->result_source->handle,
     }],
-    map { $_ => $attrs->{$_} } qw/select as alias/
-
-   });
+    alias => $attrs->{alias},
+  });
 }
 
 # This code is called by search_related, and makes sure there
index 6945cd6..310e10e 100644 (file)
@@ -518,16 +518,18 @@ this method.
 
 sub update {
   my ($self, $upd) = @_;
-  $self->throw_exception( "Not in database" ) unless $self->in_storage;
 
   my $ident_cond = $self->{_orig_ident} || $self->ident_condition;
 
-  $self->throw_exception('Unable to update a row with incomplete or no identity')
-    if ! keys %$ident_cond;
-
   $self->set_inflated_columns($upd) if $upd;
   my %to_update = $self->get_dirty_columns;
   return $self unless keys %to_update;
+
+  $self->throw_exception( "Not in database" ) unless $self->in_storage;
+
+  $self->throw_exception('Unable to update a row with incomplete or no identity')
+    if ! keys %$ident_cond;
+
   my $rows = $self->result_source->storage->update(
     $self->result_source, \%to_update, $ident_cond
   );
index d6abcba..40bc7a3 100644 (file)
@@ -405,8 +405,6 @@ sub select {
     $table = $self->_quote($table);
   }
 
-  local $self->{rownum_hack_count} = 1
-    if (defined $rest[0] && $self->{limit_dialect} eq 'RowNum');
   @rest = (-1) unless defined $rest[0];
   croak "LIMIT 0 Does Not Compute" if $rest[0] == 0;
     # and anyway, SQL::Abstract::Limit will cause a barf if we don't first
@@ -532,7 +530,7 @@ sub _parse_rs_attrs {
 
   my $sql = '';
 
-  if (my $g = $self->_recurse_fields($arg->{group_by}, { no_rownum_hack => 1 }) ) {
+  if (my $g = $self->_recurse_fields($arg->{group_by}) ) {
     $sql .= $self->_sqlcase(' group by ') . $g;
   }
 
index ee0f70b..4214463 100644 (file)
@@ -1012,7 +1012,10 @@ sub _server_info {
 
     my %info;
 
-    my $server_version = $self->_get_server_version;
+    my $server_version = do {
+      local $@; # might be happenin in some sort of destructor
+      eval { $self->_get_server_version };
+    };
 
     if (defined $server_version) {
       $info{dbms_version} = $server_version;
@@ -1044,7 +1047,7 @@ sub _server_info {
 }
 
 sub _get_server_version {
-  eval { shift->_get_dbh->get_info(18) };
+  shift->_get_dbh->get_info(18);
 }
 
 sub _determine_driver {
index 38c615b..515ff9b 100644 (file)
@@ -205,11 +205,23 @@ sub sql_maker {
 
   unless ($self->_sql_maker) {
     unless ($self->{_sql_maker_opts}{limit_dialect}) {
+      my $have_rno = 0;
 
-      my $version = $self->_server_info->{normalized_dbms_version} || 0;
+      if (exists $self->_server_info->{normalized_dbms_version}) {
+        $have_rno = 1 if $self->_server_info->{normalized_dbms_version} >= 9;
+      }
+      else {
+        # User is connecting via DBD::Sybase and has no permission to run
+        # stored procedures like xp_msver, or version detection failed for some
+        # other reason.
+        # So, we use a query to check if RNO is implemented.
+        $have_rno = 1 if (eval { local $@; ($self->_get_dbh
+          ->selectrow_array('SELECT row_number() OVER (ORDER BY rand())')
+          )[0] });
+      }
 
       $self->{_sql_maker_opts} = {
-        limit_dialect => ($version >= 9 ? 'RowNumberOver' : 'Top'),
+        limit_dialect => ($have_rno ? 'RowNumberOver' : 'Top'),
         %{$self->{_sql_maker_opts}||{}}
       };
     }
index 69d99ed..41adcb2 100644 (file)
@@ -45,6 +45,8 @@ my %fake_dirty = $art->get_dirty_columns();
 is(scalar(keys(%fake_dirty)), 1, '1 fake dirty column');
 ok(grep($_ eq 'name', keys(%fake_dirty)), 'name is fake dirty');
 
+ok($art->update, 'Update run');
+
 my $record_jp = $schema->resultset("Artist")->search(undef, { join => 'cds' })->search(undef, { prefetch => 'cds' })->next;
 
 ok($record_jp, "prefetch on same rel okay");
@@ -67,6 +69,8 @@ is(@art, 2, 'And then there were two');
 
 is($art->in_storage, 0, "It knows it's dead");
 
+lives_ok { $art->update } 'No changes so update should be OK';
+
 dies_ok ( sub { $art->delete }, "Can't delete twice");
 
 is($art->name, 'We Are In Rehab', 'But the object is still live');
index a882e99..78e4691 100644 (file)
@@ -172,6 +172,38 @@ SQL
 
   is $rs->first, undef, 'rolled back';
   $rs->reset;
+
+  # test RNO detection when version detection fails
+  SKIP: {
+    my $storage = $schema->storage;
+    my $version = $storage->_server_info->{normalized_dbms_version};
+    
+    skip 1, 'could not detect SQL Server version' if not defined $version;
+
+    my $have_rno = $version >= 9 ? 1 : 0;
+
+    # Delete version information to force RNO check when rebuilding SQLA
+    # instance.
+    no strict 'refs';
+    no warnings 'redefine';
+    local *{(ref $storage).'::_get_server_version'} = sub { undef };
+
+    my $server_info = { %{ $storage->_server_info_hash } }; # clone
+
+    delete @$server_info{qw/dbms_version normalized_dbms_version/};
+
+    local $storage->{_server_info_hash} = $server_info;
+    local $storage->{_sql_maker}        = undef;
+    local $storage->{_sql_maker_opts}   = undef;
+
+    $storage->sql_maker;
+
+    my $rno_detected =
+      ($storage->{_sql_maker_opts}{limit_dialect} eq 'RowNumberOver');
+
+    ok ((not ($have_rno xor $rno_detected)),
+      'row_number() over support detected correctly');
+  }
 }
 
 # test op-induced autoconnect
index c143d11..1453f63 100644 (file)
@@ -22,4 +22,21 @@ lives_ok { $new_rs->search({ 'artwork_to_artist.artwork_cd_id' => 1})->as_subsel
    '... and chaining off the virtual view works';
 dies_ok  { $new_rs->as_subselect_rs->search({'artwork_to_artist.artwork_cd_id'=> 1})->count }
    q{... but chaining off of a virtual view using join doesn't work};
+
+my $book_rs = $schema->resultset ('BooksInLibrary')->search ({}, { join => 'owner' });
+
+is_same_sql_bind (
+  $book_rs->as_subselect_rs->as_query,
+  '(SELECT me.id, me.source, me.owner, me.title, me.price 
+      FROM (
+        SELECT me.id, me.source, me.owner, me.title, me.price
+          FROM books me
+          JOIN owners owner ON owner.id = me.owner
+        WHERE ( source = ? )
+      ) me
+  )',
+  [ [ source => 'Library' ] ],
+  'Resultset-class attributes do not seep outside of the subselect',
+);
+
 done_testing;