aboutsummaryrefslogtreecommitdiff
path: root/include/sqlitepp/sqlitepp.h
blob: ad09b6be15eb7ebcab7d6c3a886e17bda6d8fe2a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
// Copyright (C) 2014--2015 Robin Krahl <robin.krahl@ireas.org>
// MIT license -- http://opensource.org/licenses/MIT

#ifndef SQLITEPP_SQLITEPP_H_
#define SQLITEPP_SQLITEPP_H_

#include <sqlite3.h>
#include <stdexcept>
#include <memory>
#include <string>

/// \file
/// \brief Defines all classes of the sqlitepp library in the namespace
///        sqlitepp.

/// \mainpage sqlitepp -- C++ wrapper for SQLite3
/// **sqlitepp** is a C++ wrapper for the official SQLite3 C API.
///
/// \section compile Compiling sqlitepp
/// sqlitepp uses GNU Make as a build tool. To build sqlitepp from source,
/// download the source from <a href="https://git.ireas.org/sqlitepp"
/// title="Git repository sqlitepp on git.ireas.org">git.ireas.org</a> and then
/// run `make`.  You might have to change the settings in `config.mk`.
///
/// \code{.unparsed}
/// $ git clone https://git.ireas.org/sqlitepp
/// $ cd sqlitepp
/// $ make
/// \endcode
///
/// \section using Using sqlitepp
///
/// \subsection connect Connecting to a database
/// To connect to a SQLite database, you just have to create a new
/// sqlitepp::Database object.
/// \code{.cpp}
/// sqlitepp::Database database("/path/to/database.sqlite");
/// \endcode
/// This snippet is equivalent to:
/// \code{.cpp}
/// sqlitepp::Database database;
/// database.open("/path/to/database.sqlite");
/// \endcode
/// If the database file does not already exist, it is created. If an error
/// occurs during the creation of the database, a sqlitepp::DatabaseError
/// is thrown.
///
/// \subsection execute Executing a simple statement
/// To execute a simple statement, use sqlitepp::Database::execute:
/// \code{.cpp}
/// sqlitepp::Database database("/path/to/database.sqlite");
/// database.execute("CREATE TABLE test (id, value);");
/// \endcode
///
/// \subsection prepare Executing complex statements
/// If you want to execute more complex statements, for example selection or
/// insertion, use prepared statements. You can prepare a statement using
/// sqlitepp::Database::prepare. You can then bind values (if necessary) using
/// the `bind` methods of sqlitepp::Statement and execute the statement using
/// sqlitepp::Statement::execute. `execute` returns a sqlitepp::ResultSet that
/// stores the returned values (if any).
///
/// \subsubsection insert Example 1: insert
/// The recommended way to handle insertions are named bindings:
/// \code{.cpp}
/// sqlitepp::Database database("/path/to/database.sqlite");
/// std::shared_ptr<sqlitepp::Statement> statement = database.prepare(
///     "INSERT INTO test (id, value) VALUES (:id, :value);");
///
/// // insert (1, "test value")
/// statement->bind(":id", 1);
/// statement->bind(":value", "test value");
/// statement->execute();
/// statement->reset();
///
/// // insert (2, "other value")
/// statement->bind(":id", 2);
/// statement->bind(":value", "other value");
/// statement->execute();
/// \endcode
///
/// \subsubsection select Example 2: select
/// \code{.cpp}
/// sqlitepp::Database database("/path/to/database.sqlite");
/// std::shared_ptr<sqlitepp::Statement> statement = database.prepare(
///     "SELECT id, value FROM test;");
/// ResultSet resultSet = statement.execute();
/// while (resultSet.canRead()) {
///   std::cout << "ID: " << resultSet.readInt(0) << "\tvalue: "
///       << resultSet.readString(1) << std::endl;
///   resultSet.next();
/// }
/// \endcode
///
/// \section concepts Concepts
/// \subsection error Error handling
/// If an error occurs during an operation, an exception is thrown. All
/// SQLite3 database errors are wrapped in sqlitepp::DatabaseError. If a
/// method returns, it was successful (if not stated otherwise in the method
/// documentation).
///
/// \subsection resources Resources
/// sqlitepp uses RAII. This means that the destructors of sqlitepp::Database
/// and sqlitepp::Statement take care of freeing their resources once they
/// are destroyed. You can force them to free their resources using the
/// `close` methods.

