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