Belle II Software  release-08-01-10
sqlite.h
1 /**************************************************************************
2  * basf2 (Belle II Analysis Software Framework) *
3  * Author: The Belle II Collaboration *
4  * *
5  * See git log for contributors and copyright holders. *
6  * This file is licensed under LGPL-3.0, see LICENSE.md. *
7  **************************************************************************/
8 #pragma once
9 
10 #include <sqlite3.h>
11 
12 #include <optional>
13 #include <string>
14 #include <vector>
15 #include <tuple>
16 #include <stdexcept>
17 
34 namespace sqlite {
36  class SQLiteError: public std::runtime_error {
37  public:
39  explicit SQLiteError(int code, const std::string& prefix = ""): std::runtime_error(prefix + sqlite3_errstr(code)), m_code(code) {}
41  int code() const { return m_code; }
42  private:
43  int m_code;
44  };
45 
46  namespace detail {
51  inline void checkSQLiteError(int code, const std::string& prefix = "")
52  {
53  if (code != SQLITE_OK) throw SQLiteError(code, prefix);
54  }
55 
63  class ColumnFiller {
64  public:
66  explicit ColumnFiller(sqlite3_stmt* statement): m_stmt(statement) {}
68  void operator()(int index, int& col) { col = sqlite3_column_int(m_stmt, index); }
70  void operator()(int index, int64_t& col) { col = sqlite3_column_int(m_stmt, index); }
72  void operator()(int index, double& col) { col = sqlite3_column_double(m_stmt, index); }
74  void operator()(int index, std::string& col)
75  {
76  // ptr is owned by sqlite, no need to free but we need to copy
77  if (auto ptr = reinterpret_cast<const char*>(sqlite3_column_text(m_stmt, index)); ptr != nullptr) {
78  col = ptr;
79  }
80  }
82  void operator()(int index, std::vector<std::byte>& col)
83  {
84  // ptr is owned by sqlite, no need to free but we need to copy
85  if (auto ptr = reinterpret_cast<const std::byte*>(sqlite3_column_blob(m_stmt, index)); ptr != nullptr) {
86  size_t bytes = sqlite3_column_bytes(m_stmt, index);
87  col = std::vector<std::byte>(ptr, ptr + bytes);
88  }
89  }
91  template<class T>
92  void operator()(int index, std::optional<T>& col)
93  {
94  if (sqlite3_column_type(m_stmt, index) != SQLITE_NULL) {
95  col.emplace();
96  (*this)(index, *col);
97  }
98  }
99  private:
100  sqlite3_stmt* m_stmt;
101  };
102 
111  public:
113  explicit ParameterBinder(sqlite3_stmt* statement): m_stmt(statement) {}
114 
116  template<class T>
117  void operator()(int index, T&& param)
118  {
119  // For some reason bind parameters start at one ...
120  checkSQLiteError(bind(index + 1, std::forward<T>(param)));
121  }
122  private:
124  int bind(int index, int param) { return sqlite3_bind_int(m_stmt, index, param);}
126  int bind(int index, int64_t param) { return sqlite3_bind_int64(m_stmt, index, param); }
128  int bind(int index, double param) { return sqlite3_bind_double(m_stmt, index, param);}
130  int bind(int index, const std::string& param)
131  {
132  return sqlite3_bind_text(m_stmt, index, param.data(), param.size(), SQLITE_TRANSIENT);
133  }
135  int bind(int index, const std::vector<std::byte>& param)
136  {
137  return sqlite3_bind_blob(m_stmt, index, param.data(), param.size(), SQLITE_TRANSIENT);
138  }
140  template<class T>
141  int bind(int index, const std::optional<T>& param)
142  {
143  if (param.has_value()) return bind(*param);
144  return sqlite3_bind_null(m_stmt, index);
145  }
146  sqlite3_stmt* m_stmt;
147  };
148 
150  template<class Tuple, class Func, std::size_t ...Is>
151  void visitTupleWithIndexImpl(Tuple&& t, Func&& f, std::index_sequence<Is...>)
152  {
153  // C++17 fold expression to just do all the calls in one separated by "," operator ...
154  (f(std::integral_constant<std::size_t, Is> {}, std::get<Is>(t)), ...);
155  }
156 
163  template<class ... T, class Func>
164  void visitTupleWithIndex(std::tuple<T...>& t, Func&& f)
165  {
166  visitTupleWithIndexImpl(t, std::forward<Func>(f), std::make_index_sequence<sizeof...(T)> {});
167  }
168  }
169 
193  template<class ObjectType, class ... Columns>
195  public:
197  using value_type = ObjectType;
198 
200  class iterator: public std::iterator<std::input_iterator_tag, value_type, size_t> {
201  public:
203  iterator() = default;
205  explicit iterator(ObjectStatement* instance): m_instance(instance), m_row{0}
206  {
207  (*this)++;
208  }
211  {
212  if (m_row < 0) return *this;
213  ++m_row;
214  if (!m_instance->step()) m_row = -1;
215  return *this;
216  }
218  iterator operator++(int) {iterator retval = *this; ++(*this); return retval;}
220  bool operator==(const iterator& other) const { return m_row == other.m_row; }
222  bool operator!=(const iterator& other) const { return m_row != other.m_row; }
224  value_type operator*() const { return m_instance->getRow(); }
225  private:
229  int64_t m_row{ -1};
230  };
231 
233  iterator begin() { return iterator(this); }
235  iterator end() const { return iterator(); }
236 
238  ObjectStatement(sqlite3* db, const std::string& query, bool persistent)
239  {
240  detail::checkSQLiteError(sqlite3_prepare_v3(db, query.data(), query.size(), persistent ? SQLITE_PREPARE_PERSISTENT : 0,
241  &m_statement, nullptr), "Cannot prepare database statement: ");
242  if (auto cols = sqlite3_column_count(m_statement); cols != sizeof...(Columns)) {
243  throw std::runtime_error("Number of column (" + std::to_string(cols) + ") doesn't match number of template parameters (" +
244  std::to_string(sizeof...(Columns)) + ')');
245  }
246  }
248  ~ObjectStatement() { if (m_statement) sqlite3_finalize(m_statement); }
249 
251  template<class ... Parameters>
252  ObjectStatement& execute(Parameters... parameters)
253  {
254  if (auto params = sqlite3_bind_parameter_count(m_statement); params != sizeof...(Parameters)) {
255  throw std::runtime_error("Number of arguments (" + std::to_string(sizeof...(Parameters)) +
256  ") doesn't match number of statement parameters (" + std::to_string(params) + ")");
257  }
258  sqlite3_reset(m_statement);
259  auto parameter_tuple = std::make_tuple(parameters...);
260  detail::visitTupleWithIndex(parameter_tuple, detail::ParameterBinder{m_statement});
261  return *this;
262  }
263 
267  bool step()
268  {
269  if (auto ret = sqlite3_step(m_statement); ret != SQLITE_ROW) {
270  if (ret == SQLITE_DONE) return false;
271  detail::checkSQLiteError(ret);
272  }
273  return true;
274  }
275 
278  {
279  std::tuple<Columns...> result;
280  detail::visitTupleWithIndex(result, detail::ColumnFiller{m_statement});
281  return std::make_from_tuple<value_type>(std::move(result));
282  }
283  private:
285  sqlite3_stmt* m_statement{nullptr};
286  };
287 
289  template<class ... Columns> using Statement = ObjectStatement<std::tuple<Columns...>, Columns...>;
291  template<class T> using SimpleStatement = ObjectStatement<T, T>;
292 
294  class Connection {
295  public:
297  explicit Connection(const std::string& filename)
298  {
299  detail::checkSQLiteError(sqlite3_open_v2(filename.c_str(), &m_connection, SQLITE_OPEN_READONLY, nullptr));
300  }
302  ~Connection() { if (m_connection) sqlite3_close_v2(m_connection); }
304  template<class ... Columns>
305  Statement<Columns...> prepare(const std::string& query, bool persistent = false) { return Statement<Columns...>(m_connection, query, persistent); }
307  operator sqlite3* () const { return m_connection; }
308  private:
310  sqlite3* m_connection;
311  };
312 }
Simple wrapper for a SQLite database connection.
Definition: sqlite.h:294
Statement< Columns... > prepare(const std::string &query, bool persistent=false)
Return a prepared statement for execution.
Definition: sqlite.h:305
Connection(const std::string &filename)
Create from filename.
Definition: sqlite.h:297
~Connection()
And clean up.
Definition: sqlite.h:302
sqlite3 * m_connection
Pointer to the sqlite database object.
Definition: sqlite.h:310
Iterator class to allow iterating over the rows.
Definition: sqlite.h:200
bool operator!=(const iterator &other) const
as well as unequality check
Definition: sqlite.h:222
iterator(ObjectStatement *instance)
and a real constructor to a statement
Definition: sqlite.h:205
iterator()=default
with a default constructor
iterator operator++(int)
also postfix increment
Definition: sqlite.h:218
bool operator==(const iterator &other) const
and equality check
Definition: sqlite.h:220
ObjectStatement * m_instance
pointer to the statement
Definition: sqlite.h:227
value_type operator*() const
and a dereference operator
Definition: sqlite.h:224
iterator & operator++()
increment to next row, stop in case we are done
Definition: sqlite.h:210
int64_t m_row
current row index
Definition: sqlite.h:229
SQLite prepared statement wrapper.
Definition: sqlite.h:194
ObjectStatement & execute(Parameters... parameters)
Execute the statement, providing all necessary parameters.
Definition: sqlite.h:252
bool step()
Step to the next row in the result.
Definition: sqlite.h:267
value_type getRow() const
Return the current row.
Definition: sqlite.h:277
iterator end() const
Iterator to the end.
Definition: sqlite.h:235
sqlite3_stmt * m_statement
pointer to the statement object
Definition: sqlite.h:285
ObjectStatement(sqlite3 *db, const std::string &query, bool persistent)
Create a statement for an existing database object.
Definition: sqlite.h:238
~ObjectStatement()
Clean up the statement.
Definition: sqlite.h:248
iterator begin()
Iterator to the beginning.
Definition: sqlite.h:233
ObjectType value_type
each row gets converted to a instance of this type
Definition: sqlite.h:197
Simple error class to be thrown if there is any sqlite error on any of the operations.
Definition: sqlite.h:36
int m_code
SQLite error code.
Definition: sqlite.h:43
int code() const
Return the sqlite error code.
Definition: sqlite.h:41
SQLiteError(int code, const std::string &prefix="")
Construct an instance from a prefix string and an sqlite error code.
Definition: sqlite.h:39
Struct to fill the different columns in a sqlite result row into a std::tuple.
Definition: sqlite.h:63
ColumnFiller(sqlite3_stmt *statement)
Create a new instance for the given statement.
Definition: sqlite.h:66
sqlite3_stmt * m_stmt
statement on which to work on
Definition: sqlite.h:100
void operator()(int index, int64_t &col)
Fill 64bit integer column.
Definition: sqlite.h:70
void operator()(int index, double &col)
Fill double column.
Definition: sqlite.h:72
void operator()(int index, std::string &col)
Fill string column.
Definition: sqlite.h:74
void operator()(int index, std::optional< T > &col)
Fill an possibly null column in an optional.
Definition: sqlite.h:92
void operator()(int index, int &col)
Fill integer column.
Definition: sqlite.h:68
void operator()(int index, std::vector< std::byte > &col)
Fill blob column.
Definition: sqlite.h:82
Bind the given parameter to the sqlite statement.
Definition: sqlite.h:110
int bind(int index, const std::vector< std::byte > &param)
bind a bytestream
Definition: sqlite.h:135
ParameterBinder(sqlite3_stmt *statement)
Create a new object for the given statement.
Definition: sqlite.h:113
int bind(int index, const std::optional< T > &param)
bind an optional: if it doesn't have a value we bind NULL
Definition: sqlite.h:141
void operator()(int index, T &&param)
Bind the parameter with the given index to the statement.
Definition: sqlite.h:117
sqlite3_stmt * m_stmt
statement on which to work on
Definition: sqlite.h:146
int bind(int index, double param)
bind a double parameter
Definition: sqlite.h:128
int bind(int index, int param)
bind an integer parameter
Definition: sqlite.h:124
int bind(int index, const std::string &param)
bind a string parameter
Definition: sqlite.h:130
int bind(int index, int64_t param)
bind a 64bit integer parameter
Definition: sqlite.h:126
C++ wrapper around the sqlite C Api for convenient use of SQLite statements in C++.
Definition: sqlite.h:34