From 832e89440f2586a2b027dfa56e5d14baf9ecd188 Mon Sep 17 00:00:00 2001 From: Douglas Heriot Date: Thu, 26 May 2016 00:34:34 +1000 Subject: [PATCH 1/2] Better exception messages when statements fail Added new constructor to Exception that takes sqlite3* pointer, allowing for getting more information out. Makes it much easier to debug syntax errors in prepared statements, for example. --- include/SQLiteCpp/Exception.h | 33 ++++++++++++++++++++++++++++++++- include/SQLiteCpp/Statement.h | 2 +- src/Statement.cpp | 4 ++-- 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/include/SQLiteCpp/Exception.h b/include/SQLiteCpp/Exception.h index ae7a8ed9..d58915e8 100644 --- a/include/SQLiteCpp/Exception.h +++ b/include/SQLiteCpp/Exception.h @@ -12,6 +12,8 @@ #include #include +#include +#include namespace SQLite @@ -30,9 +32,38 @@ class Exception : public std::runtime_error * @param[in] aErrorMessage The string message describing the SQLite error */ explicit Exception(const std::string& aErrorMessage) : - std::runtime_error(aErrorMessage) + std::runtime_error(aErrorMessage), + errcode(0), + extendedErrcode(0), + errstr(), + errmsg() { } + + /** + * @brief Encapsulation of the error message from SQLite3, based on std::runtime_error. + * + * @param[in] apSQLite The SQLite object, to obtain detailed error messages from. + * @param[in] ret Return value from function call that failed. + */ + explicit Exception(sqlite3* apSQLite, int ret) : + errcode(ret), + extendedErrcode(sqlite3_extended_errcode(apSQLite)), + errstr(sqlite3_errstr(ret)), + errmsg(sqlite3_errmsg(apSQLite)), + + std::runtime_error([apSQLite, ret](){ + std::ostringstream message; + message << sqlite3_errstr(ret) << ": " << sqlite3_errmsg(apSQLite); + return message.str(); + }()) + { + } + + const int errcode; + const int extendedErrcode; + const std::string errstr; + const std::string errmsg; }; diff --git a/include/SQLiteCpp/Statement.h b/include/SQLiteCpp/Statement.h index 3ee31627..dc44b6ec 100644 --- a/include/SQLiteCpp/Statement.h +++ b/include/SQLiteCpp/Statement.h @@ -457,7 +457,7 @@ class Statement { if (SQLITE_OK != aRet) { - throw SQLite::Exception(sqlite3_errstr(aRet)); + throw SQLite::Exception(mStmtPtr, aRet); } } diff --git a/src/Statement.cpp b/src/Statement.cpp index 20d4bc5f..f8db0c27 100644 --- a/src/Statement.cpp +++ b/src/Statement.cpp @@ -227,7 +227,7 @@ int Statement::exec() { mbOk = false; mbDone = false; - throw SQLite::Exception(sqlite3_errstr(ret)); + throw SQLite::Exception(mStmtPtr, ret); } } else @@ -317,7 +317,7 @@ Statement::Ptr::Ptr(sqlite3* apSQLite, std::string& aQuery) : const int ret = sqlite3_prepare_v2(apSQLite, aQuery.c_str(), static_cast(aQuery.size()), &mpStmt, NULL); if (SQLITE_OK != ret) { - throw SQLite::Exception(sqlite3_errstr(ret)); + throw SQLite::Exception(apSQLite, ret); } // Initialize the reference counter of the sqlite3_stmt : // used to share the mStmtPtr between Statement and Column objects; From 2cd31179f4060f357de0f1def250f302b549394e Mon Sep 17 00:00:00 2001 From: Douglas Heriot Date: Thu, 26 May 2016 12:34:14 +1000 Subject: [PATCH 2/2] Use new Exception constructors everywhere appropriate. Fix compiling with C++11. Added public assessor methods to Exception class. Added more Exception constructors as needed. --- include/SQLiteCpp/Database.h | 2 +- include/SQLiteCpp/Exception.h | 110 ++++++++++++++++++++++------------ src/Backup.cpp | 3 +- src/Database.cpp | 8 +-- src/Statement.cpp | 2 +- 5 files changed, 79 insertions(+), 46 deletions(-) diff --git a/include/SQLiteCpp/Database.h b/include/SQLiteCpp/Database.h index f64c9951..b82ccc68 100644 --- a/include/SQLiteCpp/Database.h +++ b/include/SQLiteCpp/Database.h @@ -378,7 +378,7 @@ class Database { if (SQLITE_OK != aRet) { - throw SQLite::Exception(sqlite3_errstr(aRet)); + throw SQLite::Exception(mpSQLite, aRet); } } diff --git a/include/SQLiteCpp/Exception.h b/include/SQLiteCpp/Exception.h index d58915e8..e3a5ecea 100644 --- a/include/SQLiteCpp/Exception.h +++ b/include/SQLiteCpp/Exception.h @@ -16,6 +16,25 @@ #include +/// Compatibility with non-clang compilers. +#ifndef __has_feature + #define __has_feature(x) 0 +#endif + +// Detect whether the compiler supports C++11 noexcept exception specifications. +#if ( defined(__GNUC__) && ((__GNUC__ == 4 && __GNUC_MINOR__ >= 7) || (__GNUC__ > 4)) \ + && defined(__GXX_EXPERIMENTAL_CXX0X__)) +// GCC 4.7 and following have noexcept +#elif defined(__clang__) && __has_feature(cxx_noexcept) +// Clang 3.0 and above have noexcept +#elif defined(_MSC_VER) && _MSC_VER > 1800 +// Visual Studio 2015 and above have noexcept +#else + // Visual Studio 2013 does not support noexcept, and "throw()" is deprecated by C++11 + #define noexcept +#endif + + namespace SQLite { @@ -33,13 +52,23 @@ class Exception : public std::runtime_error */ explicit Exception(const std::string& aErrorMessage) : std::runtime_error(aErrorMessage), - errcode(0), - extendedErrcode(0), - errstr(), - errmsg() + mErrcode(-1), // 0 would be SQLITE_OK, which doesn't make sense + mExtendedErrcode(-1) + { + } + + /** + * @brief Encapsulation of the error message from SQLite3, based on std::runtime_error. + * + * @param[in] apSQLite The SQLite object, to obtain detailed error messages from. + */ + explicit Exception(sqlite3* apSQLite) : + std::runtime_error(sqlite3_errmsg(apSQLite)), + mErrcode(sqlite3_errcode(apSQLite)), + mExtendedErrcode(sqlite3_extended_errcode(apSQLite)) { } - + /** * @brief Encapsulation of the error message from SQLite3, based on std::runtime_error. * @@ -47,43 +76,48 @@ class Exception : public std::runtime_error * @param[in] ret Return value from function call that failed. */ explicit Exception(sqlite3* apSQLite, int ret) : - errcode(ret), - extendedErrcode(sqlite3_extended_errcode(apSQLite)), - errstr(sqlite3_errstr(ret)), - errmsg(sqlite3_errmsg(apSQLite)), - - std::runtime_error([apSQLite, ret](){ - std::ostringstream message; - message << sqlite3_errstr(ret) << ": " << sqlite3_errmsg(apSQLite); - return message.str(); - }()) + std::runtime_error(sqlite3_errmsg(apSQLite)), + mErrcode(ret), + mExtendedErrcode(sqlite3_extended_errcode(apSQLite)) + { + } + + /** + * @brief Encapsulation of the error message from SQLite3, based on std::runtime_error. + * + * @param[in] apSQLite The SQLite object, to obtain detailed error messages from. + * @param[in] ret Return value from function call that failed. + * @param[in] aErrorMessage String providing more context, added to the SQLite errmsg + */ + explicit Exception(sqlite3* apSQLite, int ret, const std::string &aErrorMessage) : + std::runtime_error(aErrorMessage + ": " + sqlite3_errmsg(apSQLite)), + mErrcode(ret), + mExtendedErrcode(sqlite3_extended_errcode(apSQLite)) { } - - const int errcode; - const int extendedErrcode; - const std::string errstr; - const std::string errmsg; -}; + /// @brief Return the result code (if any, otherwise -1). + inline int getErrorCode() const noexcept // nothrow + { + return mErrcode; + } -} // namespace SQLite + /// @brief Return the extended numeric result code (if any, otherwise -1). + inline int getExtendedErrorCode() const noexcept // nothrow + { + return mExtendedErrcode; + } + /// @brief Return a string, solely based on the error code + inline const char *getErrStr() const noexcept // nothrow + { + return sqlite3_errstr(mErrcode); + } -/// Compatibility with non-clang compilers. -#ifndef __has_feature - #define __has_feature(x) 0 -#endif +private: + const int mErrcode; + const int mExtendedErrcode; +}; -// Detect whether the compiler supports C++11 noexcept exception specifications. -#if ( defined(__GNUC__) && ((__GNUC__ == 4 && __GNUC_MINOR__ >= 7) || (__GNUC__ > 4)) \ - && defined(__GXX_EXPERIMENTAL_CXX0X__)) -// GCC 4.7 and following have noexcept -#elif defined(__clang__) && __has_feature(cxx_noexcept) -// Clang 3.0 and above have noexcept -#elif defined(_MSC_VER) && _MSC_VER > 1800 -// Visual Studio 2015 and above have noexcept -#else - // Visual Studio 2013 does not support noexcept, and "throw()" is deprecated by C++11 - #define noexcept -#endif + +} // namespace SQLite diff --git a/src/Backup.cpp b/src/Backup.cpp index ba8815a4..c46ecfd8 100644 --- a/src/Backup.cpp +++ b/src/Backup.cpp @@ -31,8 +31,7 @@ Backup::Backup(Database& aDestDatabase, apSrcDatabaseName); if (NULL == mpSQLiteBackup) { - std::string strerr = sqlite3_errmsg(aDestDatabase.getHandle()); - throw SQLite::Exception(strerr); + throw SQLite::Exception(aDestDatabase.getHandle()); } } diff --git a/src/Database.cpp b/src/Database.cpp index 2ce2183c..f19dee93 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -36,9 +36,9 @@ Database::Database(const char* apFilename, const int ret = sqlite3_open_v2(apFilename, &mpSQLite, aFlags, apVfs); if (SQLITE_OK != ret) { - std::string strerr = sqlite3_errstr(ret); + const SQLite::Exception exception(mpSQLite, ret); // must create before closing sqlite3_close(mpSQLite); // close is required even in case of error on opening - throw SQLite::Exception(strerr); + throw exception; } if (aBusyTimeoutMs > 0) @@ -58,9 +58,9 @@ Database::Database(const std::string& aFilename, const int ret = sqlite3_open_v2(aFilename.c_str(), &mpSQLite, aFlags, aVfs.empty() ? NULL : aVfs.c_str()); if (SQLITE_OK != ret) { - std::string strerr = sqlite3_errstr(ret); + const SQLite::Exception exception(mpSQLite, ret); // must create before closing sqlite3_close(mpSQLite); // close is required even in case of error on opening - throw SQLite::Exception(strerr); + throw exception; } if (aBusyTimeoutMs > 0) diff --git a/src/Statement.cpp b/src/Statement.cpp index f8db0c27..eec9a92b 100644 --- a/src/Statement.cpp +++ b/src/Statement.cpp @@ -195,7 +195,7 @@ bool Statement::executeStep() { mbOk = false; mbDone = false; - throw SQLite::Exception(sqlite3_errstr(ret)); + throw SQLite::Exception(mStmtPtr, ret); } } else