using DbType = std::optional<std::variant<int, double, std::string,
std::vector<unsigned char>>>;
+class DbException : public std::runtime_error {
+ public:
+ explicit DbException(const std::string& msg, int code = SQLITE_ERROR)
+ : std::runtime_error(msg), code_(code) {
+ }
+
+ DbException(const std::string& msg, int code, const std::string& file,
+ int line) : std::runtime_error(msg), code_(code) {
+ msg_ = msg + " at " + file + ":" + std::to_string(line);
+ }
+
+ int code() const {
+ return code_;
+ }
+
+ const char* msg() const {
+ if (msg_.empty())
+ return what();
+ return msg_.c_str();
+ }
+
+ private:
+ int code_;
+ std::string msg_;
+};
+
class AutoDbType {
public:
AutoDbType() = default;
explicit operator int () {
if (!db_type_)
- throw std::runtime_error("invalid type conversion from nullopt to int");
+ throw DbException("invalid type conversion from nullopt to int");
return std::get<int>(*db_type_);
}
explicit operator std::string () {
if (!db_type_) {
- throw std::runtime_error(
+ throw DbException(
"invalid type conversion from nullopt to string");
}
explicit operator double () {
if (!db_type_) {
- throw std::runtime_error(
+ throw DbException(
"invalid type conversion from nullopt to double");
}
explicit operator std::vector<unsigned char> () {
if (!db_type_) {
- throw std::runtime_error(
+ throw DbException(
"invalid type conversion from nullopt to std::vector<unsigned char>");
}
}
explicit TransactionGuard(sqlite3* db) : db_(db) {
- if (sqlite3_exec(db, "BEGIN DEFERRED", nullptr, nullptr, nullptr)
- != SQLITE_OK) {
- throw std::runtime_error("begin transaction failed");
+ int r = sqlite3_exec(db, "BEGIN DEFERRED", nullptr, nullptr, nullptr);
+ if (r != SQLITE_OK) {
+ throw DbException("begin transaction failed", r);
}
}
int len = sqlite3_column_bytes(stmt, pos);
if (!val || len < 0) {
- throw std::runtime_error("invalid blob");;
+ throw DbException("invalid blob");;
} else {
dbt = DbType(std::vector<unsigned char>(val, val + len));
}
} else if (type == SQLITE_NULL) {
dbt = DbType(std::nullopt);
} else {
- throw std::runtime_error("invalid column type");
+ throw DbException("invalid column type", type);
}
return AutoDbType(dbt);
Database(std::string db, int flags) {
int r = sqlite3_open_v2(db.c_str(), &db_, flags, nullptr);
if (r != SQLITE_OK)
- throw std::runtime_error("open failed");
+ throw DbException("open failed", r);
}
Database(std::string db, int flags, std::function<bool(int)> busy_handler) {
int r = sqlite3_open_v2(db.c_str(), &db_, flags, nullptr);
if (r != SQLITE_OK)
- throw std::runtime_error("sqlite3_open_v2() failed");
+ throw DbException("sqlite3_open_v2() failed", r);
busy_handler_ = std::move(busy_handler);
r = sqlite3_busy_handler(db_, [](void* data, int count) {
if (r != SQLITE_OK) {
sqlite3_close_v2(db_);
- throw std::runtime_error("sqlite3_busy_handler() failed");
+ throw DbException("sqlite3_busy_handler() failed", r);
}
}
return *this;
}
- TransactionGuard CreateTransactionGuard() {
+ TransactionGuard CreateTransactionGuard() const {
return TransactionGuard(db_);
}
Result Exec(const Sql& sql) const {
if (!db_)
- throw std::runtime_error("Not opened");
+ throw DbException("Not opened");
sqlite3_stmt* stmt = nullptr;
int r = sqlite3_prepare_v2(db_, sql.GetQuery().c_str(),
for (const auto& i : sql.GetBindingNameMap()) {
int pos = sqlite3_bind_parameter_index(stmt, i.first.c_str());
if (pos == 0)
- throw std::runtime_error("Invalid binding");
+ throw DbException("Invalid binding");
Bind(pos, i.second, stmt);
}
bool Exec(const Sql& sql, const Result& previous_stmt) {
if (sql.GetQuery() != previous_stmt.GetQuery())
- throw std::runtime_error("Query is different");
+ throw DbException("Query is different");
sqlite3_stmt* stmt = previous_stmt.GetRaw();
if (!stmt)
for (const auto& i : sql.GetBindingNameMap()) {
int pos = sqlite3_bind_parameter_index(stmt, i.first.c_str());
if (pos == 0)
- throw std::runtime_error("Invalid binding");
+ throw DbException("Invalid binding");
Bind(pos, i.second, stmt);
}
if (!type) {
r = sqlite3_bind_null(stmt, pos);
if (r != SQLITE_OK) {
- throw std::runtime_error("Invalid binding");
+ throw DbException("Invalid binding", r);
}
return;
}
if (r != SQLITE_OK) {
- throw std::runtime_error("Invalid binding");
+ throw DbException("Invalid binding");
}
}