DBIx::Class::Schema::Slave - L<DBIx::Class::Schema> for slave B<(EXPERIMENTAL)>


DBIx-Class-Schema-Slave documentation Contained in the DBIx-Class-Schema-Slave distribution.

Index


Code Index:

NAME

Top

DBIx::Class::Schema::Slave - DBIx::Class::Schema for slave (EXPERIMENTAL)

CAUTION

Top

DIBx::Class::Schema::Slave is EXPERIMENTAL and DO NOT use. Please check DBIx::Class::Storage::DBI::Replicated or DBIx::Class::Storage::DBI::Replication. DBIx::Class::Schema::Slave will be deleted.

SYNOPSIS

Top

  # In your MyApp::Schema (DBIx::Class::Schema based)
  package MyApp::Schema;

  use base 'DBIx::Class::Schema';

  __PACKAGE__->load_components( qw/ Schema::Slave / );
  __PACKAGE__->slave_moniker('::Slave');
  __PACKAGE__->slave_connect_info( [
      [ 'dbi:mysql:database:hostname=host', 'username', 'passsword', { ... } ],
      [ 'dbi:mysql:database:hostname=host', 'username', 'passsword', { ... } ],
      [ 'dbi:mysql:database:hostname=host', 'username', 'passsword', { ... } ],
      ...,
  ] );

  # As it is now, DBIx::Class::Schema::Slave works out with DBIx::Class::Schema::Loader.
  # If you use DBIx::Class::Schema::Loader based MyApp::Schema, maybe it is just like below.

  # In your MyApp::Schema (DBIx::Class::Schema::Loader based)
  package MyApp::Schema;

  use base 'DBIx::Class::Schema::Loader';

  __PACKAGE__->load_components( qw/ Schema::Slave / );
  __PACKAGE__->slave_moniker('::Slave');
  __PACKAGE__->slave_connect_info( [
      [ 'dbi:mysql:database:hostname=host', 'username', 'passsword', { ... } ],
      [ 'dbi:mysql:database:hostname=host', 'username', 'passsword', { ... } ],
      [ 'dbi:mysql:database:hostname=host', 'username', 'passsword', { ... } ],
      ...,
  ] );
  __PACKAGE__->loader_options(
      relationships => 1,
      components    => [ qw/
          ...
          ...
          ...
          Row::Slave # DO NOT forget to specify
          Core
      / ],
  );

  # Somewhere in your code
  use MyApp::Schema;

  # First, connect to master
  my $schema = MyApp::Schema->connect( @master_connect_info );

  # Retrieving from master
  my $master = $schema->resultset('Track')->find( $id );

  # Retrieving from slave
  my $slave = $schema->resultset('Track::Slave')->find( $id );

See DBIx::Class::Schema.

DESCRIPTION

Top

DBIx::Class::Schema::Slave is DBIx::Class::Schema for slave. DBIx::Class::Schema::Slave creates result_source classes for slave automatically, and connects slave datasources as you like (or at rondom). You can retrieve rows from either master or slave in the same way DBIx::Class::Schema provies but you can neither add nor remove rows from slave.

SETTIN UP DBIx::Class::Schema::Slave

Top

Setting it up manually

First, load DBIx::Class::Schema::Slave as component in your MyApp::Schema.

  # In your MyApp::Schema
  package MyApp::Schema;

  use base 'DBIx::Class::Schema';

  __PACKAGE__->load_components( qw/ Schema::Slave / );

Set slave_moniker as you like. If you do not specify, ::Slave is set.

  __PACKAGE__->slave_moniker('::Slave');

Set slave_connect_info as ARRAYREF of ARRAYREF.

  __PACKAGE__->slave_connect_info( [
      [ 'dbi:mysql:database:hostname=host', 'user', 'passsword', { ... } ],
      [ 'dbi:mysql:database:hostname=host', 'user', 'passsword', { ... } ],
      [ 'dbi:mysql:database:hostname=host', 'user', 'passsword', { ... } ],
      ...,
  ] );

Next, you have MyApp::Schema::Artist, MyApp::Schema::Album, MyApp::Schema::Track, load these result_source classes.

  __PACKAGE__->load_classes( qw/ Artist Album Track / );

In running register_source, DBIx::Class::Schema::Slave creates slave result_source classes MyApp::Schema::Artist::Slave, MyApp::Schema::Album::Slave and MyApp::Schema::Track::Slave automatically. If you set ::MySlave to slave_moniker, it creates MyApp::Schema::Artist::MySlave, MyApp::Schema::Album::MySlave and MyApp::Schema::Track::MySlave.

  # MyApp::Schema::Artist wouldn't be loaded
  # MyApp::Schema::Artist::Slave wouldn't be created
  __PACKAGE__->load_classes( qw/ Album Track / );

