dbic_chado
Author: Robert Buels

# Introduction to DBIx::Class

## Portland, OR

### April 14, 2010

# What is DBIx::Class?

- Object-relational mapping framework for Perl
- is now the de-facto standard
- nice features

# What is DBIx::Class?

- query building (the magic of chainable ResultSets)
- cross-database deployment (using `SQL::Translator` in the backend)
- lots and lots of plugins availabe (dates, testing, ...)

see the manual: http://search.cpan.org/dist/DBIx-Class/lib/DBIx/Class/Manual.pod

# What it looks like

    package My::Schema;

    __PACKAGE__->load_namespaces;

    package My::Schema::Result::Foo;

    __PACKAGE__->table('foo');
    __PACKAGE__->add_columns(qw( foo_id bar baz ));
    __PACKAGE__->has_many('bars' => 'My::Schema::Result::Bar');
    # also belongs_to  many_to_many

    package My::Schema::Result::Bar;

    __PACKAGE__->table('bar');
    # and so on

- use `DBIx::Class::Schema::Loader` to generate this from a DB
- use `$schema->deploy` to make a DB from this

# Basic Usage

- open the schema. ( actual DB connection is deferred )

continued...

# Basic Usage

- open the schema. ( actual DB connection is deferred )

    my $schema = My::Schema->connect( 'dbi:Pg:...', $user, $pass );

continued...

# Basic Usage

- open the schema. ( actual DB connection is deferred )

    my $schema = My::Schema->connect( 'dbi:Pg:...', $user, $pass );

- get a ResultSet object representing a set of Rows

continued...

# Basic Usage

- open the schema. ( actual DB connection is deferred )

    my $schema = My::Schema->connect( 'dbi:Pg:...', $user, $pass );

- get a ResultSet object representing a set of Rows

    my $orgs_rs = $schema->resultset('Organism');

continued...

# Basic Usage

- open the schema. ( actual DB connection is deferred )

    my $schema = My::Schema->connect( 'dbi:Pg:...', $user, $pass );

- get a ResultSet object representing a set of Rows

    my $orgs_rs = $schema->resultset('Organism');

- take a subset of those (another ResultSet)

continued...

# Basic Usage

- open the schema. ( actual DB connection is deferred )

    my $schema = My::Schema->connect( 'dbi:Pg:...', $user, $pass );

- get a ResultSet object representing a set of Rows

    my $orgs_rs = $schema->resultset('Organism');

- take a subset of those (another ResultSet)

    my $chickeny_things = $orgs_rs->search({ genus => 'Gallus' });

continued...

# Basic Usage

- open the schema. ( actual DB connection is deferred )

    my $schema = My::Schema->connect( 'dbi:Pg:...', $user, $pass );

- get a ResultSet object representing a set of Rows

    my $orgs_rs = $schema->resultset('Organism');

- take a subset of those (another ResultSet)

    my $chickeny_things = $orgs_rs->search({ genus => 'Gallus' });

- first one in the result set

continued...

# Basic Usage

- open the schema. ( actual DB connection is deferred )

    my $schema = My::Schema->connect( 'dbi:Pg:...', $user, $pass );

- get a ResultSet object representing a set of Rows

    my $orgs_rs = $schema->resultset('Organism');

- take a subset of those (another ResultSet)

    my $chickeny_things = $orgs_rs->search({ genus => 'Gallus' });

- first one in the result set

    my $chicken = $chickeny_things->first;     #< actual query is run

# Basic Usage

continued...

# Basic Usage

- getting actual data

continued...

# Basic Usage

- getting actual data

    my $wombat = $orgs_rs->find( 232432 );

continued...

# Basic Usage

- getting actual data

    my $wombat = $orgs_rs->find( 232432 );
    say $wombat->genus.' '.$wombat->species; #< print 'Vombatus ursinus' or something

    # or search in list context returns all resulting rows
    my @all_nightshades = $orgs_rs->search({ genus => 'Solanum' });

# Basic Usage

- traverse relationships (joined queries)

continued...

# Basic Usage

- traverse relationships (joined queries)

    my $chickeny_things = $ck_rs->search({ genus => 'Gallus' });

continued...

# Basic Usage

- traverse relationships (joined queries)

    my $chickeny_things = $ck_rs->search({ genus => 'Gallus' });
    my @chr = $chickeny_things->chromosomes;  #< query is run here

continued...

# Basic Usage

- traverse relationships (joined queries)

    my $chickeny_things = $ck_rs->search({ genus => 'Gallus' });
    my @chr = $chickeny_things->chromosomes;  #< query is run here
    my @genes = $chicken->chromosomes->features->search({ type => 'gene' });

