using DBIx::Class::DeploymentHandler

I used to work with DBIx::Class::Schema::Versioned to upgrade my DBIC Schema but soon I needed more then it offered.

Starting with DBIx::Class::DeploymentHandler was a bit troublesome because I had a hard-ish time understanding the extensive documentation.

Now that I moved past that phase I want to present my way of using DeploymentHandler and hopefully spare you some of the burden.

Note: I write my DBIC schema resultclasses by hand and deploy to whatever database system I need.

Feature list * each schema version should be installable to a clean/empty database * single step upgrades of schema versions * create pl scripts that do something on the schema before and/or after upgrade

 ./script/database.pl 
usage:
  database.pl --cmd prepare [ --from-version $from --to-version $to ]
  database.pl --cmd install [ --version $version ]
  database.pl --cmd upgrade
  database.pl --cmd database-version
  database.pl --cmd schema-version

Simply create your DBIC schema and once you are ready add

our $VERSION = 1;

to Schema.pm

Run

database.pl --cmd prepare

this command creates the following files

db_upgrades/
├── PostgreSQL
│   └── deploy
│       └── 1
│           ├── 001-auto.sql
│           └── 001-auto-__VERSION.sql
└── _source
    └── deploy
        └── 1
            ├── 001-auto-__VERSION.yml
            └── 001-auto.yml

Run

database.pl --cmd install

to deploy the schema to your database

Next we change the schema. add a column to a table and increase the schema version to 2.

then run

database.pl --cmd prepare --from-version 1 --to-version 2

the deployment directory now look like this:

db_upgrades/
├── PostgreSQL
│   ├── deploy
│   │   ├── 1
│   │   │   ├── 001-auto.sql
│   │   │   └── 001-auto-__VERSION.sql
│   │   └── 2
│   │       ├── 001-auto.sql
│   │       └── 001-auto-__VERSION.sql
│   └── upgrade
│       └── 1-2
│           └── 001-auto.sql
└── _source
    └── deploy
        ├── 1
        │   ├── 001-auto-__VERSION.yml
        │   └── 001-auto.yml
        └── 2
            ├── 001-auto-__VERSION.yml
            └── 001-auto.yml

Sometimes you want to do stuff with your schema before you change the DDL. With DBIx::Class::DeploymentHandler you can run SQL and or PL files before and after changing the DDL.

Since perl scripts are mostly independent of your choice of DBRMS it's best to put them in the special directory _common. Files from _common will be merged with the storage specific files So we have to make sure the file names reflect the order we want them exectuted in.

create directroy

mkdir -p db_upgrades/_common/upgrade/1-2/

create perl script

touch db_upgrades/_common/upgrade/1-2/001_do_stuff_BEFORE_ddl_change.pl

rename auto-generated sql file so the perl script is exectured before the DDL change

mv db_upgrades/PostgreSQL//upgrade/1-2/001-auto.sql db_upgrades/PostgreSQL//upgrade/1-2/002-auto.sql

db_upgrades/
├── _common
│   └── upgrade
│       └── 1-2
│           └── 001_do_stuff_BEFORE_ddl_change.pl
├── PostgreSQL
│   ├── deploy
│   │   ├── 1
│   │   │   ├── 002-auto.sql
│   │   │   └── 001-auto-__VERSION.sql
│   │   └── 2
│   │       ├── 001-auto.sql
│   │       └── 001-auto-__VERSION.sql
│   └── upgrade
│       └── 1-2
│           └── 001-auto.sql
└── _source
    └── deploy
        ├── 1
        │   ├── 001-auto-__VERSION.yml
        │   └── 001-auto.yml
        └── 2
            ├── 001-auto-__VERSION.yml
            └── 001-auto.yml

Here is an example script 001dostuffBEFOREddl_change.pl

#!/usr/bin/env perl
use strict;
use warnings;

use DBIx::Class::DeploymentHandler::DeployMethod::SQL::Translator::ScriptHelpers 'schema_from_schema_loader';

schema_from_schema_loader(
    { naming => 'current' },
    sub {
        my ( $schema, $versions ) = @_;
        # do stuff with $schema
    }
);

and finally my database.pl file

#!/usr/bin/env perl

