#include <list>
#include <map>
#include <memory>
+#include <optional>
#include <string>
#include <tuple>
#include <utility>
for_(func, std::make_index_sequence<N>());
}
-using DbType = std::variant<int, double, std::string,
- std::vector<unsigned char>>;
+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;
- AutoDbType(DbType db_type) : db_type_(db_type) {}
+ explicit AutoDbType(DbType db_type) : db_type_(db_type) {}
+
+ explicit operator int () {
+ if (!db_type_)
+ throw DbException("invalid type conversion from nullopt to int");
+ return std::get<int>(*db_type_);
+ }
+
+ explicit operator std::string () {
+ if (!db_type_) {
+ throw DbException(
+ "invalid type conversion from nullopt to string");
+ }
+
+ return std::get<std::string>(*db_type_);
+ }
+
+ explicit operator double () {
+ if (!db_type_) {
+ throw DbException(
+ "invalid type conversion from nullopt to double");
+ }
+
+ return std::get<double>(*db_type_);
+ }
+
+ explicit operator std::vector<unsigned char> () {
+ if (!db_type_) {
+ throw DbException(
+ "invalid type conversion from nullopt to std::vector<unsigned char>");
+ }
+
+ return std::get<std::vector<unsigned char>>(*db_type_);
+ }
- operator int () {
- return std::get<int>(db_type_);
+ operator std::optional<int> () {
+ if (!db_type_)
+ return std::nullopt;
+ return std::get<int>(*db_type_);
}
- operator std::string () {
- return std::get<std::string>(db_type_);
+ operator std::optional<std::string> () {
+ if (!db_type_)
+ return std::nullopt;
+ return std::get<std::string>(*db_type_);
}
- operator double () {
- return std::get<double>(db_type_);
+ operator std::optional<double> () {
+ if (!db_type_)
+ return std::nullopt;
+ return std::get<double>(*db_type_);
}
- operator std::vector<unsigned char> () {
- return std::get<std::vector<unsigned char>>(db_type_);
+ operator std::optional<std::vector<unsigned char>> () {
+ if (!db_type_)
+ return std::nullopt;
+ return std::get<std::vector<unsigned char>>(*db_type_);
}
private:
return *this;
}
- TransactionGuard(sqlite3* db) : db_(db) {
- if (sqlite3_exec(db, "BEGIN DEFERRED", nullptr, nullptr, nullptr)
- != SQLITE_OK) {
- throw std::runtime_error("begin transaction failed");
+ explicit TransactionGuard(sqlite3* db) : db_(db) {
+ int r = sqlite3_exec(db, "BEGIN DEFERRED", nullptr, nullptr, nullptr);
+ if (r != SQLITE_OK) {
+ throw DbException("begin transaction failed", r);
}
}
Sql(std::string query) : query_(std::move(query)) {}
Sql& Bind(std::string val) {
- bindings_.push_back(DbType(std::move(val)));
+ if (empty_string_as_null_ && val.empty())
+ bindings_.push_back(DbType(std::nullopt));
+ else
+ bindings_.push_back(DbType(std::move(val)));
return *this;
}
}
Sql& Bind(std::vector<unsigned char> val) {
- bindings_.push_back(DbType(std::move(val)));
+ if (empty_vector_as_null_ && val.empty())
+ bindings_.push_back(DbType(std::nullopt));
+ else
+ bindings_.push_back(DbType(std::move(val)));
+ return *this;
+ }
+
+ Sql& Bind(const std::nullopt_t val) {
+ bindings_.push_back(DbType(val));
return *this;
}
Sql& Bind(int pos, std::string val) {
- binding_map_[pos] = DbType(std::move(val));
+ if (empty_string_as_null_ && val.empty())
+ binding_map_[pos] = DbType(std::nullopt);
+ else
+ binding_map_[pos] = DbType(std::move(val));
return *this;
}
}
Sql& Bind(int pos, std::vector<unsigned char> val) {
- binding_map_[pos] = DbType(std::move(val));
+ if (empty_vector_as_null_ && val.empty())
+ binding_map_[pos] = DbType(std::nullopt);
+ else
+ binding_map_[pos] = DbType(std::move(val));
+ return *this;
+ }
+
+ Sql& Bind(int pos, const std::nullopt_t& val) {
+ binding_map_[pos] = DbType(val);
return *this;
}
Sql& Bind(std::string name, std::string val) {
- binding_name_map_[std::move(name)] = DbType(std::move(val));
+ if (empty_string_as_null_ && val.empty())
+ binding_name_map_[std::move(name)] = DbType(std::nullopt);
+ else
+ binding_name_map_[std::move(name)] = DbType(std::move(val));
return *this;
}
}
Sql& Bind(std::string name, std::vector<unsigned char> val) {
- binding_name_map_[std::move(name)] = DbType(std::move(val));
+ if (empty_vector_as_null_ && val.empty())
+ binding_name_map_[std::move(name)] = DbType(std::nullopt);
+ else
+ binding_name_map_[std::move(name)] = DbType(std::move(val));
+ return *this;
+ }
+
+ Sql& Bind(std::string name, const std::nullopt_t& val) {
+ binding_name_map_[std::move(name)] = DbType(val);
return *this;
}
return query_;
}
+ Sql& SetEmptyStringAsNull(bool as_null) {
+ empty_string_as_null_ = as_null;
+ return *this;
+ }
+
+ Sql& SetEmptyVectorAsNull(bool as_null) {
+ empty_vector_as_null_ = as_null;
+ return *this;
+ }
+
+ Sql& Reset() {
+ bindings_.clear();
+ binding_map_.clear();
+ binding_name_map_.clear();
+ empty_string_as_null_ = false;
+ empty_vector_as_null_ = false;
+ return *this;
+ }
+
private:
std::string query_;
std::vector<DbType> bindings_;
std::map<int, DbType> binding_map_;
std::map<std::string, DbType> binding_name_map_;
+ bool empty_string_as_null_ = false;
+ bool empty_vector_as_null_ = false;
};
class Result {
class Record {
public:
- Record(const sqlite3_stmt* stmt) : stmt_(stmt) {}
+ explicit Record(const sqlite3_stmt* stmt) : stmt_(stmt) {}
AutoDbType Get(int pos) const {
sqlite3_stmt* stmt = const_cast<sqlite3_stmt*>(stmt_);
int type = sqlite3_column_type(stmt, pos);
- if (type == SQLITE_NULL)
- throw std::runtime_error("invalid column");
DbType dbt;
if (type == SQLITE_TEXT) {
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);
class Iterator {
public:
- Iterator(sqlite3_stmt* stmt) : stmt_(stmt) {}
+ explicit Iterator(sqlite3_stmt* stmt) : stmt_(stmt) {}
Record operator*() { return Record(stmt_); }
return std::nullopt;
}
+ sqlite3_stmt* GetRaw() const {
+ return stmt_;
+ }
+
+ const std::string& GetQuery() const {
+ return query_;
+ }
+
private:
friend class Database;
- Result(sqlite3_stmt* stmt, sqlite3* db) : stmt_(stmt), db_(db) {}
+ Result(sqlite3_stmt* stmt, sqlite3* db, std::string query)
+ : stmt_(stmt), db_(db), query_(std::move(query)) {}
sqlite3_stmt* stmt_ = nullptr;
sqlite3* db_ = nullptr;
+ std::string query_;
};
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(),
-1, &stmt, nullptr);
if (r != SQLITE_OK) {
- return { nullptr, nullptr };
+ return { nullptr, nullptr, "" };
}
std::unique_ptr<sqlite3_stmt, decltype(sqlite3_finalize)*> stmt_auto(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);
}
r = sqlite3_step(stmt);
if (r != SQLITE_ROW && r != SQLITE_DONE) {
- return { nullptr, db_ };
+ return { nullptr, db_, "" };
}
- return { stmt_auto.release(), db_ };
+ return { stmt_auto.release(), db_, sql.GetQuery() };
+ }
+
+ bool Exec(const Sql& sql, const Result& previous_stmt) {
+ if (sql.GetQuery() != previous_stmt.GetQuery())
+ throw DbException("Query is different");
+
+ sqlite3_stmt* stmt = previous_stmt.GetRaw();
+ if (!stmt)
+ return false;
+
+ int r = sqlite3_reset(stmt);
+ if (r != SQLITE_ROW && r != SQLITE_DONE && r != SQLITE_OK)
+ return false;
+
+ int pos = 1;
+ for (const auto& i : sql.GetBindings()) {
+ Bind(pos++, i, stmt);
+ }
+
+ for (const auto& i : sql.GetBindingMap()) {
+ Bind(i.first, i.second, stmt);
+ }
+
+ for (const auto& i : sql.GetBindingNameMap()) {
+ int pos = sqlite3_bind_parameter_index(stmt, i.first.c_str());
+ if (pos == 0)
+ throw DbException("Invalid binding");
+ Bind(pos, i.second, stmt);
+ }
+
+ r = sqlite3_step(stmt);
+ if (r != SQLITE_ROW && r != SQLITE_DONE)
+ return false;
+
+ return true;
+ }
+
+ void OneStepExec(const Sql& sql) const {
+ char* errmsg = nullptr;
+ int ret = sqlite3_exec(db_, sql.GetQuery().c_str(), nullptr, nullptr,
+ &errmsg);
+ if (ret != SQLITE_OK) {
+ std::unique_ptr<char, decltype(free)*> errmsg_auto(errmsg, free);
+ throw DbException(errmsg);
+ }
}
private:
void Bind(int pos, const DbType& type, sqlite3_stmt* stmt) const {
int r;
- if (const std::string* pstr = std::get_if<std::string>(&type)) {
+ if (!type) {
+ r = sqlite3_bind_null(stmt, pos);
+ if (r != SQLITE_OK) {
+ throw DbException("Invalid binding", r);
+ }
+
+ return;
+ }
+
+ if (const std::string* pstr = std::get_if<std::string>(&(*type))) {
r = sqlite3_bind_text(stmt, pos, (*pstr).c_str(), -1,
SQLITE_TRANSIENT);
- } else if (const int* pint = std::get_if<int>(&type)) {
+ } else if (const int* pint = std::get_if<int>(&(*type))) {
r = sqlite3_bind_int(stmt, pos, (*pint));
- } else if (const double* pdouble = std::get_if<double>(&type)) {
+ } else if (const double* pdouble = std::get_if<double>(&(*type))) {
r = sqlite3_bind_double(stmt, pos, (*pdouble));
} else if (const std::vector<unsigned char>* pvector =
- std::get_if<std::vector<unsigned char>>(&type)) {
+ std::get_if<std::vector<unsigned char>>(&(*type))) {
r = sqlite3_bind_blob(stmt, pos, (*pvector).data(),
(*pvector).size(), nullptr);
} else {
}
if (r != SQLITE_OK) {
- throw std::runtime_error("Invalid binding");
+ throw DbException("Invalid binding");
}
}