all your tabs belong to spaces
[dbsrgits/DBIx-Class.git] / lib / DBIx / Class / Storage / DBI / Replicated.pm
index 6408cb4..03a3fcd 100644 (file)
@@ -43,15 +43,21 @@ one master and numerous slave database connections. All write-type queries
 database, all read-type queries (SELECTs) go to the slave database.
 
 Basically, any method request that L<DBIx::Class::Storage::DBI> would normally
-handle gets delegated to one of the two attributes: L</master_storage> or to
-L</current_replicant_storage>.  Additionally, some methods need to be distributed
+handle gets delegated to one of the two attributes: L</read_handler> or to
+L</write_handler>.  Additionally, some methods need to be distributed
 to all existing storages.  This way our storage class is a drop in replacement
 for L<DBIx::Class::Storage::DBI>.
 
 Read traffic is spread across the replicants (slaves) occuring to a user
 selected algorithm.  The default algorithm is random weighted.
 
-TODO more details about the algorithm.
+=head1 NOTES
+
+The consistancy betweeen master and replicants is database specific.  The Pool
+gives you a method to validate it's replicants, removing and replacing them
+when they fail/pass predefined criteria.  It is recommened that your application
+define two schemas, one using the replicated storage and another that just 
+connects to the master.
 
 =head1 ATTRIBUTES
 
@@ -69,10 +75,26 @@ has 'pool_type' => (
     isa=>'ClassName',
     lazy_build=>1,
     handles=>{
-       'create_pool' => 'new',
+        'create_pool' => 'new',
     },
 );
 
+=head2 pool_args
+
+Contains a hashref of initialized information to pass to the Balancer object.
+See L<DBIx::Class::Storage::Replicated::Pool> for available arguments.
+
+=cut
+
+has 'pool_args' => (
+    is=>'ro',
+    isa=>'HashRef',
+    lazy=>1,
+    required=>1,
+    default=>sub { {} },
+);
+
+
 =head2 balancer_type
 
 The replication pool requires a balance class to provider the methods for
@@ -85,10 +107,25 @@ has 'balancer_type' => (
     isa=>'ClassName',
     lazy_build=>1,
     handles=>{
-       'create_balancer' => 'new',
+        'create_balancer' => 'new',
     },
 );
 
+=head2 balancer_args
+
+Contains a hashref of initialized information to pass to the Balancer object.
+See L<DBIx::Class::Storage::Replicated::Balancer> for available arguments.
+
+=cut
+
+has 'balancer_args' => (
+    is=>'ro',
+    isa=>'HashRef',
+    lazy=>1,
+    required=>1,
+    default=>sub { {} },
+);
+
 =head2 pool
 
 Is a <DBIx::Class::Storage::DBI::Replicated::Pool> or derived class.  This is a
@@ -104,8 +141,6 @@ has 'pool' => (
         connect_replicants    
         replicants
         has_replicants
-        num_replicants
-        delete_replicant
     /],
 );
 
@@ -120,6 +155,7 @@ has 'balancer' => (
     is=>'ro',
     isa=>'DBIx::Class::Storage::DBI::Replicated::Balancer',
     lazy_build=>1,
+    handles=>[qw/auto_validate_every/],
 );
 
 =head2 master
@@ -194,6 +230,7 @@ has 'write_handler' => (
         sth
         deploy
         schema
+        reload_row
     /],
 );
 
