discard changes now is forced to use master for replication. changed discard_changes...
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBI.pm
index 27bd0d6..6c9b97d 100644 (file)
@@ -3,17 +3,31 @@ package DBIx::Class::Storage::DBI;
 
 use base 'DBIx::Class::Storage';
 
-use strict;
+use strict;    
 use warnings;
+use Carp::Clan qw/^DBIx::Class/;
 use DBI;
 use SQL::Abstract::Limit;
 use DBIx::Class::Storage::DBI::Cursor;
 use DBIx::Class::Storage::Statistics;
-use IO::File;
-use Carp::Clan qw/DBIx::Class/;
+use Scalar::Util qw/blessed weaken/;
+
+__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 savepoints/
+);
+
+__PACKAGE__->cursor_class('DBIx::Class::Storage::DBI::Cursor');
+
+__PACKAGE__->mk_group_accessors('inherited' => qw/sql_maker_class/);
+__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/;
 
@@ -56,7 +70,6 @@ use Scalar::Util 'blessed';
 sub _find_syntax {
   my ($self, $syntax) = @_;
   my $dbhname = blessed($syntax) ?  $syntax->{Driver}{Name} : $syntax;
-#  print STDERR "Found DBH $syntax >$dbhname< ", $syntax->{Driver}->{Name}, "\n";
   if(ref($self) && $dbhname && $dbhname eq 'DB2') {
     return 'RowNumberOver';
   }
@@ -76,6 +89,15 @@ sub select {
   my ($sql, @ret) = $self->SUPER::select(
     $table, $self->_recurse_fields($fields), $where, $order, @rest
   );
+  $sql .= 
+    $self->{for} ?
+    (
+      $self->{for} eq 'update' ? ' FOR UPDATE' :
+      $self->{for} eq 'shared' ? ' FOR SHARE'  :
+      ''
+    ) :
+    ''
+  ;
   return wantarray ? ($sql, @ret, @{$self->{having_bind}}) : $sql;
 }
 
@@ -110,7 +132,7 @@ sub _emulate_limit {
 }
 
 sub _recurse_fields {
-  my ($self, $fields) = @_;
+  my ($self, $fields, $params) = @_;
   my $ref = ref $fields;
   return $self->_quote($fields) unless $ref;
   return $$fields if $ref eq 'SCALAR';
@@ -118,10 +140,10 @@ sub _recurse_fields {
   if ($ref eq 'ARRAY') {
     return join(', ', map {
       $self->_recurse_fields($_)
-      .(exists $self->{rownum_hack_count}
-         ? ' AS col'.$self->{rownum_hack_count}++
-         : '')
-     } @$fields);
+        .(exists $self->{rownum_hack_count} && !($params && $params->{no_rownum_hack})
+          ? ' AS col'.$self->{rownum_hack_count}++
+          : '')
+      } @$fields);
   } elsif ($ref eq 'HASH') {
     foreach my $func (keys %$fields) {
       return $self->_sqlcase($func)
@@ -137,7 +159,7 @@ sub _order_by {
   if (ref $_[0] eq 'HASH') {
     if (defined $_[0]->{group_by}) {
       $ret = $self->_sqlcase(' group by ')
-               .$self->_recurse_fields($_[0]->{group_by});
+        .$self->_recurse_fields($_[0]->{group_by}, { no_rownum_hack => 1 });
     }
     if (defined $_[0]->{having}) {
       my $frag;
@@ -287,14 +309,6 @@ sub name_sep {
 
 } # End of BEGIN block
 
-use base qw/DBIx::Class/;
-
-__PACKAGE__->load_components(qw/AccessorGroup/);
-
-__PACKAGE__->mk_group_accessors('simple' =>
-  qw/_connect_info _dbh _sql_maker _sql_maker_opts _conn_pid _conn_tid
-     debug debugobj cursor on_connect_do transaction_depth/);
-
 =head1 NAME
 
 DBIx::Class::Storage::DBI - DBI storage handler
@@ -303,49 +317,24 @@ DBIx::Class::Storage::DBI - DBI storage handler
 
 =head1 DESCRIPTION
 
-This class represents the connection to the database
+This class represents the connection to an RDBMS via L<DBI>.  See
+L<DBIx::Class::Storage> for general information.  This pod only
+documents DBI-specific methods and behaviors.
 
 =head1 METHODS
 
-=head2 new
-
 =cut
 
 sub new {
-  my $new = {};
-  bless $new, (ref $_[0] || $_[0]);
+  my $new = shift->next::method(@_);
 
-  $new->cursor("DBIx::Class::Storage::DBI::Cursor");
   $new->transaction_depth(0);
-
-  $new->debugobj(new DBIx::Class::Storage::Statistics());
-
-  my $fh;
-
-  my $debug_env = $ENV{DBIX_CLASS_STORAGE_DBI_DEBUG}
-                  || $ENV{DBIC_TRACE};
-
-  if (defined($debug_env) && ($debug_env =~ /=(.+)$/)) {
-    $fh = IO::File->new($1, 'w')
-      or $new->throw_exception("Cannot open trace file $1");
-  } else {
-    $fh = IO::File->new('>&STDERR');
-  }
-  $new->debugfh($fh);
-  $new->debug(1) if $debug_env;
   $new->_sql_maker_opts({});
-  return $new;
-}
-
-=head2 throw_exception
-
-Throws an exception - croaks.
+  $new->{savepoints} = [];
+  $new->{_in_dbh_do} = 0;
+  $new->{_dbh_gen} = 0;
 
-=cut
-
-sub throw_exception {
-  my ($self, $msg) = @_;
-  croak($msg);
+  $new;
 }
 
 =head2 connect_info
@@ -358,7 +347,13 @@ C<connect_info> here.
 
 The arrayref can either contain the same set of arguments one would
 normally pass to L<DBI/connect>, or a lone code reference which returns
-a connected database handle.
+a connected database handle.  Please note that the L<DBI> docs
+recommend that you always explicitly set C<AutoCommit> to either
+C<0> or C<1>.   L<DBIx::Class> further recommends that it be set
+to C<1>, and that you perform transactions via our L</txn_do>
+method.  L<DBIx::Class> will set it to C<1> if you do not do explicitly
+set it to zero.  This is the default for most DBDs.  See below for more
+details.
 
 In either case, if the final argument in your connect_info happens
 to be a hashref, C<connect_info> will look there for several
@@ -368,9 +363,35 @@ connection-specific options:
 
 =item on_connect_do
 
-This can be set to an arrayref of literal sql statements, which will
-be executed immediately after making the connection to the database
-every time we [re-]connect.
+Specifies things to do immediately after connecting or re-connecting to
+the database.  Its value may contain:
+
+=over
+
+=item an array reference
+
+This contains SQL statements to execute in order.  Each element contains
+a string or a code reference that returns a string.
+
+=item a code reference
+
+This contains some code to execute.  Unlike code references within an
+array reference, its return value is ignored.
+
+=back
+
+=item on_disconnect_do
+
+Takes arguments in the same form as L<on_connect_do> and executes them
+immediately before disconnecting from the database.
+
+Note, this only runs if you explicitly call L<disconnect> on the
+storage object.
+
+=item disable_sth_caching
+
+If set to a true value, this option will disable the caching of
+statement handles via L<DBI/prepare_cached>.
 
 =item limit_dialect 
 
@@ -396,6 +417,28 @@ This only needs to be used in conjunction with L<quote_char>, and is used to
 specify the charecter that seperates elements (schemas, tables, columns) from 
 each other. In most cases this is simply a C<.>.
 
+=item unsafe
+
+This Storage driver normally installs its own C<HandleError>, sets
+C<RaiseError> and C<ShowErrorStatement> on, and sets C<PrintError> off on
+all database handles, including those supplied by a coderef.  It does this
+so that it can have consistent and useful error behavior.
+
+If you set this option to a true value, Storage will not do its usual
+modifications to the database handle's attributes, and instead relies on
+the settings in your connect_info DBI options (or the values you set in
+your connection coderef, in the case that you are connecting via coderef).
+
+Note that your custom settings can cause Storage to malfunction,
+especially if you set a C<HandleError> handler that suppresses exceptions
+and/or disable C<RaiseError>.
+
+=item auto_savepoint
+
+If this option is true, L<DBIx::Class> will use savepoints when nesting
+transactions, making it possible to recover from failure in the inner
+transaction without having to abort all outer transactions.
+
 =back
 
 These options can be mixed in with your other L<DBI> connection attributes,
@@ -406,6 +449,21 @@ Every time C<connect_info> is invoked, any previous settings for
 these options will be cleared before setting the new ones, regardless of
 whether any options are specified in the new C<connect_info>.
 
+Another Important Note:
+
+DBIC can do some wonderful magic with handling exceptions,
+disconnections, and transactions when you use C<< AutoCommit => 1 >>
+combined with C<txn_do> for transaction support.
+
+If you set C<< AutoCommit => 0 >> in your connect info, then you are always
+in an assumed transaction between commits, and you're telling us you'd
+like to manage that manually.  A lot of DBIC's magic protections
+go away.  We can't protect you from exceptions due to database
+disconnects because we don't know anything about how to restart your
+transactions.  You're on your own for handling all sorts of exceptional
+cases if you choose the C<< AutoCommit => 0 >> path, just as you would
+be with raw DBI.
+
 Examples:
 
   # Simple SQLite connection
@@ -420,7 +478,7 @@ Examples:
       'dbi:Pg:dbname=foo',
       'postgres',
       'my_pg_password',
-      { AutoCommit => 0 },
+      { AutoCommit => 1 },
       { quote_char => q{"}, name_sep => q{.} },
     ]
   );
@@ -431,7 +489,7 @@ Examples:
       'dbi:Pg:dbname=foo',
       'postgres',
       'my_pg_password',
-      { AutoCommit => 0, quote_char => q{"}, name_sep => q{.} },
+      { AutoCommit => 1, quote_char => q{"}, name_sep => q{.} },
     ]
   );
 
@@ -443,67 +501,196 @@ Examples:
           quote_char => q{`},
           name_sep => q{@},
           on_connect_do => ['SET search_path TO myschema,otherschema,public'],
+          disable_sth_caching => 1,
       },
     ]
   );
 
+=cut
+
+sub connect_info {
+  my ($self, $info_arg) = @_;
+
+  return $self->_connect_info if !$info_arg;
+
+  # Kill sql_maker/_sql_maker_opts, so we get a fresh one with only
+  #  the new set of options
+  $self->_sql_maker(undef);
+  $self->_sql_maker_opts({});
+  $self->_connect_info([@$info_arg]); # copy for _connect_info
+
+  my $dbi_info = [@$info_arg]; # copy for _dbi_connect_info
+
+  my $last_info = $dbi_info->[-1];
+  if(ref $last_info eq 'HASH') {
+    $last_info = { %$last_info }; # so delete is non-destructive
+    my @storage_option = qw(
+      on_connect_do on_disconnect_do disable_sth_caching unsafe cursor_class
+      auto_savepoint
+    );
+    for my $storage_opt (@storage_option) {
+      if(my $value = delete $last_info->{$storage_opt}) {
+        $self->$storage_opt($value);
+      }
+    }
+    for my $sql_maker_opt (qw/limit_dialect quote_char name_sep/) {
+      if(my $opt_val = delete $last_info->{$sql_maker_opt}) {
+        $self->_sql_maker_opts->{$sql_maker_opt} = $opt_val;
+      }
+    }
+    # re-insert modified hashref
+    $dbi_info->[-1] = $last_info;
+
+    # Get rid of any trailing empty hashref
+    pop(@$dbi_info) if !keys %$last_info;
+  }
+  $self->_dbi_connect_info($dbi_info);
+
+  $self->_connect_info;
+}
+
 =head2 on_connect_do
 
 This method is deprecated in favor of setting via L</connect_info>.
 
-=head2 debug
+=head2 dbh_do
 
-Causes SQL trace information to be emitted on the C<debugobj> object.
-(or C<STDERR> if C<debugobj> has not specifically been set).
+Arguments: ($subref | $method_name), @extra_coderef_args?
 
-This is the equivalent to setting L</DBIC_TRACE> in your
-shell environment.
+Execute the given $subref or $method_name using the new exception-based
+connection management.
 
-=head2 debugfh
+The first two arguments will be the storage object that C<dbh_do> was called
+on and a database handle to use.  Any additional arguments will be passed
+verbatim to the called subref as arguments 2 and onwards.
 
-Set or retrieve the filehandle used for trace/debug output.  This should be
-an IO::Handle compatible ojbect (only the C<print> method is used.  Initially
-set to be STDERR - although see information on the
-L<DBIC_TRACE> environment variable.
+Using this (instead of $self->_dbh or $self->dbh) ensures correct
+exception handling and reconnection (or failover in future subclasses).
+
+Your subref should have no side-effects outside of the database, as
+there is the potential for your subref to be partially double-executed
+if the database connection was stale/dysfunctional.
+
+Example:
+
+  my @stuff = $schema->storage->dbh_do(
+    sub {
+      my ($storage, $dbh, @cols) = @_;
+      my $cols = join(q{, }, @cols);
+      $dbh->selectrow_array("SELECT $cols FROM foo");
+    },
+    @column_list
+  );
 
 =cut
 
-sub debugfh {
-    my $self = shift;
+sub dbh_do {
+  my $self = shift;
+  my $code = shift;
+
+  my $dbh = $self->_dbh;
+
+  return $self->$code($dbh, @_) if $self->{_in_dbh_do}
+      || $self->{transaction_depth};
+
+  local $self->{_in_dbh_do} = 1;
+
+  my @result;
+  my $want_array = wantarray;
+
+  eval {
+    $self->_verify_pid if $dbh;
+    if( !$dbh ) {
+        $self->_populate_dbh;
+        $dbh = $self->_dbh;
+    }
 
-    if ($self->debugobj->can('debugfh')) {
-        return $self->debugobj->debugfh(@_);
+    if($want_array) {
+        @result = $self->$code($dbh, @_);
+    }
+    elsif(defined $want_array) {
+        $result[0] = $self->$code($dbh, @_);
+    }
+    else {
+        $self->$code($dbh, @_);
     }
+  };
+
+  my $exception = $@;
+  if(!$exception) { return $want_array ? @result : $result[0] }
+
+  $self->throw_exception($exception) if $self->connected;
+
+  # We were not connected - reconnect and retry, but let any
+  #  exception fall right through this time
+  $self->_populate_dbh;
+  $self->$code($self->_dbh, @_);
 }
 
-=head2 debugobj
+# This is basically a blend of dbh_do above and DBIx::Class::Storage::txn_do.
+# It also informs dbh_do to bypass itself while under the direction of txn_do,
+#  via $self->{_in_dbh_do} (this saves some redundant eval and errorcheck, etc)
+sub txn_do {
+  my $self = shift;
+  my $coderef = shift;
 
-Sets or retrieves the object used for metric collection. Defaults to an instance
-of L<DBIx::Class::Storage::Statistics> that is campatible with the original
-method of using a coderef as a callback.  See the aforementioned Statistics
-class for more information.
+  ref $coderef eq 'CODE' or $self->throw_exception
+    ('$coderef must be a CODE reference');
 
-=head2 debugcb
+  return $coderef->(@_) if $self->{transaction_depth} && ! $self->auto_savepoint;
 
-Sets a callback to be executed each time a statement is run; takes a sub
-reference.  Callback is executed as $sub->($op, $info) where $op is
-SELECT/INSERT/UPDATE/DELETE and $info is what would normally be printed.
+  local $self->{_in_dbh_do} = 1;
 
-See L<debugobj> for a better way.
+  my @result;
+  my $want_array = wantarray;
 
-=cut
+  my $tried = 0;
+  while(1) {
+    eval {
+      $self->_verify_pid if $self->_dbh;
+      $self->_populate_dbh if !$self->_dbh;
 
-sub debugcb {
-    my $self = shift;
+      $self->txn_begin;
+      if($want_array) {
+          @result = $coderef->(@_);
+      }
+      elsif(defined $want_array) {
+          $result[0] = $coderef->(@_);
+      }
+      else {
+          $coderef->(@_);
+      }
+      $self->txn_commit;
+    };
 
-    if ($self->debugobj->can('callback')) {
-        return $self->debugobj->callback(@_);
+    my $exception = $@;
+    if(!$exception) { return $want_array ? @result : $result[0] }
+
+    if($tried++ > 0 || $self->connected) {
+      eval { $self->txn_rollback };
+      my $rollback_exception = $@;
+      if($rollback_exception) {
+        my $exception_class = "DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION";
+        $self->throw_exception($exception)  # propagate nested rollback
+          if $rollback_exception =~ /$exception_class/;
+
+        $self->throw_exception(
+          "Transaction aborted: ${exception}. "
+          . "Rollback failed: ${rollback_exception}"
+        );
+      }
+      $self->throw_exception($exception)
     }
+
+    # We were not connected, and was first try - reconnect and retry
+    # via the while loop
+    $self->_populate_dbh;
+  }
 }
 
 =head2 disconnect
 
-Disconnect the L<DBI> handle, performing a rollback first if the
+Our C<disconnect> method also performs a rollback first if the
 database is not in C<AutoCommit> mode.
 
 =cut
@@ -512,28 +699,28 @@ sub disconnect {
   my ($self) = @_;
 
   if( $self->connected ) {
-    $self->_dbh->rollback unless $self->_dbh->{AutoCommit};
+    my $connection_do = $self->on_disconnect_do;
+    $self->_do_connection_actions($connection_do) if ref($connection_do);
+
+    $self->_dbh->rollback unless $self->_dbh_autocommit;
     $self->_dbh->disconnect;
     $self->_dbh(undef);
+    $self->{_dbh_gen}++;
   }
 }
 
-=head2 connected
-
-Check if the L<DBI> handle is connected.  Returns true if the handle
-is connected.
-
-=cut
-
-sub connected { my ($self) = @_;
+sub connected {
+  my ($self) = @_;
 
   if(my $dbh = $self->_dbh) {
       if(defined $self->_conn_tid && $self->_conn_tid != threads->tid) {
-          return $self->_dbh(undef);
+          $self->_dbh(undef);
+          $self->{_dbh_gen}++;
+          return;
       }
-      elsif($self->_conn_pid != $$) {
-          $self->_dbh->{InactiveDestroy} = 1;
-          return $self->_dbh(undef);
+      else {
+          $self->_verify_pid;
+          return 0 if !$self->_dbh;
       }
       return ($dbh->FETCH('Active') && $dbh->ping);
   }
@@ -541,12 +728,19 @@ sub connected { my ($self) = @_;
   return 0;
 }
 
-=head2 ensure_connected
+# handle pid changes correctly
+#  NOTE: assumes $self->_dbh is a valid $dbh
+sub _verify_pid {
+  my ($self) = @_;
 
-Check whether the database handle is connected - if not then make a
-connection.
+  return if defined $self->_conn_pid && $self->_conn_pid == $$;
 
-=cut
+  $self->_dbh->{InactiveDestroy} = 1;
+  $self->_dbh(undef);
+  $self->{_dbh_gen}++;
+
+  return;
+}
 
 sub ensure_connected {
   my ($self) = @_;
@@ -572,252 +766,468 @@ sub dbh {
 sub _sql_maker_args {
     my ($self) = @_;
     
-    return ( limit_dialect => $self->dbh, %{$self->_sql_maker_opts} );
+    return ( bindtype=>'columns', limit_dialect => $self->dbh, %{$self->_sql_maker_opts} );
 }
 
-=head2 sql_maker
-
-Returns a C<sql_maker> object - normally an object of class
-C<DBIC::SQL::Abstract>.
-
-=cut
-
 sub sql_maker {
   my ($self) = @_;
   unless ($self->_sql_maker) {
-    $self->_sql_maker(new DBIC::SQL::Abstract( $self->_sql_maker_args ));
+    my $sql_maker_class = $self->sql_maker_class;
+    $self->_sql_maker($sql_maker_class->new( $self->_sql_maker_args ));
   }
   return $self->_sql_maker;
 }
 
-sub connect_info {
-  my ($self, $info_arg) = @_;
-
-  if($info_arg) {
-    # Kill sql_maker/_sql_maker_opts, so we get a fresh one with only
-    #  the new set of options
-    $self->_sql_maker(undef);
-    $self->_sql_maker_opts({});
-
-    my $info = [ @$info_arg ]; # copy because we can alter it
-    my $last_info = $info->[-1];
-    if(ref $last_info eq 'HASH') {
-      if(my $on_connect_do = delete $last_info->{on_connect_do}) {
-        $self->on_connect_do($on_connect_do);
-      }
-      for my $sql_maker_opt (qw/limit_dialect quote_char name_sep/) {
-        if(my $opt_val = delete $last_info->{$sql_maker_opt}) {
-          $self->_sql_maker_opts->{$sql_maker_opt} = $opt_val;
-        }
-      }
-
-      # Get rid of any trailing empty hashref
-      pop(@$info) if !keys %$last_info;
-    }
-
-    $self->_connect_info($info);
-  }
-
-  $self->_connect_info;
-}
+sub _rebless {}
 
 sub _populate_dbh {
   my ($self) = @_;
-  my @info = @{$self->_connect_info || []};
+  my @info = @{$self->_dbi_connect_info || []};
   $self->_dbh($self->_connect(@info));
 
+  # Always set the transaction depth on connect, since
+  #  there is no transaction in progress by definition
+  $self->{transaction_depth} = $self->_dbh_autocommit ? 0 : 1;
+
   if(ref $self eq 'DBIx::Class::Storage::DBI') {
     my $driver = $self->_dbh->{Driver}->{Name};
     if ($self->load_optional_class("DBIx::Class::Storage::DBI::${driver}")) {
       bless $self, "DBIx::Class::Storage::DBI::${driver}";
-      $self->_rebless() if $self->can('_rebless');
+      $self->_rebless();
     }
   }
 
-  # if on-connect sql statements are given execute them
-  foreach my $sql_statement (@{$self->on_connect_do || []}) {
-    $self->debugobj->query_start($sql_statement) if $self->debug();
-    $self->_dbh->do($sql_statement);
-    $self->debugobj->query_end($sql_statement) if $self->debug();
-  }
+  my $connection_do = $self->on_connect_do;
+  $self->_do_connection_actions($connection_do) if ref($connection_do);
 
   $self->_conn_pid($$);
   $self->_conn_tid(threads->tid) if $INC{'threads.pm'};
 }
 
+sub _do_connection_actions {
+  my $self = shift;
+  my $connection_do = shift;
+
+  if (ref $connection_do eq 'ARRAY') {
+    $self->_do_query($_) foreach @$connection_do;
+  }
+  elsif (ref $connection_do eq 'CODE') {
+    $connection_do->();
+  }
+
+  return $self;
+}
+
+sub _do_query {
+  my ($self, $action) = @_;
+
+  if (ref $action eq 'CODE') {
+    $action = $action->($self);
+    $self->_do_query($_) foreach @$action;
+  }
+  else {
+    my @to_run = (ref $action eq 'ARRAY') ? (@$action) : ($action);
+    $self->_query_start(@to_run);
+    $self->_dbh->do(@to_run);
+    $self->_query_end(@to_run);
+  }
+
+  return $self;
+}
+
 sub _connect {
   my ($self, @info) = @_;
 
   $self->throw_exception("You failed to provide any connection info")
-      if !@info;
+    if !@info;
 
   my ($old_connect_via, $dbh);
 
   if ($INC{'Apache/DBI.pm'} && $ENV{MOD_PERL}) {
-      $old_connect_via = $DBI::connect_via;
-      $DBI::connect_via = 'connect';
+    $old_connect_via = $DBI::connect_via;
+    $DBI::connect_via = 'connect';
   }
 
   eval {
-    $dbh = ref $info[0] eq 'CODE'
-         ? &{$info[0]}
-         : DBI->connect(@info);
+    if(ref $info[0] eq 'CODE') {
+       $dbh = &{$info[0]}
+    }
+    else {
+       $dbh = DBI->connect(@info);
+    }
+
+    if($dbh && !$self->unsafe) {
+      my $weak_self = $self;
+      weaken($weak_self);
+      $dbh->{HandleError} = sub {
+          $weak_self->throw_exception("DBI Exception: $_[0]")
+      };
+      $dbh->{ShowErrorStatement} = 1;
+      $dbh->{RaiseError} = 1;
+      $dbh->{PrintError} = 0;
+    }
   };
 
   $DBI::connect_via = $old_connect_via if $old_connect_via;
 
-  if (!$dbh || $@) {
-    $self->throw_exception("DBI Connection failed: " . ($@ || $DBI::errstr));
-  }
+  $self->throw_exception("DBI Connection failed: " . ($@||$DBI::errstr))
+    if !$dbh || $@;
+
+  $self->_dbh_autocommit($dbh->{AutoCommit});
 
   $dbh;
 }
 
-=head2 txn_begin
+sub svp_begin {
+  my ($self, $name) = @_;
 
-Calls begin_work on the current dbh.
+  $name = $self->_svp_generate_name
+    unless defined $name;
 
-See L<DBIx::Class::Schema> for the txn_do() method, which allows for
-an entire code block to be executed transactionally.
+  $self->throw_exception ("You can't use savepoints outside a transaction")
+    if $self->{transaction_depth} == 0;
 
-=cut
+  $self->throw_exception ("Your Storage implementation doesn't support savepoints")
+    unless $self->can('_svp_begin');
+  
+  push @{ $self->{savepoints} }, $name;
 
-sub txn_begin {
-  my $self = shift;
-  if ($self->{transaction_depth}++ == 0) {
-    my $dbh = $self->dbh;
-    if ($dbh->{AutoCommit}) {
-      $self->debugobj->txn_begin()
-        if ($self->debug);
-      $dbh->begin_work;
-    }
-  }
+  $self->debugobj->svp_begin($name) if $self->debug;
+  
+  return $self->_svp_begin($name);
 }
 
-=head2 txn_commit
+sub svp_release {
+  my ($self, $name) = @_;
 
-Issues a commit against the current dbh.
+  $self->throw_exception ("You can't use savepoints outside a transaction")
+    if $self->{transaction_depth} == 0;
 
-=cut
+  $self->throw_exception ("Your Storage implementation doesn't support savepoints")
+    unless $self->can('_svp_release');
 
-sub txn_commit {
-  my $self = shift;
-  my $dbh = $self->dbh;
-  if ($self->{transaction_depth} == 0) {
-    unless ($dbh->{AutoCommit}) {
-      $self->debugobj->txn_commit()
-        if ($self->debug);
-      $dbh->commit;
-    }
+  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;
+
+    do { $svp = pop @{ $self->{savepoints} } } while $svp ne $name;
+  } else {
+    $name = pop @{ $self->{savepoints} };
   }
-  else {
-    if (--$self->{transaction_depth} == 0) {
-      $self->debugobj->txn_commit()
-        if ($self->debug);
-      $dbh->commit;
-    }
+
+  $self->debugobj->svp_release($name) if $self->debug;
+
+  return $self->_svp_release($name);
+}
+
+sub svp_rollback {
+  my ($self, $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_rollback');
+
+  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;
+  
+  return $self->_svp_rollback($name);
 }
 
-=head2 txn_rollback
+sub _svp_generate_name {
+    my ($self) = @_;
 
-Issues a rollback against the current dbh. A nested rollback will
-throw a L<DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION> exception,
-which allows the rollback to propagate to the outermost transaction.
+    return 'savepoint_'.scalar(@{ $self->{'savepoints'} });
+}
 
-=cut
+sub txn_begin {
+  my $self = shift;
+  $self->ensure_connected();
+  if($self->{transaction_depth} == 0) {
+    $self->debugobj->txn_begin()
+      if $self->debug;
+    # this isn't ->_dbh-> because
+    #  we should reconnect on begin_work
+    #  for AutoCommit users
+    $self->dbh->begin_work;
+  } elsif ($self->auto_savepoint) {
+    $self->svp_begin;
+  }
+  $self->{transaction_depth}++;
+}
 
-sub txn_rollback {
+sub txn_commit {
   my $self = shift;
+  if ($self->{transaction_depth} == 1) {
+    my $dbh = $self->_dbh;
+    $self->debugobj->txn_commit()
+      if ($self->debug);
+    $dbh->commit;
+    $self->{transaction_depth} = 0
+      if $self->_dbh_autocommit;
+  }
+  elsif($self->{transaction_depth} > 1) {
+    $self->{transaction_depth}--;
+    $self->svp_release
+      if $self->auto_savepoint;
+  }
+}
 
+sub txn_rollback {
+  my $self = shift;
+  my $dbh = $self->_dbh;
   eval {
-    my $dbh = $self->dbh;
-    if ($self->{transaction_depth} == 0) {
-      unless ($dbh->{AutoCommit}) {
-        $self->debugobj->txn_rollback()
-          if ($self->debug);
-        $dbh->rollback;
+    if ($self->{transaction_depth} == 1) {
+      $self->debugobj->txn_rollback()
+        if ($self->debug);
+      $self->{transaction_depth} = 0
+        if $self->_dbh_autocommit;
+      $dbh->rollback;
+    }
+    elsif($self->{transaction_depth} > 1) {
+      $self->{transaction_depth}--;
+      if ($self->auto_savepoint) {
+        $self->svp_rollback;
+        $self->svp_release;
       }
     }
     else {
-      if (--$self->{transaction_depth} == 0) {
-        $self->debugobj->txn_rollback()
-          if ($self->debug);
-        $dbh->rollback;
-      }
-      else {
-        die DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION->new;
-      }
+      die DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION->new;
     }
   };
-
   if ($@) {
     my $error = $@;
     my $exception_class = "DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION";
     $error =~ /$exception_class/ and $self->throw_exception($error);
-    $self->{transaction_depth} = 0;          # ensure that a failed rollback
-    $self->throw_exception($error);          # resets the transaction depth
+    # ensure that a failed rollback resets the transaction depth
+    $self->{transaction_depth} = $self->_dbh_autocommit ? 0 : 1;
+    $self->throw_exception($error);
   }
 }
 
-sub _execute {
-  my ($self, $op, $extra_bind, $ident, @args) = @_;
-  my ($sql, @bind) = $self->sql_maker->$op($ident, @args);
-  unshift(@bind, @$extra_bind) if $extra_bind;
-  if ($self->debug) {
-      my @debug_bind = map { defined $_ ? qq{'$_'} : q{'NULL'} } @bind;
-      $self->debugobj->query_start($sql, @debug_bind);
-  }
-  my $sth = eval { $self->sth($sql,$op) };
+# This used to be the top-half of _execute.  It was split out to make it
+#  easier to override in NoBindVars without duping the rest.  It takes up
+#  all of _execute's args, and emits $sql, @bind.
+sub _prep_for_execute {
+  my ($self, $op, $extra_bind, $ident, $args) = @_;
 
-  if (!$sth || $@) {
-    $self->throw_exception(
-      'no sth generated via sql (' . ($@ || $self->_dbh->errstr) . "): $sql"
-    );
-  }
-  @bind = map { ref $_ ? ''.$_ : $_ } @bind; # stringify args
-  my $rv;
-  if ($sth) {
-    my $time = time();
-    $rv = eval { $sth->execute(@bind) };
-
-    if ($@ || !$rv) {
-      $self->throw_exception("Error executing '$sql': ".($@ || $sth->errstr));
+  my ($sql, @bind) = $self->sql_maker->$op($ident, @$args);
+  unshift(@bind,
+    map { ref $_ eq 'ARRAY' ? $_ : [ '!!dummy', $_ ] } @$extra_bind)
+      if $extra_bind;
+
+  return ($sql, \@bind);
+}
+
+sub _fix_bind_params {
+    my ($self, @bind) = @_;
+
+    ### Turn @bind from something like this:
+    ###   ( [ "artist", 1 ], [ "cdid", 1, 3 ] )
+    ### to this:
+    ###   ( "'1'", "'1'", "'3'" )
+    return
+        map {
+            if ( defined( $_ && $_->[1] ) ) {
+                map { qq{'$_'}; } @{$_}[ 1 .. $#$_ ];
+            }
+            else { q{'NULL'}; }
+        } @bind;
+}
+
+sub _query_start {
+    my ( $self, $sql, @bind ) = @_;
+
+    if ( $self->debug ) {
+        @bind = $self->_fix_bind_params(@bind);
+        
+        $self->debugobj->query_start( $sql, @bind );
     }
-  } else {
-    $self->throw_exception("'$sql' did not generate a statement.");
+}
+
+sub _query_end {
+    my ( $self, $sql, @bind ) = @_;
+
+    if ( $self->debug ) {
+        @bind = $self->_fix_bind_params(@bind);
+        $self->debugobj->query_end( $sql, @bind );
+    }
+}
+
+sub _dbh_execute {
+  my ($self, $dbh, $op, $extra_bind, $ident, $bind_attributes, @args) = @_;
+  
+  if( blessed($ident) && $ident->isa("DBIx::Class::ResultSource") ) {
+    $ident = $ident->from();
   }
-  if ($self->debug) {
-      my @debug_bind = map { defined $_ ? qq{`$_'} : q{`NULL'} } @bind; 
-      $self->debugobj->query_end($sql, @debug_bind);
+
+  my ($sql, $bind) = $self->_prep_for_execute($op, $extra_bind, $ident, \@args);
+
+  $self->_query_start( $sql, @$bind );
+
+  my $sth = $self->sth($sql,$op);
+
+  my $placeholder_index = 1; 
+
+  foreach my $bound (@$bind) {
+    my $attributes = {};
+    my($column_name, @data) = @$bound;
+
+    if ($bind_attributes) {
+      $attributes = $bind_attributes->{$column_name}
+      if defined $bind_attributes->{$column_name};
+    }
+
+    foreach my $data (@data) {
+      $data = ref $data ? ''.$data : $data; # stringify args
+
+      $sth->bind_param($placeholder_index, $data, $attributes);
+      $placeholder_index++;
+    }
   }
-  return (wantarray ? ($rv, $sth, @bind) : $rv);
+
+  # Can this fail without throwing an exception anyways???
+  my $rv = $sth->execute();
+  $self->throw_exception($sth->errstr) if !$rv;
+
+  $self->_query_end( $sql, @$bind );
+
+  return (wantarray ? ($rv, $sth, @$bind) : $rv);
+}
+
+sub _execute {
+    my $self = shift;
+    $self->dbh_do('_dbh_execute', @_)
 }
 
 sub insert {
-  my ($self, $ident, $to_insert) = @_;
-  $self->throw_exception(
-    "Couldn't insert ".join(', ',
-      map "$_ => $to_insert->{$_}", keys %$to_insert
-    )." into ${ident}"
-  ) unless ($self->_execute('insert' => [], $ident, $to_insert));
+  my ($self, $source, $to_insert) = @_;
+  
+  my $ident = $source->from; 
+  my $bind_attributes = $self->source_bind_attributes($source);
+
+  foreach my $col ( $source->columns ) {
+    if ( !defined $to_insert->{$col} ) {
+      my $col_info = $source->column_info($col);
+
+      if ( $col_info->{auto_nextval} ) {
+        $self->ensure_connected; 
+        $to_insert->{$col} = $self->_sequence_fetch( 'nextval', $col_info->{sequence} || $self->_dbh_get_autoinc_seq($self->dbh, $source) );
+      }
+    }
+  }
+
+  $self->_execute('insert' => [], $source, $bind_attributes, $to_insert);
+
   return $to_insert;
 }
 
+## Still not quite perfect, and EXPERIMENTAL
+## Currently it is assumed that all values passed will be "normal", i.e. not 
+## scalar refs, or at least, all the same type as the first set, the statement is
+## only prepped once.
+sub insert_bulk {
+  my ($self, $source, $cols, $data) = @_;
+  my %colvalues;
+  my $table = $source->from;
+  @colvalues{@$cols} = (0..$#$cols);
+  my ($sql, @bind) = $self->sql_maker->insert($table, \%colvalues);
+  
+  $self->_query_start( $sql, @bind );
+  my $sth = $self->sth($sql);
+
+#  @bind = map { ref $_ ? ''.$_ : $_ } @bind; # stringify args
+
+  ## This must be an arrayref, else nothing works!
+  
+  my $tuple_status = [];
+  
+  ##use Data::Dumper;
+  ##print STDERR Dumper( $data, $sql, [@bind] );
+
+  my $time = time();
+
+  ## Get the bind_attributes, if any exist
+  my $bind_attributes = $self->source_bind_attributes($source);
+
+  ## Bind the values and execute
+  my $placeholder_index = 1; 
+
+  foreach my $bound (@bind) {
+
+    my $attributes = {};
+    my ($column_name, $data_index) = @$bound;
+
+    if( $bind_attributes ) {
+      $attributes = $bind_attributes->{$column_name}
+      if defined $bind_attributes->{$column_name};
+    }
+
+    my @data = map { $_->[$data_index] } @$data;
+
+    $sth->bind_param_array( $placeholder_index, [@data], $attributes );
+    $placeholder_index++;
+  }
+  my $rv = $sth->execute_array({ArrayTupleStatus => $tuple_status});
+  $self->throw_exception($sth->errstr) if !$rv;
+
+  $self->_query_end( $sql, @bind );
+  return (wantarray ? ($rv, $sth, @bind) : $rv);
+}
+
 sub update {
-  return shift->_execute('update' => [], @_);
+  my $self = shift @_;
+  my $source = shift @_;
+  my $bind_attributes = $self->source_bind_attributes($source);
+  
+  return $self->_execute('update' => [], $source, $bind_attributes, @_);
 }
 
+
 sub delete {
-  return shift->_execute('delete' => [], @_);
+  my $self = shift @_;
+  my $source = shift @_;
+  
+  my $bind_attrs = {}; ## If ever it's needed...
+  
+  return $self->_execute('delete' => [], $source, $bind_attrs, @_);
 }
 
 sub _select {
   my ($self, $ident, $select, $condition, $attrs) = @_;
   my $order = $attrs->{order_by};
+
   if (ref $condition eq 'SCALAR') {
     $order = $1 if $$condition =~ s/ORDER BY (.*)$//i;
   }
+
+  my $for = delete $attrs->{for};
+  my $sql_maker = $self->sql_maker;
+  local $sql_maker->{for} = $for;
+
   if (exists $attrs->{group_by} || $attrs->{having}) {
     $order = {
       group_by => $attrs->{group_by},
@@ -825,18 +1235,37 @@ sub _select {
       ($order ? (order_by => $order) : ())
     };
   }
-  my @args = ('select', $attrs->{bind}, $ident, $select, $condition, $order);
+  my $bind_attrs = {}; ## Future support
+  my @args = ('select', $attrs->{bind}, $ident, $bind_attrs, $select, $condition, $order);
   if ($attrs->{software_limit} ||
       $self->sql_maker->_default_limit_syntax eq "GenericSubQ") {
         $attrs->{software_limit} = 1;
   } else {
     $self->throw_exception("rows attribute must be positive if present")
       if (defined($attrs->{rows}) && !($attrs->{rows} > 0));
+
+    # MySQL actually recommends this approach.  I cringe.
+    $attrs->{rows} = 2**48 if not defined $attrs->{rows} and defined $attrs->{offset};
     push @args, $attrs->{rows}, $attrs->{offset};
   }
+
   return $self->_execute(@args);
 }
 
+sub source_bind_attributes {
+  my ($self, $source) = @_;
+  
+  my $bind_attributes;
+  foreach my $column ($source->columns) {
+  
+    my $data_type = $source->column_info($column)->{data_type} || '';
+    $bind_attributes->{$column} = $self->bind_attribute_by_data_type($data_type)
+     if $data_type;
+  }
+
+  return $bind_attributes;
+}
+
 =head2 select
 
 =over 4
@@ -852,26 +1281,43 @@ Handle a SQL select statement.
 sub select {
   my $self = shift;
   my ($ident, $select, $condition, $attrs) = @_;
-  return $self->cursor->new($self, \@_, $attrs);
+  return $self->cursor_class->new($self, \@_, $attrs);
 }
 
-=head2 select_single
-
-Performs a select, fetch and return of data - handles a single row
-only.
-
-=cut
-
-# Need to call finish() to work round broken DBDs
-
 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;
 }
 
+sub reload_row { 
+  my ($self, $row) = @_;
+  delete $row->{_dirty_columns};
+  return unless $row->in_storage; # Don't reload if we aren't real!
+
+  my $reload = $row->result_source->resultset->find(
+    map { $row->$_ } $row->primary_columns
+  );
+  unless ($reload) { # If we got deleted in the mean-time
+    $row->in_storage(0);
+    return $row;
+  }
+
+  $row = %$reload;
+  
+  # Avoid a possible infinite loop with
+  # sub DESTROY { $_[0]->discard_changes }
+  bless $reload, 'Do::Not::Exist';
+
+  return $row;
+}
+
 =head2 sth
 
 =over 4
@@ -884,32 +1330,35 @@ Returns a L<DBI> sth (statement handle) for the supplied SQL.
 
 =cut
 
-sub sth {
-  my ($self, $sql) = @_;
-  # 3 is the if_active parameter which avoids active sth re-use
-  return $self->dbh->prepare_cached($sql, {}, 3);
-}
+sub _dbh_sth {
+  my ($self, $dbh, $sql) = @_;
 
-=head2 columns_info_for
+  # 3 is the if_active parameter which avoids active sth re-use
+  my $sth = $self->disable_sth_caching
+    ? $dbh->prepare($sql)
+    : $dbh->prepare_cached($sql, {}, 3);
 
-Returns database type info for a given table column.
+  # XXX You would think RaiseError would make this impossible,
+  #  but apparently that's not true :(
+  $self->throw_exception($dbh->errstr) if !$sth;
 
-=cut
+  $sth;
+}
 
-sub columns_info_for {
-  my ($self, $table) = @_;
+sub sth {
+  my ($self, $sql) = @_;
+  $self->dbh_do('_dbh_sth', $sql);
+}
 
-  my $dbh = $self->dbh;
+sub _dbh_columns_info_for {
+  my ($self, $dbh, $table) = @_;
 
   if ($dbh->can('column_info')) {
     my %result;
-    local $dbh->{RaiseError} = 1;
-    local $dbh->{PrintError} = 0;
     eval {
       my ($schema,$tab) = $table =~ /^(.+?)\.(.+)$/ ? ($1,$2) : (undef,$table);
       my $sth = $dbh->column_info( undef,$schema, $tab, '%' );
       $sth->execute();
-
       while ( my $info = $sth->fetchrow_hashref() ){
         my %column_info;
         $column_info{data_type}   = $info->{TYPE_NAME};
@@ -926,7 +1375,7 @@ sub columns_info_for {
   }
 
   my %result;
-  my $sth = $dbh->prepare("SELECT * FROM $table WHERE 1=0");
+  my $sth = $dbh->prepare($self->sql_maker->select($table, undef, \'1 = 0'));
   $sth->execute;
   my @columns = @{$sth->{NAME_lc}};
   for my $i ( 0 .. $#columns ){
@@ -958,17 +1407,26 @@ sub columns_info_for {
   return \%result;
 }
 
+sub columns_info_for {
+  my ($self, $table) = @_;
+  $self->dbh_do('_dbh_columns_info_for', $table);
+}
+
 =head2 last_insert_id
 
 Return the row id of the last insert.
 
 =cut
 
-sub last_insert_id {
-  my ($self, $row) = @_;
-    
-  return $self->dbh->func('last_insert_rowid');
+sub _dbh_last_insert_id {
+    my ($self, $dbh, $source, $col) = @_;
+    # XXX This is a SQLite-ism as a default... is there a DBI-generic way?
+    $dbh->func('last_insert_rowid');
+}
 
+sub last_insert_id {
+  my $self = shift;
+  $self->dbh_do('_dbh_last_insert_id', @_);
 }
 
 =head2 sqlt_type
@@ -979,25 +1437,36 @@ Returns the database driver name.
 
 sub sqlt_type { shift->dbh->{Driver}->{Name} }
 
-=head2 create_ddl_dir (EXPERIMENTAL)
+=head2 bind_attribute_by_data_type
+
+Given a datatype from column info, returns a database specific bind attribute for
+$dbh->bind_param($val,$attribute) or nothing if we will let the database planner
+just handle it.
+
+Generally only needed for special case column types, like bytea in postgres.
+
+=cut
+
+sub bind_attribute_by_data_type {
+    return;
+}
+
+=head2 create_ddl_dir
 
 =over 4
 
-=item Arguments: $schema \@databases, $version, $directory, $sqlt_args
+=item Arguments: $schema \@databases, $version, $directory, $preversion, $sqlt_args
 
 =back
 
 Creates a SQL file based on the Schema, for each of the specified
 database types, in the given directory.
 
-Note that this feature is currently EXPERIMENTAL and may not work correctly
-across all databases, or fully handle complex relationships.
-
 =cut
 
 sub create_ddl_dir
 {
-  my ($self, $schema, $databases, $version, $dir, $sqltargs) = @_;
+  my ($self, $schema, $databases, $version, $dir, $preversion, $sqltargs) = @_;
 
   if(!$dir || !-d $dir)
   {
@@ -1009,40 +1478,119 @@ sub create_ddl_dir
   $version ||= $schema->VERSION || '1.x';
   $sqltargs = { ( add_drop_table => 1 ), %{$sqltargs || {}} };
 
-  eval "use SQL::Translator";
-  $self->throw_exception("Can't deploy without SQL::Translator: $@") if $@;
+  $self->throw_exception(q{Can't create a ddl file without SQL::Translator 0.09: '}
+      . $self->_check_sqlt_message . q{'})
+          if !$self->_check_sqlt_version;
+
+  my $sqlt = SQL::Translator->new( $sqltargs );
+
+  $sqlt->parser('SQL::Translator::Parser::DBIx::Class');
+  my $sqlt_schema = $sqlt->translate({ data => $schema }) or die $sqlt->error;
 
-  my $sqlt = SQL::Translator->new($sqltargs);
   foreach my $db (@$databases)
   {
     $sqlt->reset();
-    $sqlt->parser('SQL::Translator::Parser::DBIx::Class');
-#    $sqlt->parser_args({'DBIx::Class' => $schema);
-    $sqlt->data($schema);
+    $sqlt = $self->configure_sqlt($sqlt, $db);
+    $sqlt->{schema} = $sqlt_schema;
     $sqlt->producer($db);
 
     my $file;
     my $filename = $schema->ddl_filename($db, $dir, $version);
     if(-e $filename)
     {
-      $self->throw_exception("$filename already exists, skipping $db");
-      next;
-    }
-    open($file, ">$filename") 
-      or $self->throw_exception("Can't open $filename for writing ($!)");
-    my $output = $sqlt->translate;
-#use Data::Dumper;
-#    print join(":", keys %{$schema->source_registrations});
-#    print Dumper($sqlt->schema);
-    if(!$output)
+      warn("$filename already exists, skipping $db");
+      next unless ($preversion);
+    } else {
+      my $output = $sqlt->translate;
+      if(!$output)
+      {
+        warn("Failed to translate to $db, skipping. (" . $sqlt->error . ")");
+        next;
+      }
+      if(!open($file, ">$filename"))
+      {
+          $self->throw_exception("Can't open $filename for writing ($!)");
+          next;
+      }
+      print $file $output;
+      close($file);
+    } 
+    if($preversion)
     {
-      $self->throw_exception("Failed to translate to $db. (" . $sqlt->error . ")");
-      next;
+      require SQL::Translator::Diff;
+
+      my $prefilename = $schema->ddl_filename($db, $dir, $preversion);
+#      print "Previous version $prefilename\n";
+      if(!-e $prefilename)
+      {
+        warn("No previous schema file found ($prefilename)");
+        next;
+      }
+
+      my $difffile = $schema->ddl_filename($db, $dir, $version, $preversion);
+      print STDERR "Diff: $difffile: $db, $dir, $version, $preversion \n";
+      if(-e $difffile)
+      {
+        warn("$difffile already exists, skipping");
+        next;
+      }
+
+      my $source_schema;
+      {
+        my $t = SQL::Translator->new($sqltargs);
+        $t->debug( 0 );
+        $t->trace( 0 );
+        $t->parser( $db )                       or die $t->error;
+        $t = $self->configure_sqlt($t, $db);
+        my $out = $t->translate( $prefilename ) or die $t->error;
+        $source_schema = $t->schema;
+        unless ( $source_schema->name ) {
+          $source_schema->name( $prefilename );
+        }
+      }
+
+      # The "new" style of producers have sane normalization and can support 
+      # diffing a SQL file against a DBIC->SQLT schema. Old style ones don't
+      # And we have to diff parsed SQL against parsed SQL.
+      my $dest_schema = $sqlt_schema;
+
+      unless ( "SQL::Translator::Producer::$db"->can('preprocess_schema') ) {
+        my $t = SQL::Translator->new($sqltargs);
+        $t->debug( 0 );
+        $t->trace( 0 );
+        $t->parser( $db )                    or die $t->error;
+        $t = $self->configure_sqlt($t, $db);
+        my $out = $t->translate( $filename ) or die $t->error;
+        $dest_schema = $t->schema;
+        $dest_schema->name( $filename )
+          unless $dest_schema->name;
+      }
+
+      $DB::single = 1;
+      my $diff = SQL::Translator::Diff::schema_diff($source_schema, $db,
+                                                    $dest_schema,   $db,
+                                                    $sqltargs
+                                                   );
+      if(!open $file, ">$difffile")
+      { 
+        $self->throw_exception("Can't write to $difffile ($!)");
+        next;
+      }
+      print $file $diff;
+      close($file);
     }
-    print $file $output;
-    close($file);
   }
+}
 
+sub configure_sqlt() {
+  my $self = shift;
+  my $tr = shift;
+  my $db = shift || $self->sqlt_type;
+  if ($db eq 'PostgreSQL') {
+    $tr->quote_table_names(0);
+    $tr->quote_field_names(0);
+  }
+  return $tr;
 }
 
 =head2 deployment_statements
@@ -1075,56 +1623,56 @@ sub deployment_statements {
   $type ||= $self->sqlt_type;
   $version ||= $schema->VERSION || '1.x';
   $dir ||= './';
-  eval "use SQL::Translator";
-  if(!$@)
-  {
-    eval "use SQL::Translator::Parser::DBIx::Class;";
-    $self->throw_exception($@) if $@;
-    eval "use SQL::Translator::Producer::${type};";
-    $self->throw_exception($@) if $@;
-    my $tr = SQL::Translator->new(%$sqltargs);
-    SQL::Translator::Parser::DBIx::Class::parse( $tr, $schema );
-    return "SQL::Translator::Producer::${type}"->can('produce')->($tr);
-  }
-
   my $filename = $schema->ddl_filename($type, $dir, $version);
-  if(!-f $filename)
+  if(-f $filename)
   {
-#      $schema->create_ddl_dir([ $type ], $version, $dir, $sqltargs);
-      $self->throw_exception("No SQL::Translator, and no Schema file found, aborting deploy");
-      return;
+      my $file;
+      open($file, "<$filename") 
+        or $self->throw_exception("Can't open $filename ($!)");
+      my @rows = <$file>;
+      close($file);
+      return join('', @rows);
   }
-  my $file;
-  open($file, "<$filename") 
-      or $self->throw_exception("Can't open $filename ($!)");
-  my @rows = <$file>;
-  close($file);
 
-  return join('', @rows);
-  
-}
+  $self->throw_exception(q{Can't deploy without SQL::Translator 0.09: '}
+      . $self->_check_sqlt_message . q{'})
+          if !$self->_check_sqlt_version;
 
-=head2 deploy
+  require SQL::Translator::Parser::DBIx::Class;
+  eval qq{use SQL::Translator::Producer::${type}};
+  $self->throw_exception($@) if $@;
 
-Sends the appropriate statements to create or modify tables to the
-db. This would normally be called through
-L<DBIx::Class::Schema/deploy>.
+  # sources needs to be a parser arg, but for simplicty allow at top level 
+  # coming in
+  $sqltargs->{parser_args}{sources} = delete $sqltargs->{sources}
+      if exists $sqltargs->{sources};
 
-=cut
+  my $tr = SQL::Translator->new(%$sqltargs);
+  SQL::Translator::Parser::DBIx::Class::parse( $tr, $schema );
+  return "SQL::Translator::Producer::${type}"->can('produce')->($tr);
+
+  return;
+
+}
 
 sub deploy {
   my ($self, $schema, $type, $sqltargs, $dir) = @_;
   foreach my $statement ( $self->deployment_statements($schema, $type, undef, $dir, { no_comments => 1, %{ $sqltargs || {} } } ) ) {
-    for ( split(";\n", $statement)) {
-      next if($_ =~ /^--/);
-      next if(!$_);
-#      next if($_ =~ /^DROP/m);
-      next if($_ =~ /^BEGIN TRANSACTION/m);
-      next if($_ =~ /^COMMIT/m);
-      next if $_ =~ /^\s+$/; # skip whitespace only
-      $self->debugobj->query_start($_) if $self->debug;
-      $self->dbh->do($_) or warn "SQL was:\n $_";
-      $self->debugobj->query_end($_) if $self->debug;
+    foreach my $line ( split(";\n", $statement)) {
+      next if($line =~ /^--/);
+      next if(!$line);
+#      next if($line =~ /^DROP/m);
+      next if($line =~ /^BEGIN TRANSACTION/m);
+      next if($line =~ /^COMMIT/m);
+      next if $line =~ /^\s+$/; # skip whitespace only
+      $self->_query_start($line);
+      eval {
+        $self->dbh->do($line); # shouldn't be using ->dbh ?
+      };
+      if ($@) {
+        warn qq{$@ (running "${line}")};
+      }
+      $self->_query_end($line);
     }
   }
 }
@@ -1137,7 +1685,10 @@ Returns the datetime parser class
 
 sub datetime_parser {
   my $self = shift;
-  return $self->{datetime_parser} ||= $self->build_datetime_parser(@_);
+  return $self->{datetime_parser} ||= do {
+    $self->ensure_connected;
+    $self->build_datetime_parser(@_);
+  };
 }
 
 =head2 datetime_parser_type
@@ -1163,15 +1714,51 @@ sub build_datetime_parser {
   return $type;
 }
 
-sub DESTROY {
-  # NOTE: if there's a merge conflict here when -current is pushed
-  #  back to trunk, take -current's version and ignore this trunk one :)
-  my $self = shift;
+{
+    my $_check_sqlt_version; # private
+    my $_check_sqlt_message; # private
+    sub _check_sqlt_version {
+        return $_check_sqlt_version if defined $_check_sqlt_version;
+        eval 'use SQL::Translator "0.09"';
+        $_check_sqlt_message = $@ || '';
+        $_check_sqlt_version = !$@;
+    }
 
-  if($self->_dbh && $self->_conn_pid != $$) {
-    $self->_dbh->{InactiveDestroy} = 1;
-  }
+    sub _check_sqlt_message {
+        _check_sqlt_version if !defined $_check_sqlt_message;
+        $_check_sqlt_message;
+    }
+}
+
+=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;
+  $self->_verify_pid;
   $self->_dbh(undef);
 }
 
@@ -1212,25 +1799,6 @@ For setting, this method is deprecated in favor of L</connect_info>.
 
 =back
 
-=head1 ENVIRONMENT VARIABLES
-
-=head2 DBIC_TRACE
-
-If C<DBIC_TRACE> is set then SQL trace information
-is produced (as when the L<debug> method is set).
-
-If the value is of the form C<1=/path/name> then the trace output is
-written to the file C</path/name>.
-
-This environment variable is checked when the storage object is first
-created (when you call connect on your schema).  So, run-time changes 
-to this environment variable will not take effect unless you also 
-re-connect on your schema.
-
-=head2 DBIX_CLASS_STORAGE_DBI_DEBUG
-
-Old name for DBIC_TRACE
-
 =head1 AUTHORS
 
 Matt S. Trout <mst@shadowcatsystems.co.uk>