Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Dear Monks

how would i put this code to connect to a database in a function. The code itself is inside a function which needs to 'return' if the connection fails. This is the code as it is now:

#database connection variables defined here with 'our' scope sub function1 { eval { my $db = DBI->connect("DBI:mysql:database=$sql_database;host=$ +sql_host;port=$sql_port", $username, $password,{'RaiseError' => 1}); }; return "some value", "Error: $@" if $@; }
The only thing i can think of is really convoluted and i don't know how to tell if the return type is a string or a connection object. I borrowed the typeof function from another language :)
sub function1 { my $db=&connect(pass in connection parameters...) if typeof $db = string return "some value", "Error: $db"; } sub connect{ eval { my $db = DBI->connect("DBI:mysql:database=$sql_database;ho +st=$sql_host;port=$sql_port", $username, $password,{'RaiseError' => 1 +}); }; if $@ return $@; else return $db }
thanks a lot

Replies are listed 'Best First'.
Re: how to put this db connection code into a function
by ELISHEVA (Prior) on Jan 05, 2011 at 08:22 UTC

    The way you are using $@ is not very stable. The short version is that destruction of automatic variables in the dying stack frame can wipe out and reset $@ so that even if you have an error $@ evalutes to false. For a much more in depth explanation see Try::Tiny. A much better pattern relies on (a) the ability to return from an eval block without leaving the surrounding subroutine and (b) the guarentee that if an eval block dies an unnatural death it will always evaluate to undef. It looks something like this and unfortunately it is ugly:

    sub function1 { my $bSuccess=1; # flag in case $e is undef even though we died my $e; # for saving and returning $@ when $@ isn't wiped out my $db; # declare outside of eval since you want to return this eval { $db = DBI->connect("DBI:mysql:database=$sql_database;host=$ +sql_host;port=$sql_port", $username, $password,{'RaiseError' => 1}); return 1; #guarantee true return if no death } or do { # we get here if _and only if_ we die # save fragile $@ - prevent further opportunities to wipe out $e=$@; # flag failure so we know we died even if $@ is undef $bSuccess=0; }; return $bSuccess ? $db : $e;

    However, I have to ask - why are you returning $@ ($e) rather than propagating it or handling it? It feels like you are mixing patterns here: errors as something to return vs. errors as something that short circuits a process and can be ignored unless there is a way of handling or working around it. The handling/working around code should be in that do { ... } block. Or if you want to avoid repeating the above code for each query you could do something like this (untested - read as psuedocode):

    sub callDBI { my ($aParams, $crOnError) = @_; my $db; eval { $db = DBI->connect(@$aParams,{'RaiseError' => 1}); return 1; } or do { my $e=$@; if (defined($crOnError)) { $db = $crOnError->($aParams, $e); } else { $@=$e; die; # rethrow } }; return $db; }

    The above code handles the exception if $crOnError is defined. If $crOnError can handle the problem, it returns $db otherwise it just rethrows the exception. If $crOnError is not defined, the exception is just automatically rethrown.

    The above function will only return normally if $db is properly populated, so the code that calls callDBI can act as if exceptions never happen and process the results of the database transaction without a care.

Re: how to put this db connection code into a function
by ikegami (Patriarch) on Jan 04, 2011 at 23:12 UTC
    sub connect { return DBI->connect( "DBI:mysql:".join(';', "database=$sql_database", "host=$sql_host", "port=$sql_port", ), $username, $password, { RaiseError => 0, AutoCommit => 1, }, ); }; my $dbh = connect() or die("Can't connect: $DBI::errstr\n");

      I suppose the above leaves RaiseError in the wrong state for subsequent prepare and other calls. Fixed:

      sub connect { return eval { DBI->connect( "DBI:mysql:".join(';', "database=$sql_database", "host=$sql_host", "port=$sql_port", ), $username, $password, { RaiseError => 1, AutoCommit => 1, }, ) }; }; my $dbh = connect() or die("Can't connect: $DBI::errstr\n");
        i don't really want to use the $DBI::errstr as it returns far less information than what is in the $@ variable. That has the line number of the problem plus the statement that was executed, whereas DBI::errstr just has a limited error message
Re: how to put this db connection code into a function
by Anonyrnous Monk (Hermit) on Jan 05, 2011 at 00:30 UTC
    and i don't know how to tell if the return type is a string or a connection object. I borrowed the typeof function from another language

    ref is Perl's 'typeof'.