/// \brief Contains all classes of the sqlitepp library.
namespace sqlitepp {

/// \brief A class that forbids copying and assignments for all subclasses.
///
/// This class defines a private, unimplemented copy constructor and assignment
/// method so that copies and assignments fail at compile-time. This class is
/// inspired by Scott Meyers, <em>Effective C++</em>, 3rd Edition, Item 6.
class Uncopyable {
 protected:
  Uncopyable() {}
  ~Uncopyable() {}

 private:
  Uncopyable(const Uncopyable&);
  Uncopyable& operator=(const Uncopyable&);
};

/// \brief An element that has the two states *open* and *closed*.
///
/// Subclasses of this class may define methods that require the object to be
/// in a specific state. Refer to the implementing class&rsquo;s documentation
/// for more information about the methods that require a specific state.
///
/// The default state depends on the implementation. You can check the state of
/// an object using the isOpen() method.
///
/// Implementing classes may use setOpen() to change the state and
/// requireOpen() to throw a std::logic_error if the object is currently not
/// open.
class Openable {
 public:
  /// \brief Checks whether this object is open.
  ///
  /// \returns `true` if this object is open; `false` if it is closed
  bool isOpen() const;

 protected:
  /// \brief Creates a new Openable.
  ///
  /// \param open `true` if the objet should be open per default; `false` if
  ///        it shoukd be closed per defaut
  /// \param name the name of the implementing class used in error messages
  Openable(const bool open, const std::string& name);

  /// \brief Requires this object to be open and throws an exception if it is
  ///        not.
  ///
  /// This method should be used at the beginning of other subclass methods
  /// that require this object to be open. The error message of the exception
  /// will contain the class name passed to the constructor.
  ///
  /// \throws std::logic_error if this object is not open
  void requireOpen() const;

  /// \brief Changes the state of this object.
  ///
  /// \param open the new state of this object (`true` if it should be opened;
  ///        `false` if it should be closed)
  void setOpen(const bool open);

 private:
  bool m_open;
  const std::string& m_name;
};

/// \brief An error that occurred during a database operation.
///
/// This error class is only used for errors that occured in the SQLite3
/// library and that are related to database operations. If there are other
/// problems, for example wrong states or illegal arguments, appropriate other
/// exceptions are thrown.
///
/// This exception class stores the SQLite3 error code and the error message.
///
/// \sa [SQLite Result Codes](https://www.sqlite.org/c3ref/c_abort.html)
class DatabaseError : public std::runtime_error {
 public:
  /// \brief Creates a new DatabaseError with the given code and the default
  ///        message.
  ///
  /// The message is retrieved from the default SQLite3 error messages.
  ///
  /// \param errorCode the SQLite3 error code
  /// \sa [SQLite Result Codes](https://www.sqlite.org/c3ref/c_abort.html)
  explicit DatabaseError(const int errorCode);

  /// \brief Creates a new DatabaseError with the given code and message.
  ///
  /// \param errorCode the SQLite3 error code
  /// \param errorMessage the according error message
  /// \sa [SQLite Result Codes](https://www.sqlite.org/c3ref/c_abort.html)
  DatabaseError(const int errorCode, const std::string& errorMessage);

  /// \brief Returns the SQLite3 error code for this error.
  ///
  /// \sa [SQLite Result Codes](https://www.sqlite.org/c3ref/c_abort.html)
  int errorCode() const;

 private:
  const int m_errorCode;

  static std::string getErrorMessage(const int errorCode,
                                     const std::string& errorMessage);
};

class Database;
class ResultSet;

/// \brief A handle for a SQLite3 statement.
///
/// This class stores a reference to a prepared SQLite3 statement and provides
/// methods to bind parameters to the query, execute it and read the results.
/// If a database operation fails, a DatabaseError is thrown.
///
/// Use Database::prepare to obtain instances of this class.
class Statement : private Uncopyable, public Openable {
 public:
  /// \brief Deconstructs this object and finalizes the statement.
  ///
  /// Errors that occur when the statement is finalized are ignored as they
  /// already occured during the last operation.
  ~Statement();

  /// \brief Binds the given double value to the column with the given index.
  ///
  /// \param index the index of the column to bind the value to
  /// \param value the value to bind to that column
  /// \throws std::logic_error if the statement is not open
  /// \throws std::out_of_range if the given index is out of range
  /// \throws std::runtime_error if there is not enough memory to bind the
  ///         value
  /// \throws DatabaseError if an database error occured during the binding
  void bind(const int index, const double value);

