• Aucun résultat trouvé

Checking for Errors

Dans le document MySQL Cookbook (Page 70-79)

Beware of Class.forName()!

2.2. Checking for Errors

Class.forName ("com.mysql.jdbc.Driver").newInstance ();

conn = DriverManager.getConnection (url);

System.out.println ("Connected");

}

The character that separates the user and password parameters should be &, not ;. Additional connection parameters. Connector/J does not support Unix domain socket file connections, so even connections for which the hostname is localhost are made via TCP/

IP. To specify an explicit port number, add :port_num to the hostname in the connection URL:

String url = "jdbc:mysql://127.0.0.1:3307/cookbook";

2.2. Checking for Errors

Problem

Something went wrong with your program, and you don’t know what.

42 | Chapter 2: Writing MySQL-Based Programs

Solution

Everyone has problems getting programs to work correctly. But if you don’t anticipate problems by checking for errors, the job becomes much more difficult. Add some error-checking code so your programs can help you figure out what went wrong.

Discussion

After working through Recipe 2.1, you know how to connect to the MySQL server. It’s also a good idea to know how to check for errors and how to retrieve specific error information from the API, so we cover that next. You’re probably anxious to do more interesting things (such as executing statements and getting back the results), but error checking is fundamentally important. Programs sometimes fail, especially during de‐

velopment, and if you can’t determine why failures occur, you’re flying blind.

The need to check for errors is not so obvious or widely appreciated as one might hope.

Many messages posted on MySQL-related mailing lists are requests for help with pro‐

grams that fail for reasons unknown to the people who wrote them. Surprisingly often, people have put in no error checking, thus giving themselves no way to know that there was a problem or to find out what it was! Plan for failure by checking for errors so that you can take appropriate action.

When an error occurs, MySQL provides three values:

• A MySQL-specific error number

• A MySQL-specific descriptive text error message

• A five-character SQLSTATE error code defined according to the ANSI and ODBC standards

The recipes in this section show how to access this information. The example programs are deliberately designed to fail, so that the error-handling code executes. That’s why they attempt to connect using a username and password of baduser and badpass.

A general debugging aid not specific to any API is to use the avail‐

able logs. Check the MySQL server’s query log to see what state‐

ments the server is receiving. (This requires that log to be enabled;

see Recipe 22.3.) The query log might show that your program is not constructing the SQL statement string you expect. Similarly, if you run a script under a web server and it fails, check the web server’s error log.

2.2. Checking for Errors | 43

Perl

The DBI module provides two attributes that control what happens when DBI method invocations fail:

• PrintError, if enabled, causes DBI to print an error message using warn().

• RaiseError, if enabled, causes DBI to print an error message using die(). This terminates your script.

By default, PrintError is enabled and RaiseError is disabled, so a script continues executing after printing a message if an error occurs. Either or both attributes can be specified in the connect() call. Setting an attribute to 1 or 0 enables or disables it, respectively. To specify either or both attributes, pass them in a hash reference as the fourth argument to the connect() call.

The following code sets only the AutoCommit attribute and uses the default settings for the error-handling attributes. If the connect() call fails, a warning message results, but the script continues to execute:

my $conn_attrs = {AutoCommit => 1};

my $dbh = DBI->connect ($dsn, "baduser", "badpass", $conn_attrs);

Because you really can’t do much if the connection attempt fails, it’s often prudent to exit instead after DBI prints a message:

my $conn_attrs = {AutoCommit => 1};

my $dbh = DBI->connect ($dsn, "baduser", "badpass", $conn_attrs) or exit;

To print your own error messages, leave RaiseError disabled and disable PrintError as well. Then test the results of DBI method calls yourself. When a method fails, the

$DBI::err, $DBI::errstr, and $DBI::state variables contain the MySQL error num‐

ber, a descriptive error string, and the SQLSTATE value, respectively:

my $conn_attrs = {PrintError => 0, AutoCommit => 1};

my $dbh = DBI->connect ($dsn, "baduser", "badpass", $conn_attrs) or die "Connection error: "

. "$DBI::errstr ($DBI::err/$DBI::state)\n";

If no error occurs, $DBI::err is 0 or undef, $DBI::errstr is the empty string or un def, and $DBI::state is empty or 00000.

When you check for errors, access these variables immediately after invoking the DBI method that sets them. If you invoke another method before using them, DBI resets their values.

If you print your own messages, the default settings (PrintError enabled, RaiseEr ror disabled) are not so useful. DBI prints a message automatically, then your script

44 | Chapter 2: Writing MySQL-Based Programs

prints its own message. This is redundant, as well as confusing to the person using the script.

