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