use strict;
use warnings;
use 5.010;
use DBIx::Class::DeploymentHandler;
use feature qw/ switch /;
use Getopt::Long;

my $cmd = '';
my $from_version;
my $to_version;
my $version;
GetOptions(
    'command|cmd|c=s' => \$cmd,
    'from-version=i'  => \$from_version,
    'to-version=i'    => \$to_version,
    'version=i'       => \$version,
);

sub usage {
    say <<'HERE';
usage:
  database.pl --cmd prepare [ --from-version $from --to-version $to ]
  database.pl --cmd install [ --version $version ]
  database.pl --cmd upgrade
  database.pl --cmd database-version
  database.pl --cmd schema-version
HERE
    exit(0);
}

my $schema = '...'; # wherever you get your schema from.
my $deployment_handler_dir = './db_upgrades'

my $dh = DBIx::Class::DeploymentHandler->new(
    {   schema           => $schema,
        script_directory => $deployment_handler_dir,
        databases        => 'PostgreSQL',
        force_overwrite  => 1,
    }
);

die "We only support positive integers for versions."
    unless $dh->schema_version =~ /^\d+$/;

for ($cmd) {
    when ('prepare')          { prepare() }
    when ('install')          { install() }
    when ('upgrade')          { upgrade() }
    when ('database-version') { database_version() }
    when ('schema-version')   { schema_version() }
    default                   { usage() }
}

sub prepare {
    say "running prepare_install()";
    $dh->prepare_install;

    if ( defined $from_version && defined $to_version ) {
        say
            "running prepare_upgrade({ from_version => $from_version, to_version => $to_version })";
        $dh->prepare_upgrade(
            {   from_version => $from_version,
                to_version   => $to_version,
            }
        );
    }
}

sub install {
    if ( defined $version ) {
        $dh->install({ version => $version });
    }
    else {
        $dh->install;
    }
}

sub upgrade {
    $dh->upgrade;
}

sub database_version {
    say $dh->database_version;
}

sub schema_version {
    say $dh->schema_version;
}

Nginx: FastCGI vs. starman (Erratum)

2 weeks ago I've written Moving my Catalyst Apps from Apache/FCGI to Nginx/Starman

Yesterday I have discussed my setup with mst, jnap and joel on irc.perl.org #catalyst

Here is the gist of what mst had to say.

15:50 <@mst> you're using unix sockets
15:50 <@mst> ergo there's no gain to using starman over fastcgi, you're just using a less efficient protocol
15:51 <@mst> the modern ways are starman via http TCP proxy, and fastcgi via unix socket
15:51 <@mst> you're currently using starman to emulate myapp_fastcgi.pl
15:51 <@mst> and I can't see the point

Here is another post about the same topic. nginx and Perl: FastCGI vs reverse proxy Forget the last downside bullet from the first reply though. thats bogus.

Here is my new setup (debian wheezy, stick to the official deployment instructions if you run into problems with my config)

nginx

server {
    listen 80;
    server_name soundgarden.com *.soundgarden.com;
    client_max_body_size 50m;

    location / {
      include /etc/nginx/fastcgi_params;
      fastcgi_param SCRIPT_NAME '';
      fastcgi_param PATH_INFO $fastcgi_script_name;
      fastcgi_pass unix:/var/www/Soundgarden/soundgarden.socket;
    }

    location /static {
      root /var/www/Soundgarden/root;
      expires 30d;
    }
}

initd

#!/usr/bin/env perl
use warnings;
use strict;
use Daemon::Control;

# 1) create initd file
# ./soundgarden.starman.initd get_init_file > foo
#
# 2) copy to /etc/init.d/cat-soundgarden
# cp foo /etc/init.d/cat-soundgarden
#
# 3) install to runlevels
# update-rc.d cat-soundgarden defaults


my $app_home = '/var/www/Soundgarden';
my $perl     = '/var/www/perl5/perlbrew/perls/perl-5.16.2/bin/perl';
my $program  = $app_home . '/script/soundgarden_fastcgi.pl';
my $name     = 'Soundgarden';
my $workers  = 1;
my $pid_file = $app_home . '/soundgarden.pid';
my $socket   = $app_home . '/soundgarden.socket';

