Incorporating std::error_code
Closed this issue · 3 comments
This is just for the sake of discussion. Feel free to close.
Have you considered replacing your errno-style return types with std::error_code
? Doing this would give us inter-operable, type-stripped ways to propagate errors without losing any information.
int database::execute(char const* sql);
would become
std::error_code database::execute(char const* sql);
Instead of using upstream SQLITE_OK
and sqlite3_errstr()
directly like this:
sqlite3pp::database db;
int ec = db.connect(...);
if (ec != SQLITE_OK)
throw std::runtime_error( sqlite3_errstr(ec) );
we could use an sqlite3pp-supplied C++ wrapper:
sqlite3pp::database db;
std::error_code ec = db.connect(...);
if (ec != sqlite3pp::ok)
throw std::runtime_error( ec.message() );
It could also help us to combine sqlite3pp errors with our own errors:
std::error_code MyDb::AddUser(User u)
{
if (u.size() < 3)
return UserError::too_short;
return _db->execute( SqlInsert(u) );
}
It could also be used to permit the use of optional exceptions:
void database::detach(char const* name) throws;
void database::detach(char const* name, std::error_code& ec) nothrow;
Or you could also splice in your own error codes.
inline std::error_code statement::bind(char const* name, int value)
{
auto idx = sqlite3_bind_parameter_index(stmt_, name);
if (idx == 0)
return sqlite3pp::bad_bind_name;
return bind(idx, value);
}
If you really want to take this a step further, you could implement chaining patterns for your binds:
inline statement& bind(const char* name, int value, std::error_code& ec){
auto idx = sqlite3_bind_parameter_index(stmt_, name);
if (idx == 0)
{
ec = sqlite3pp::bad_bind_name;
return *this;
}
ec = bind(idx, value);
return *this;
}
allowing us to:
std::error_code ec1, ec2, ec3, ec4;
sqlite3pp::command cmd(
db, "INSERT INTO contacts (name, phone) VALUES (:user, :phone)", ec1
).bind(":user", "Mike", ec2)
.bind(":phone", "555-1234", ec3)
.execute(ec4);
An implementation could look like this:
namespace sqlite3pp {
enum class error {
ok = SQLITE_OK ,
row = SQLITE_ROW ,
done = SQLITE_DONE ,
error = SQLITE_ERROR ,
internal = SQLITE_INTERNAL ,
perm = SQLITE_PERM ,
abort = SQLITE_ABORT ,
busy = SQLITE_BUSY ,
locked = SQLITE_LOCKED ,
...
};
class error_category : public std::error_category {
public:
virtual const char* name() const noexcept override { return "sqlite3pp"; }
virtual std::string message(int ev) const override { return sqlite3_errstr(ev); }
};
const std::error_category& ErrorCategory() noexcept {
static class error_category c;
return c;
}
}
std::error_code make_error_code(sqlite3pp::error code) noexcept {
return std::error_code(static_cast<int>(code), sqlite3pp::ErrorCategory() );
}
template<>
struct std::is_error_code_enum<sqlite3::error> : public std::true_type {};
Of course, the major disadvantage is the break in the API this would cause. But I'm curious, if it wasn't for API compatibility, would this be interesting?
Although I haven't still gone through your ideas in depth, I do think they hit the right point, and that it would result in something practical and valuable.
I'm willing to work incorporating some of these changes, if the author allows it.
I'm afraid that I would not to this. Here are the reasons.
- As @stewbond mentioned, it is not backward compatible.
- It was not a goal of this library to abstract all sqlite3 c interfaces away. It gives the ways to access the underlying data such as error_code(), extended_error_code(), and error_msg().
- We can introduce a method to return std::error_code using the error code related methods above, but it will incur the maintenance costs to keep them up-to-date.
Thanks for responding. I was curious more than anything.