Aktualizr
C++ SOTA Client
sql_utils.h
1 #ifndef SQL_UTILS_H_
2 #define SQL_UTILS_H_
3 
4 #include <list>
5 #include <memory>
6 #include <mutex>
7 
8 #include <boost/filesystem.hpp>
9 #include <boost/optional.hpp>
10 
11 #include <sqlite3.h>
12 
13 #include "logging/logging.h"
14 
15 // Unique ownership SQLite3 statement creation
16 
17 struct SQLBlob {
18  const std::string& content;
19  explicit SQLBlob(const std::string& str) : content(str) {}
20 };
21 
22 class SQLException : public std::runtime_error {
23  public:
24  SQLException(const std::string& what = "SQL error") : std::runtime_error(what) {}
25  ~SQLException() noexcept override = default;
26 };
27 
29  public:
30  SQLInternalException(const std::string& what = "SQL internal error") : SQLException(what) {}
31  ~SQLInternalException() noexcept override = default;
32 };
33 
35  public:
36  template <typename... Types>
37  SQLiteStatement(sqlite3* db, const std::string& zSql, const Types&... args)
38  : db_(db), stmt_(nullptr, sqlite3_finalize), bind_cnt_(1) {
39  sqlite3_stmt* statement;
40 
41  if (sqlite3_prepare_v2(db_, zSql.c_str(), -1, &statement, nullptr) != SQLITE_OK) {
42  LOG_ERROR << "Could not prepare statement: " << sqlite3_errmsg(db_);
43  throw SQLInternalException(std::string("Could not prepare statement: ") + sqlite3_errmsg(db_));
44  }
45  stmt_.reset(statement);
46 
47  bindArguments(args...);
48  }
49 
50  inline sqlite3_stmt* get() const { return stmt_.get(); }
51  inline int step() const { return sqlite3_step(stmt_.get()); }
52 
53  // get results
54  inline boost::optional<std::string> get_result_col_blob(int iCol) {
55  const auto* b = reinterpret_cast<const char*>(sqlite3_column_blob(stmt_.get(), iCol));
56  if (b == nullptr) {
57  return boost::none;
58  }
59  auto length = static_cast<size_t>(sqlite3_column_bytes(stmt_.get(), iCol));
60  return std::string(b, length);
61  }
62 
63  inline boost::optional<std::string> get_result_col_str(int iCol) {
64  const auto* b = reinterpret_cast<const char*>(sqlite3_column_text(stmt_.get(), iCol));
65  if (b == nullptr) {
66  return boost::none;
67  }
68  return std::string(b);
69  }
70 
71  inline int64_t get_result_col_int(int iCol) { return sqlite3_column_int64(stmt_.get(), iCol); }
72 
73  private:
74  void bindArgument(int v) {
75  if (sqlite3_bind_int(stmt_.get(), bind_cnt_, v) != SQLITE_OK) {
76  LOG_ERROR << "Could not bind: " << sqlite3_errmsg(db_);
77  throw SQLInternalException(std::string("SQLite bind error: ") + sqlite3_errmsg(db_));
78  }
79  }
80 
81  void bindArgument(int64_t v) {
82  if (sqlite3_bind_int64(stmt_.get(), bind_cnt_, v) != SQLITE_OK) {
83  LOG_ERROR << "Could not bind: " << sqlite3_errmsg(db_);
84  throw SQLInternalException(std::string("SQLite bind error: ") + sqlite3_errmsg(db_));
85  }
86  }
87 
88  void bindArgument(const std::string& v) {
89  owned_data_.push_back(v);
90  const std::string& oe = owned_data_.back();
91 
92  if (sqlite3_bind_text(stmt_.get(), bind_cnt_, oe.c_str(), -1, nullptr) != SQLITE_OK) {
93  LOG_ERROR << "Could not bind: " << sqlite3_errmsg(db_);
94  throw SQLInternalException(std::string("SQLite bind error: ") + sqlite3_errmsg(db_));
95  }
96  }
97 
98  void bindArgument(const char* v) { bindArgument(std::string(v)); }
99 
100  void bindArgument(const SQLBlob& blob) {
101  owned_data_.emplace_back(blob.content);
102  const std::string& oe = owned_data_.back();
103 
104  if (sqlite3_bind_blob(stmt_.get(), bind_cnt_, oe.c_str(), static_cast<int>(oe.size()), SQLITE_STATIC) !=
105  SQLITE_OK) {
106  LOG_ERROR << "Could not bind: " << sqlite3_errmsg(db_);
107  throw SQLInternalException("SQLite bind error");
108  }
109  }
110 
111  /* end of template specialization */
112  void bindArguments() {}
113 
114  template <typename T, typename... Types>
115  void bindArguments(const T& v, const Types&... args) {
116  bindArgument(v);
117  bind_cnt_ += 1;
118  bindArguments(args...);
119  }
120 
121  sqlite3* db_;
122  std::unique_ptr<sqlite3_stmt, int (*)(sqlite3_stmt*)> stmt_;
123  int bind_cnt_;
124  // copies of data that need to persist for the object duration
125  // (avoid vector because of resizing issues)
126  std::list<std::string> owned_data_;
127 };
128 
129 // Unique ownership SQLite3 connection
130 extern std::mutex sql_mutex;
132  public:
133  sqlite3* get() { return handle_.get(); }
134  int get_rc() const { return rc_; }
135 
136  explicit SQLite3Guard(const char* path, bool readonly, std::shared_ptr<std::mutex> mutex = nullptr)
137  : handle_(nullptr, sqlite3_close), rc_(0), m_(std::move(mutex)) {
138  if (m_) {
139  m_->lock();
140  }
141  if (sqlite3_threadsafe() == 0) {
142  throw SQLInternalException("sqlite3 has been compiled without multitheading support");
143  }
144  sqlite3* h;
145  if (readonly) {
146  rc_ = sqlite3_open_v2(path, &h, SQLITE_OPEN_READONLY, nullptr);
147  } else {
148  rc_ = sqlite3_open_v2(path, &h, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX, nullptr);
149  }
150 
151  /* retry operations for 2 seconds before returning SQLITE_BUSY */
152  sqlite3_busy_timeout(h, 2000);
153 
154  handle_.reset(h);
155  }
156 
157  explicit SQLite3Guard(const boost::filesystem::path& path, bool readonly = false,
158  std::shared_ptr<std::mutex> mutex = nullptr)
159  : SQLite3Guard(path.c_str(), readonly, std::move(mutex)) {}
160  SQLite3Guard(SQLite3Guard&& guard) noexcept : handle_(std::move(guard.handle_)), rc_(guard.rc_) {}
161  ~SQLite3Guard() {
162  if (m_) {
163  m_->unlock();
164  }
165  }
166  SQLite3Guard(const SQLite3Guard& guard) = delete;
167  SQLite3Guard operator=(const SQLite3Guard& guard) = delete;
168 
169  int exec(const char* sql, int (*callback)(void*, int, char**, char**), void* cb_arg) {
170  return sqlite3_exec(handle_.get(), sql, callback, cb_arg, nullptr);
171  }
172 
173  int exec(const std::string& sql, int (*callback)(void*, int, char**, char**), void* cb_arg) {
174  return exec(sql.c_str(), callback, cb_arg);
175  }
176 
177  template <typename... Types>
178  SQLiteStatement prepareStatement(const std::string& zSql, const Types&... args) {
179  return SQLiteStatement(handle_.get(), zSql, args...);
180  }
181 
182  std::string errmsg() const { return sqlite3_errmsg(handle_.get()); }
183 
184  // Transaction handling
185  //
186  // A transactional series of db operations should be realized between calls of
187  // `beginTranscation()` and `commitTransaction()`. If no commit is done before
188  // the destruction of the `SQLite3Guard` (and thus the SQLite connection) or
189  // if `rollbackTransaction()` is called explicitely, the changes will be
190  // rolled back
191 
192  void beginTransaction() {
193  // Note: transaction cannot be nested and this will fail if another
194  // transaction was open on the same connection
195  if (exec("BEGIN TRANSACTION;", nullptr, nullptr) != SQLITE_OK) {
196  LOG_ERROR << "Can't begin transaction: " << errmsg();
197  throw SQLInternalException(std::string("Can't begin transaction: ") + errmsg());
198  }
199  }
200 
201  void commitTransaction() {
202  if (exec("COMMIT TRANSACTION;", nullptr, nullptr) != SQLITE_OK) {
203  LOG_ERROR << "Can't commit transaction: " << errmsg();
204  throw SQLInternalException(std::string("Can't begin transaction: ") + errmsg());
205  }
206  }
207 
208  void rollbackTransaction() {
209  if (exec("ROLLBACK TRANSACTION;", nullptr, nullptr) != SQLITE_OK) {
210  LOG_ERROR << "Can't rollback transaction: " << errmsg();
211  throw SQLInternalException(std::string("Can't begin transaction: ") + errmsg());
212  }
213  }
214 
215  private:
216  std::unique_ptr<sqlite3, int (*)(sqlite3*)> handle_;
217  int rc_;
218  std::shared_ptr<std::mutex> m_ = nullptr;
219 };
220 
221 #endif // SQL_UTILS_H_
SQLBlob
Definition: sql_utils.h:17
SQLInternalException
Definition: sql_utils.h:28
SQLite3Guard
Definition: sql_utils.h:131
SQLiteStatement
Definition: sql_utils.h:34
SQLException
Definition: sql_utils.h:22