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