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  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  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() { return rc_; }
129 
130  explicit SQLite3Guard(const char* path, bool readonly) : handle_(nullptr, sqlite3_close), rc_(0) {
131  if (sqlite3_threadsafe() == 0) {
132  throw std::runtime_error("sqlite3 has been compiled without multitheading support");
133  }
134  sqlite3* h;
135  if (readonly) {
136  rc_ = sqlite3_open_v2(path, &h, SQLITE_OPEN_READONLY, nullptr);
137  } else {
138  rc_ = sqlite3_open_v2(path, &h, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_NOMUTEX, nullptr);
139  }
140  handle_.reset(h);
141  }
142 
143  explicit SQLite3Guard(const boost::filesystem::path& path, bool readonly = false)
144  : SQLite3Guard(path.c_str(), readonly) {}
145  SQLite3Guard(SQLite3Guard&& guard) noexcept : handle_(std::move(guard.handle_)), rc_(guard.rc_) {}
146  SQLite3Guard(const SQLite3Guard& guard) = delete;
147  SQLite3Guard operator=(const SQLite3Guard& guard) = delete;
148 
149  int exec(const char* sql, int (*callback)(void*, int, char**, char**), void* cb_arg) {
150  return sqlite3_exec(handle_.get(), sql, callback, cb_arg, nullptr);
151  }
152 
153  int exec(const std::string& sql, int (*callback)(void*, int, char**, char**), void* cb_arg) {
154  return exec(sql.c_str(), callback, cb_arg);
155  }
156 
157  template <typename... Types>
158  SQLiteStatement prepareStatement(const std::string& zSql, const Types&... args) {
159  return SQLiteStatement(handle_.get(), zSql, args...);
160  }
161 
162  std::string errmsg() const { return sqlite3_errmsg(handle_.get()); }
163 
164  // Transaction handling
165  //
166  // A transactional series of db operations should be realized between calls of
167  // `beginTranscation()` and `commitTransaction()`. If no commit is done before
168  // the destruction of the `SQLite3Guard` (and thus the SQLite connection) or
169  // if `rollbackTransaction()` is called explicitely, the changes will be
170  // rolled back
171 
172  bool beginTransaction() {
173  // Note: transaction cannot be nested and this will fail if another
174  // transaction was open on the same connection
175  int ret = exec("BEGIN TRANSACTION;", nullptr, nullptr);
176  if (ret != SQLITE_OK) {
177  LOG_ERROR << "Can't begin transaction: " << errmsg();
178  }
179  return ret == SQLITE_OK;
180  }
181 
182  bool commitTransaction() {
183  int ret = exec("COMMIT TRANSACTION;", nullptr, nullptr);
184  if (ret != SQLITE_OK) {
185  LOG_ERROR << "Can't commit transaction: " << errmsg();
186  }
187  return ret == SQLITE_OK;
188  }
189 
190  bool rollbackTransaction() {
191  int ret = exec("ROLLBACK TRANSACTION;", nullptr, nullptr);
192  if (ret != SQLITE_OK) {
193  LOG_ERROR << "Can't rollback transaction: " << errmsg();
194  }
195  return ret == SQLITE_OK;
196  }
197 
198  private:
199  std::unique_ptr<sqlite3, int (*)(sqlite3*)> handle_;
200  int rc_;
201 };
202 
203 #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