X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=blobdiff_plain;f=lib%2FDBIx%2FClass%2FStorage%2FDBI%2FReplicated.pm;h=bb6517a6b25d511849f718bedf2b523830526fdd;hb=2ce6e9a68d68fa86e3b28df61496e85c996b2c1a;hp=df0734faa21e2831d6c0ac9b9de2065a0cb22bc6;hpb=955a6df69eb864ba348f87f5a06a8b7a68ffc76c;p=dbsrgits%2FDBIx-Class.git diff --git a/lib/DBIx/Class/Storage/DBI/Replicated.pm b/lib/DBIx/Class/Storage/DBI/Replicated.pm index df0734f..bb6517a 100644 --- a/lib/DBIx/Class/Storage/DBI/Replicated.pm +++ b/lib/DBIx/Class/Storage/DBI/Replicated.pm @@ -1,12 +1,11 @@ package DBIx::Class::Storage::DBI::Replicated; use Moose; +use Class::MOP; +use Moose::Util::TypeConstraints; use DBIx::Class::Storage::DBI; use DBIx::Class::Storage::DBI::Replicated::Pool; use DBIx::Class::Storage::DBI::Replicated::Balancer; -use Scalar::Util qw(blessed); - -extends 'DBIx::Class::Storage::DBI', 'Moose::Object'; =head1 NAME @@ -18,18 +17,18 @@ The Following example shows how to change an existing $schema to a replicated storage type, add some replicated (readonly) databases, and perform reporting tasks. - ## Change storage_type in your schema class - $schema->storage_type( '::DBI::Replicated' ); - - ## Add some slaves. Basically this is an array of arrayrefs, where each - ## arrayref is database connect information - - $schema->storage->connect_replicants( - [$dsn1, $user, $pass, \%opts], - [$dsn1, $user, $pass, \%opts], - [$dsn1, $user, $pass, \%opts], - ); - + ## Change storage_type in your schema class + $schema->storage_type( ['::DBI::Replicated', {balancer=>'::Random'}] ); + + ## Add some slaves. Basically this is an array of arrayrefs, where each + ## arrayref is database connect information + + $schema->storage->connect_replicants( + [$dsn1, $user, $pass, \%opts], + [$dsn2, $user, $pass, \%opts], + [$dsn3, $user, $pass, \%opts], + ); + =head1 DESCRIPTION Warning: This class is marked ALPHA. We are using this in development and have @@ -43,105 +42,69 @@ 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 would normally -handle gets delegated to one of the two attributes: L or to -L. Additionally, some methods need to be distributed +handle gets delegated to one of the two attributes: L or to +L. Additionally, some methods need to be distributed to all existing storages. This way our storage class is a drop in replacement for L. 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 This class defines the following attributes. -=head2 master +=head2 schema -The master defines the canonical state for a pool of connected databases. All -the replicants are expected to match this databases state. Thus, in a classic -Master / Slaves distributed system, all the slaves are expected to replicate -the Master's state as quick as possible. This is the only database in the -pool of databases that is allowed to handle write traffic. +The underlying L object this storage is attaching =cut -has 'master' => ( - is=> 'ro', - isa=>'DBIx::Class::Storage::DBI', - lazy_build=>1, - handles=>[qw/ - on_connect_do - on_disconnect_do - connect_info - throw_exception - sql_maker - sqlt_type - create_ddl_dir - deployment_statements - datetime_parser - datetime_parser_type - last_insert_id - insert - insert_bulk - update - delete - dbh - txn_do - txn_commit - txn_rollback - sth - deploy - schema - /], +has 'schema' => ( + is=>'rw', + isa=>'DBIx::Class::Schema', + weak_ref=>1, + required=>1, ); +=head2 pool_type -=head2 current_replicant - -Replicant storages (slaves) handle all read only traffic. The assumption is -that your database will become readbound well before it becomes write bound -and that being able to spread your read only traffic around to multiple -databases is going to help you to scale traffic. - -This attribute returns the next slave to handle a read request. Your L -attribute has methods to help you shuffle through all the available replicants -via it's balancer object. - -We split the reader/writer to make it easier to selectively override how the -replicant is altered. +Contains the classname which will instantiate the L object. Defaults +to: L. =cut -has 'current_replicant' => ( - is=> 'rw', - isa=>'DBIx::Class::Storage::DBI', - lazy_build=>1, - handles=>[qw/ - select - select_single - columns_info_for - /], +has 'pool_type' => ( + is=>'ro', + isa=>'ClassName', + required=>1, + default=>'DBIx::Class::Storage::DBI::Replicated::Pool', + handles=>{ + 'create_pool' => 'new', + }, ); +=head2 pool_args -=head2 pool_type - -Contains the classname which will instantiate the L object. Defaults -to: L. +Contains a hashref of initialized information to pass to the Balancer object. +See L for available arguments. =cut -has 'pool_type' => ( - is=>'ro', - isa=>'ClassName', - required=>1, - lazy=>1, - default=>'DBIx::Class::Storage::DBI::Replicated::Pool', - handles=>{ - 'create_pool' => 'new', - }, +has 'pool_args' => ( + is=>'ro', + isa=>'HashRef', + lazy=>1, + required=>1, + default=>sub { {} }, ); @@ -152,17 +115,45 @@ choose how to spread the query load across each replicant in the pool. =cut +subtype 'DBIx::Class::Storage::DBI::Replicated::BalancerClassNamePart', + as 'ClassName'; + +coerce 'DBIx::Class::Storage::DBI::Replicated::BalancerClassNamePart', + from 'Str', + via { + my $type = $_; + if($type=~m/^::/) { + $type = 'DBIx::Class::Storage::DBI::Replicated::Balancer'.$type; + } + Class::MOP::load_class($type); + $type; + }; + has 'balancer_type' => ( - is=>'ro', - isa=>'ClassName', - required=>1, - lazy=>1, - default=>'DBIx::Class::Storage::DBI::Replicated::Balancer', - handles=>{ - 'create_balancer' => 'new', - }, + is=>'ro', + isa=>'DBIx::Class::Storage::DBI::Replicated::BalancerClassNamePart', + coerce=>1, + required=>1, + default=> 'DBIx::Class::Storage::DBI::Replicated::Balancer::First', + handles=>{ + 'create_balancer' => 'new', + }, ); +=head2 balancer_args + +Contains a hashref of initialized information to pass to the Balancer object. +See L for available arguments. + +=cut + +has 'balancer_args' => ( + is=>'ro', + isa=>'HashRef', + lazy=>1, + required=>1, + default=>sub { {} }, +); =head2 pool @@ -172,19 +163,16 @@ container class for one or more replicated databases. =cut has 'pool' => ( - is=>'ro', - isa=>'DBIx::Class::Storage::DBI::Replicated::Pool', - lazy_build=>1, - handles=>[qw/ - replicants - has_replicants - connect_replicants - num_replicants - delete_replicant - /], + is=>'ro', + isa=>'DBIx::Class::Storage::DBI::Replicated::Pool', + lazy_build=>1, + handles=>[qw/ + connect_replicants + replicants + has_replicants + /], ); - =head2 balancer Is a or derived class. This @@ -193,39 +181,121 @@ is a class that takes a pool () =cut has 'balancer' => ( - is=>'ro', - isa=>'DBIx::Class::Storage::DBI::Replicated::Balancer', - lazy_build=>1, - handles=>[qw/next_storage/], + is=>'ro', + isa=>'DBIx::Class::Storage::DBI::Replicated::Balancer', + lazy_build=>1, + handles=>[qw/auto_validate_every/], +); + +=head2 master + +The master defines the canonical state for a pool of connected databases. All +the replicants are expected to match this databases state. Thus, in a classic +Master / Slaves distributed system, all the slaves are expected to replicate +the Master's state as quick as possible. This is the only database in the +pool of databases that is allowed to handle write traffic. + +=cut + +has 'master' => ( + is=> 'ro', + isa=>'DBIx::Class::Storage::DBI', + lazy_build=>1, +); + +=head1 ATTRIBUTES IMPLEMENTING THE DBIx::Storage::DBI INTERFACE + +The following methods are delegated all the methods required for the +L interface. + +=head2 read_handler + +Defines an object that implements the read side of L. + +=cut + +has 'read_handler' => ( + is=>'rw', + isa=>'Object', + lazy_build=>1, + handles=>[qw/ + select + select_single + columns_info_for + /], +); + +=head2 write_handler + +Defines an object that implements the write side of L. + +=cut + +has 'write_handler' => ( + is=>'ro', + isa=>'Object', + lazy_build=>1, + lazy_build=>1, + handles=>[qw/ + on_connect_do + on_disconnect_do + connect_info + throw_exception + sql_maker + sqlt_type + create_ddl_dir + deployment_statements + datetime_parser + datetime_parser_type + last_insert_id + insert + insert_bulk + update + delete + dbh + txn_begin + txn_do + txn_commit + txn_rollback + txn_scope_guard + sth + deploy + + reload_row + _prep_for_execute + configure_sqlt + + /], ); =head1 METHODS This class defines the following methods. -=head2 _build_master +=head2 new -Lazy builder for the L attribute. +L when instantiating it's storage passed itself as the +first argument. So we need to massage the arguments a bit so that all the +bits get put into the correct places. =cut -sub _build_master { - DBIx::Class::Storage::DBI->new; -} - +around 'new' => sub { + my ($new, $self, $schema, $storage_type_args, @args) = @_; + return $self->$new(schema=>$schema, %$storage_type_args, @args); +}; -=head2 _build_current_replicant +=head2 _build_master -Lazy builder for the L attribute. +Lazy builder for the L attribute. =cut -sub _build_current_replicant { - my $self = shift @_; - $self->next_storage($self->pool); +sub _build_master { + my $self = shift @_; + DBIx::Class::Storage::DBI->new($self->schema); } - =head2 _build_pool Lazy builder for the L attribute. @@ -233,63 +303,58 @@ Lazy builder for the L attribute. =cut sub _build_pool { - my $self = shift @_; - $self->create_pool; + my $self = shift @_; + $self->create_pool(%{$self->pool_args}); } - =head2 _build_balancer -Lazy builder for the L attribute. +Lazy builder for the L attribute. This takes a Pool object so that +the balancer knows which pool it's balancing. =cut sub _build_balancer { - my $self = shift @_; - $self->create_balancer; + my $self = shift @_; + $self->create_balancer( + pool=>$self->pool, + master=>$self->master, + %{$self->balancer_args}, + ); } +=head2 _build_write_handler -=head2 around: create_replicants - -All calls to create_replicants needs to have an existing $schema tacked onto -top of the args +Lazy builder for the L attribute. The default is to set this to +the L. =cut -around 'connect_replicants' => sub { - my ($method, $self, @args) = @_; - $self->$method($self->schema, @args); -}; - +sub _build_write_handler { + return shift->master; +} -=head2 after: select, select_single, columns_info_for +=head2 _build_read_handler -Advice on the current_replicant_storage attribute. Each time we use a replicant -we need to change it via the storage pool algorithm. That way we are spreading -the load evenly (hopefully) across existing capacity. +Lazy builder for the L attribute. The default is to set this to +the L. =cut -after 'select' => sub { - my $self = shift @_; - my $next_replicant = $self->next_storage($self->pool); - - $self->current_replicant($next_replicant); -}; +sub _build_read_handler { + return shift->balancer; +} -after 'select_single' => sub { - my $self = shift @_; - my $next_replicant = $self->next_storage($self->pool); +=head2 around: connect_replicants - $self->current_replicant($next_replicant); -}; +All calls to connect_replicants needs to have an existing $schema tacked onto +top of the args, since L needs it. -after 'columns_info_for' => sub { - my $self = shift @_; - my $next_replicant = $self->next_storage($self->pool); +=cut - $self->current_replicant($next_replicant); +around 'connect_replicants' => sub { + my ($method, $self, @args) = @_; + $self->$method($self->schema, @args); }; =head2 all_storages @@ -301,14 +366,134 @@ 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, 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 + +Sets the current $schema to be 'reliable', that is all queries, both read and +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); +} + +=head2 set_balanced_storage + +Sets the current $schema to be use the for all reads, while all +writea are sent to the master only + +=cut + +sub set_balanced_storage { + my $self = shift @_; + my $schema = $self->schema; + my $write_handler = $self->schema->storage->balancer; + + $schema->storage->read_handler($write_handler); +} + +=head2 around: txn_do ($coderef) + +Overload to the txn_do method, which is delegated to whatever the +L is set to. We overload this in order to wrap in inside a +L 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 @@ -317,14 +502,12 @@ 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 Make sure all the storages are connected. @@ -332,13 +515,12 @@ Make sure all the storages are connected. =cut sub ensure_connected { - my $self = shift @_; - foreach my $source ($self->all_storages) { - $source->ensure_connected(@_); - } + my $self = shift @_; + foreach my $source ($self->all_storages) { + $source->ensure_connected(@_); + } } - =head2 limit_dialect Set the limit_dialect for all existing storages @@ -346,13 +528,12 @@ Set the limit_dialect for all existing storages =cut sub limit_dialect { - my $self = shift @_; - foreach my $source ($self->all_storages) { - $source->limit_dialect(@_); - } + my $self = shift @_; + foreach my $source ($self->all_storages) { + $source->limit_dialect(@_); + } } - =head2 quote_char Set the quote_char for all existing storages @@ -360,13 +541,12 @@ Set the quote_char for all existing storages =cut sub quote_char { - my $self = shift @_; - foreach my $source ($self->all_storages) { - $source->quote_char(@_); - } + my $self = shift @_; + foreach my $source ($self->all_storages) { + $source->quote_char(@_); + } } - =head2 name_sep Set the name_sep for all existing storages @@ -374,13 +554,12 @@ Set the name_sep for all existing storages =cut sub name_sep { - my $self = shift @_; - foreach my $source ($self->all_storages) { - $source->name_sep(@_); - } + my $self = shift @_; + foreach my $source ($self->all_storages) { + $source->name_sep(@_); + } } - =head2 set_schema Set the schema object for all existing storages @@ -388,13 +567,12 @@ 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 set a debug flag across all storages @@ -402,13 +580,12 @@ set a debug flag across all storages =cut sub debug { - my $self = shift @_; - foreach my $source ($self->all_storages) { - $source->debug(@_); - } + my $self = shift @_; + foreach my $source ($self->all_storages) { + $source->debug(@_); + } } - =head2 debugobj set a debug object across all storages @@ -416,13 +593,12 @@ set a debug object across all storages =cut sub debugobj { - my $self = shift @_; - foreach my $source ($self->all_storages) { - $source->debugobj(@_); - } + my $self = shift @_; + foreach my $source ($self->all_storages) { + $source->debugobj(@_); + } } - =head2 debugfh set a debugfh object across all storages @@ -430,13 +606,12 @@ set a debugfh object across all storages =cut sub debugfh { - my $self = shift @_; - foreach my $source ($self->all_storages) { - $source->debugfh(@_); - } + my $self = shift @_; + foreach my $source ($self->all_storages) { + $source->debugfh(@_); + } } - =head2 debugcb set a debug callback across all storages @@ -444,13 +619,12 @@ set a debug callback across all storages =cut sub debugcb { - my $self = shift @_; - foreach my $source ($self->all_storages) { - $source->debugcb(@_); - } + my $self = shift @_; + foreach my $source ($self->all_storages) { + $source->debugcb(@_); + } } - =head2 disconnect disconnect everything @@ -458,305 +632,20 @@ disconnect everything =cut sub disconnect { - my $self = shift @_; - foreach my $source ($self->all_storages) { - $source->disconnect(@_); - } -} - - -=head2 DESTROY - -Make sure we pass destroy events down to the storage handlers - -=cut - -sub DESTROY { - my $self = shift; - ## TODO, maybe we can just leave this alone ??? + my $self = shift @_; + foreach my $source ($self->all_storages) { + $source->disconnect(@_); + } } - -=head1 AUTHOR - -Norbert Csongrádi - -Peter Siklósi - -John Napiorkowski - -=head1 LICENSE - -You may distribute this code under the same terms as Perl itself. - -=cut - -1; - -__END__ - -use strict; -use warnings; - -use DBIx::Class::Storage::DBI; -use DBD::Multi; - -use base qw/Class::Accessor::Fast/; - -__PACKAGE__->mk_accessors( qw/read_source write_source/ ); - -=head1 NAME - -DBIx::Class::Storage::DBI::Replicated - ALPHA Replicated database support - -=head1 SYNOPSIS - -The Following example shows how to change an existing $schema to a replicated -storage type and update it's connection information to contain a master DSN and -an array of slaves. - - ## Change storage_type in your schema class - $schema->storage_type( '::DBI::Replicated' ); - - ## Set your connection. - $schema->connect( - $dsn, $user, $password, { - AutoCommit => 1, - ## Other standard DBI connection or DBD custom attributes added as - ## usual. Additionally, we have two custom attributes for defining - ## slave information and controlling how the underlying DBD::Multi - connect_replicants => [ - ## Define each slave like a 'normal' DBI connection, but you add - ## in a DBD::Multi custom attribute to define how the slave is - ## prioritized. Please see DBD::Multi for more. - [$slave1dsn, $user, $password, {%slave1opts}], - [$slave2dsn, $user, $password, {%slave2opts}], - [$slave3dsn, $user, $password, {%slave3opts}], - ], - }, - ); - - ## Now, just use the schema as normal - $schema->resultset('Table')->find(< unique >); ## Reads will use slaves - $schema->resultset('Table')->create(\%info); ## Writes will use master - -=head1 DESCRIPTION - -Warning: This class is marked ALPHA. We are using this in development and have -some basic test coverage but the code hasn't yet been stressed by a variety -of databases. Individual DB's may have quirks we are not aware of. Please -use this in development and pass along your experiences/bug fixes. - -This class implements replicated data store for DBI. Currently you can define -one master and numerous slave database connections. All write-type queries -(INSERT, UPDATE, DELETE and even LAST_INSERT_ID) are routed to master -database, all read-type queries (SELECTs) go to the slave database. - -For every slave database you can define a priority value, which controls data -source usage pattern. It uses L, so first the lower priority data -sources used (if they have the same priority, the are used randomized), than -if all low priority data sources fail, higher ones tried in order. - -=head1 CONFIGURATION - -Please see L for most configuration information. - -=cut - -sub new { - my $proto = shift; - my $class = ref( $proto ) || $proto; - my $self = {}; - - bless( $self, $class ); - - $self->write_source( DBIx::Class::Storage::DBI->new ); - $self->read_source( DBIx::Class::Storage::DBI->new ); - - return $self; -} - -sub all_sources { - my $self = shift; - - my @sources = ($self->read_source, $self->write_source); - - return wantarray ? @sources : \@sources; -} - -sub _connect_info { - my $self = shift; - my $master = $self->write_source->_connect_info; - $master->[-1]->{slave_connect_info} = $self->read_source->_connect_info; - return $master; -} - -sub connect_info { - my ($self, $source_info) = @_; - - ## if there is no $source_info, treat this sub like an accessor - return $self->_connect_info - if !$source_info; - - ## Alright, let's conect the master - $self->write_source->connect_info($source_info); - - ## Now, build and then connect the Slaves - my @slaves_connect_info = @{$source_info->[-1]->{slaves_connect_info}}; - my $dbd_multi_config = ref $slaves_connect_info[-1] eq 'HASH' - ? pop @slaves_connect_info : {}; - - ## We need to do this since SQL::Abstract::Limit can't guess what DBD::Multi is - $dbd_multi_config->{limit_dialect} = $self->write_source->sql_maker->limit_dialect - unless defined $dbd_multi_config->{limit_dialect}; - - @slaves_connect_info = map { - ## if the first element in the arrayhash is a ref, make that the value - my $db = ref $_->[0] ? $_->[0] : $_; - my $priority = $_->[-1]->{priority} || 10; ## default priority is 10 - $priority => $db; - } @slaves_connect_info; - - $self->read_source->connect_info([ - 'dbi:Multi:', undef, undef, { - dsns => [@slaves_connect_info], - %$dbd_multi_config, - }, - ]); - - ## Return the formated connection information - return $self->_connect_info; -} - -sub select { - shift->read_source->select( @_ ); -} -sub select_single { - shift->read_source->select_single( @_ ); -} -sub throw_exception { - shift->read_source->throw_exception( @_ ); -} -sub sql_maker { - shift->read_source->sql_maker( @_ ); -} -sub columns_info_for { - shift->read_source->columns_info_for( @_ ); -} -sub sqlt_type { - shift->read_source->sqlt_type( @_ ); -} -sub create_ddl_dir { - shift->read_source->create_ddl_dir( @_ ); -} -sub deployment_statements { - shift->read_source->deployment_statements( @_ ); -} -sub datetime_parser { - shift->read_source->datetime_parser( @_ ); -} -sub datetime_parser_type { - shift->read_source->datetime_parser_type( @_ ); -} -sub build_datetime_parser { - shift->read_source->build_datetime_parser( @_ ); -} - -sub limit_dialect { $_->limit_dialect( @_ ) for( shift->all_sources ) } -sub quote_char { $_->quote_char( @_ ) for( shift->all_sources ) } -sub name_sep { $_->quote_char( @_ ) for( shift->all_sources ) } -sub disconnect { $_->disconnect( @_ ) for( shift->all_sources ) } -sub set_schema { $_->set_schema( @_ ) for( shift->all_sources ) } - -sub DESTROY { - my $self = shift; - - undef $self->{write_source}; - undef $self->{read_sources}; -} - -sub last_insert_id { - shift->write_source->last_insert_id( @_ ); -} -sub insert { - shift->write_source->insert( @_ ); -} -sub update { - shift->write_source->update( @_ ); -} -sub update_all { - shift->write_source->update_all( @_ ); -} -sub delete { - shift->write_source->delete( @_ ); -} -sub delete_all { - shift->write_source->delete_all( @_ ); -} -sub create { - shift->write_source->create( @_ ); -} -sub find_or_create { - shift->write_source->find_or_create( @_ ); -} -sub update_or_create { - shift->write_source->update_or_create( @_ ); -} -sub connected { - shift->write_source->connected( @_ ); -} -sub ensure_connected { - shift->write_source->ensure_connected( @_ ); -} -sub dbh { - shift->write_source->dbh( @_ ); -} -sub txn_do { - shift->write_source->txn_do( @_ ); -} -sub txn_commit { - shift->write_source->txn_commit( @_ ); -} -sub txn_rollback { - shift->write_source->txn_rollback( @_ ); -} -sub sth { - shift->write_source->sth( @_ ); -} -sub deploy { - shift->write_source->deploy( @_ ); -} -sub _prep_for_execute { - shift->write_source->_prep_for_execute(@_); -} - -sub debugobj { - shift->write_source->debugobj(@_); -} -sub debug { - shift->write_source->debug(@_); -} - -sub debugfh { shift->_not_supported( 'debugfh' ) }; -sub debugcb { shift->_not_supported( 'debugcb' ) }; - -sub _not_supported { - my( $self, $method ) = @_; - - die "This Storage does not support $method method."; -} - -=head1 SEE ALSO - -L, L, L - =head1 AUTHOR -Norbert Csongrádi + John Napiorkowski -Peter Siklósi +Based on code originated by: -John Napiorkowski + Norbert Csongrádi + Peter Siklósi =head1 LICENSE