Merge 'trunk' into 'DBIx-Class-current'
Brandon L. Black [Thu, 3 May 2007 03:03:06 +0000 (03:03 +0000)]
r30777@brandon-blacks-computer (orig r3224):  nigel | 2007-05-02 09:58:45 -0500
Documented use of cursor->next for fast but uncomfortable data fetches
r30778@brandon-blacks-computer (orig r3225):  blblack | 2007-05-02 22:02:14 -0500
revert part of 3220, apparently it is breaking cloning behavior in subtle ways that we have no tests for

1  2 
lib/DBIx/Class/Manual/Cookbook.pod
lib/DBIx/Class/Schema.pm
lib/DBIx/Class/Storage/DBI/Cursor.pm

@@@ -494,7 -494,7 +494,7 @@@ To order C<< $book->pages >> by descend
  =head2 Transactions
  
  As of version 0.04001, there is improved transaction support in
 -L<DBIx::Class::Storage::DBI> and L<DBIx::Class::Schema>.  Here is an
 +L<DBIx::Class::Storage> and L<DBIx::Class::Schema>.  Here is an
  example of the recommended way to use it:
  
    my $genus = $schema->resultset('Genus')->find(12);
@@@ -914,7 -914,7 +914,7 @@@ method
  
  =head2 Profiling
  
 -When you enable L<DBIx::Class::Storage::DBI>'s debugging it prints the SQL
 +When you enable L<DBIx::Class::Storage>'s debugging it prints the SQL
  executed as well as notifications of query completion and transaction
  begin/commit.  If you'd like to profile the SQL you can subclass the
  L<DBIx::Class::Storage::Statistics> class and write your own profiling
@@@ -1109,18 -1109,58 +1109,34 @@@ B<Test File> test.p
  
  DBIx::Class is not built for speed, it's built for convenience and
  ease of use, but sometimes you just need to get the data, and skip the
 -fancy objects. Luckily this is also fairly easy using
 -C<inflate_result>:
 -
 -  # Define a class which just returns the results as a hashref:
 -  package My::HashRefInflator;
 -
 -  ## $me is the hashref of cols/data from the immediate resultsource
 -  ## $prefetch is a deep hashref of all the data from the prefetched
 -  ##   related sources.
 -
 -  sub mk_hash {
 -     my ($me, $rest) = @_;
 -
 -     return { %$me, 
 -        map { ($_ => mk_hash(@{$rest->{$_}})) } keys %$rest
 -     };
 -  }
 -
 -  sub inflate_result {
 -     my ($self, $source, $me, $prefetch) = @_;
 -     return mk_hash($me, $prefetch); 
 -  }
 -
 -  # Change the object inflation to a hashref for just this resultset:
 -  $rs->result_class('My::HashRefInflator');
 -
 -  my $datahashref = $rs->next;
 -  foreach my $col (keys %$datahashref) {
 -     if(!ref($datahashref->{$col})) {
 -        # It's a plain value
 -     }
 -     elsif(ref($datahashref->{$col} eq 'HASH')) {
 -        # It's a related value in a hashref
 -     }
 -  }
 -
 +fancy objects.
 +  
 +To do this simply use L<DBIx::Class::ResultClass::HashRefInflator>.
 +  
 + my $rs = $schema->resultset('CD');
 + 
 + $rs->result_class('DBIx::Class::ResultClass::HashRefInflator');
 + 
 + my $hash_ref = $rs->find(1);
 +  
 +Wasn't that easy?
 +  
+ =head2 Get raw data for blindingly fast results
+ If the C<inflate_result> solution above is not fast enough for you, you
+ can use a DBIx::Class to return values exactly as they come out of the
+ data base with none of the convenience methods wrapped round them.
+ This is used like so:-
+   my $cursor = $rs->cursor
+   while (my @vals = $cursor->next) {
+       # use $val[0..n] here
+   }
+ You will need to map the array offsets to particular columns (you can
+ use the I<select> attribute of C<search()> to force ordering).
  =head2 Want to know if find_or_create found or created a row?
  
  Just use C<find_or_new> instead, then check C<in_storage>:
diff --combined lib/DBIx/Class/Schema.pm
@@@ -5,8 -5,6 +5,8 @@@ use warnings
  
  use Carp::Clan qw/^DBIx::Class/;
  use Scalar::Util qw/weaken/;
 +use File::Spec;
 +require Module::Find;
  
  use base qw/DBIx::Class/;
  
