Mojolicious and DBI handles

Tags:

From http://toroid.org/ams/etc/mojolicious-db-handles we find this bit of code:

sub startup {
  my $app = shift;

  my ($user, $pass);
  # read $user and $pass from config file
  (ref $app)->attr(db => sub {
    DBI->connect("dbi:Mysql:database=$db",
                 $user, $pass)
    }
  );
}

The concept is that Morbo, or Starman, or whatever PSGI server is running your program, can use any kind of forking, but each instance of your program will execute the startup code and create its own database handle.

Some of the code is not too obvious:

And here's how you get the data back out:

  $app->get('/test')->to(cb =>
               sub{ 
               my $self = shift;
               $Data::Dumper::Indent = 1;
               $self->render( 'text' => "TEST: ATTR of foo" . $self->app->foo .
                    "\n<pre>\n" . Dumper($self) . "</pre>" );
               });

It's really a better idea to use DBIx::Connector anyway, but the startup attribute trick might come in handy elsewhere. One note on DBIX::Connector − Under the heading, Execution Methods we see the code:

  <span class="sh_variable">$conn</span><span class="sh_symbol">-></span><span class="sh_function">run</span><span class="sh_symbol">(</span><span class="sh_keyword">sub</span> <span class="sh_cbracket">{</span> <span class="sh_variable">$_</span><span class="sh_symbol">-></span><span class="sh_keyword">do</span><span class="sh_symbol">(</span><span class="sh_variable">$query</span><span class="sh_symbol">)</span> <span class="sh_cbracket">}</span><span class="sh_symbol">);</span>

It is not documented, but by reading the source (let's hear it for free software!) what happens is that $_ is set, via the «local» keyword, to temporarily be the database handle (dbh) of the connection within execution of the code reference.

Thanks to mst and others on #perl for the assistance.

Cleanly create SQL with SQL::Abstract::More

Tags:

Some years ago now, I had written my own wrapper around DBI to make common SQL statements easier to write. Awhile back I found SQL::Abstract and recently revisited some old code to bring it up to the modern era. I found that the related SQL::Abstract::More was almost a direct replacement for what I had written − except with more features and better debugged. Here's an example:

#!/usr/env/perl

use strict;
use warnings;

use SQL::Abstract::More;

my $sqla = SQL::Abstract::More->new;

# More maintainable with named parameters.
($stmt, @bind) = $sqla->select(
    -columns => [qw(listname id description from_email),
         'mailing_list.active|active',
         "COUNT(subscriber.person_id)|subcribercount",
    ],
    -from => [qw(mailing_list subscriber)],
    -where => {'subscriber.list_id' => {'=', \ 'mailing_list.id'},
           'subscriber.active' => {'<>', \ 0}
    },
    -group_by => "list_id",
    );

print "$stmt\n" . join(',',@bind) . "\n";

The Above example produces the code ready to pass to SQL:

    SELECT listname, id, description, from_email, 
      mailing_list.active AS active, 
      COUNT(subscriber.person_id) AS subcribercount
    FROM mailing_list, subscriber
    WHERE ( ( subscriber.active <> 0 AND 
      subscriber.list_id = mailing_list.id ) ) 
    GROUP BY list_id

After that, it's just a matter of execution:

my $sth = $dbh->prepare($stmt);
$sqla->bind_params($sth, @bind);
$sth->execute;

You could do the same thing with SQL::Abstract itself, but you have to use somewhat-undocumented features for the "as" and "group by" pieces. Specifically, you can use a scalar reference for a literal field name in the columns part, and shove the "group by" clause into the order field; but you'll have to build the group clause yourself:

use SQL::Abstract;

my $sql = SQL::Abstract->new;

my ($stmt, @bind) =
    $sql->select([qw(mailing_list subscriber)], 
                 [qw(listname id description from_email),
                  \ "mailing_list.active AS active",
                  \ "COUNT(subscriber.person_id) AS subcribercount"
                 ],
                 {'subscriber.list_id' => \ '= mailing_list.id',
                  'subscriber.active' => \ '<> 0'},
                 "asc GROUP by list_id"
    );
print "$stmt\n" . join(',',@bind) . "\n";

Altogether, SQL::Abstract::More is superior.