I recommend every result_source classes to be loaded.

  # Every result_source classes are loaded
  __PACKAGE__->load_classes;

Next, load DBIx::Class::Row::Slave as component in your result_source classes.

  # In your MyApp::Schema::Artist;
  package MyApp::Schema::Artist;

  use base 'DBIx::Class';

  __PACKEAGE__->load_components( qw/ ... Row::Slave Core / );

Using DBIx::Class::Schema::Loader

As it is now, DBIx::Class::Schema::Slave WORKS OUT with DBIx::Class::Schema::Loader. First, load DBIx::Class::Schema::Slave as component in your MyApp::Schema.

  # In your MyApp::Schema
  package MyApp::Schema;

  use base 'DBIx::Class::Schema::Loader';

  __PACKAGE__->load_components( qw/ Schema::Slave / );

Set slave_moniker as you like. If you do not specify, ::Slave is set.

  __PACKAGE__->slave_moniker('::Slave');

Set slave_connect_info as ARRAYREF of ARRAYREF.

  __PACKAGE__->slave_connect_info( [
      [ 'dbi:mysql:database:hostname=host', 'user', 'passsword', { ... } ],
      [ 'dbi:mysql:database:hostname=host', 'user', 'passsword', { ... } ],
      [ 'dbi:mysql:database:hostname=host', 'user', 'passsword', { ... } ],
      ...,
  ] );

Call loader_options in DBIx::Class::Schema::Loader. DO NOT forget to specify DBIx::Class::Row::Slave as component.

  __PACKAGE__->loader_options(
      relationships => 1,
      components    => [ qw/
          ...
          Row::Slave # DO NOT forget to load
          Core
      / ],
  );

Connecting (Create Schema instance)

To connect your Schema, provive connect_info not for slave but for master.

  my $schema = MyApp::Schema->connect( @master_connect_info );

Retrieving

Retrieving from master, you don't have to care about anything.

  my $album_master     = $schema->resultset('Album')->find( $id );
  my $itr_album_master = $schema->resultset('Album')->search( { ... }, { ... } );

Retrieving from slave, set slave moniker to resultset.

  my $track_slave     = $schema->resultset('Album::Slave')->find( $id );
  my $itr_track_slave = $schema->resultset('Album::Slave')->search( { ... }, { ... } );

Adding and removing rows

You can either create a new row or remove some rows from master. But you can neither create a new row nor remove some rows from slave.

  # These complete normally
  my $track = $schema->resultset('Track')->create( {
      created_on  => $dt->now || undef,
      modified_on => $dt->now || undef,
      album_id    => $album->id || undef,
      title       => $title || undef,
      time        => $time || undef,
  } );
  $track->title('WORLD\'S END SUPERNOVA');
  $track->update;
  $track->delete;

  # You got an error!
  # DBIx::Class::ResultSet::create(): Can't insert via result source "Track::Slave". This is slave connection.
  my $track = $schema->resultset('Track::Slave')->create( {
      created_on  => $dt->now || undef,
      modified_on => $dt->now || undef,
      album_id    => $album->id || undef,
      title       => $title || undef,
      time        => $time || undef,
  } );

  $track->title('TEAM ROCK');
  # You got an error!
  # DBIx::Class::Row::Slave::update(): Can't update via result source "Track::Slave". This is slave connection.
  $track->update;

  # And, you got an error!
  # DBIx::Class::Row::Slave::delete(): Can't delete via result source "Track::Slave". This is slave connection.
  $track->delete;

DO NOT call "update_all" in DBIx::Class::ResultSet, "delete_all" in DBIx::Class::ResultSet, "populate" in DBIx::Class::ResultSet and "update_or_create" in DBIx::Class::ResultSet via slave result_sources. Also you SHOULD NOT call "find_or_new" in DBIx::Class::ResultSet, "find_or_create" in DBIx::Class::ResultSet via slave result_sources.

CLASS DATA

Top

slave_moniker

Moniker suffix for slave. ::Slave default.

  # In your MyApp::Schema
  __PACKAGE__->slave_moniker('::Slave');

IMPORTANT: If you have already MyApp::Schema::Artist::Slave, DO NOT set ::Slave to slave_moniker. Set ::SlaveFor or something else.

slave_connect_info

connect_infos ARRAYREF of ARRAYREF for slave.

  # In your MyApp::Schema
  __PACKAGE__->slave_connect_info( [
      [ 'dbi:mysql:database:hostname=host', 'username', 'passsword', { ... } ],
      [ 'dbi:mysql:database:hostname=host', 'username', 'passsword', { ... } ],
      [ 'dbi:mysql:database:hostname=host', 'username', 'passsword', { ... } ],
      ...,
  ] );