  /// \brief Binds the given double value to the column with the given name.
  ///
  /// \param index the name of the column to bind the value to
  /// \param value the value to bind to that column
  /// \throws std::logic_error if the statement is not open
  /// \throws std::invalid_argument if there is no column witht the given name
  /// \throws std::runtime_error if there is not enough memory to bind the
  ///         value
  /// \throws DatabaseError if an database error occured during the binding
  void bind(const std::string& name, const double value);

  /// \brief Binds the given integer value to the column with the given index.
  ///
  /// \param index the index of the column to bind the value to
  /// \param value the value to bind to that column
  /// \throws std::logic_error if the statement is not open
  /// \throws std::out_of_range if the given index is out of range
  /// \throws std::runtime_error if there is not enough memory to bind the
  ///         value
  /// \throws DatabaseError if an database error occured during the binding
  void bind(const int index, const int value);

  /// \brief Binds the given integer value to the column with the given name.
  ///
  /// \param index the name of the column to bind the value to
  /// \param value the value to bind to that column
  /// \throws std::logic_error if the statement is not open
  /// \throws std::invalid_argument if there is no column witht the given name
  /// \throws std::runtime_error if there is not enough memory to bind the
  ///         value
  /// \throws DatabaseError if an database error occured during the binding
  void bind(const std::string& name, const int value);

  /// \brief Binds the given string value to the column with the given index.
  ///
  /// \param index the index of the column to bind the value to
  /// \param value the value to bind to that column
  /// \throws std::logic_error if the statement is not open
  /// \throws std::out_of_range if the given index is out of range
  /// \throws std::runtime_error if there is not enough memory to bind the
  ///         value
  /// \throws DatabaseError if an database error occured during the binding
  void bind(const int index, const std::string& value);

  /// \brief Binds the given string value to the column with the given name.
  ///
  /// \param index the name of the column to bind the value to
  /// \param value the value to bind to that column
  /// \throws std::logic_error if the statement is not open
  /// \throws std::invalid_argument if there is no column witht the given name
  /// \throws std::runtime_error if there is not enough memory to bind the
  ///         value
  /// \throws DatabaseError if an database error occured during the binding
  void bind(const std::string& name, const std::string& value);

  /// \brief Closes this statement.
  ///
  /// Once you closed this statement, you may no longer access it. Any errors
  /// that occur during finalization are ignored as they already occurred
  /// during the last operation.
  void close();

  /// \brief Executes this statement and returns the result (if any).
  ///
  /// \returns the result returned from the query (empty if there was no result)
  /// \throws std::logic_error if the statement is not open
  /// \throws DatabaseError if a database error occurs during the query
  ///         execution
  ResultSet execute();

  /// \brief Resets the statement.
  ///
  /// Resets the statement so that it can be re-executed. Bindings are not
  /// resetted.
  ///
  /// \returns `true` if the reset was successful; otherwise `false`
  /// \throws std::logic_error if the statement is not open
  bool reset();

 private:
  explicit Statement(sqlite3_stmt* handle);

  int getParameterIndex(const std::string& name) const;
  void handleBindResult(const int index, const int result) const;
  void requireCanRead() const;
  void setInstancePointer(const std::weak_ptr<Statement>& instancePointer);
  bool step();

  sqlite3_stmt* m_handle;
  bool m_canRead;
  std::weak_ptr<Statement> m_instancePointer;

  friend class Database;
  friend class ResultSet;
};

/// \brief A handle for a SQLite3 database.
///
/// This class stores a reference to a SQLite3 database and provides methods
/// to open, query and change this database. After you successfully opened a
/// database (using the constructor Database(const std::string&) or using the
/// open(const std::string&)  method), you can execute and prepare statements.
/// You can check whether the database is currently open using isOpen().
///
/// If you try to call a method that queries or updates the database and the
/// database is not open, a std::logic_error is thrown. If a database operation
/// fails, a DatabaseError is thrown.
class Database : private Uncopyable, public Openable {
 public:
  /// \brief Creates a new closed database.
  ///
  /// Before you can access this database, you have to open a database file
  /// using open(std::string&).
  Database();

  /// \brief Creates a new database and opens the given file.
  ///
  /// The given file must either be a valid SQLite3 database file or may not
  /// exist yet. This constructor is an abbreviation for:
  /// \code{.cpp}
  /// Database database;
  /// database.open(file);
  /// \endcode
  ///
  /// \param file the name of the database file (not required to exist)
  /// \throws std::runtime_error if there is not enough memory to create a
  ///         database connection
  /// \throws DatabaseError if the SQLite3 database could not be opened
  explicit Database(const std::string& file);