If you enable RaiseError, you can call DBI methods without checking for return values that indicate errors. If a method fails, DBI prints an error and terminates your script. If the method returns, you can assume it succeeded. This is the easiest approach for script writers: let DBI do all the error checking! However, if both PrintError and RaiseEr ror are enabled, DBI may call warn() and die() in succession, resulting in error mes‐

sages being printed twice. To avoid this problem, disable PrintError whenever you enable RaiseError:

my $conn_attrs = {PrintError => 0, RaiseError => 1, AutoCommit => 1};

my $dbh = DBI->connect ($dsn, "baduser", "badpass", $conn_attrs);

This book generally uses that approach. If you don’t want the all-or-nothing behavior of enabling RaiseError for automatic error checking versus having to do all your own checking, adopt a mixed approach. Individual handles have PrintError and RaiseEr ror attributes that can be enabled or disabled selectively. For example, you can enable RaiseError globally by turning it on when you call connect(), and then disable it selectively on a per-handle basis.

Suppose that a script reads the username and password from the command-line argu‐

ments, and then loops while the user enters statements to be executed. In this case, you’d probably want DBI to die and print the error message automatically if the connection fails (you cannot proceed to the statement-execution loop in that case). After connect‐

ing, however, you wouldn’t want the script to exit just because the user enters a syntac‐

tically invalid statement. Instead, print an error message and loop to get the next state‐

ment. The following code shows how to do this. The do() method used in the example executes a statement and returns undef to indicate an error:

my $user_name = shift (@ARGV);

my $password = shift (@ARGV);

my $conn_attrs = {PrintError => 0, RaiseError => 1, AutoCommit => 1};

my $dbh = DBI->connect ($dsn, $user_name, $password, $conn_attrs);

$dbh->{RaiseError} = 0; # disable automatic termination on error

print "Enter statements to execute, one per line; terminate with Control-D\n";

while (<>) # read and execute queries {

$dbh->do ($_) or warn "Statement failed: $DBI::errstr ($DBI::err)\n";

}

If RaiseError is enabled, you can execute code within an eval block to trap errors without terminating your program. If an error occurs, eval returns a message in the $@

variable:

eval {

# statements that might fail go here...

};

2.2. Checking for Errors | 45

if ($@) {

print "An error occurred: $@\n";

}

This eval technique is commonly used to perform transactions (see Recipe 17.4).

Using RaiseError in combination with eval differs from using RaiseError alone:

• Errors terminate only the eval block, not the entire script.

• Any error terminates the eval block, whereas RaiseError applies only to DBI-related errors.

When you use eval with RaiseError enabled, disable PrintError. Otherwise, in some versions of DBI, an error may simply cause warn() to be called without terminating the eval block as you expect.

In addition to using the error-handling attributes PrintError and RaiseError, lots of information about your script’s execution is available using DBI’s tracing mechanism.

Invoke the trace() method with an argument indicating the trace level. Levels 1 to 9 enable tracing with increasingly more verbose output, and level 0 disables tracing:

DBI->trace (1); # enable tracing, minimal output DBI->trace (3); # elevate trace level

DBI->trace (0); # disable tracing

Individual database and statement handles also have trace() methods, so you can lo‐

calize tracing to a single handle if you want.

Trace output normally goes to your terminal (or, in the case of a web script, to the web server’s error log). To write trace output to a specific file, provide a second argument that indicates the filename:

DBI->trace (1, "/tmp/trace.out");

If the trace file already exists, its contents are not cleared first; trace output is appended to the end. Beware of turning on a file trace while developing a script, but forgetting to disable the trace when you put the script into production. You’ll eventually find to your chagrin that the trace file has become quite large. Or worse, a filesystem will fill up, and you’ll have no idea why!

Ruby

Ruby signals errors by raising exceptions and Ruby programs handle errors by catching exceptions in a rescue clause of a begin block. Ruby DBI methods raise exceptions when they fail and provide error information by means of a DBI::DatabaseError object.

To get the MySQL error number, error message, and SQLSTATE value, access the err, errstr, and state methods of this object. The following example shows how to trap exceptions and access error information in a DBI script:

46 | Chapter 2: Writing MySQL-Based Programs

begin

dsn = "DBI:Mysql:host=localhost;database=cookbook"

dbh = DBI.connect(dsn, "baduser", "badpass") puts "Connected"

rescue DBI::DatabaseError => e puts "Cannot connect to server"

puts "Error code: #{e.err}"

puts "Error message: #{e.errstr}"

puts "Error SQLSTATE: #{e.state}"

exit(1) end

PHP

The newPDO() constructor raises an exception if it fails, but other PDO methods by default indicate success or failure by their return value. To cause all PDO methods to raise exceptions for errors, use the database handle resulting from a successful connec‐

