From 96b1230a9248c62fe47eea3898c20e4dc6c52e3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20Kr=C3=BCger?= Date: Tue, 14 Feb 2017 07:24:17 +0100 Subject: [PATCH 1/2] Include SQL statement in exception --- README.md | 6 ++- hdr/sqlite_modern_cpp.h | 104 ++++++++++++++++++++++------------------ tests/exceptions.cc | 8 +++- 3 files changed, 69 insertions(+), 49 deletions(-) diff --git a/README.md b/README.md index 82f1b4c0..75ead965 100644 --- a/README.md +++ b/README.md @@ -284,7 +284,8 @@ Errors ---- On error, the library throws an error class indicating the type of error. The error classes are derived from the SQLITE3 error names, so if the error code is SQLITE_CONSTRAINT, the error class thrown is sqlite::exceptions::constraint. Note that all errors are derived from sqlite::sqlite_exception and that itself is derived from std::runtime_exception. -sqlite::sqlite_exception has a get_code() member function to get the SQLITE3 error code. +sqlite::sqlite_exception has a `get_code()` member function to get the SQLITE3 error code. +Additionally you can use `get_sql()` to see the SQL statement leading to the error. ```c++ database db(":memory:"); @@ -298,7 +299,8 @@ sqlite::sqlite_exception has a get_code() member function to get the SQLITE3 err /* if you are trying to catch all sqlite related exceptions * make sure to catch them by reference */ catch (sqlite_exception& e) { - cerr << e.get_code() << ": " << e.what() << endl; + cerr << e.get_code() << ": " << e.what() << " during " + << e.get_sql() << endl; } /* you can catch specific exceptions as well, catch(sqlite::exceptions::constraint e) { } */ diff --git a/hdr/sqlite_modern_cpp.h b/hdr/sqlite_modern_cpp.h index df729faf..19df0496 100644 --- a/hdr/sqlite_modern_cpp.h +++ b/hdr/sqlite_modern_cpp.h @@ -30,11 +30,13 @@ namespace sqlite { class sqlite_exception: public std::runtime_error { public: - sqlite_exception(const char* msg, int code = -1): runtime_error(msg), code(code) {} - sqlite_exception(int code): runtime_error(sqlite3_errstr(code)), code(code) {} + sqlite_exception(const char* msg, std::string sql, int code = -1): runtime_error(msg), code(code), sql(sql) {} + sqlite_exception(int code, std::string sql): runtime_error(sqlite3_errstr(code)), code(code), sql(sql) {} int get_code() {return code;} + std::string get_sql() {return sql;} private: int code; + std::string sql; }; namespace exceptions { @@ -74,34 +76,34 @@ namespace sqlite { class more_rows: public sqlite_exception { using sqlite_exception::sqlite_exception; }; class no_rows: public sqlite_exception { using sqlite_exception::sqlite_exception; }; - static void throw_sqlite_error(const int& error_code) { - if(error_code == SQLITE_ERROR) throw exceptions::error(error_code); - else if(error_code == SQLITE_INTERNAL) throw exceptions::internal (error_code); - else if(error_code == SQLITE_PERM) throw exceptions::perm(error_code); - else if(error_code == SQLITE_ABORT) throw exceptions::abort(error_code); - else if(error_code == SQLITE_BUSY) throw exceptions::busy(error_code); - else if(error_code == SQLITE_LOCKED) throw exceptions::locked(error_code); - else if(error_code == SQLITE_NOMEM) throw exceptions::nomem(error_code); - else if(error_code == SQLITE_READONLY) throw exceptions::readonly(error_code); - else if(error_code == SQLITE_INTERRUPT) throw exceptions::interrupt(error_code); - else if(error_code == SQLITE_IOERR) throw exceptions::ioerr(error_code); - else if(error_code == SQLITE_CORRUPT) throw exceptions::corrupt(error_code); - else if(error_code == SQLITE_NOTFOUND) throw exceptions::notfound(error_code); - else if(error_code == SQLITE_FULL) throw exceptions::full(error_code); - else if(error_code == SQLITE_CANTOPEN) throw exceptions::cantopen(error_code); - else if(error_code == SQLITE_PROTOCOL) throw exceptions::protocol(error_code); - else if(error_code == SQLITE_EMPTY) throw exceptions::empty(error_code); - else if(error_code == SQLITE_SCHEMA) throw exceptions::schema(error_code); - else if(error_code == SQLITE_TOOBIG) throw exceptions::toobig(error_code); - else if(error_code == SQLITE_CONSTRAINT) throw exceptions::constraint(error_code); - else if(error_code == SQLITE_MISMATCH) throw exceptions::mismatch(error_code); - else if(error_code == SQLITE_MISUSE) throw exceptions::misuse(error_code); - else if(error_code == SQLITE_NOLFS) throw exceptions::nolfs(error_code); - else if(error_code == SQLITE_AUTH) throw exceptions::auth(error_code); - else if(error_code == SQLITE_FORMAT) throw exceptions::format(error_code); - else if(error_code == SQLITE_RANGE) throw exceptions::range(error_code); - else if(error_code == SQLITE_NOTADB) throw exceptions::notadb(error_code); - else throw sqlite_exception(error_code); + static void throw_sqlite_error(const int& error_code, const std::string &sql = "") { + if(error_code == SQLITE_ERROR) throw exceptions::error(error_code, sql); + else if(error_code == SQLITE_INTERNAL) throw exceptions::internal (error_code, sql); + else if(error_code == SQLITE_PERM) throw exceptions::perm(error_code, sql); + else if(error_code == SQLITE_ABORT) throw exceptions::abort(error_code, sql); + else if(error_code == SQLITE_BUSY) throw exceptions::busy(error_code, sql); + else if(error_code == SQLITE_LOCKED) throw exceptions::locked(error_code, sql); + else if(error_code == SQLITE_NOMEM) throw exceptions::nomem(error_code, sql); + else if(error_code == SQLITE_READONLY) throw exceptions::readonly(error_code, sql); + else if(error_code == SQLITE_INTERRUPT) throw exceptions::interrupt(error_code, sql); + else if(error_code == SQLITE_IOERR) throw exceptions::ioerr(error_code, sql); + else if(error_code == SQLITE_CORRUPT) throw exceptions::corrupt(error_code, sql); + else if(error_code == SQLITE_NOTFOUND) throw exceptions::notfound(error_code, sql); + else if(error_code == SQLITE_FULL) throw exceptions::full(error_code, sql); + else if(error_code == SQLITE_CANTOPEN) throw exceptions::cantopen(error_code, sql); + else if(error_code == SQLITE_PROTOCOL) throw exceptions::protocol(error_code, sql); + else if(error_code == SQLITE_EMPTY) throw exceptions::empty(error_code, sql); + else if(error_code == SQLITE_SCHEMA) throw exceptions::schema(error_code, sql); + else if(error_code == SQLITE_TOOBIG) throw exceptions::toobig(error_code, sql); + else if(error_code == SQLITE_CONSTRAINT) throw exceptions::constraint(error_code, sql); + else if(error_code == SQLITE_MISMATCH) throw exceptions::mismatch(error_code, sql); + else if(error_code == SQLITE_MISUSE) throw exceptions::misuse(error_code, sql); + else if(error_code == SQLITE_NOLFS) throw exceptions::nolfs(error_code, sql); + else if(error_code == SQLITE_AUTH) throw exceptions::auth(error_code, sql); + else if(error_code == SQLITE_FORMAT) throw exceptions::format(error_code, sql); + else if(error_code == SQLITE_RANGE) throw exceptions::range(error_code, sql); + else if(error_code == SQLITE_NOTADB) throw exceptions::notadb(error_code, sql); + else throw sqlite_exception(error_code, sql); } } @@ -149,10 +151,20 @@ namespace sqlite { while((hresult = sqlite3_step(_stmt.get())) == SQLITE_ROW) {} if(hresult != SQLITE_DONE) { - exceptions::throw_sqlite_error(hresult); + exceptions::throw_sqlite_error(hresult, sql()); } used(true); /* prevent from executing again when goes out of scope */ } + + std::string sql() { + auto sqlite_deleter = [](void *ptr) {sqlite3_free(ptr);}; + std::unique_ptr str(sqlite3_expanded_sql(_stmt.get()), sqlite_deleter); + return str ? str.get() : original_sql(); + } + + std::string original_sql() { + return sqlite3_sql(_stmt.get()); + } void used(bool state) { execution_started = state; } bool used() const { return execution_started; } @@ -174,7 +186,7 @@ namespace sqlite { } if(hresult != SQLITE_DONE) { - exceptions::throw_sqlite_error(hresult); + exceptions::throw_sqlite_error(hresult, sql()); } reset(); } @@ -186,15 +198,15 @@ namespace sqlite { if((hresult = sqlite3_step(_stmt.get())) == SQLITE_ROW) { call_back(); } else if(hresult == SQLITE_DONE) { - throw exceptions::no_rows("no rows to extract: exactly 1 row expected", SQLITE_DONE); + throw exceptions::no_rows("no rows to extract: exactly 1 row expected", sql(), SQLITE_DONE); } if((hresult = sqlite3_step(_stmt.get())) == SQLITE_ROW) { - throw exceptions::more_rows("not all rows extracted", SQLITE_ROW); + throw exceptions::more_rows("not all rows extracted", sql(), SQLITE_ROW); } if(hresult != SQLITE_DONE) { - exceptions::throw_sqlite_error(hresult); + exceptions::throw_sqlite_error(hresult, sql()); } reset(); } @@ -211,7 +223,7 @@ namespace sqlite { int hresult; sqlite3_stmt* tmp = nullptr; hresult = sqlite3_prepare_v2(_db.get(), sql.data(), -1, &tmp, nullptr); - if((hresult) != SQLITE_OK) exceptions::throw_sqlite_error(hresult); + if((hresult) != SQLITE_OK) exceptions::throw_sqlite_error(hresult, sql); return tmp; } @@ -416,7 +428,7 @@ namespace sqlite { inline database_binder& operator<<(database_binder& db, const int& val) { int hresult; if((hresult = sqlite3_bind_int(db._stmt.get(), db._inx, val)) != SQLITE_OK) { - exceptions::throw_sqlite_error(hresult); + exceptions::throw_sqlite_error(hresult, db.sql()); } ++db._inx; return db; @@ -433,7 +445,7 @@ namespace sqlite { inline database_binder& operator <<(database_binder& db, const sqlite_int64& val) { int hresult; if((hresult = sqlite3_bind_int64(db._stmt.get(), db._inx, val)) != SQLITE_OK) { - exceptions::throw_sqlite_error(hresult); + exceptions::throw_sqlite_error(hresult, db.sql()); } ++db._inx; @@ -451,7 +463,7 @@ namespace sqlite { inline database_binder& operator <<(database_binder& db, const float& val) { int hresult; if((hresult = sqlite3_bind_double(db._stmt.get(), db._inx, double(val))) != SQLITE_OK) { - exceptions::throw_sqlite_error(hresult); + exceptions::throw_sqlite_error(hresult, db.sql()); } ++db._inx; @@ -469,7 +481,7 @@ namespace sqlite { inline database_binder& operator <<(database_binder& db, const double& val) { int hresult; if((hresult = sqlite3_bind_double(db._stmt.get(), db._inx, val)) != SQLITE_OK) { - exceptions::throw_sqlite_error(hresult); + exceptions::throw_sqlite_error(hresult, db.sql()); } ++db._inx; @@ -489,7 +501,7 @@ namespace sqlite { int bytes = vec.size() * sizeof(T); int hresult; if((hresult = sqlite3_bind_blob(db._stmt.get(), db._inx, buf, bytes, SQLITE_TRANSIENT)) != SQLITE_OK) { - exceptions::throw_sqlite_error(hresult); + exceptions::throw_sqlite_error(hresult, db.sql()); } ++db._inx; return db; @@ -508,7 +520,7 @@ namespace sqlite { inline database_binder& operator <<(database_binder& db, std::nullptr_t) { int hresult; if((hresult = sqlite3_bind_null(db._stmt.get(), db._inx)) != SQLITE_OK) { - exceptions::throw_sqlite_error(hresult); + exceptions::throw_sqlite_error(hresult, db.sql()); } ++db._inx; return db; @@ -550,7 +562,7 @@ namespace sqlite { inline database_binder& operator <<(database_binder& db, const std::string& txt) { int hresult; if((hresult = sqlite3_bind_text(db._stmt.get(), db._inx, txt.data(), -1, SQLITE_TRANSIENT)) != SQLITE_OK) { - exceptions::throw_sqlite_error(hresult); + exceptions::throw_sqlite_error(hresult, db.sql()); } ++db._inx; @@ -570,7 +582,7 @@ namespace sqlite { inline database_binder& operator <<(database_binder& db, const std::u16string& txt) { int hresult; if((hresult = sqlite3_bind_text16(db._stmt.get(), db._inx, txt.data(), -1, SQLITE_TRANSIENT)) != SQLITE_OK) { - exceptions::throw_sqlite_error(hresult); + exceptions::throw_sqlite_error(hresult, db.sql()); } ++db._inx; @@ -584,7 +596,7 @@ namespace sqlite { } int hresult; if((hresult = sqlite3_bind_null(db._stmt.get(), db._inx)) != SQLITE_OK) { - exceptions::throw_sqlite_error(hresult); + exceptions::throw_sqlite_error(hresult, db.sql()); } ++db._inx; @@ -610,7 +622,7 @@ namespace sqlite { } int hresult; if((hresult = sqlite3_bind_null(db._stmt.get(), db._inx)) != SQLITE_OK) { - exceptions::throw_sqlite_error(hresult); + exceptions::throw_sqlite_error(hresult, db.sql()); } ++db._inx; diff --git a/tests/exceptions.cc b/tests/exceptions.cc index b68394a9..4e203382 100644 --- a/tests/exceptions.cc +++ b/tests/exceptions.cc @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -17,8 +18,13 @@ int main() { // inserting again to produce error db << "INSERT INTO person (id,name) VALUES (?,?)" << 1 << "jack"; } catch (sqlite_exception& e) { - cerr << e.get_code() << ": " << e.what() << endl; + cerr << e.get_code() << ": " << e.what() << " during " + << quoted(e.get_sql()) << endl; expception_thrown = true; + if(e.get_sql() != "INSERT INTO person (id,name) VALUES (1,'jack')") { + cerr << "Wrong statement failed\n"; + exit(EXIT_FAILURE); + } } catch (...) { cerr << "Ok, we have our excpetion thrown" << endl; expception_thrown = true; From 6c99c9b80eeabba329d745c82fa390148a03cd83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcel=20Kr=C3=BCger?= Date: Tue, 14 Feb 2017 19:46:54 +0100 Subject: [PATCH 2/2] Fallback for old SQLite versions --- hdr/sqlite_modern_cpp.h | 4 ++++ tests/exceptions.cc | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/hdr/sqlite_modern_cpp.h b/hdr/sqlite_modern_cpp.h index 19df0496..b917d8c8 100644 --- a/hdr/sqlite_modern_cpp.h +++ b/hdr/sqlite_modern_cpp.h @@ -157,9 +157,13 @@ namespace sqlite { } std::string sql() { +#if SQLITE_VERSION_NUMBER >= 3014000 auto sqlite_deleter = [](void *ptr) {sqlite3_free(ptr);}; std::unique_ptr str(sqlite3_expanded_sql(_stmt.get()), sqlite_deleter); return str ? str.get() : original_sql(); +#else + return original_sql(); +#endif } std::string original_sql() { diff --git a/tests/exceptions.cc b/tests/exceptions.cc index 4e203382..47581246 100644 --- a/tests/exceptions.cc +++ b/tests/exceptions.cc @@ -21,7 +21,11 @@ int main() { cerr << e.get_code() << ": " << e.what() << " during " << quoted(e.get_sql()) << endl; expception_thrown = true; +#if SQLITE_VERSION_NUMBER >= 3014000 if(e.get_sql() != "INSERT INTO person (id,name) VALUES (1,'jack')") { +#else + if(e.get_sql() != "INSERT INTO person (id,name) VALUES (?,?)") { +#endif cerr << "Wrong statement failed\n"; exit(EXIT_FAILURE); }