diff --git a/README.md b/README.md index ade74e00..6c45471b 100644 --- a/README.md +++ b/README.md @@ -402,6 +402,30 @@ Additionally you can use `get_sql()` to see the SQL statement leading to the err catch(sqlite::errors::constraint_primarykey e) { } */ ``` +You can also register a error logging function with `sqlite::error_log`. +The `` header has to be included to make this function available. +The call to `sqlite::error_log` has to be the first call to any `sqlite_modern_cpp` function by your program. + +```c++ + error_log( + [&](sqlite_exception& e) { + cerr << e.get_code() << ": " << e.what() << endl; + }, + [&](errors::misuse& e) { + /* You can behave differently to specific errors */ + } + ); + database db(":memory:"); + db << "create table person (id integer primary key not null, name text);"; + + try { + db << "insert into person (id, name) values (?,?)" << 1 << "jack"; + // inserting again to produce error + db << "insert into person (id, name) values (?,?)" << 1 << "jack"; + } + catch (sqlite_exception& e) {} +``` + Custom SQL functions ---- diff --git a/hdr/sqlite_modern_cpp/log.h b/hdr/sqlite_modern_cpp/log.h new file mode 100644 index 00000000..a8f7be22 --- /dev/null +++ b/hdr/sqlite_modern_cpp/log.h @@ -0,0 +1,101 @@ +#include "errors.h" + +#include + +#include +#include +#include + +namespace sqlite { + namespace detail { + template + using void_t = void; + template + struct is_callable : std::false_type {}; + template + struct is_callable()(std::declval()...))>> : std::true_type {}; + template + class FunctorOverload: public Functor, public FunctorOverload { + public: + template + FunctorOverload(Functor1 &&functor, Remaining &&... remaining): + Functor(std::forward(functor)), + FunctorOverload(std::forward(remaining)...) {} + using Functor::operator(); + using FunctorOverload::operator(); + }; + template + class FunctorOverload: public Functor { + public: + template + FunctorOverload(Functor1 &&functor): + Functor(std::forward(functor)) {} + using Functor::operator(); + }; + template + class WrapIntoFunctor: public Functor { + public: + template + WrapIntoFunctor(Functor1 &&functor): + Functor(std::forward(functor)) {} + using Functor::operator(); + }; + template + class WrapIntoFunctor { + ReturnType(*ptr)(Arguments...); + public: + WrapIntoFunctor(ReturnType(*ptr)(Arguments...)): ptr(ptr) {} + ReturnType operator()(Arguments... arguments) { return (*ptr)(std::forward(arguments)...); } + }; + inline void store_error_log_data_pointer(std::shared_ptr ptr) { + static std::shared_ptr stored; + stored = std::move(ptr); + } + template + std::shared_ptr::type> make_shared_inferred(T &&t) { + return std::make_shared::type>(std::forward(t)); + } + } + template + typename std::enable_if::value>::type + error_log(Handler &&handler); + template + typename std::enable_if::value>::type + error_log(Handler &&handler); + template + typename std::enable_if=2>::type + error_log(Handler &&...handler) { + return error_log(detail::FunctorOverload::type>...>(std::forward(handler)...)); + } + template + typename std::enable_if::value>::type + error_log(Handler &&handler) { + return error_log(std::forward(handler), [](const sqlite_exception&) {}); + } + template + typename std::enable_if::value>::type + error_log(Handler &&handler) { + auto ptr = detail::make_shared_inferred([handler = std::forward(handler)](int error_code, const char *errstr) mutable { + switch(error_code & 0xFF) { +#define SQLITE_MODERN_CPP_ERROR_CODE(NAME,name,derived) \ + case SQLITE_ ## NAME: switch(error_code) { \ + derived \ + default: handler(errors::name(errstr, "", error_code)); \ + };break; +#define SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED(BASE,SUB,base,sub) \ + case SQLITE_ ## BASE ## _ ## SUB: \ + handler(errors::base ## _ ## sub(errstr, "", error_code)); \ + break; +#include "lists/error_codes.h" +#undef SQLITE_MODERN_CPP_ERROR_CODE_EXTENDED +#undef SQLITE_MODERN_CPP_ERROR_CODE + default: handler(sqlite_exception(errstr, "", error_code)); \ + } + }); + + sqlite3_config(SQLITE_CONFIG_LOG, (void(*)(void*,int,const char*))[](void *functor, int error_code, const char *errstr) { + (*static_cast(functor))(error_code, errstr); + }, ptr.get()); + detail::store_error_log_data_pointer(std::move(ptr)); + } +} diff --git a/tests/error_log.cc b/tests/error_log.cc new file mode 100644 index 00000000..d59cf08c --- /dev/null +++ b/tests/error_log.cc @@ -0,0 +1,38 @@ +#include +#include +#include +#include +#include +#include +#include +using namespace sqlite; +using namespace std; + + +int main() { + bool error_detected = false; + error_log( + [&](errors::constraint) { + cerr << "Wrong error detected!" << endl; + }, + [&](errors::constraint_primarykey e) { + cerr << e.get_code() << '/' << e.get_extended_code() << ": " << e.what() << endl; + error_detected = true; + } + ); + database db(":memory:"); + db << "CREATE TABLE person (id integer primary key not null, name TEXT);"; + + try { + db << "INSERT INTO person (id,name) VALUES (?,?)" << 1 << "jack"; + // inserting again to produce error + db << "INSERT INTO person (id,name) VALUES (?,?)" << 1 << "jack"; + } catch (errors::constraint& e) { + } + + if(!error_detected) { + exit(EXIT_FAILURE); + } + + exit(EXIT_SUCCESS); +} diff --git a/tests/error_log2.cc b/tests/error_log2.cc new file mode 100644 index 00000000..fc0a5bdc --- /dev/null +++ b/tests/error_log2.cc @@ -0,0 +1,35 @@ +#include +#include +#include +#include +#include +#include +#include +using namespace sqlite; +using namespace std; + + +int main() { + bool error_detected = false; + error_log( + [&](errors::constraint e) { + cerr << e.get_code() << '/' << e.get_extended_code() << ": " << e.what() << endl; + error_detected = true; + } + ); + database db(":memory:"); + db << "CREATE TABLE person (id integer primary key not null, name TEXT);"; + + try { + db << "INSERT INTO person (id,name) VALUES (?,?)" << 1 << "jack"; + // inserting again to produce error + db << "INSERT INTO person (id,name) VALUES (?,?)" << 1 << "jack"; + } catch (errors::constraint& e) { + } + + if(!error_detected) { + exit(EXIT_FAILURE); + } + + exit(EXIT_SUCCESS); +}