tion attempt to set the error-handling mode. This enables uniform handling of all PDO errors without checking the result of every call. The following example shows how to set the error mode if the connection attempt succeeds and how to handle exceptions if it fails:

try {

$dsn = "mysql:host=localhost;dbname=cookbook";

$dbh = new PDO ($dsn, "baduser", "badpass");

$dbh->setAttribute (PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

print ("Connected\n");

}

catch (PDOException $e) {

print ("Cannot connect to server\n");

print ("Error code: " . $e->getCode () . "\n");

print ("Error message: " . $e->getMessage () . "\n");

}

When PDO raises an exception, the resulting PDOException object provides error in‐

formation. The getCode() method returns the SQLSTATE value. The getMessage() method returns a string containing the SQLSTATE value, MySQL error number, and error message.

Database and statement handles also provide information when an error occurs. For either type of handle, errorCode() returns the SQLSTATE value and errorInfo() returns a three-element array containing the SQLSTATE value and a driver-specific error code and message. For MySQL, the latter two values are the error number and message string. The following example demonstrates how to get information from the exception object and the database handle:

try {

2.2. Checking for Errors | 47

$dbh->query ("SELECT"); # malformed query }

catch (PDOException $e) {

print ("Cannot execute query\n");

print ("Error information using exception object:\n");

print ("SQLSTATE value: " . $e->getCode () . "\n");

print ("Error message: " . $e->getMessage () . "\n");

print ("Error information using database handle:\n");

print ("Error code: " . $dbh->errorCode () . "\n");

$errorInfo = $dbh->errorInfo ();

print ("SQLSTATE value: " . $errorInfo[0] . "\n");

print ("Error number: " . $errorInfo[1] . "\n");

print ("Error message: " . $errorInfo[2] . "\n");

}

Python

Python signals errors by raising exceptions, and Python programs handle errors by catching exceptions in the except clause of a try statement. To obtain MySQL-specific error information, name an exception class, and provide a variable to receive the in‐

formation. Here’s an example:

conn_params = {

"database": "cookbook", "host": "localhost", "user": "baduser", "password": "badpass"

} try:

conn = mysql.connector.connect(**conn_params) print("Connected")

except mysql.connector.Error as e:

print("Cannot connect to server") print("Error code: %s" % e.errno) print("Error message: %s" % e.msg) print("Error SQLSTATE: %s" % e.sqlstate)

If an exception occurs, the errno, msg, and sqlstate members of the exception object contain the error number, error message, and SQLSTATE values, respectively. Note that access to the Error class is through the driver module name.

Java

Java programs handle errors by catching exceptions. To do the minimum amount of work, print a stack trace to inform the user where the problem lies:

try {

/* ... some database operation ... */

48 | Chapter 2: Writing MySQL-Based Programs

}

catch (Exception e) {

e.printStackTrace ();

}

The stack trace shows the location of the problem but not necessarily what the problem was. Also, it may not be meaningful except to you, the program’s developer. To be more specific, print the error message and code associated with an exception:

• All Exception objects support the getMessage() method. JDBC methods may throw exceptions using SQLException objects; these are like Exception objects but also support getErrorCode() and getSQLState() methods. getErrorCode() and getMessage() return the MySQL-specific error number and message string, and getSQLState() returns a string containing the SQLSTATE value.

• Some methods generate SQLWarning objects to provide information about nonfatal warnings. SQLWarning is a subclass of SQLException, but warnings are accumulated in a list rather than thrown immediately. They don’t interrupt your program, and you can print them at your leisure.

The following example program, Error.java, demonstrates how to access error messages by printing all the error information available to it. It attempts to connect to the MySQL server and prints exception information if the attempt fails. Then it executes a statement and prints exception and warning information if the statement fails:

// Error.java: demonstrate MySQL error handling

import java.sql.*;

public class Error {

public static void main (String[] args) {

Connection conn = null;

String url = "jdbc:mysql://localhost/cookbook";

String userName = "baduser";

String password = "badpass";

try {

Class.forName ("com.mysql.jdbc.Driver").newInstance ();

conn = DriverManager.getConnection (url, userName, password);

System.out.println ("Connected");

tryQuery (conn); // issue a query }

catch (Exception e) {

System.err.println ("Cannot connect to server");

System.err.println (e);

2.2. Checking for Errors | 49

if (e instanceof SQLException) // JDBC-specific exception?

public static void tryQuery (Connection conn) {

public static void printException (SQLException e) {

// print general message, plus any database-specific message

50 | Chapter 2: Writing MySQL-Based Programs

System.err.println ("SQLException: " + e.getMessage ());

System.err.println ("SQLState: " + e.getSQLState ());

System.err.println ("Vendor code: " + e.getErrorCode ());

} }

Dans le document MySQL Cookbook (Page 70-79)