slave_schema

Schema for slave. You can get this by slave.

METHODS

Top

register_source

Arguments: $moniker, $result_source
Return Value: none

Registers the DBIx::Class::ResultSource in the schema with the given moniker and re-maps class_mappings and source_registrations.

  # Re-mapped class_mappings
  class_mappings => {
      MyApp::Schema::Artist        => 'Artist',
      MyApp::Schema::Artist::Slave => 'Artist::Slave',
      MyApp::Schema::Album         => 'Album',
      MyApp::Schema::Album::Slave  => 'Album::Slave',
      MyApp::Schema::Track         => 'Track',
      MyApp::Schema::Track::Slave  => 'Track::Slave',
  }

  # Re-mapped source_registrations
  source_registrations => {
      MyApp::Schema::Artist => {
          bless( {
              ...,
              ...,
              ...,
          }, DBIx::Class::ResultSource::Table )
      },
      MyApp::Schema::Artist::Slave => {
          bless( {
              ...,
              ...,
              ...,
          }, DBIx::Class::ResultSource::Table )
      },
      ...,
      ...,
      ...,
      MyApp::Schema::Track::Slave => {
          bless( {
              ...,
              ...,
              ...,
          }, DBIx::Class::ResultSource::Table )
      },
  }

See "register_source" in DBIx::Class::Schema.

resultset

Arguments: $moniker
Return Value: $result_set

If $moniker is slave moniker, this method returns $result_set for slave. See "resultset" in DBIx::Class::Schema.

  my $master_rs = $schema->resultset('Artist');
  my $slave_rs  = $schema->resultset('Artist::Slave');

sources

Argunemts: none
Return Value: @sources

This method returns the sorted alphabetically source monikers of all source registrations on this schema. See "sources" in DBIx::Class::Schema.

  # Returns all sources including slave sources
  my @all_sources = $schema->sources;

master_sources

Argunemts: none
Return Value: @sources

This method returns the sorted alphabetically master source monikers of all source registrations on this schema.

  my @master_sources = $schema->master_sources;

slave_sources

Argunemts: none
Return Value: @sources

This method returns the sorted alphabetically slave source monikers of all source registrations on this schema.

  my @slave_sources = $schema->slave_sources;

slave_connect

Arguments: @info
Return Value: $slave_schema

This method creates slave connection, and store it in slave_schema. You can get this by slave. Usualy, you don't have to call it directry.

slave

Getter for slave_schema. You can get schema for slave if it stored in slave_schema.

  my $slave_schema = $schema->slave;

select_connect_info

Argunemts: none
Return Value: $connect_info

You can define this method in your schema class as you like. This method has to return $connect_info as ARRAYREF. If select_connect_info returns undef, undef value or not ARRAYREF, _select_connect_info will be called, and return $connect_info at random from slave_connect_info.

  # In your MyApp::Schame
  sub select_connect_info {
      my $self = shift;

      my @connect_info = @{$self->slave_connect_info};
      my $connect_info;
      # Some algorithm to select connect_info here

      return $connect_info;
  }

is_slave

Arguments: $string
Return Value: 1 or 0

This method returns 1 if $string (moniker, class name and so on) is slave stuff, otherwise returns 0.

  __PACKAGE__->slave_moniker('::Slave');

  # Returns 0
  $self->is_slave('Artist');

  # Returns 1
  $self->is_slave('Artist::Slave');

  # Returns 1
  $self->is_slave('MyApp::Model::DBIC::MyApp::Artist::Slave');

  __PACKAGE__->slave_moniker('::SlaveFor');

  # Returns 0
  $self->is_slave('Artist');

  # Returns 1
  $self->is_slave('Artist::SlaveFor');

  # Returns 1
  $self->is_slave('MyApp::Model::DBIC::MyApp::Artist::SlaveFor');

INTERNAL METHOD

Top

_select_connect_info

Return Value: $connect_info

Internal method. This method returns $connect_info for slave as ARRAYREF. Usually, you don't have to call it directry. If you select $connect_info as you like, define select_connect_info in your schema class. See select_connect_info for more information.

AUTHOR

Top

travail travail@cabane.no-ip.org

COPYRIGHT

Top


DBIx-Class-Schema-Slave documentation Contained in the DBIx-Class-Schema-Slave distribution.
package DBIx::Class::Schema::Slave;

use strict;
use warnings;
use base qw/ DBIx::Class /;
use Clone qw//;

our $VERSION = '0.02400';

__PACKAGE__->mk_classdata( slave_moniker => '::Slave' );
__PACKAGE__->mk_classdata('slave_schema');
__PACKAGE__->mk_classdata('slave_connect_info' => [] );

