Question

Following the guide here I wanted to place a DBI connection handling subroutine in my perl code. I figured the BEGIN block was a reasonable place to put it so if I ever have an issue with the DBI connection it will fail prior to moving through the remaining (which is rather long).

BEGIN {
        my $dbh;
        use constant {
                my_host => 'database=MYDB;host=MYHOST',
                my_user => 'USER',
                my_pass => 'PASSWORD'
        };

        sub get_db_handle {
                unless (defined ($dbh)) {
                my $dbh = DBI->connect(
                                ('DBI:mysql:' . my_host),
                                my_user, my_pass,
                                {PrintError => 0, AutoCommit => 0}
                                ) or die $DBI::errstr;
        }
        return $dbh;
        }
}

...
        # Everything works fine without it being in the BEGIN
        my $db = get_db_handle();
        my $sth = $db->prepare($sql);

When run however I get the expected error:

Can't call method "prepare" on an undefined value

To get around this I run the DBI connect string right after declaring the use constant values and it works, but this doesn't seem to be the best approach and the author did not have any additional information on this implementation posted.

Is there a typically usage illustrating this approach with creating the DBI handler and a sub to pass it around a script that I should look into, or is there a better approach to the defined statement that will allow me to use the above method?

Was it helpful?

Solution 3

You're declaring two variables named $dbh. Use $dbh instead of my $dbh to use an existing variable.


Also, you don't actually try to connect at compile-time as you want to do, and you forgot to check if prepare succeeded. Fixed:

use constant {
   MY_HOST => 'database=MYDB;host=MYHOST',
   MY_USER => 'USER',
   MY_PASS => 'PASSWORD',
};

{
   my $dbh;

   sub get_db_handle {
       $dbh ||= DBI->connect(
          'DBI:mysql:' . MY_HOST,
          MY_USER, MY_PASS,
          { PrintError => 0, AutoCommit => 0 },
       )
          or die $DBI::errstr;

       return $dbh;
   }
}

# Make sure DB errors are discovered early.
BEGIN { get_db_handle(); }

...

my $dbh = get_db_handle();
my $sth = $dbh->prepare($sql)
   or die $DBI::errstr;

If you use RaiseError => 1, this can be shortened to

use constant {
   MY_HOST => 'database=MYDB;host=MYHOST',
   MY_USER => 'USER',
   MY_PASS => 'PASSWORD',
};

{
   my $dbh;

   sub get_db_handle {
       return $dbh ||= DBI->connect(
          'DBI:mysql:' . MY_HOST,
          MY_USER, MY_PASS,
          { RaiseError => 1, PrintError => 0, AutoCommit => 0 },
       );
   }
}

# Make sure DB errors are discovered early.
BEGIN { get_db_handle(); }

...

my $dbh = get_db_handle();
my $sth = $dbh->prepare($sql);

Not sure how much sense it makes to cache a handle that has transactions enabled, though.

OTHER TIPS

Remove 2-nd my $dbh:

sub get_db_handle {
                unless (defined ($dbh)) {
                $dbh = DBI->connect(# <- no "my" here
                                ('DBI:mysql:' . my_host),
                                my_user, my_pass,
                                {PrintError => 0, AutoCommit => 0}
                                ) or die $DBI::errstr;
        }

The problem is that you have declared two $dbh variables inside the BEGIN block.

The unless checks whether the outer $dbh has been defined and, if not, declares a new $dbh, assigns a database handle to it, and then throws it away.

The subroutine returns the value of the outer $dbh, which is always undef.

The solution is to remove the my from the beginning of the connect statement; then the same variable is being referred to everywhere.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top