Daemon::Control->new({
    name        => $name,
    lsb_start   => '$nginx',
    lsb_stop    => '$nginx',
    lsb_sdesc   => $name,
    lsb_desc    => $name,
    path        => $app_home . '/soundgarden.fastcgi.initd',

    user        => 'www-data',
    group       => 'www-data',
    directory   => $app_home,
    program     => "$perl $program --nproc $workers --listen $socket",

    pid_file    => $pid_file,
    stderr_file => $app_home . '/soundgarden.out',
    stdout_file => $app_home . '/soundgarden.out',

    fork        => 2,
})->run;

Moving my Catalyst Apps from Apache/FCGI to Nginx/Starman

Recently I had to move all my projects to a new server and decided to give Nginx and Starman a chance.

The Nginx config is rather simple.

my Catalyst Application is located at /var/www/MyApp

server {
    listen 80;
    server_name myapp.at *.myapp.at;

    location / {
      include /etc/nginx/proxy_params;
      proxy_pass http://unix:/var/www/MyApp/myapp.socket:/;
    }

    location /static {
      root /var/www/MyApp/root;
      expires 30d;
    }
}

Nginx expects the Catalyst Application to listen on the socket /var/www/MyApp/myapp.socket.

Here is my initd script that takes care of starting and stopping the starman processes. (/var/www/MyApp/myapp.starman.initd)

#!/usr/bin/env perl
use warnings;
use strict;
use Daemon::Control;

my $app_home = '/var/www/MyApp';
my $program  = '/var/www/perl5/perlbrew/perls/perl-5.16.2/bin/starman';
my $name     = 'MyApp';
my $workers  = 1;
my $pid_file = $app_home . '/myapp.pid';
my $socket   = $app_home . '/myapp.socket';

Daemon::Control->new({
    name        => $name,
    lsb_start   => '$nginx',
    lsb_stop    => '$nginx',
    lsb_sdesc   => $name,
    lsb_desc    => $name,
    path        => $app_home . '/myapp.starman.initd',

    user        => 'www-data',
    group       => 'www-data',
    directory   => $app_home,
    program     => "$program -Ilib myapp.psgi --workers $workers --listen $socket",

    pid_file    => $pid_file,
    stderr_file => $app_home . '/myapp.out',
    stdout_file => $app_home . '/myapp.out',

    fork        => 2,
})->run;

Check out Daemon::Control, it's a really helpful CPAN module that eases the creation of init scripts. Let's create the initd script.

./myapp.starman.initd get_init_file > /etc/init.d/cat-myapp

You want the starman processes to survive a reboot, so let's install to runlevels

update-rc.d cat-myapp defaults

Now reload Nginx and start your starman processes and you should be good to go.

I just started using this kind of config, so any problems wouldn't surprise me too much.

CPAN ratings

There are a couple of indicators I take into account when evaluating perl modules.

  • Update frequency and date of last update
  • Usually I look at the source and check for existance of test files.
  • The next step would be to check cpantesters results.
  • Some cpan authors are known for writing quality code
  • Also helpful sometimes are the cpan ratings.

I just wanted to submit ratings for some of the modules I really like but “failed” to do so because apparently it requires a bitcard login.

Q: What is bitcard and why can’t I use my PAUSE login?
A: http://en.wikibooks.org/wiki/Perl_Programming/CPAN/Bitcard

Bitcard is an open single-sign-on web-authentication service. It’s free for both users and web sites. It is used by most perl.org services.

Ideally I would like to use one account for all perl services. If I click “login” on blogs.perl.org, cpan, … a small disclaimer that bitcard is used in the background would suffice.

PS: after proofreading this post I’d like to make sure no one feels offended. So don’t!

open module under cursor in vim

Inspired by http://www.slideshare.net/c9s/perlhacksonvim I wrote (well ... copied for the larger part) a script to open the Module currently under the cursor in vim.

Typing \fm will lookup the first Module found in available Perl library paths (plus current working directoy . '/lib')

I did search for some time and read a bit about ctags and pltags but ended up confused. add this to your vimrc


""""""""""""""""""""""""""""""""""""
" find module in perl INC and edit "
""""""""""""""""""""""""""""""""""""
function!…