## TODO remove next major release
sub slave_connection {
    my $self = shift;
    warn "DBIx::Class::Schema::Slave::slave_connection is changed to " .
        "DBIx::Clas::Schema::Slave::slave_schema. " .
        "This message will be removed next major release.";
    return $self->slave_schema;
}

## TODO remove next major release
sub connect_slave {
    my $self = shift;
    warn "DBIx::Class::Schema::Slave::connect_slave was changed to " .
        "DBIx::Class::Schema::Slave::slave_connect. " .
        "This message will be removed next major release.";
    return $self->slave_connect( @_ );
}

sub register_source {
    my ( $self, $moniker, $source ) = @_;

    unless ( $self->is_slave( $moniker ) ) {
        my $s_moniker = $moniker . $self->slave_moniker;
        my $s_source  = $source->new( $self->_clone_source( $source ) );
        $self->next::method( $s_moniker, $s_source );
    }

    $self->next::method( $moniker, $source );
}

sub _clone_source {
    my ( $self, $source ) = @_;

    my $s_moniker = $self->slave_moniker;
    my $c_source  = Clone::clone( $source );
    $self->_slave_relationships( $c_source );

    if ( ref $self ) {
        no strict 'refs';
        no warnings 'redefine';
        local *Class::C3::reinitialize = sub {};
        my $s_result_class = $c_source->result_class . $s_moniker;
        ## Set VERSION to create pseudo namespace.
        local ${"${s_result_class}::VERSION"} ||= 1;
        $self->inject_base( $s_result_class => $source->result_class );
        $c_source->result_class( $s_result_class );
    }

    return $c_source;
}

sub _slave_relationships {
    my ( $self, $source ) = @_;

    my $s_moniker = $self->slave_moniker;
    my %rels = %{$source->_relationships};
    return unless %rels;

    foreach my $rel ( keys %rels ) {
        $rels{$rel}->{source} = $rels{$rel}->{source} . $s_moniker
            unless $self->is_slave( $rels{$rel}->{source} );
        $rels{$rel}->{class} = $rels{$rel}->{class} . $s_moniker
            unless $self->is_slave( $rels{$rel}->{class} );
    }

    $source->_relationships( \%rels );
}

sub resultset {
    my ( $self, $moniker ) = @_;

    if ( $self->is_slave( $moniker ) ) {
        ## connect slave
        if ( $self->slave ) {
            ## TODO re-select per not ->resultset('Foo::Slave'), but request.
            $self->slave->storage->connect_info( $self->_select_connect_info );
        } else {
            $self->slave_connect( @{$self->_select_connect_info} );
        }
        ## TODO more tidily
        $self->slave->storage->debug( $self->storage->debug );
        $self->slave->storage->debugobj( $self->storage->debugobj );
        $self->slave->next::method( $moniker );
    } else {
        ## connect master
        $self->next::method( $moniker );
    }
}

sub sources {
    my $self = shift;

    $self->next::method( @_ );
    return sort( { $b cmp $a } keys( %{$self->source_registrations} ) );
}

sub master_sources { grep { !$_[0]->is_slave( $_ ) } $_[0]->sources }

sub slave_sources { grep { $_[0]->is_slave( $_ ) } $_[0]->sources }

sub slave_connect { $_[0]->slave_schema( shift->connect( @_ ) ) }

sub slave { shift->slave_connection }

sub select_connect_info {}

sub is_slave {
    my ( $self, $string ) = @_;

    my $match = $self->slave_moniker;
    return $string =~ m/$match$/o ? 1 : 0;
}

sub _select_connect_info {
    my $self = shift;

    my $info = ( $self->can('select_connect_info')
                 && $self->select_connect_info
                 && ref $self->select_connect_info eq 'ARRAY' )
        ? $self->select_connect_info
        : $self->slave_connect_info->[ rand @{$self->slave_connect_info} ];

#    warn "Select slave_connect_info $info";
    return $info;
}

# sub _dbd_multi {
#     my $self = shift;

#     my @connect_info  = @{$self->slave_connect_info};
#     my $dbd_multi_opt = ref $connect_info[-1] eq 'HASH' ?
#         pop @connect_info : {};
#     $dbd_multi_opt->{limit_dialect} = $self->storage->sql_maker->limit_dialect
#         unless defined $dbd_multi_opt->{limit_dialect};

#     @connect_info = map {
#         my $dsn = ref $_->[0] ? $_->[0] : $_;
#         my $pri = ( ref $_->[-1] && $_->[-1]->{priority} ) ?
#             $_->[-1]->{priority} : 10;
#         $pri => $dsn;
#     } @connect_info;

#     return [ 'dbi:Multi:', undef, undef,
#             { dsns => \@connect_info, %$dbd_multi_opt } ];
# }

1;