Darwin Neuroevolution Framework
database.h
1 // Copyright 2018 The Darwin Neuroevolution Framework Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #pragma once
16 
17 #include "utils.h"
18 #include "exception.h"
19 
20 #include <cstddef>
21 #include <optional>
22 #include <string>
23 #include <tuple>
24 #include <utility>
25 #include <vector>
26 using namespace std;
27 
28 // CONSIDER:
29 // - lazy rezultset / cursor (call step on demand)
30 // - aggregate type mapping (for params/results, ex. exec<MyStruct>(...))
31 
32 struct sqlite3;
33 struct sqlite3_stmt;
34 
35 namespace db {
36 
38 enum class OpenMode {
40  CreateNew
41 };
42 
44 using RowId = int64_t;
45 
47 class Statement {
48  public:
49  explicit Statement(::sqlite3* db, const string& sql_statement);
50  ~Statement();
51 
52  // no copy/move semantics
53  Statement(const Statement&) = delete;
54  Statement& operator=(const Statement&) = delete;
55 
56  template <class... PARAMS>
57  void bind(PARAMS&&... params) {
58  bindHelper(1, std::forward<PARAMS>(params)...);
59  }
60 
61  // statement parameter binding
62  // (normally done through the bind helper)
63  void bindValue(int index, std::nullopt_t);
64  void bindValue(int index, int value);
65  void bindValue(int index, int64_t value);
66  void bindValue(int index, const string& value);
67  void bindValue(int index, const char* value);
68  void bindValue(int index, double value);
69 
70  // use std::nullopt for NULL values
71  void bindValue(int index, nullptr_t) = delete;
72 
73  template <class T>
74  void bindValue(int index, const optional<T>& value) {
75  if (value.has_value())
76  bindValue(index, *value);
77  else
78  bindValue(index, nullopt);
79  }
80 
81  // returns true if result row available
82  // (and false if it's done)
83  bool step();
84 
85  int columnCount() const;
86 
87  // current result row accessors
88  // (normally used by a ResultSet)
89  void columnValue(int column, optional<int>& value) const;
90  void columnValue(int column, optional<int64_t>& value) const;
91  void columnValue(int column, optional<string>& value) const;
92  void columnValue(int column, optional<double>& value) const;
93 
94  private:
95  template <class T, class... PARAMS>
96  void bindHelper(int index, T&& value, PARAMS&&... params) {
97  bindValue(index, std::forward<T>(value));
98  bindHelper(index + 1, std::forward<PARAMS>(params)...);
99  }
100 
101  void bindHelper(int /*index*/) {}
102 
103  private:
104  ::sqlite3_stmt* stmt_ = nullptr;
105 };
106 
109 template <class... TYPES>
110 class ResultSet {
111  public:
113  using Row = tuple<optional<TYPES>...>;
114 
115  public:
117  const Row& operator[](size_t index) const { return results_[index]; }
118 
120  int columnCount() const { return tuple_size<Row>::value; }
121 
123  size_t size() const { return results_.size(); }
124 
126  bool empty() const { return results_.empty(); }
127 
129  auto begin() const { return results_.begin(); }
130 
132  auto end() const { return results_.end(); }
133 
136  const auto& singleValue() const {
137  if (tuple_size<Row>::value != 1 || results_.size() != 1)
138  throw core::Exception("ResultSet is not a single value");
139  return get<0>(results_[0]);
140  }
141 
142  void extractRow(const Statement& statement) {
143  if (columnCount() > 0 && statement.columnCount() != columnCount()) {
144  throw core::Exception(
145  "ResultSet column count does not match the statement column count");
146  }
147 
148  Row row;
149  extractColumns(statement, row, index_sequence_for<TYPES...>{});
150  results_.push_back(std::move(row));
151  }
152 
153  private:
154  template <size_t... COLUMNS>
155  void extractColumns([[maybe_unused]] const Statement& statement,
156  [[maybe_unused]] Row& row,
157  index_sequence<COLUMNS...>) {
158  (statement.columnValue(COLUMNS, std::get<COLUMNS>(row)), ...);
159  }
160 
161  private:
162  vector<Row> results_;
163 };
164 
171 enum class TransactionOption {
172  Deferred,
173  Immediate,
174  Exclusive
175 };
176 
179  public:
181  explicit Connection(const string& filename, OpenMode open_mode, int busy_wait_ms = 500);
182 
183  ~Connection();
184 
186  void beginTransaction(TransactionOption option = TransactionOption::Deferred);
187 
189  void commit();
190 
192  void rollback();
193 
195  RowId lastInsertRowId() const;
196 
197  // TODO: consider stm = prepare() / exec(stm)
198 
208  template <class... RESULTS, class... PARAMS>
209  ResultSet<RESULTS...> exec(const string& sql_statement, PARAMS&&... params) {
210  Statement prepared_statement(db_, sql_statement);
211  prepared_statement.bind(std::forward<PARAMS>(params)...);
212 
213  ResultSet<RESULTS...> results;
214  while (prepared_statement.step())
215  results.extractRow(prepared_statement);
216  return results;
217  }
218 
219  private:
220  ::sqlite3* db_ = nullptr;
221 };
222 
225  public:
227  explicit TransactionScope(Connection& databaseTransaction,
228  TransactionOption option = TransactionOption::Deferred);
229 
232  ~TransactionScope();
233 
235  void commit();
236 
238  void rollback();
239 
240  private:
241  Connection& database_;
242  bool completed_ = false;
243 };
244 
245 } // namespace db
int columnCount() const
Count of columns in the results.
Definition: database.h:120
const auto & singleValue() const
Convenience helper for extracting a single value.
Definition: database.h:136
A very simple relational database abstraction on top of Sqlite.
Definition: database.h:178
OpenMode
Open database flag.
Definition: database.h:38
Maps to BEGIN DEFERRED TRANSACTION
The base for exception types in the Darwin framework.
Definition: exception.h:27
STL namespace.
int64_t RowId
Represents the ID of a row in the database.
Definition: database.h:44
const Row & operator[](size_t index) const
Indexed access to the rows in the result.
Definition: database.h:117
auto begin() const
ResultSet begin iterator.
Definition: database.h:129
size_t size() const
Count of rows in the results.
Definition: database.h:123
Creates a new database (will fail if file already exists)
A prepared Sqlite statement.
Definition: database.h:47
tuple< optional< TYPES >... > Row
A results row.
Definition: database.h:113
Maps to BEGIN IMMEDIATE TRANSACTION
bool empty() const
Returns true if the ResultSet is empty.
Definition: database.h:126
Represents the results of executing a query.
Definition: database.h:110
A scope-based transaction guard.
Definition: database.h:224
Low level database utilities.
Definition: database.cpp:19
Classes derived from this are not copyable or movable.
Definition: utils.h:69
The database file must exist.
ResultSet< RESULTS... > exec(const string &sql_statement, PARAMS &&... params)
Executes the specified Sqlite statement and returns the results as a ResultSet.
Definition: database.h:209
Maps to BEGIN EXCLUSIVE TRANSACTION
TransactionOption
The type of Sqlite transaction.
Definition: database.h:171
auto end() const
ResultSet end iterator.
Definition: database.h:132