From: Date: September 26 2007 3:29pm Subject: RE: Error num in BadQuery patch List-Archive: http://lists.mysql.com/plusplus/7056 Message-Id: <5AA52B773286DA4E83B1F2D034FFED37247287@mailexchange.klausatlanta.local> MIME-Version: 1.0 Content-Type: text/plain; charset="US-ASCII" Content-Transfer-Encoding: quoted-printable Some of you may have thought this was pretty bogus. I was thinking that too when doing to more testing, but digging in found the patch is still valid. However, what I thought was causing mysql_errno to be cleared was incorrect. I thought the only API called after the error was mysql_error. Instead it is a successful rollback in ~Transaction that sets mysql_errno to zero. If you are using the Transaction class and get a BadQuery exception, the query/conn.errnum() in the catch is *not* that of the problem, but of the rollback. My change is still quite useful since it captures the errnum associated with the error string in the BadQuery instead of losing it. Here's the test case try { Transaction trans(con); query.execute( ... insert dupe key ... ) trans.commit(); } catch ( BadQuery e ) // tran is destroyed when goes out of scope, calling rollback, // which sets errnum to zero { // prints correct error message cerr << "BadQuery Error: " << e2.what() << endl; =09 // at this point we've lost the errnum for the real problem since // the rollback call was successful so errnum()=20 // is always zero (unless it was a connection issue or something // that also caused the rollback to fail) cerr << "Error num is " << con.errnum() << endl;=20 =09 } -----Original Message----- From: Jim Wallace=20 Sent: Tuesday, September 25, 2007 10:43 AM To: 'plusplus@stripped' Subject: Error num in BadQuery patch As for background of this, I needed it in two cases. 1) Detecting duplicate keys, and 2) adding retry logic in the event of a deadlock (and, yes, I know how and why they should be avoided but fault-tolerant code is good). I have created a sample to test this with a deadlock. Quite simply, this gets the errnum from the API so it can be checked when a BadQuery exception is caught. You can not call the connection's errnum after the exception is thrown since when BadQuery is constructed it calls the API to get the error as a string, which clears the error number. This is against the 2.3.2 codebase since I couldn't get the head of svn to build. I'd be happy to re-do the patch against the head if you think this is a useful feature, after I can build the head. This is my first attempt at this, so any constructive criticism is welcome. jmw --- connection.cpp 2007-07-11 17:37:16.000000000 -0400 +++ connection.cpp 2007-09-25 07:31:39.000000000 -0400 @@ -300,21 +300,21 @@ bool suc =3D !mysql_reload(&mysql_); if (throw_exceptions() && !suc) { // Reloading grant tables through this API isn't precisely a // query, but it's acceptable to signal errors with BadQuery // because the new mechanism is the FLUSH PRIVILEGES query. // A program won't have to change when doing it the new way. - throw BadQuery(error()); + throw BadQuery(error(),errnum()); } else { return suc; } } else { if (throw_exceptions()) { - throw BadQuery("MySQL++ connection not established"); + throw BadQuery("MySQL++ connection not established",errnum()); } else { return false; } } } --- exceptions.h 2007-07-11 17:37:16.000000000 -0400 +++ exceptions.h 2007-09-25 08:54:07.000000000 -0400 @@ -242,23 +242,26 @@ /// SQL query. In v1.7, it was used as a more generic exception type, /// for no particularly good reason. =20 class MYSQLPP_EXPORT BadQuery : public Exception { public: - /// \brief Create exception object, taking C string - explicit BadQuery(const char* w =3D "") : - Exception(w) + int errnum; ///< error number associated with execption +=09 + /// \brief Create exception object, taking C string and error number + explicit BadQuery(const char* w =3D "", int err =3D 0) : + Exception(w), errnum(err) { } =20 - /// \brief Create exception object, taking C++ string - explicit BadQuery(const std::string& w) : - Exception(w) + /// \brief Create exception object, taking C++ string and error number + explicit BadQuery(const std::string& w, int err =3D 0) : + Exception(w), errnum(err) { } +=09 }; =20 =20 /// \brief Exception thrown when there is a problem establishing the /// database server connection. It's also thrown if /// Connection::shutdown() fails. --- query.cpp 2007-07-11 17:37:16.000000000 -0400 +++ query.cpp 2007-09-25 09:05:12.000000000 -0400 @@ -81,13 +81,12 @@ my_ulonglong Query::affected_rows() const { return conn_->affected_rows(); } =20 - std::string Query::error() { return conn_->error(); } =20 @@ -95,13 +94,13 @@ bool Query::exec(const std::string& str) { success_ =3D !mysql_real_query(&conn_->mysql_, str.data(), static_cast(str.length())); if (!success_ && throw_exceptions()) { - throw BadQuery(error()); + throw BadQuery(error(),errnum()); } else { return success_; } } =20 @@ -149,13 +148,13 @@ =20 unlock(); if (success_) { return ResNSel(conn_); } else if (throw_exceptions()) { - throw BadQuery(error()); + throw BadQuery(error(),errnum()); } else { return ResNSel(); } } =20 @@ -463,13 +462,13 @@ // that doesn't always return results. While it's better to use // exec*() in that situation, it's legal to call store() instead, // and sometimes you have no choice. For example, if the SQL comes // from outside the program so you can't predict whether there will // be results. if (conn_->errnum() && throw_exceptions()) { - throw BadQuery(error()); + throw BadQuery(error(),errnum()); } else { return Result(); } } =20 @@ -510,25 +509,25 @@ else { // Result set is null, but throw an exception only i it is // null because of some error. If not, it's just an empty // result set, which is harmless. We return an empty result // set if exceptions are disabled, as well. if (conn_->errnum() && throw_exceptions()) { - throw BadQuery(error()); + throw BadQuery(error(),errnum()); }=20 else { return Result(); } } } else { // No more results, or some other error occurred. unlock(); if (throw_exceptions()) { if (ret > 0) { - throw BadQuery(error()); + throw BadQuery(error(),errnum()); } else { throw EndOfResultSets(); } } else { @@ -630,13 +629,13 @@ // that doesn't always return results. While it's better to use // exec*() in that situation, it's legal to call use() instead, and // sometimes you have no choice. For example, if the SQL comes // from outside the program so you can't predict whether there will // be results. if (conn_->errnum() && throw_exceptions()) { - throw BadQuery(error()); + throw BadQuery(error(),errnum()); } else { return ResUse(); } } =20 --- query.h 2007-07-11 17:37:16.000000000 -0400 +++ query.h 2007-09-25 08:53:49.000000000 -0400 @@ -150,12 +150,20 @@ /// \brief Assign another query's state to this object /// /// The same caveats apply to this operator as apply to the copy /// ctor. Query& operator=3D(const Query& rhs); =20 + /// \brief Get the last error number. + /// + /// This just delegates to Connection::errnum(). Query has nothing + /// extra to say, so use either, as makes sense in your program. + /// If you want the string *and* number you must=20 + /// call errnum() before calling error() since error() clears errnum() + int errnum() const { return conn_->errnum(); } + /// \brief Get the last error message that was set. /// /// This class has an internal error message string, but if it /// isn't set, we return the last error message that happened /// on the connection we're bound to instead. std::string error(); --- result.h 2007-07-11 17:37:16.000000000 -0400 +++ result.h 2007-09-25 09:04:38.000000000 -0400 @@ -35,12 +35,14 @@ #include "field_names.h" #include "field_types.h" #include "noexceptions.h" #include "resiter.h" #include "row.h" =20 +#include + #include #include #include =20 namespace mysqlpp { =20 @@ -103,13 +105,13 @@ /// This is not a thin wrapper. It does a lot of error checking before /// returning the mysqlpp::Row object containing the row data. Row fetch_row() { if (!result_) { if (throw_exceptions()) { - throw BadQuery("Results not fetched"); + throw BadQuery("Results not fetched",ER_UNKNOWN_ERROR); } else { return Row(); } } MYSQL_ROW row =3D mysql_fetch_row(result_); @@ -370,13 +372,13 @@ /// \link mysqlpp::ResUse parent class \endlink . Why this cannot /// actually \e be in our parent class is beyond me. const Row fetch_row() const { if (!result_) { if (throw_exceptions()) { - throw BadQuery("Results not fetched"); + throw BadQuery("Results not fetched",ER_UNKNOWN_ERROR); } else { return Row(); } } MYSQL_ROW row =3D mysql_fetch_row(result_); --- row.cpp 2007-07-11 17:37:16.000000000 -0400 +++ row.cpp 2007-09-25 08:56:16.000000000 -0400 @@ -35,13 +35,13 @@ OptionalExceptions(te), res_(r), initialized_(false) { if (!d || !r) { if (throw_exceptions()) { - throw BadQuery("ROW or RES is NULL"); + throw BadQuery("ROW or RES is NULL",ER_INVALID_USE_OF_NULL); } else { return; } } =20