@@@ -14,7 -12,6 +14,7 @@@ __PACKAGE__->mk_classdata('class_mappin
  __PACKAGE__->mk_classdata('source_registrations' => {});
  __PACKAGE__->mk_classdata('storage_type' => '::DBI');
  __PACKAGE__->mk_classdata('storage');
 +__PACKAGE__->mk_classdata('exception_action');
  
  =head1 NAME
  
@@@ -94,32 -91,18 +94,36 @@@ moniker
  
  sub register_source {
    my ($self, $moniker, $source) = @_;
 +
 +  %$source = %{ $source->new( { %$source, source_name => $moniker }) };
 +
-   $self->source_registrations->{$moniker} = $source;
+   my %reg = %{$self->source_registrations};
+   $reg{$moniker} = $source;
+   $self->source_registrations(\%reg);
 +
    $source->schema($self);
 +
    weaken($source->{schema}) if ref($self);
    if ($source->result_class) {
-     $self->class_mappings->{$source->result_class} = $moniker;
+     my %map = %{$self->class_mappings};
+     $map{$source->result_class} = $moniker;
+     $self->class_mappings(\%map);
    }
  }
  
 +sub _unregister_source {
 +    my ($self, $moniker) = @_;
 +    my %reg = %{$self->source_registrations}; 
 +
 +    my $source = delete $reg{$moniker};
 +    $self->source_registrations(\%reg);
 +    if ($source->result_class) {
 +        my %map = %{$self->class_mappings};
 +        delete $map{$source->result_class};
 +        $self->class_mappings(\%map);
 +    }
 +}
 +
  =head2 class
  
  =over 4
@@@ -270,6 -253,10 +274,6 @@@ sub load_classes 
        }
      }
    } else {
 -    eval "require Module::Find;";
 -    $class->throw_exception(
 -      "No arguments to load_classes and couldn't load Module::Find ($@)"
 -    ) if $@;
      my @comp = map { substr $_, length "${class}::"  }
                   Module::Find::findallmod($class);
      $comps_for{$class} = \@comp;
            }
          }
          $class->ensure_class_loaded($comp_class);
 -        $comp_class->source_name($comp) unless $comp_class->source_name;
  
 -        push(@to_register, [ $comp_class->source_name, $comp_class ]);
 +        $comp = $comp_class->source_name || $comp;
 +#  $DB::single = 1;
 +        push(@to_register, [ $comp, $comp_class ]);
        }
      }
    }
    }
  }
  
 -=head2 compose_connection
 +=head2 load_namespaces
 +
 +=over 4
 +
 +=item Arguments: %options?
 +
 +=back
 +
 +This is an alternative to L</load_classes> above which assumes an alternative
 +layout for automatic class loading.  It assumes that all result
 +classes are underneath a sub-namespace of the schema called C<Result>, any
 +corresponding ResultSet classes are underneath a sub-namespace of the schema
 +called C<ResultSet>.
 +
 +Both of the sub-namespaces are configurable if you don't like the defaults,
 +via the options C<result_namespace> and C<resultset_namespace>.
 +
 +If (and only if) you specify the option C<default_resultset_class>, any found
 +Result classes for which we do not find a corresponding
 +ResultSet class will have their C<resultset_class> set to
 +C<default_resultset_class>.
 +
 +C<load_namespaces> takes care of calling C<resultset_class> for you where
 +neccessary if you didn't do it for yourself.
 +
 +All of the namespace and classname options to this method are relative to
 +the schema classname by default.  To specify a fully-qualified name, prefix
 +it with a literal C<+>.
 +
 +Examples:
 +
 +  # load My::Schema::Result::CD, My::Schema::Result::Artist,
 +  #    My::Schema::ResultSet::CD, etc...
 +  My::Schema->load_namespaces;
 +
 +  # Override everything to use ugly names.
 +  # In this example, if there is a My::Schema::Res::Foo, but no matching
 +  #   My::Schema::RSets::Foo, then Foo will have its
 +  #   resultset_class set to My::Schema::RSetBase
 +  My::Schema->load_namespaces(
 +    result_namespace => 'Res',
 +    resultset_namespace => 'RSets',
 +    default_resultset_class => 'RSetBase',
 +  );
 +
 +  # Put things in other namespaces
 +  My::Schema->load_namespaces(
 +    result_namespace => '+Some::Place::Results',
 +    resultset_namespace => '+Another::Place::RSets',
 +  );
 +
 +If you'd like to use multiple namespaces of each type, simply use an arrayref
 +of namespaces for that option.  In the case that the same result
 +(or resultset) class exists in multiple namespaces, the latter entries in
 +your list of namespaces will override earlier ones.
 +
 +  My::Schema->load_namespaces(
 +    # My::Schema::Results_C::Foo takes precedence over My::Schema::Results_B::Foo :
 +    result_namespace => [ 'Results_A', 'Results_B', 'Results_C' ],
 +    resultset_namespace => [ '+Some::Place::RSets', 'RSets' ],
 +  );
 +
 +=cut
 +
 +# Pre-pends our classname to the given relative classname or
 +#   class namespace, unless there is a '+' prefix, which will
 +#   be stripped.
 +sub _expand_relative_name {
 +  my ($class, $name) = @_;
 +  return if !$name;
 +  $name = $class . '::' . $name if ! ($name =~ s/^\+//);
 +  return $name;
 +}
 +
 +# returns a hash of $shortname => $fullname for every package
 +#  found in the given namespaces ($shortname is with the $fullname's
 +#  namespace stripped off)
 +sub _map_namespaces {
 +  my ($class, @namespaces) = @_;
 +
 +  my @results_hash;
 +  foreach my $namespace (@namespaces) {
 +    push(
 +      @results_hash,
 +      map { (substr($_, length "${namespace}::"), $_) }
 +      Module::Find::findallmod($namespace)
 +    );
 +  }
 +
 +  @results_hash;
 +}
 +
 +sub load_namespaces {
 +  my ($class, %args) = @_;
 +
 +  my $result_namespace = delete $args{result_namespace} || 'Result';
 +  my $resultset_namespace = delete $args{resultset_namespace} || 'ResultSet';
 +  my $default_resultset_class = delete $args{default_resultset_class};
 +
 +  $class->throw_exception('load_namespaces: unknown option(s): '
 +    . join(q{,}, map { qq{'$_'} } keys %args))
 +      if scalar keys %args;
 +
 +  $default_resultset_class
 +    = $class->_expand_relative_name($default_resultset_class);
 +
 +  for my $arg ($result_namespace, $resultset_namespace) {
 +    $arg = [ $arg ] if !ref($arg) && $arg;
 +
 +    $class->throw_exception('load_namespaces: namespace arguments must be '
 +      . 'a simple string or an arrayref')
 +        if ref($arg) ne 'ARRAY';
 +
 +    $_ = $class->_expand_relative_name($_) for (@$arg);
 +  }
 +
 +  my %results = $class->_map_namespaces(@$result_namespace);
 +  my %resultsets = $class->_map_namespaces(@$resultset_namespace);
 +
 +  my @to_register;
 +  {
 +    no warnings 'redefine';
 +    local *Class::C3::reinitialize = sub { };
 +    use warnings 'redefine';
 +
 +    foreach my $result (keys %results) {
 +      my $result_class = $results{$result};
 +      $class->ensure_class_loaded($result_class);
 +      $result_class->source_name($result) unless $result_class->source_name;
 +
 +      my $rs_class = delete $resultsets{$result};
 +      my $rs_set = $result_class->resultset_class;
 +      if($rs_set && $rs_set ne 'DBIx::Class::ResultSet') {
 +        if($rs_class && $rs_class ne $rs_set) {
 +          warn "We found ResultSet class '$rs_class' for '$result', but it seems "
 +             . "that you had already set '$result' to use '$rs_set' instead";
 +        }
 +      }
 +      elsif($rs_class ||= $default_resultset_class) {
 +        $class->ensure_class_loaded($rs_class);
 +        $result_class->resultset_class($rs_class);
 +      }
 +
 +      push(@to_register, [ $result_class->source_name, $result_class ]);
 +    }
 +  }
 +
 +  foreach (sort keys %resultsets) {
 +    warn "load_namespaces found ResultSet class $_ with no "
 +      . 'corresponding Result class';
 +  }
 +
 +  Class::C3->reinitialize;
 +  $class->register_class(@$_) for (@to_register);
 +
 +  return;
 +}
 +
 +=head2 compose_connection (DEPRECATED)
  
  =over 4
  
  
  =back
  
 +DEPRECATED. You probably wanted compose_namespace.
 +
 +Actually, you probably just wanted to call connect.
 +
 +=for hidden due to deprecation
 +
  Calls L<DBIx::Class::Schema/"compose_namespace"> to the target namespace,
  calls L<DBIx::Class::Schema/connection> with @db_info on the new schema,
  then injects the L<DBix::Class::ResultSetProxy> component and a
@@@ -493,50 -315,43 +497,50 @@@ more information
  
  =cut
  
 -sub compose_connection {
 -  my ($self, $target, @info) = @_;
 -  my $base = 'DBIx::Class::ResultSetProxy';
 -  eval "require ${base};";
 -  $self->throw_exception
 -    ("No arguments to load_classes and couldn't load ${base} ($@)")
 -      if $@;
 -
 -  if ($self eq $target) {
 -    # Pathological case, largely caused by the docs on early C::M::DBIC::Plain
 -    foreach my $moniker ($self->sources) {
 -      my $source = $self->source($moniker);
 +{
 +  my $warn;
 +
 +  sub compose_connection {
 +    my ($self, $target, @info) = @_;
 +
 +    warn "compose_connection deprecated as of 0.08000" unless $warn++;
 +
 +    my $base = 'DBIx::Class::ResultSetProxy';
 +    eval "require ${base};";
 +    $self->throw_exception
 +      ("No arguments to load_classes and couldn't load ${base} ($@)")
 +        if $@;
 +  
 +    if ($self eq $target) {
 +      # Pathological case, largely caused by the docs on early C::M::DBIC::Plain
 +      foreach my $moniker ($self->sources) {
 +        my $source = $self->source($moniker);
 +        my $class = $source->result_class;
 +        $self->inject_base($class, $base);
 +        $class->mk_classdata(resultset_instance => $source->resultset);
 +        $class->mk_classdata(class_resolver => $self);
 +      }
 +      $self->connection(@info);
 +      return $self;
 +    }
 +  
 +    my $schema = $self->compose_namespace($target, $base);
 +    {
 +      no strict 'refs';
 +      *{"${target}::schema"} = sub { $schema };
 +    }
 +  
 +    $schema->connection(@info);
 +    foreach my $moniker ($schema->sources) {
 +      my $source = $schema->source($moniker);
        my $class = $source->result_class;
 -      $self->inject_base($class, $base);
 +      #warn "$moniker $class $source ".$source->storage;
 +      $class->mk_classdata(result_source_instance => $source);
        $class->mk_classdata(resultset_instance => $source->resultset);
 -      $class->mk_classdata(class_resolver => $self);
 +      $class->mk_classdata(class_resolver => $schema);
      }
 -    $self->connection(@info);
 -    return $self;
 +    return $schema;
    }
 -
 -  my $schema = $self->compose_namespace($target, $base);
 -  {
 -    no strict 'refs';
 -    *{"${target}::schema"} = sub { $schema };
 -  }
 -
 -  $schema->connection(@info);
 -  foreach my $moniker ($schema->sources) {
 -    my $source = $schema->source($moniker);
 -    my $class = $source->result_class;
 -    #warn "$moniker $class $source ".$source->storage;
 -    $class->mk_classdata(result_source_instance => $source);
 -    $class->mk_classdata(resultset_instance => $source->resultset);
 -    $class->mk_classdata(class_resolver => $schema);
 -  }
 -  return $schema;
  }
  
  =head2 compose_namespace
@@@ -652,10 -467,8 +656,10 @@@ C<::DBI::Sybase::MSSQL>
  
  Instantiates a new Storage object of type
  L<DBIx::Class::Schema/"storage_type"> and passes the arguments to
 -$storage->connect_info. Sets the connection in-place on the schema. See
 -L<DBIx::Class::Storage::DBI/"connect_info"> for more information.
 +$storage->connect_info. Sets the connection in-place on the schema.
 +
 +See L<DBIx::Class::Storage::DBI/"connect_info"> for DBI-specific syntax,
 +or L<DBIx::Class::Storage> in general.
  
  =cut
  
@@@ -669,10 -482,9 +673,10 @@@ sub connection 
    $self->throw_exception(
      "No arguments to load_classes and couldn't load ${storage_class} ($@)"
    ) if $@;
 -  my $storage = $storage_class->new;
 +  my $storage = $storage_class->new($self);
    $storage->connect_info(\@info);
    $self->storage($storage);
 +  $self->on_connect() if($self->can('on_connect'));
    return $self;
  }
  
@@@ -694,83 -506,138 +698,83 @@@ information
  
  sub connect { shift->clone->connection(@_) }
  
 -=head2 txn_begin
 -
 -Begins a transaction (does nothing if AutoCommit is off). Equivalent to
 -calling $schema->storage->txn_begin. See
 -L<DBIx::Class::Storage::DBI/"txn_begin"> for more information.
 +=head2 txn_do
  
 -=cut
 +=over 4
  
 -sub txn_begin { shift->storage->txn_begin }
 +=item Arguments: C<$coderef>, @coderef_args?
  
 -=head2 txn_commit
 +=item Return Value: The return value of $coderef
  
 -Commits the current transaction. Equivalent to calling
 -$schema->storage->txn_commit. See L<DBIx::Class::Storage::DBI/"txn_commit">
 -for more information.
 +=back
  
 -=cut
 +Executes C<$coderef> with (optional) arguments C<@coderef_args> atomically,
 +returning its result (if any). Equivalent to calling $schema->storage->txn_do.
 +See L<DBIx::Class::Storage/"txn_do"> for more information.
  
 -sub txn_commit { shift->storage->txn_commit }
 +This interface is preferred over using the individual methods L</txn_begin>,
 +L</txn_commit>, and L</txn_rollback> below.
  
 -=head2 txn_rollback
 +=cut
  
 -Rolls back the current transaction. Equivalent to calling
 -$schema->storage->txn_rollback. See
 -L<DBIx::Class::Storage::DBI/"txn_rollback"> for more information.
 +sub txn_do {
 +  my $self = shift;
  
 -=cut
 +  $self->storage or $self->throw_exception
 +    ('txn_do called on $schema without storage');
  
 -sub txn_rollback { shift->storage->txn_rollback }
 +  $self->storage->txn_do(@_);
 +}
  
 -=head2 txn_do
 +=head2 txn_begin
  
 -=over 4
 +Begins a transaction (does nothing if AutoCommit is off). Equivalent to
 +calling $schema->storage->txn_begin. See
 +L<DBIx::Class::Storage::DBI/"txn_begin"> for more information.
  
 -=item Arguments: C<$coderef>, @coderef_args?
 +=cut
  
 -=item Return Value: The return value of $coderef
 +sub txn_begin {
 +  my $self = shift;
  
 -=back
 +  $self->storage or $self->throw_exception
 +    ('txn_begin called on $schema without storage');
  
 -Executes C<$coderef> with (optional) arguments C<@coderef_args> atomically,
 -returning its result (if any). If an exception is caught, a rollback is issued
 -and the exception is rethrown. If the rollback fails, (i.e. throws an
 -exception) an exception is thrown that includes a "Rollback failed" message.
 +  $self->storage->txn_begin;
 +}
  
 -For example,
 +=head2 txn_commit
  
 -  my $author_rs = $schema->resultset('Author')->find(1);
 -  my @titles = qw/Night Day It/;
 +Commits the current transaction. Equivalent to calling
 +$schema->storage->txn_commit. See L<DBIx::Class::Storage::DBI/"txn_commit">
 +for more information.
  
 -  my $coderef = sub {
 -    # If any one of these fails, the entire transaction fails
 -    $author_rs->create_related('books', {
 -      title => $_
 -    }) foreach (@titles);
 +=cut
  
 -    return $author->books;
 -  };
 +sub txn_commit {
 +  my $self = shift;
  
 -  my $rs;
 -  eval {
 -    $rs = $schema->txn_do($coderef);
 -  };
 +  $self->storage or $self->throw_exception
 +    ('txn_commit called on $schema without storage');
  
 -  if ($@) {                                  # Transaction failed
 -    die "something terrible has happened!"   #
 -      if ($@ =~ /Rollback failed/);          # Rollback failed
 +  $self->storage->txn_commit;
 +}
  
 -    deal_with_failed_transaction();
 -  }
 +=head2 txn_rollback
  
 -In a nested transaction (calling txn_do() from within a txn_do() coderef) only
 -the outermost transaction will issue a L<DBIx::Class::Schema/"txn_commit"> on
 -the Schema's storage, and txn_do() can be called in void, scalar and list
 -context and it will behave as expected.
 +Rolls back the current transaction. Equivalent to calling
 +$schema->storage->txn_rollback. See
 +L<DBIx::Class::Storage::DBI/"txn_rollback"> for more information.
  
  =cut
  
 -sub txn_do {
 -  my ($self, $coderef, @args) = @_;
 +sub txn_rollback {
 +  my $self = shift;
  
    $self->storage or $self->throw_exception
 -    ('txn_do called on $schema without storage');
 -  ref $coderef eq 'CODE' or $self->throw_exception
 -    ('$coderef must be a CODE reference');
 -
 -  my (@return_values, $return_value);
 -
 -  $self->txn_begin; # If this throws an exception, no rollback is needed
 -
 -  my $wantarray = wantarray; # Need to save this since the context
 -                             # inside the eval{} block is independent
 -                             # of the context that called txn_do()
 -  eval {
 -
 -    # Need to differentiate between scalar/list context to allow for
 -    # returning a list in scalar context to get the size of the list
 -    if ($wantarray) {
 -      # list context
 -      @return_values = $coderef->(@args);
 -    } elsif (defined $wantarray) {
 -      # scalar context
 -      $return_value = $coderef->(@args);
 -    } else {
 -      # void context
 -      $coderef->(@args);
 -    }
 -    $self->txn_commit;
 -  };
 +    ('txn_rollback called on $schema without storage');
  
 -  if ($@) {
 -    my $error = $@;
 -
 -    eval {
 -      $self->txn_rollback;
 -    };
 -
 -    if ($@) {
 -      my $rollback_error = $@;
 -      my $exception_class = "DBIx::Class::Storage::NESTED_ROLLBACK_EXCEPTION";
 -      $self->throw_exception($error)  # propagate nested rollback
 -        if $rollback_error =~ /$exception_class/;
 -
 -      $self->throw_exception(
 -        "Transaction aborted: $error. Rollback failed: ${rollback_error}"
 -      );
 -    } else {
 -      $self->throw_exception($error); # txn failed but rollback succeeded
 -    }
 -  }
 -
 -  return $wantarray ? @return_values : $return_value;
 +  $self->storage->txn_rollback;
  }
  
  =head2 clone
@@@ -796,7 -663,6 +800,7 @@@ sub clone 
      my $new = $source->new($source);
      $clone->register_source($moniker => $new);
    }
 +  $clone->storage->set_schema($clone) if $clone->storage;
    return $clone;
  }
  
@@@ -812,12 -678,7 +816,12 @@@ Pass this method a resultsource name, a
  arrayrefs. The arrayrefs should contain a list of column names,
  followed by one or many sets of matching data for the given columns. 
  
 -Each set of data is inserted into the database using
 +In void context, C<insert_bulk> in L<DBIx::Class::Storage::DBI> is used
 +to insert the data, as this is a fast method. However, insert_bulk currently
 +assumes that your datasets all contain the same type of values, using scalar
 +references in a column in one row, and not in another will probably not work.
 +
 +Otherwise, each set of data is inserted into the database using
  L<DBIx::Class::ResultSet/create>, and a arrayref of the resulting row
  objects is returned.
  
@@@ -836,50 -697,15 +840,50 @@@ sub populate 
    my ($self, $name, $data) = @_;
    my $rs = $self->resultset($name);
    my @names = @{shift(@$data)};
 -  my @created;
 -  foreach my $item (@$data) {
 -    my %create;
 -    @create{@names} = @$item;
 -    push(@created, $rs->create(\%create));
 +  if(defined wantarray) {
 +    my @created;
 +    foreach my $item (@$data) {
 +      my %create;
 +      @create{@names} = @$item;
 +      push(@created, $rs->create(\%create));
 +    }
 +    return @created;
    }
 -  return @created;
 +  $self->storage->insert_bulk($self->source($name), \@names, $data);
  }
  
 +=head2 exception_action
 +
 +=over 4
 +
 +=item Arguments: $code_reference
 +
 +=back
 +
 +If C<exception_action> is set for this class/object, L</throw_exception>
 +will prefer to call this code reference with the exception as an argument,
 +rather than its normal <croak> action.
 +
 +Your subroutine should probably just wrap the error in the exception
 +object/class of your choosing and rethrow.  If, against all sage advice,
 +you'd like your C<exception_action> to suppress a particular exception
 +completely, simply have it return true.
 +
 +Example:
 +
 +   package My::Schema;
 +   use base qw/DBIx::Class::Schema/;
 +   use My::ExceptionClass;
 +   __PACKAGE__->exception_action(sub { My::ExceptionClass->throw(@_) });
 +   __PACKAGE__->load_classes;
 +
 +   # or:
 +   my $schema_obj = My::Schema->connect( .... );
 +   $schema_obj->exception_action(sub { My::ExceptionClass->throw(@_) });
 +
 +   # suppress all exceptions, like a moron:
 +   $schema_obj->exception_action(sub { 1 });
 +
  =head2 throw_exception
  
  =over 4
  =back
  
  Throws an exception. Defaults to using L<Carp::Clan> to report errors from
 -user's perspective.
 +user's perspective.  See L</exception_action> for details on overriding
 +this method's behavior.
  
  =cut
  
  sub throw_exception {
 -  my ($self) = shift;
 -  croak @_;
 +  my $self = shift;
 +  croak @_ if !$self->exception_action || !$self->exception_action->(@_);
  }
  
  =head2 deploy (EXPERIMENTAL)
  Attempts to deploy the schema to the current storage using L<SQL::Translator>.
  
  Note that this feature is currently EXPERIMENTAL and may not work correctly
 -across all databases, or fully handle complex relationships.
 +across all databases, or fully handle complex relationships. Saying that, it
 +has been used successfully by many people, including the core dev team.
  
  See L<SQL::Translator/METHODS> for a list of values for C<$sqlt_args>. The most
  common value for this would be C<< { add_drop_table => 1, } >> to have the SQL
  produced include a DROP TABLE statement for each table created.
  
 +Additionally, the DBIx::Class parser accepts a C<sources> parameter as a hash 
 +ref or an array ref, containing a list of source to deploy. If present, then 
 +only the sources listed will get deployed.
 +
  =cut
  
  sub deploy {
  
  =over 4
  
 -=item Arguments: \@databases, $version, $directory, $sqlt_args
 +=item Arguments: \@databases, $version, $directory, $preversion, $sqlt_args
  
  =back
  
  Creates an SQL file based on the Schema, for each of the specified
 -database types, in the given directory.
 +database types, in the given directory. Given a previous version number,
 +this will also create a file containing the ALTER TABLE statements to
 +transform the previous schema into the current one. Note that these
 +statements may contain DROP TABLE or DROP COLUMN statements that can
 +potentially destroy data.
 +
 +The file names are created using the C<ddl_filename> method below, please
 +override this method in your schema if you would like a different file
 +name format. For the ALTER file, the same format is used, replacing
 +$version in the name with "$preversion-$version".
 +
 +If no arguments are passed, then the following default values are used:
 +
 +=over 4
 +
 +=item databases  - ['MySQL', 'SQLite', 'PostgreSQL']
 +
 +=item version    - $schema->VERSION
 +
 +=item directory  - './'
 +
 +=item preversion - <none>
 +
 +=back
  
  Note that this feature is currently EXPERIMENTAL and may not work correctly
  across all databases, or fully handle complex relationships.
  
 +WARNING: Please check all SQL files created, before applying them.
 +
  =cut
  
  sub create_ddl_dir {
  
  =head2 ddl_filename (EXPERIMENTAL)
  
 -  my $filename = $table->ddl_filename($type, $dir, $version)
 +=over 4
 +
 +=item Arguments: $directory, $database-type, $version, $preversion
 +
 +=back
 +
 +  my $filename = $table->ddl_filename($type, $dir, $version, $preversion)
 +
 +This method is called by C<create_ddl_dir> to compose a file name out of
 +the supplied directory, database type and version number. The default file
 +name format is: C<$dir$schema-$version-$type.sql>.
  
 -Creates a filename for a SQL file based on the table class name.  Not
 -intended for direct end user use.
 +You may override this method in your schema if you wish to use a different
 +format.
  
  =cut
  
  sub ddl_filename {
 -    my ($self, $type, $dir, $version) = @_;
 +    my ($self, $type, $dir, $version, $pversion) = @_;
  
      my $filename = ref($self);
      $filename =~ s/::/-/g;
 -    $filename = "$dir$filename-$version-$type.sql";
 +    $filename = File::Spec->catfile($dir, "$filename-$version-$type.sql");
 +    $filename =~ s/$version/$pversion-$version/ if($pversion);
  
      return $filename;
  }
@@@ -43,9 -43,11 +43,9 @@@ sub new 
      args => $args,
      pos => 0,
      attrs => $attrs,
 -    pid => $$,
 +    _dbh_gen => $storage->{_dbh_gen},
    };
  
 -  $new->{tid} = threads->tid if $INC{'threads.pm'};
 -  
    return bless ($new, $class);
  }
  
  
  =back
  
- Advances the cursor to the next row and returns an arrayref of column values.
+ Advances the cursor to the next row and returns an array of column
+ values (the result of L<DBI/fetchrow_array> method).
  
  =cut
  
 -sub next {
 -  my ($self) = @_;
 +sub _dbh_next {
 +  my ($storage, $dbh, $self) = @_;
  
 -  $self->_check_forks_threads;
 +  $self->_check_dbh_gen;
    if ($self->{attrs}{rows} && $self->{pos} >= $self->{attrs}{rows}) {
      $self->{sth}->finish if $self->{sth}->{Active};
      delete $self->{sth};
@@@ -74,7 -77,7 +75,7 @@@
    }
    return if $self->{done};
    unless ($self->{sth}) {
 -    $self->{sth} = ($self->{storage}->_select(@{$self->{args}}))[1];
 +    $self->{sth} = ($storage->_select(@{$self->{args}}))[1];
      if ($self->{attrs}{software_limit}) {
        if (my $offset = $self->{attrs}{offset}) {
          $self->{sth}->fetch for 1 .. $offset;
    return @row;
  }
  
 +sub next {
 +  my ($self) = @_;
 +  $self->{storage}->dbh_do($self->can('_dbh_next'), $self);
 +}
 +
  =head2 all
  
  =over 4
@@@ -111,22 -109,17 +112,22 @@@ L<DBIx::Class::ResultSet>
  
  =cut
  
 -sub all {
 -  my ($self) = @_;
 +sub _dbh_all {
 +  my ($storage, $dbh, $self) = @_;
  
 -  $self->_check_forks_threads;
 -  return $self->SUPER::all if $self->{attrs}{rows};
 +  $self->_check_dbh_gen;
    $self->{sth}->finish if $self->{sth}->{Active};
    delete $self->{sth};
 -  my ($rv, $sth) = $self->{storage}->_select(@{$self->{args}});
 +  my ($rv, $sth) = $storage->_select(@{$self->{args}});
    return @{$sth->fetchall_arrayref};
  }
  
 +sub all {
 +  my ($self) = @_;
 +  return $self->SUPER::all if $self->{attrs}{rows};
 +  $self->{storage}->dbh_do($self->can('_dbh_all'), $self);
 +}
 +
  =head2 reset
  
  Resets the cursor to the beginning of the L<DBIx::Class::ResultSet>.
  sub reset {
    my ($self) = @_;
  
 -  $self->_check_forks_threads;
 -  $self->{sth}->finish if $self->{sth}->{Active};
 +  # No need to care about failures here
 +  eval { $self->{sth}->finish if $self->{sth} && $self->{sth}->{Active} };
    $self->_soft_reset;
  }
  
@@@ -145,25 -138,30 +146,25 @@@ sub _soft_reset 
    my ($self) = @_;
  
    delete $self->{sth};
 -  $self->{pos} = 0;
    delete $self->{done};
 +  $self->{pos} = 0;
    return $self;
  }
  
 -sub _check_forks_threads {
 +sub _check_dbh_gen {
    my ($self) = @_;
  
 -  if($INC{'threads.pm'} && $self->{tid} != threads->tid) {
 -      $self->_soft_reset;
 -      $self->{tid} = threads->tid;
 -  }
 -
 -  if($self->{pid} != $$) {
 -      $self->_soft_reset;
 -      $self->{pid} = $$;
 +  if($self->{_dbh_gen} != $self->{storage}->{_dbh_gen}) {
 +    $self->{_dbh_gen} = $self->{storage}->{_dbh_gen};
 +    $self->_soft_reset;
    }
  }
  
  sub DESTROY {
    my ($self) = @_;
  
 -  $self->_check_forks_threads;
 -  $self->{sth}->finish if $self->{sth} && $self->{sth}->{Active};
 +  # None of the reasons this would die matter if we're in DESTROY anyways
 +  eval { $self->{sth}->finish if $self->{sth} && $self->{sth}->{Active} };
  }
  
  1;