  /// \brief Destructs this object and closes the database connection.
  ///
  /// Errors that occur closing the database are ignored.
  ~Database();

  /// \brief Closes the database if it is open.
  ///
  /// \throws DatabaseError if the database cannot be closed
  void close();

  /// \brief Executes the given SQL string.
  ///
  /// You can only call this method if there is an open database connection.
  /// If you want to access the values returned by a SQL statement, use
  /// prepare(const std::string&) instead.
  ///
  /// \param sql the SQL statement to execute
  /// \throws std::logic_error if the database is not open
  /// \throws DatabaseError if an error occurred during the execution
  void execute(const std::string& sql);

  /// \brief Returns the row ID of the last element that was inserted.
  ///
  /// If no entry has been inserted into the database, this method returns
  /// zero.
  ///
  /// \returns the index of the last element inserted into the database or
  ///          zero
  /// \throws std::logic_error if the database is not open
  /// \throws DatabaseError if an error occurred during the execution
  int lastInsertRowId() const;

  /// \brief Opens the given database file.
  ///
  /// The given file must either be a valid SQLite3 database file or may not
  /// exist yet. You can only open a new connection when the previous
  /// connection has been closed (if any).
  ///
  /// \param file the name of the database file (not required to exist)
  /// \throws std::logic_error if the database is already open
  /// \throws std::runtime_error if there is not enough memory to create a
  ///         database connection
  /// \throws DatabaseError if the SQLite3 database could not be opened
  void open(const std::string& file);

  /// \brief Prepares a statement and returns a pointer to it.
  ///
  /// You can either pass a complete SQL statement or a statement with
  /// wildcards. If you use wildcards, you can bind them to a value using the
  /// returned Statement.
  ///
  /// \param sql the SQL statement to prepare (may contain wildcards)
  /// \returns a pointer to the prepared statement
  /// \throws std::logic_error if the database is not open
  /// \throws DatabaseError if an error occurred during the preparation
  std::shared_ptr<Statement> prepare(const std::string& sql);

 private:
  sqlite3* m_handle;
};

/// \brief A result set returned from a SQL query.
///
/// As long as there is data (`canRead()`), you can read it using the
/// `read*Type*` methods. To advance to the next row, use `next()`.
class ResultSet {
 public:
  /// \brief Checks whether there is data to read.
  ///
  /// \returns `true` if there is data to read; otherwise `false`
  bool canRead() const;

  /// \brief Returns the column count of the result data.
  ///
  /// You may only call this method when there is data to read (canRead()).
  ///
  /// \returns the column count of the result
  /// \throws std::logic_error if the statement is not open or there is no
  ///         data to read
  int columnCount() const;

  /// \brief Steps to the next row of the result (if there is one).
  ///
  /// \returns `true` if there is new data to read or `false` if there are
  ///          no more results
  /// \throws std::logic_error if the statement is not open
  /// \throws DatabaseError if a database error occurs during the query
  ///         execution
  bool next();

  /// \brief Returns the current double value of the result column with the
  ///        given index.
  ///
  /// You may only call this metod when there is data to read (canRead()).
  ///
  /// \param column the index of the column to read from
  /// \returns the current value of the result column with the given index
  /// \throws std::logic_error if the statement is not open or there is no
  ///         data to read
  double readDouble(const int column) const;

  /// \brief Returns the current integer value of the result column with the
  ///        given index.
  ///
  /// You may only call this metod when there is data to read (canRead()).
  ///
  /// \param column the index of the column to read from
  /// \returns the current value of the result column with the given index
  /// \throws std::logic_error if the statement is not open or there is no
  ///         data to read
  int readInt(const int column) const;

  /// \brief Returns the current string value of the result column with the
  ///        given index.
  ///
  /// You may only call this metod when there is data to read (canRead()).
  ///
  /// \param column the index of the column to read from
  /// \returns the current value of the result column with the given index
  /// \throws std::logic_error if the statement is not open or there is no
  ///         data to read
  std::string readString(const int column) const;

 private:
  explicit ResultSet(const std::shared_ptr<Statement> statement);

  const std::shared_ptr<Statement> m_statement;

  friend class Statement;
};

}  // namespace sqlitepp

#endif  // SQLITEPP_SQLITEPP_H_