@@ -220,8 +257,8 @@ sub new {
     ## Hate to do it this way, but can't seem to get advice on the attribute working right
     ## maybe we can do a type and coercion for it. 
     if( $storage_type_args->{balancer_type} && $storage_type_args->{balancer_type}=~m/^::/) {
-       $storage_type_args->{balancer_type} = 'DBIx::Class::Storage::DBI::Replicated::Balancer'.$storage_type_args->{balancer_type};
-       eval "require $storage_type_args->{balancer_type}";
+        $storage_type_args->{balancer_type} = 'DBIx::Class::Storage::DBI::Replicated::Balancer'.$storage_type_args->{balancer_type};
+        eval "require $storage_type_args->{balancer_type}";
     }
     
     return $class->meta->new_object(
@@ -238,7 +275,7 @@ Lazy builder for the L</master> attribute.
 =cut
 
 sub _build_master {
-       DBIx::Class::Storage::DBI->new;
+    DBIx::Class::Storage::DBI->new;
 }
 
 =head2 _build_pool_type
@@ -258,7 +295,8 @@ Lazy builder for the L</pool> attribute.
 =cut
 
 sub _build_pool {
-    shift->create_pool;
+    my $self = shift @_;
+    $self->create_pool(%{$self->pool_args});
 }
 
 =head2 _build_balancer_type
@@ -268,7 +306,7 @@ Lazy builder for the L</balancer_type> attribute.
 =cut
 
 sub _build_balancer_type {
-    return 'DBIx::Class::Storage::DBI::Replicated::Balancer';
+    return 'DBIx::Class::Storage::DBI::Replicated::Balancer::First';
 }
 
 =head2 _build_balancer
@@ -282,7 +320,9 @@ sub _build_balancer {
     my $self = shift @_;
     $self->create_balancer(
         pool=>$self->pool, 
-        master=>$self->master);
+        master=>$self->master,
+        %{$self->balancer_args},
+    );
 }
 
 =head2 _build_write_handler
@@ -315,8 +355,8 @@ top of the args, since L<DBIx::Storage::DBI> needs it.
 =cut
 
 around 'connect_replicants' => sub {
-       my ($method, $self, @args) = @_;
-       $self->$method($self->schema, @args);
+    my ($method, $self, @args) = @_;
+    $self->$method($self->schema, @args);
 };
 
 =head2 all_storages
@@ -328,12 +368,78 @@ replicants.
 =cut
 
 sub all_storages {
-       my $self = shift @_;
-       
-       return grep {defined $_ && blessed $_} (
-          $self->master,
-          $self->replicants,
-       );
+    my $self = shift @_;
+    
+    return grep {defined $_ && blessed $_} (
+       $self->master,
+       $self->replicants,
+    );
+}
+
+=head2 execute_reliably ($coderef, ?@args)
+
+Given a coderef, saves the current state of the L</read_handler>, forces it to
+use reliable storage (ie sets it to the master), executes a coderef and then
+restores the original state.
+
+Example:
+
+    my $reliably = sub {
+        my $name = shift @_;
+        $schema->resultset('User')->create({name=>$name});
+        my $user_rs = $schema->resultset('User')->find({name=>$name}); 
+        return $user_rs;
+    };
+
+    my $user_rs = $schema->storage->execute_reliably($reliably, 'John');
+
+Use this when you must be certain of your database state, such as when you just
+inserted something and need to get a resultset including it, etc.
+
+=cut
+
+sub execute_reliably {
+    my ($self, $coderef, @args) = @_;
+    
+    unless( ref $coderef eq 'CODE') {
+        $self->throw_exception('Second argument must be a coderef');
+    }
+
+    ##Get copy of master storage
+    my $master = $self->master;
+    
+    ##Get whatever the current read hander is
+    my $current = $self->read_handler;
+    
+    ##Set the read handler to master
+    $self->read_handler($master);
+    
+    ## do whatever the caller needs
+    my @result;
+    my $want_array = wantarray;
+    
+    eval {
+        if($want_array) {
+            @result = $coderef->(@args);
+        }
+        elsif(defined $want_array) {
+            ($result[0]) = ($coderef->(@args));
+        } else {
+            $coderef->(@args);
+        }       
+    };
+    
+    ##Reset to the original state
+    $self->read_handler($current); 
+    
+    ##Exception testing has to come last, otherwise you might leave the 
+    ##read_handler set to master.
+    
+    if($@) {
+        $self->throw_exception("coderef returned an error: $@");
+    } else {
+        return $want_array ? @result : $result[0];
+    }
 }
 
 =head2 set_reliable_storage
@@ -344,11 +450,11 @@ write are sent to the master
 =cut
 
 sub set_reliable_storage {
-       my $self = shift @_;
-       my $schema = $self->schema;
-       my $write_handler = $self->schema->storage->write_handler;
-       
-       $schema->storage->read_handler($write_handler);
+    my $self = shift @_;
+    my $schema = $self->schema;
+    my $write_handler = $self->schema->storage->write_handler;
+    
+    $schema->storage->read_handler($write_handler);
 }
 
 =head2 set_balanced_storage
@@ -366,6 +472,33 @@ sub set_balanced_storage {
     $schema->storage->read_handler($write_handler);
 }
 
+=head2 around: txn_do ($coderef)
+
+Overload to the txn_do method, which is delegated to whatever the
+L<write_handler> is set to.  We overload this in order to wrap in inside a
+L</execute_reliably> method.
+
+=cut
+
+around 'txn_do' => sub {
+    my($txn_do, $self, $coderef, @args) = @_;
+    $self->execute_reliably(sub {$self->$txn_do($coderef, @args)}); 
+};
+
+=head2 reload_row ($row)
+
+Overload to the reload_row method so that the reloading is always directed to
+the master storage.
+
+=cut
+
+around 'reload_row' => sub {
+    my ($reload_row, $self, $row) = @_;
+    return $self->execute_reliably(sub {
+        return $self->$reload_row(shift);
+    }, $row);
+};
+
 =head2 connected
 
 Check that the master and at least one of the replicants is connected.
@@ -373,11 +506,11 @@ Check that the master and at least one of the replicants is connected.
 =cut
 
 sub connected {
-       my $self = shift @_;
-       
-       return
-          $self->master->connected &&
-          $self->pool->connected_replicants;
+    my $self = shift @_;
+    
+    return
+       $self->master->connected &&
+       $self->pool->connected_replicants;
 }
 
 =head2 ensure_connected
@@ -439,10 +572,10 @@ Set the schema object for all existing storages
 =cut
 
 sub set_schema {
-       my $self = shift @_;
-       foreach my $source ($self->all_storages) {
-               $source->set_schema(@_);
-       }
+    my $self = shift @_;
+    foreach my $source ($self->all_storages) {
+        $source->set_schema(@_);
+    }
 }
 
 =head2 debug
@@ -512,11 +645,12 @@ sub disconnect {
 
 =head1 AUTHOR
 
-Norbert Csongrádi <bert@cpan.org>
+    John Napiorkowski <john.napiorkowski@takkle.com>
 
-Peter Siklósi <einon@einon.hu>
+Based on code originated by:
 
-John Napiorkowski <john.napiorkowski@takkle.com>
+    Norbert Csongrádi <bert@cpan.org>
+    Peter Siklósi <einon@einon.hu>
 
 =head1 LICENSE