continued...

# Basic Usage

- traverse relationships (joined queries)

    my $chickeny_things = $ck_rs->search({ genus => 'Gallus' });
    my @chr = $chickeny_things->chromosomes;  #< query is run here
    my @genes = $chicken->chromosomes->features->search({ type => 'gene' });

- loading

continued...

# Basic Usage

- traverse relationships (joined queries)

    my $chickeny_things = $ck_rs->search({ genus => 'Gallus' });
    my @chr = $chickeny_things->chromosomes;  #< query is run here
    my @genes = $chicken->chromosomes->features->search({ type => 'gene' });

- loading

    $orgs_rs>create({ genus => 'Mus', species => 'musculus' })

continued...

# Basic Usage

- traverse relationships (joined queries)

    my $chickeny_things = $ck_rs->search({ genus => 'Gallus' });
    my @chr = $chickeny_things->chromosomes;  #< query is run here
    my @genes = $chicken->chromosomes->features->search({ type => 'gene' });

- loading

    $orgs_rs>create({ genus => 'Mus', species => 'musculus' })
    # or piecewise

continued...

# Basic Usage

- traverse relationships (joined queries)

    my $chickeny_things = $ck_rs->search({ genus => 'Gallus' });
    my @chr = $chickeny_things->chromosomes;  #< query is run here
    my @genes = $chicken->chromosomes->features->search({ type => 'gene' });

- loading

    $orgs_rs>create({ genus => 'Mus', species => 'musculus' })
    # or piecewise
    my $new = $orgs_rs->new;
    $new->genus('Mus');  $new->species('musculus');
    $new->insert;

continued...

# Basic Usage

- traverse relationships (joined queries)

    my $chickeny_things = $ck_rs->search({ genus => 'Gallus' });
    my @chr = $chickeny_things->chromosomes;  #< query is run here
    my @genes = $chicken->chromosomes->features->search({ type => 'gene' });

- loading

    $orgs_rs>create({ genus => 'Mus', species => 'musculus' })
    # or piecewise
    my $new = $orgs_rs->new;
    $new->genus('Mus');  $new->species('musculus');
    $new->insert;

- deleting

    $chicken->chromosomes->features->search({ type => 'gene' })->delete;

# When not to use DBIC

- tiny app, tiny schema (setup overhead)

continued...

# When not to use DBIC

- tiny app, tiny schema (setup overhead)
- or tiny number of different queries

continued...

# When not to use DBIC

- tiny app, tiny schema (setup overhead)
- or tiny number of different queries
- or hate objects

strengths/weaknesses ...

# Strengths and Weaknesses

- strengths

continued...

# Strengths and Weaknesses

- strengths

- chainable ResultSets

continued...

# Strengths and Weaknesses

- strengths

- chainable ResultSets
- lazy querying

continued...

# Strengths and Weaknesses

- strengths

- chainable ResultSets
- lazy querying
- good support for custom SQL

continued...

# Strengths and Weaknesses

- strengths

- chainable ResultSets
- lazy querying
- good support for custom SQL
- prefetching

continued...

# Strengths and Weaknesses

- strengths

- chainable ResultSets
- lazy querying
- good support for custom SQL
- prefetching
- lots of nice extensions

continued...

# Strengths and Weaknesses

- strengths

- chainable ResultSets
- lazy querying
- good support for custom SQL
- prefetching
- lots of nice extensions

- weaknesses

continued...

# Strengths and Weaknesses

- strengths

- chainable ResultSets
- lazy querying
- good support for custom SQL
- prefetching
- lots of nice extensions

- weaknesses

- non-ORM-y things are possible, but can be cumbersome

# Chainable ResultSets

    my $styx_tracks =
         $music_schema->resultset('Artist')
                      ->search({ name => 'Styx' })
                      ->albums
                      ->tracks;

    SELECT ...
      FROM artist
      JOIN albums ON ...
      JOIN tracks ON ...
      WHERE artist.name = 'Styx'

# Lazy Querying

- does not run the query until data is actually needed

    # fetch all
    my @tracks = $styx_tracks->all;

    # or iterate
    while( my $track = $styx_tracks->next ) { ... }

# Custom SQL

- can make a view `ResultSource` for big tuned queries

    package My::Schema::SummarizedFrobs;
    __PACKAGE__->table_class('DBIx::Class::ResultSource::View');
    __PACKAGE__->result_source_instance->view_definition(<<'');
       SELECT omg_so_much_stuff
       UNION
       SELECT ( SELECT blah blah blah FROM blah ),
              ( blah blah blah ),
       WHERE blah blah blah
       UNION
          blah blah blah

    __PACKAGE__->add_column( <cols in your view> )

