Merge 'trunk' into 'replication_dedux'
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Schema.pm
index 3103d29..a51ab96 100644 (file)
@@ -3,8 +3,10 @@ package DBIx::Class::Schema;
 use strict;
 use warnings;
 
+use DBIx::Class::Exception;
 use Carp::Clan qw/^DBIx::Class/;
 use Scalar::Util qw/weaken/;
+use File::Spec;
 require Module::Find;
 
 use base qw/DBIx::Class/;
@@ -14,6 +16,8 @@ __PACKAGE__->mk_classdata('source_registrations' => {});
 __PACKAGE__->mk_classdata('storage_type' => '::DBI');
 __PACKAGE__->mk_classdata('storage');
 __PACKAGE__->mk_classdata('exception_action');
+__PACKAGE__->mk_classdata('stacktrace' => $ENV{DBIC_TRACE} || 0);
+__PACKAGE__->mk_classdata('default_resultset_attributes' => {});
 
 =head1 NAME
 
@@ -93,10 +97,15 @@ moniker.
 
 sub register_source {
   my ($self, $moniker, $source) = @_;
+
+  %$source = %{ $source->new( { %$source, source_name => $moniker }) };
+
   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) {
     my %map = %{$self->class_mappings};
@@ -105,6 +114,19 @@ sub register_source {
   }
 }
 
+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
@@ -171,6 +193,12 @@ For example:
 
 sub sources { return keys %{shift->source_registrations}; }
 
+=head2 storage
+
+  my $storage = $schema->storage;
+
+Returns the L<DBIx::Class::Storage> object for this Schema.
+
 =head2 resultset
 
 =over 4
@@ -261,10 +289,18 @@ sub load_classes {
     foreach my $prefix (keys %comps_for) {
       foreach my $comp (@{$comps_for{$prefix}||[]}) {
         my $comp_class = "${prefix}::${comp}";
+        { # try to untaint module name. mods where this fails
+          # are left alone so we don't have to change the old behavior
+          no locale; # localized \w doesn't untaint expression
+          if ( $comp_class =~ m/^( (?:\w+::)* \w+ )$/x ) {
+            $comp_class = $1;
+          }
+        }
         $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 ]);
       }
     }
   }
@@ -285,16 +321,16 @@ sub load_classes {
 =back
 
 This is an alternative to L</load_classes> above which assumes an alternative
-layout for automatic class loading.  It assumes that all source-definition
-classes are underneath a sub-namespace of the schema called C<Source>, any
+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<source_namespace> and C<resultset_namespace>.
+via the options C<result_namespace> and C<resultset_namespace>.
 
 If (and only if) you specify the option C<default_resultset_class>, any found
-source-definition classes for which we do not find a corresponding
+Result classes for which we do not find a corresponding
 ResultSet class will have their C<resultset_class> set to
 C<default_resultset_class>.
 
@@ -307,34 +343,34 @@ it with a literal C<+>.
 
 Examples:
 
-  # load My::Schema::Source::CD, My::Schema::Source::Artist,
+  # load My::Schema::Result::CD, My::Schema::Result::Artist,
   #    My::Schema::ResultSet::CD, etc...
   My::Schema->load_namespaces;
 
-  # Override everything...
+  # 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(
-    source_namespace => 'Srcs',
+    result_namespace => 'Res',
     resultset_namespace => 'RSets',
     default_resultset_class => 'RSetBase',
   );
-  # In the above, if there is a My::Schema::Srcs::Foo, but no matching
-  #   My::Schema::RSets::Foo, then the Foo source will have its
-  #   resultset_class set to My::Schema::RSetBase
 
   # Put things in other namespaces
   My::Schema->load_namespaces(
-    source_namespace => '+Some::Place::Sources',
+    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 source-definition
+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::Sources_C::Foo takes precedence over My::Schema::Sources_B::Foo :
-    source_namespace => [ 'Sources_A', 'Sources_B', 'Sources_C' ],
+    # 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' ],
   );
 
