Ntuple.h
Go to the documentation of this file.
1 #ifndef cetlib_sqlite_Ntuple_h
2 #define cetlib_sqlite_Ntuple_h
3 // vim: set sw=2 expandtab :
4 
5 // =====================================================================
6 //
7 // Ntuple
8 //
9 // The Ntuple class template is an interface for inserting into an
10 // SQLite database in a type-safe and thread-safe manner. It is not
11 // intended for this facility to provide facilities for making SQLite
12 // database queries. For querying, consider using the
13 // 'cet::sqlite::select' utilities.
14 //
15 //
16 // Construction
17 // ------------
18 //
19 // The c'tor receives a reference to a cet::sqlite::Connection object,
20 // that must outlive the life of the Ntuple. It is the responsibility
21 // of the user to manage the Connection's lifetime. A Connection is
22 // created by a call to ConnectionFactory::make. Please see the
23 // documentation in cetlib/sqlite/Connection(Factory).h.
24 //
25 // In addition to the Connection object, the Ntuple c'tor receives an
26 // SQLite table name and a set of column names in the form of an
27 // std::array object.
28 //
29 // See the Ntuple definition below for the full c'tor signatures.
30 //
31 // N.B. Constructing an Ntuple in a multi-threaded context is NOT
32 // thread-safe if done in parallel with any operations being
33 // performed on the same database elsewhere.
34 //
35 // Template arguments
36 // ------------------
37 //
38 // The template arguments supplied for the Ntuple type indicate the
39 // type of the column. The following Ntuple specifications are
40 // identical in semantics:
41 //
42 // Ntuple<int, double, string> n1 {...};
43 // Ntuple<column<int>, column<double>, column<string>> n2 {...};
44 //
45 // both of which indicate three columns with SQLite types of INTEGER,
46 // NUMERIC, and TEXT, respectively. Specifying the 'column' template
47 // is necessary ONLY when a column constraint is desired (e.g.):
48 //
49 // Ntuple<column<int, primary_key>, double, string> n3 {...};
50 //
51 // The names of the columns are provided as a c'tor argument (see
52 // below).
53 //
54 // Intended use
55 // ------------
56 //
57 // There are two public modifying functions that can be called:
58 //
59 // Ntuple::insert(...)
60 // Ntuple::flush()
61 //
62 // Calling 'insert' will add items to the internal buffer until the
63 // maximum buffer size has been reached, at which point, the contents
64 // of the buffer will be automatically flushed to the SQLite database.
65 //
66 // Calling 'flush' is necessary only when no more items will be
67 // inserted into the Ntuple AND querying of the Ntuple is required
68 // immediately thereafter. The Ntuple d'tor automatically calls flush
69 // whenever the Ntuple object goes out of scope.
70 //
71 // Examples of use
72 // ---------------
73 //
74 // using namespace cet::sqlite;
75 //
76 // auto c = existing_connection_factory.make("languages.db");
77 // Ntuple<string, string> langs {c, "europe", {"Country","Language"}};
78 // langs.insert("Germany", "German");
79 // langs.insert("Switzerland", "German");
80 // langs.insert("Switzerland", "Italian");
81 // langs.insert("Switzerland", "French");
82 // langs.insert("Switzerland", "Romansh");
83 // langs.flush();
84 //
85 // query_result<string> ch;
86 // ch << select("Languange").from(c,
87 // "europe").where("Country='Switzerland'");
88 // // see cet::sqlite::select documentation regarding using query_result.
89 //
90 // -----------------------------------------------------------
91 //
92 // Technical notes:
93 //
94 // In principle, one could use a concurrent container to prevent
95 // having to lock whenever an insert is done. However, since a
96 // flush occurs whenever the buffer max is reached, the buffer must
97 // be protected from any modification until the flush is complete.
98 // A lock is therefore inevitable. We could probably optimize by
99 // using an atomic variable to protect against modification only
100 // when a flush is being done. This is a potential optimization to
101 // keep in mind.
102 // ===========================================================
103 
106 #include "cetlib/sqlite/column.h"
108 #include "cetlib/sqlite/helpers.h"
109 
110 #include "sqlite3.h"
111 
112 #include <iostream>
113 #include <memory>
114 #include <string>
115 #include <tuple>
116 #include <vector>
117 
118 namespace cet::sqlite {
119 
120  template <typename... Args>
121  class Ntuple {
122  // Types
123  public:
124  // Elements of row are unique_ptr's so that it is possible to bind to a
125  // null parameter.
126  template <typename T>
127  using element_t =
128  std::unique_ptr<typename sqlite::permissive_column<T>::element_type>;
129  using row_t = std::tuple<element_t<Args>...>;
130  static constexpr auto nColumns = std::tuple_size_v<row_t>;
132  // Special Member Functions
133  public:
134  ~Ntuple() noexcept;
135  Ntuple(Connection& connection,
136  std::string const& name,
138  bool overwriteContents = false,
139  std::size_t bufsize = 1000ull);
140  Ntuple(Ntuple const&) = delete;
141  Ntuple& operator=(Ntuple const&) = delete;
142  // API
143  public:
144  std::string const&
145  name() const
146  {
147  return name_;
148  }
149  void insert(Args const...);
150  void flush();
151  // Implementation details
152  private:
153  static constexpr auto iSequence = std::make_index_sequence<nColumns>();
154  // This is the ctor that does all of the work. It exists so that
155  // the Args... and column-names array can be expanded in parallel.
156  template <std::size_t... I>
157  Ntuple(Connection& db,
158  std::string const& name,
159  name_array const& columns,
160  bool overwriteContents,
161  std::size_t bufsize,
162  std::index_sequence<I...>);
163  int flush_no_throw();
164  // Member data
165  private:
166  // Protects all of the data members.
167  std::recursive_mutex mutex_{};
170  std::size_t const max_;
171  std::vector<row_t> buffer_{};
173  };
174 
175 } // cet::sqlite
176 
177 template <typename... Args>
178 template <std::size_t... I>
180  std::string const& name,
181  name_array const& cnames,
182  bool const overwriteContents,
183  std::size_t const bufsize,
184  std::index_sequence<I...>)
185  : connection_{connection}, name_{name}, max_{bufsize}
186 {
187  std::lock_guard sentry{mutex_};
188  assert(connection);
189  sqlite::createTableIfNeeded(connection,
190  overwriteContents,
191  name,
192  sqlite::permissive_column<Args>{cnames[I]}...);
193  std::string sql{"INSERT INTO "};
194  sql += name;
195  sql += " VALUES (?";
196  for (std::size_t i = 1; i < nColumns; ++i) {
197  sql += ",?";
198  }
199  sql += ")";
200  int const rc{sqlite3_prepare_v2(
201  connection_.get(), sql.c_str(), sql.size(), &insert_statement_, nullptr)};
202  if (rc != SQLITE_OK) {
203  auto const ec = sqlite3_step(insert_statement_);
205  << "Failed to prepare insertion statement.\n"
206  << "Return code: " << ec << '\n';
207  }
208  buffer_.reserve(bufsize);
209 }
210 
211 template <typename... Args>
213  std::string const& name,
214  name_array const& cnames,
215  bool const overwriteContents,
216  std::size_t const bufsize)
217  : Ntuple{connection, name, cnames, overwriteContents, bufsize, iSequence}
218 {}
219 
220 template <typename... Args>
222 {
223  if (flush_no_throw() != SQLITE_OK) {
224  std::cerr << "SQLite step failure while flushing.\n";
225  }
226  sqlite3_finalize(insert_statement_);
227 }
228 
229 template <typename... Args>
230 void
232 {
233  std::lock_guard sentry{mutex_};
234  if (buffer_.size() == max_) {
235  flush();
236  }
237  buffer_.emplace_back(std::make_unique<Args>(args)...);
238 }
239 
240 template <typename... Args>
241 int
243 {
244  // Guard against any modifications to the buffer, which is about to
245  // be flushed to the database.
246  std::lock_guard sentry{mutex_};
247  int const rc{
249  if (rc != SQLITE_DONE) {
250  return rc;
251  }
252  buffer_.clear();
253  return SQLITE_OK;
254 }
255 
256 template <typename... Args>
257 void
259 {
260  // No lock here -- lock held by flush_no_throw;
261  if (flush_no_throw() != SQLITE_OK) {
263  << "SQLite step failure while flushing.";
264  }
265 }
266 
267 #endif /* cetlib_sqlite_Ntuple_h */
268 
269 // Local Variables:
270 // mode: c++
271 // End:
void insert(Args const ...)
Definition: Ntuple.h:231
int flush_no_throw()
Definition: Ntuple.h:242
void createTableIfNeeded(sqlite3 *db, bool const delete_contents, std::string const &tablename, permissive_column< Args > const &...cols)
Definition: helpers.h:46
std::recursive_mutex mutex_
Definition: Ntuple.h:167
std::string string
Definition: nybbler.cc:12
cet::coded_exception< errors::ErrorCodes, ExceptionDetail::translate > Exception
Definition: Exception.h:27
STL namespace.
struct sqlite3_stmt sqlite3_stmt
sqlite3 * get() const
Definition: Connection.h:48
std::string const name_
Definition: Ntuple.h:169
std::string const & name() const
Definition: Ntuple.h:145
static QCString args
Definition: declinfo.cpp:674
static constexpr auto iSequence
Definition: Ntuple.h:153
Connection & connection_
Definition: Ntuple.h:168
std::vector< row_t > buffer_
Definition: Ntuple.h:171
string columns
Definition: depos.py:13
Ntuple(Connection &connection, std::string const &name, name_array const &columns, bool overwriteContents=false, std::size_t bufsize=1000ull)
Definition: Ntuple.h:212
static constexpr auto nColumns
Definition: Ntuple.h:130
std::unique_ptr< typename sqlite::permissive_column< T >::element_type > element_t
Definition: Ntuple.h:128
sqlite3_stmt * insert_statement_
Definition: Ntuple.h:172
~Ntuple() noexcept
Definition: Ntuple.h:221
int flush_no_throw(std::vector< Row > const &buffer, sqlite3_stmt *&insertStmt)
Definition: Connection.h:82
std::array< std::string, N > name_array
Definition: column.h:40
std::size_t const max_
Definition: Ntuple.h:170