changed the way args are passed to a storage, should make it easier to use existing...
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBI.pm
index cb55d20..212be1e 100644 (file)
@@ -5,6 +5,7 @@ use base 'DBIx::Class::Storage';
 
 use strict;    
 use warnings;
+use Carp::Clan qw/^DBIx::Class/;
 use DBI;
 use SQL::Abstract::Limit;
 use DBIx::Class::Storage::DBI::Cursor;
@@ -15,7 +16,7 @@ __PACKAGE__->mk_group_accessors('simple' =>
     qw/_connect_info _dbi_connect_info _dbh _sql_maker _sql_maker_opts
        _conn_pid _conn_tid disable_sth_caching on_connect_do
        on_disconnect_do transaction_depth unsafe _dbh_autocommit
-       auto_savepoint/
+       auto_savepoint savepoints/
 );
 
 __PACKAGE__->cursor_class('DBIx::Class::Storage::DBI::Cursor');
@@ -25,7 +26,8 @@ __PACKAGE__->sql_maker_class('DBIC::SQL::Abstract');
 
 BEGIN {
 
-package DBIC::SQL::Abstract; # Would merge upstream, but nate doesn't reply :(
+package # Hide from PAUSE
+  DBIC::SQL::Abstract; # Would merge upstream, but nate doesn't reply :(
 
 use base qw/SQL::Abstract::Limit/;
 
@@ -328,6 +330,7 @@ sub new {
 
   $new->transaction_depth(0);
   $new->_sql_maker_opts({});
+  $new->{savepoints} = [];
   $new->{_in_dbh_do} = 0;
   $new->{_dbh_gen} = 0;
 
@@ -877,56 +880,87 @@ sub _connect {
 
 sub svp_begin {
   my ($self, $name) = @_;
-  $self->throw_exception("You failed to provide a savepoint name!") if !$name;
 
-  if($self->{transaction_depth} == 0) {
-    warn("Can't use savepoints without a transaction.");
-    return 0;
-  }
+  $name = $self->_svp_generate_name
+    unless defined $name;
+
+  $self->throw_exception ("You can't use savepoints outside a transaction")
+    if $self->{transaction_depth} == 0;
+
+  $self->throw_exception ("Your Storage implementation doesn't support savepoints")
+    unless $self->can('_svp_begin');
+  
+  push @{ $self->{savepoints} }, $name;
 
-  if(!$self->can('_svp_begin')) {
-    warn("Your Storage implementation doesn't support savepoints!");
-    return 0;
-  }
   $self->debugobj->svp_begin($name) if $self->debug;
-  $self->_svp_begin($name);
+  
+  return $self->_svp_begin($name);
 }
 
 sub svp_release {
   my ($self, $name) = @_;
 
-  $self->throw_exception("You failed to provide a savepoint name!") if !$name;
+  $self->throw_exception ("You can't use savepoints outside a transaction")
+    if $self->{transaction_depth} == 0;
 
-  if($self->{transaction_depth} == 0) {
-    warn("Can't use savepoints without a transaction.");
-    return 0;
-  }
+  $self->throw_exception ("Your Storage implementation doesn't support savepoints")
+    unless $self->can('_svp_release');
+
+  if (defined $name) {
+    $self->throw_exception ("Savepoint '$name' does not exist")
+      unless grep { $_ eq $name } @{ $self->{savepoints} };
+
+    # Dig through the stack until we find the one we are releasing.  This keeps
+    # the stack up to date.
+    my $svp;
 
-  if(!$self->can('_svp_release')) {
-      warn("Your Storage implementation doesn't support savepoint releasing!");
-      return 0;
+    do { $svp = pop @{ $self->{savepoints} } } while $svp ne $name;
+  } else {
+    $name = pop @{ $self->{savepoints} };
   }
+
   $self->debugobj->svp_release($name) if $self->debug;
-  $self->_svp_release($name);
+
+  return $self->_svp_release($name);
 }
 
 sub svp_rollback {
   my ($self, $name) = @_;
 
-  $self->throw_exception("You failed to provide a savepoint name!") if !$name;
+  $self->throw_exception ("You can't use savepoints outside a transaction")
+    if $self->{transaction_depth} == 0;
 
-  if($self->{transaction_depth} == 0) {
-    warn("Can't use savepoints without a transaction.");
-    return 0;
-  }
+  $self->throw_exception ("Your Storage implementation doesn't support savepoints")
+    unless $self->can('_svp_rollback');
 
-  if(!$self->can('_svp_rollback')) {
-      warn("Your Storage implementation doesn't support savepoints!");
-      return 0;
+  if (defined $name) {
+      # If they passed us a name, verify that it exists in the stack
+      unless(grep({ $_ eq $name } @{ $self->{savepoints} })) {
+          $self->throw_exception("Savepoint '$name' does not exist!");
+      }
+
+      # Dig through the stack until we find the one we are releasing.  This keeps
+      # the stack up to date.
+      while(my $s = pop(@{ $self->{savepoints} })) {
+          last if($s eq $name);
+      }
+      # Add the savepoint back to the stack, as a rollback doesn't remove the
+      # named savepoint, only everything after it.
+      push(@{ $self->{savepoints} }, $name);
+  } else {
+      # We'll assume they want to rollback to the last savepoint
+      $name = $self->{savepoints}->[-1];
   }
+
   $self->debugobj->svp_rollback($name) if $self->debug;
-  $self->_svp_rollback($name);
+  
+  return $self->_svp_rollback($name);
+}
+
+sub _svp_generate_name {
+    my ($self) = @_;
+
+    return 'savepoint_'.scalar(@{ $self->{'savepoints'} });
 }
 
 sub txn_begin {
@@ -940,7 +974,7 @@ sub txn_begin {
     #  for AutoCommit users
     $self->dbh->begin_work;
   } elsif ($self->auto_savepoint) {
-    $self->svp_begin ("savepoint_$self->{transaction_depth}");
+    $self->svp_begin;
   }
   $self->{transaction_depth}++;
 }
@@ -957,7 +991,7 @@ sub txn_commit {
   }
   elsif($self->{transaction_depth} > 1) {
     $self->{transaction_depth}--;
-    $self->svp_release ("savepoint_$self->{transaction_depth}")
+    $self->svp_release
       if $self->auto_savepoint;
   }
 }
@@ -976,8 +1010,8 @@ sub txn_rollback {
     elsif($self->{transaction_depth} > 1) {
       $self->{transaction_depth}--;
       if ($self->auto_savepoint) {
-        $self->svp_rollback ("savepoint_$self->{transaction_depth}");
-        $self->svp_release ("savepoint_$self->{transaction_depth}");
+        $self->svp_rollback;
+        $self->svp_release;
       }
     }
     else {
@@ -1029,6 +1063,7 @@ sub _query_start {
 
     if ( $self->debug ) {
         @bind = $self->_fix_bind_params(@bind);
+        
         $self->debugobj->query_start( $sql, @bind );
     }
 }
@@ -1253,6 +1288,9 @@ sub select_single {
   my $self = shift;
   my ($rv, $sth, @bind) = $self->_select(@_);
   my @row = $sth->fetchrow_array;
+  if(@row && $sth->fetchrow_array) {
+    carp "Query returned more than one row.  SQL that returns multiple rows is DEPRECATED for ->find and ->single";
+  }
   # Need to call finish() to work round broken DBDs
   $sth->finish();
   return @row;
@@ -1506,6 +1544,7 @@ sub create_ddl_dir
           unless $dest_schema->name;
       }
 
+      $DB::single = 1;
       my $diff = SQL::Translator::Diff::schema_diff($source_schema, $db,
                                                     $dest_schema,   $db,
                                                     $sqltargs
@@ -1669,6 +1708,31 @@ sub build_datetime_parser {
     }
 }
 
+=head2 is_replicating
+
+A boolean that reports if a particular L<DBIx::Class::Storage::DBI> is set to
+replicate from a master database.  Default is undef, which is the result
+returned by databases that don't support replication.
+
+=cut
+
+sub is_replicating {
+    return;
+    
+}
+
+=head2 lag_behind_master
+
+Returns a number that represents a certain amount of lag behind a master db
+when a given storage is replicating.  The number is database dependent, but
+starts at zero and increases with the amount of lag. Default in undef
+
+=cut
+
+sub lag_behind_master {
+    return;
+}
+
 sub DESTROY {
   my $self = shift;
   return if !$self->_dbh;