@@ -371,7 +407,7 @@ sub _map_namespaces {
 sub load_namespaces {
   my ($class, %args) = @_;
 
-  my $source_namespace = delete $args{source_namespace} || 'Source';
+  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};
 
@@ -382,7 +418,7 @@ sub load_namespaces {
   $default_resultset_class
     = $class->_expand_relative_name($default_resultset_class);
 
-  for my $arg ($source_namespace, $resultset_namespace) {
+  for my $arg ($result_namespace, $resultset_namespace) {
     $arg = [ $arg ] if !ref($arg) && $arg;
 
     $class->throw_exception('load_namespaces: namespace arguments must be '
@@ -392,7 +428,7 @@ sub load_namespaces {
     $_ = $class->_expand_relative_name($_) for (@$arg);
   }
 
-  my %sources = $class->_map_namespaces(@$source_namespace);
+  my %results = $class->_map_namespaces(@$result_namespace);
   my %resultsets = $class->_map_namespaces(@$resultset_namespace);
 
   my @to_register;
@@ -401,31 +437,31 @@ sub load_namespaces {
     local *Class::C3::reinitialize = sub { };
     use warnings 'redefine';
 
-    foreach my $source (keys %sources) {
-      my $source_class = $sources{$source};
-      $class->ensure_class_loaded($source_class);
-      $source_class->source_name($source) unless $source_class->source_name;
+    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{$source};
-      my $rs_set = $source_class->resultset_class;
+      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 '$source', but it seems "
-             . "that you had already set '$source' to use '$rs_set' instead";
+          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);
-        $source_class->resultset_class($rs_class);
+        $result_class->resultset_class($rs_class);
       }
 
-      push(@to_register, [ $source_class->source_name, $source_class ]);
+      push(@to_register, [ $result_class->source_name, $result_class ]);
     }
   }
 
   foreach (sort keys %resultsets) {
     warn "load_namespaces found ResultSet class $_ with no "
-      . 'corresponding source-definition class';
+      . 'corresponding Result class';
   }
 
   Class::C3->reinitialize;
@@ -434,7 +470,7 @@ sub load_namespaces {
   return;
 }
 
-=head2 compose_connection
+=head2 compose_connection (DEPRECATED)
 
 =over 4
 
@@ -444,6 +480,14 @@ sub load_namespaces {
 
 =back
 
+DEPRECATED. You probably wanted compose_namespace.
+
+Actually, you probably just wanted to call connect.
+
+=begin hidden
+
+(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
@@ -456,45 +500,55 @@ L<DBIx::Class::Schema/connect> and use the resulting schema object to operate
 on L<DBIx::Class::ResultSet> objects with L<DBIx::Class::Schema/resultset> for
 more information.
 
-=cut
+=end hidden
 
-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 $@;
+=cut
 
-  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 ($INC{"DBIx/Class/CDBICompat.pm"} || $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;
-  }
-
-  my $schema = $self->compose_namespace($target, $base);
-  {
-    no strict 'refs';
-    *{"${target}::schema"} = sub { $schema };
+    return $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
@@ -532,9 +586,6 @@ will produce the output
 
 sub compose_namespace {
   my ($self, $target, $base) = @_;
-  my %reg = %{ $self->source_registrations };
-  my %target;
-  my %map;
   my $schema = $self->clone;
   {
     no warnings qw/redefine/;
@@ -553,6 +604,7 @@ sub compose_namespace {
   Class::C3->reinitialize();
   {
     no strict 'refs';
+    no warnings 'redefine';
     foreach my $meth (qw/class source resultset/) {
       *{"${target}::${meth}"} =
         sub { shift->schema->$meth(@_) };
@@ -581,6 +633,33 @@ sub setup_connection_class {
   $target->connection(@info);
 }
 
+=head2 storage_type
+
+=over 4
+
+=item Arguments: $storage_type|{$storage_type, \%args}
+
+=item Return Value: $storage_type|{$storage_type, \%args}
+
+=back
+
+Set the storage class that will be instantiated when L</connect> is called.
+If the classname starts with C<::>, the prefix C<DBIx::Class::Storage> is
+assumed by L</connect>.  Defaults to C<::DBI>,
+which is L<DBIx::Class::Storage::DBI>.
+
+You want to use this to hardcoded subclasses of L<DBIx::Class::Storage::DBI>
+in cases where the appropriate subclass is not autodetected, such as when
+dealing with MSSQL via L<DBD::Sybase>, in which case you'd set it to
+C<::DBI::Sybase::MSSQL>.
+
+If your storage type requires instantiation arguments, those are defined as a 
+second argument in the form of a hashref and the entire value needs to be
+wrapped into an arrayref or a hashref.  We support both types of refs here in
+order to play nice with your Config::[class] or your choice.
+
+See L<DBIx::Class::Storage::DBI::Replicated> for an example of this.
+
 =head2 connection
 
 =over 4
@@ -593,27 +672,43 @@ sub setup_connection_class {
 
 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
 
 sub connection {
   my ($self, @info) = @_;
   return $self if !@info && $self->storage;
-  my $storage_class = $self->storage_type;
+  
+  my ($storage_class, $args) = ref $self->storage_type ? 
+    ($self->_normalize_storage_type($self->storage_type),{}) : ($self->storage_type, {});
+    
   $storage_class = 'DBIx::Class::Storage'.$storage_class
     if $storage_class =~ m/^::/;
   eval "require ${storage_class};";
   $self->throw_exception(
     "No arguments to load_classes and couldn't load ${storage_class} ($@)"
   ) if $@;
-  my $storage = $storage_class->new($self);
+  my $storage = $storage_class->new($self=>$args);
   $storage->connect_info(\@info);
   $self->storage($storage);
   return $self;
 }
 
+sub _normalize_storage_type {
+       my ($self, $storage_type) = @_;
+       if(ref $storage_type eq 'ARRAY') {
+               return @$storage_type;
+       } elsif(ref $storage_type eq 'HASH') {
+               return %$storage_type;
+       } else {
+               $self->throw_exception('Unsupported REFTYPE given: '. ref $storage_type);
+       }
+}
+
 =head2 connect
 
 =over 4
@@ -632,6 +727,49 @@ information.
 
 sub connect { shift->clone->connection(@_) }
 
+=head2 txn_do
+
+=over 4
+
+=item Arguments: C<$coderef>, @coderef_args?
+
+=item Return Value: The return value of $coderef
+
+=back
+
+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.
+
+This interface is preferred over using the individual methods L</txn_begin>,
+L</txn_commit>, and L</txn_rollback> below.
+
+=cut
+
+sub txn_do {
+  my $self = shift;
+
+  $self->storage or $self->throw_exception
+    ('txn_do called on $schema without storage');
+
+  $self->storage->txn_do(@_);
+}
+
+=head2 txn_scope_guard
+
+Runs C<txn_scope_guard> on the schema's storage.
+
+=cut
+
+sub txn_scope_guard {
+  my $self = shift;
+
+  $self->storage or $self->throw_exception
+    ('txn_scope_guard called on $schema without storage');
+
+  $self->storage->txn_scope_guard(@_);
+}
+
 =head2 txn_begin
 
 Begins a transaction (does nothing if AutoCommit is off). Equivalent to
@@ -640,7 +778,14 @@ L<DBIx::Class::Storage::DBI/"txn_begin"> for more information.
 
 =cut
 
-sub txn_begin { shift->storage->txn_begin }
+sub txn_begin {
+  my $self = shift;
+
+  $self->storage or $self->throw_exception
+    ('txn_begin called on $schema without storage');
+
+  $self->storage->txn_begin;
+}
 
 =head2 txn_commit
 
@@ -650,7 +795,14 @@ for more information.
 
 =cut
 
-sub txn_commit { shift->storage->txn_commit }
+sub txn_commit {
+  my $self = shift;
+
+  $self->storage or $self->throw_exception
+    ('txn_commit called on $schema without storage');
+
+  $self->storage->txn_commit;
+}
 
 =head2 txn_rollback
 
@@ -660,110 +812,64 @@ L<DBIx::Class::Storage::DBI/"txn_rollback"> for more information.
 
 =cut
 
-sub txn_rollback { shift->storage->txn_rollback }
-
-=head2 txn_do
-
-=over 4
-
-=item Arguments: C<$coderef>, @coderef_args?
-
-=item Return Value: The return value of $coderef
+sub txn_rollback {
+  my $self = shift;
 
-=back
+  $self->storage or $self->throw_exception
+    ('txn_rollback 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_rollback;
+}
 
-For example,
+=head2 svp_begin
 
-  my $author_rs = $schema->resultset('Author')->find(1);
-  my @titles = qw/Night Day It/;
+Creates a new savepoint (does nothing outside a transaction). 
+Equivalent to calling $schema->storage->svp_begin.  See
+L<DBIx::Class::Storage::DBI/"svp_begin"> 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 svp_begin {
+  my ($self, $name) = @_;
 
-  my $rs;
-  eval {
-    $rs = $schema->txn_do($coderef);
-  };
+  $self->storage or $self->throw_exception
+    ('svp_begin called on $schema without storage');
 
-  if ($@) {                                  # Transaction failed
-    die "something terrible has happened!"   #
-      if ($@ =~ /Rollback failed/);          # Rollback failed
+  $self->storage->svp_begin($name);
+}
 
-    deal_with_failed_transaction();
-  }
+=head2 svp_release
 
-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.
+Releases a savepoint (does nothing outside a transaction). 
+Equivalent to calling $schema->storage->svp_release.  See
+L<DBIx::Class::Storage::DBI/"svp_release"> for more information.
 
 =cut
 
-sub txn_do {
-  my ($self, $coderef, @args) = @_;
+sub svp_release {
+  my ($self, $name) = @_;
 
   $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;
-  };
+    ('svp_release called on $schema without storage');
 
-  if ($@) {
-    my $error = $@;
+  $self->storage->svp_release($name);
+}
 
-    eval {
-      $self->txn_rollback;
-    };
+=head2 svp_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/;
+Rollback to a savepoint (does nothing outside a transaction). 
+Equivalent to calling $schema->storage->svp_rollback.  See
+L<DBIx::Class::Storage::DBI/"svp_rollback"> for more information.
 
-      $self->throw_exception(
-        "Transaction aborted: $error. Rollback failed: ${rollback_error}"
-      );
-    } else {
-      $self->throw_exception($error); # txn failed but rollback succeeded
-    }
-  }
+=cut
+
+sub svp_rollback {
+  my ($self, $name) = @_;
 
-  return $wantarray ? @return_values : $return_value;
+  $self->storage or $self->throw_exception
+    ('svp_rollback called on $schema without storage');
+
+  $self->storage->svp_rollback($name);
 }
 
 =head2 clone
@@ -781,7 +887,9 @@ copy.
 
 sub clone {
   my ($self) = @_;
-  my $clone = bless({ (ref $self ? %$self : ()) }, ref $self || $self);
+  my $clone = { (ref $self ? %$self : ()) };
+  bless $clone, (ref $self || $self);
+
   foreach my $moniker ($self->sources) {
     my $source = $self->source($moniker);
     my $new = $source->new($source);
@@ -795,13 +903,22 @@ sub clone {
 
 =over 4
 
-=item Arguments: $moniker, \@data;
+=item Arguments: $source_name, \@data;
 
 =back
 
-Populates the source registered with the given moniker with the supplied data.
-@data should be a list of listrefs -- the first containing column names, the
-second matching values.
+Pass this method a resultsource name, and an arrayref of
+arrayrefs. The arrayrefs should contain a list of column names,
+followed by one or many sets of matching data for the given columns. 
+
+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.
 
 i.e.,
 
@@ -811,6 +928,18 @@ i.e.,
     [ 2, 'Indie Band' ],
     ...
   ]);
+  
+Since wantarray context is basically the same as looping over $rs->create(...) 
+you won't see any performance benefits and in this case the method is more for
+convenience. Void context sends the column information directly to storage
+using <DBI>s bulk insert method. So the performance will be much better for 
+storages that support this method.
+
+Because of this difference in the way void context inserts rows into your 
+database you need to note how this will effect any loaded components that
+override or augment insert.  For example if you are using a component such 
+as L<DBIx::Class::UUIDColumns> to populate your primary keys you MUST use 
+wantarray context if you want the PKs automatically created.
 
 =cut
 
@@ -818,13 +947,24 @@ 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;
+  my @results_to_create;
+  foreach my $datum (@$data) {
+    my %result_to_create;
+    foreach my $index (0..$#names) {
+      $result_to_create{$names[$index]} = $$datum[$index];
+    }
+    push @results_to_create, \%result_to_create;
+  }
+  $rs->populate(\@results_to_create);
 }
 
 =head2 exception_action
@@ -837,7 +977,7 @@ sub populate {
 
 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.
+rather than its normal C<croak> or C<confess> action.
 
 Your subroutine should probably just wrap the error in the exception
 object/class of your choosing and rethrow.  If, against all sage advice,
@@ -859,6 +999,18 @@ Example:
    # suppress all exceptions, like a moron:
    $schema_obj->exception_action(sub { 1 });
 
+=head2 stacktrace
+
+=over 4
+
+=item Arguments: boolean
+
+=back
+
+Whether L</throw_exception> should include stack trace information.
+Defaults to false normally, but defaults to true if C<$ENV{DBIC_TRACE}>
+is true.
+
 =head2 throw_exception
 
 =over 4
@@ -869,58 +1021,110 @@ Example:
 
 Throws an exception. Defaults to using L<Carp::Clan> to report errors from
 user's perspective.  See L</exception_action> for details on overriding
-this method's behavior.
+this method's behavior.  If L</stacktrace> is turned on, C<throw_exception>'s
+default behavior will provide a detailed stack trace.
 
 =cut
 
 sub throw_exception {
   my $self = shift;
-  croak @_ if !$self->exception_action || !$self->exception_action->(@_);
+
+  DBIx::Class::Exception->throw($_[0], $self->stacktrace)
+    if !$self->exception_action || !$self->exception_action->(@_);
 }
 
-=head2 deploy (EXPERIMENTAL)
+=head2 deploy
 
 =over 4
 
-=item Arguments: $sqlt_args
+=item Arguments: $sqlt_args, $dir
 
 =back
 
 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.
-
 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 {
-  my ($self, $sqltargs) = @_;
+  my ($self, $sqltargs, $dir) = @_;
   $self->throw_exception("Can't deploy without storage") unless $self->storage;
-  $self->storage->deploy($self, undef, $sqltargs);
+  $self->storage->deploy($self, undef, $sqltargs, $dir);
+}
+
+=head2 deployment_statements
+
+=over 4
+
+=item Arguments: $rdbms_type
+
+=back
+
+Returns the SQL statements used by L</deploy> and L<DBIx::Class::Schema/deploy>.
+C<$rdbms_type> provides the DBI database driver name for which the SQL
+statements are produced. If not supplied, the type of the current schema storage
+will be used.
+
+=cut
+
+sub deployment_statements {
+  my ($self, $rdbms_type) = @_;
+
+  $self->throw_exception("Can't generate deployment statements without a storage")
+    if not $self->storage;
+
+  $self->storage->deployment_statements($self, $rdbms_type);
 }
 
 =head2 create_ddl_dir (EXPERIMENTAL)
 
 =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
-{
+sub create_ddl_dir {
   my $self = shift;
 
   $self->throw_exception("Can't create_ddl_dir without storage") unless $self->storage;
@@ -929,24 +1133,81 @@ 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) = @_;
+sub ddl_filename {
+    my ($self, $type, $dir, $version, $pversion) = @_;
 
     my $filename = ref($self);
-    $filename =~ s/::/-/;
-    $filename = "$dir$filename-$version-$type.sql";
+    $filename =~ s/::/-/g;
+    $filename = File::Spec->catfile($dir, "$filename-$version-$type.sql");
+    $filename =~ s/$version/$pversion-$version/ if($pversion);
 
     return $filename;
 }
 
+=head2 sqlt_deploy_hook($sqlt_schema)
+
+An optional sub which you can declare in your own Schema class that will get 
+passed the L<SQL::Translator::Schema> object when you deploy the schema via
+L</create_ddl_dir> or L</deploy>.
+
+For an example of what you can do with this, see 
+L<DBIx::Class::Manual::Cookbook/Adding Indexes And Functions To Your SQL>.
+
+=head2 thaw
+
+Provided as the recommened way of thawing schema objects. You can call 
+C<Storable::thaw> directly if you wish, but the thawed objects will not have a
+reference to any schema, so are rather useless
+
+=cut
+
+sub thaw {
+  my ($self, $obj) = @_;
+  local $DBIx::Class::ResultSourceHandle::thaw_schema = $self;
+  return Storable::thaw($obj);
+}
+
+=head2 freeze
+
+This doesn't actualy do anything more than call L<Storable/freeze>, it is just
+provided here for symetry.
+
+=cut
+
+sub freeze {
+  return Storable::freeze($_[1]);
+}
+
+=head2 dclone
+
+Recommeneded way of dcloning objects. This is needed to properly maintain
+references to the schema object (which itself is B<not> cloned.)
+
+=cut
+
+sub dclone {
+  my ($self, $obj) = @_;
+  local $DBIx::Class::ResultSourceHandle::thaw_schema = $self;
+  return Storable::dclone($obj);
+}
+
 1;
 
 =head1 AUTHORS
@@ -958,4 +1219,3 @@ Matt S. Trout <mst@shadowcatsystems.co.uk>
 You may distribute this code under the same terms as Perl itself.
 
 =cut
-