# Custom SQL

- for custom conditions, searches accept `SQL::Abstract` syntax

    $schema->resultSet('Sequence::Feature')
           ->search({
                'me.feature_id' =>
                  \[ "IN( select feature_id from clone_feature where clone_id = ?)",
                     [ dummy => $self->clone_id ],
                   ],
                },
                { rows => 10,
                  order_by => [qw[ me.name me.type_id ]],
                  having
                });

# Prefetching

    # query will get the wombat organism, and also prefetch *all its
    # chromosomes and features on those chromosomes*
    my $rs = $schema->resultset('Organism')->search(
     { genus => 'Vombatus', species => 'ursinus' },
     { prefetch => { chromosomes => features => } },
    );

(in a real biological DB this would probably blow your memory)

# Extensions

- `DBIx::Class::Helpers`

- `fREW`'s miscellaneous things

continued...

# Extensions

- `DBIx::Class::Helpers`

- `fREW`'s miscellaneous things

- `DBIx::Class::Cursor::Cached`

- tunable, flexible resultset caching

continued...

# Extensions

- `DBIx::Class::Helpers`

- `fREW`'s miscellaneous things

- `DBIx::Class::Cursor::Cached`

- tunable, flexible resultset caching

- `DBIx::Class::Ordered`

- nice for doing ordering or ranking columns in a table

continued...

# Extensions

- `DBIx::Class::Helpers`

- `fREW`'s miscellaneous things

- `DBIx::Class::Cursor::Cached`

- tunable, flexible resultset caching

- `DBIx::Class::Ordered`

- nice for doing ordering or ranking columns in a table

- `DBIx::Class::InflateColumn::DateTime`, `DBIx::Class::Timestamp`

- work with dates, times, and timestamps as `DateTime` objects

# Thread-Safe / Fork-Safe

- `theory`'s `DBix::Connector`

# Non-linear Join

    # get features via the potato organism, also joining in the cvterms table
    my $potato_bacs =
         $schema->resultset('Organism')
                ->search({ species => 'Solanum tuberosum' })
                ->search_related( 'features',
                                 { 'type.name' => 'BAC_clone'},
                                 { 'join' => 'type' },
                               );

# Non-linear Join

    # the equivalent bare SQL
    my $potato_bacs = $dbh->selectall_arrayref( <<EOS, undef, 'Solanum tuberosum', 'BAC_clone');
    SELECT features.feature_id
         , features.dbxref_id
         , features.organism_id
         , features.name
         , features.uniquename
         , features.residues
         , features.seqlen
         , features.md5checksum
         , features.type_id
         , features.is_analysis
         , features.is_obsolete
         , features.timeaccessioned
         , features.timelastmodified
    FROM organism me
    LEFT JOIN feature features
           ON features.organism_id = me.organism_id
    JOIN cvterm type
           ON type.cvterm_id = features.type_id
    WHERE species = ? AND type.name = ?
    EOS

# Convenient Loading of Relationships

    $chado->resultset( 'Cv::Cv' )
          ->find_or_create({ name => 'My Fake Ontology' })
          ->create_related(  'cvterm',
                             { name => 'MyFakeTerm' });

makes the SQL:

    SELECT me.cv_id
         , me.name
         , me.definition
    FROM cv me
    WHERE ( me.name = 'my fake ontology' )

    INSERT INTO cv ( name )
            VALUES ( 'my fake ontology' )
         RETURNING cv_id

    INSERT INTO cvterm ( cv_id,  name        )
                VALUES ( ?,     'MyFakeTerm' )

# Transactions

    $schema->txn_do(sub {

        $schema->resultset('Cv::Cv')
               ->find_or_create({ name => 'My Fake Ontology' })
               ->create_related( 'cvterm', { name => 'MyFakeTerm' } );

    });

# The Real Advantages of DBIC

- easier to manipulate and assemble queries

# The Real Advantages of DBIC

- complex joined queries (Chado queries) are very easy and compact

# The Real Advantages of DBIC

- SQL syntax errors are more difficult to make

# The Real Advantages of DBIC

- it's all objects. you can delegate to them, pass them around, etc.
- HOWEVER:

continued...

# The Real Advantages of DBIC

- it's all objects. you can delegate to them, pass them around, etc.
- HOWEVER:

- usually you don't want to subclass them
- but, see `DBIx::Class::Manual::Cookbook`

## That's All

- The END