aboutsummaryrefslogtreecommitdiff
path: root/external/glim/sqlite.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'external/glim/sqlite.hpp')
-rw-r--r--external/glim/sqlite.hpp538
1 files changed, 538 insertions, 0 deletions
diff --git a/external/glim/sqlite.hpp b/external/glim/sqlite.hpp
new file mode 100644
index 000000000..c7e92ac4a
--- /dev/null
+++ b/external/glim/sqlite.hpp
@@ -0,0 +1,538 @@
+#ifndef GLIM_SQLITE_HPP_
+#define GLIM_SQLITE_HPP_
+
+/**
+ * A threaded interface to <a href="http://sqlite.org/">SQLite</a>.
+ * This file is a header-only library,
+ * whose sole dependencies should be standard STL and posix threading libraries.
+ * You can extract this file out of the "glim" library to include it separately in your project.
+ * @code
+Copyright 2006-2012 Kozarezov Artem Aleksandrovich
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ * @endcode
+ * @file
+ */
+
+#include <stdexcept>
+#include <string>
+#include <sqlite3.h>
+#include <pthread.h>
+#include <string.h> // strerror
+#include <sys/types.h> // stat
+#include <sys/stat.h> // stat
+#include <unistd.h> // stat
+#include <errno.h> // stat
+#include <stdio.h> // snprintf
+#include <stdint.h>
+
+namespace glim {
+
+class SqliteSession;
+class SqliteQuery;
+
+struct SqliteEx: public std::runtime_error {
+ SqliteEx (const std::string& what): std::runtime_error (what) {}
+};
+
+/**
+ * The database.
+ * According to sqlite3_open <a href="http://sqlite.org/capi3ref.html#sqlite3_open">documentation</a>,
+ * only the thread that opened the database can safely access it. This restriction was
+ * relaxed, as described in the <a href="http://www.sqlite.org/faq.html#q8">FAQ</a>
+ * (the "Is SQLite threadsafe?" question), so we can use the library from multiple
+ * threads, but only if no more than one thread at a time accesses the database.
+ * This restriction is, in fact, beneficial if the database is used from a single application:
+ * by restricting access to a sigle thread at a time, we effectively avoid all deadlock issues.\n
+ * This library goals are:\n
+ * \li to ensure that SQLite is used in a thread-safe way,
+ * \li to provide additional threaded quirks, such as delayed updates (not implemented).
+ *
+ * The library is targeted at SQLite setup which is \b not \c -DTHREADSAFE,
+ * since this is the default setup on UNIX architectures.\n
+ * \n
+ * This file is a header-only library,
+ * whose sole dependencies should be standard STL and posix threading libraries.
+ * You can extract this file out of the "glim" library to include it separately in your project.\n
+ * \n
+ * This library is targeted at UTF-8 API. There is no plans to support the UTF-16 API.\n
+ * \n
+ * See also:\n
+ * \li http://www.sqlite.org/cvstrac/fileview?f=sqlite/src/server.c\n
+ * for another way of handling multithreading with SQLite.
+ */
+class Sqlite {
+ /// No copying allowed.
+ Sqlite& operator = (const Sqlite& other) {return *this;}
+ /// No copying allowed.
+ Sqlite (const Sqlite& other) = delete;
+ friend class SqliteSession;
+ protected:
+ /// Filename the database was opened with; we need it to reopen the database on fork()s.
+ /// std::string is used to avoid memory allocation issues.
+ std::string filename;
+ ::sqlite3* handler;
+ ::pthread_mutex_t mutex;
+ public:
+ /// Flags for the Sqlite constructor.
+ enum Flags {
+ /**
+ * The file will be checked for existence.
+ * SqliteEx is thrown if the file is not accessible;
+ * format of the error description is "$filename: $strerror".\n
+ * Usage example: \code Sqlite db ("filename", Sqlite::existing); \endcode
+ */
+ existing = 1
+ };
+ /**
+ * Opens the database.
+ * @param filename Database filename (UTF-8).
+ * @param flags Optional. Currently there is the #existing flag.
+ * @throws SqliteEx Thrown if we can't open the database.
+ */
+ Sqlite (std::string filename, int flags = 0) {
+ if (flags & existing) {
+ // Check if the file exists already.
+ struct stat st; if (stat (filename.c_str(), &st))
+ throw SqliteEx (filename + ": " + ::strerror(errno));
+ }
+ ::pthread_mutex_init (&mutex, NULL);
+ this->filename = filename;
+ if (::sqlite3_open(filename.c_str(), &handler) != SQLITE_OK)
+ throw SqliteEx (std::string("sqlite3_open(") + filename + "): " + ::sqlite3_errmsg(handler));
+ }
+ /**
+ * Closes the database.
+ * @throws SqliteEx Thrown if we can't close the database.
+ */
+ ~Sqlite () {
+ ::pthread_mutex_destroy (&mutex);
+ if (::sqlite3_close(handler) != SQLITE_OK)
+ throw SqliteEx (std::string ("sqlite3_close(): ") + ::sqlite3_errmsg(handler));
+ }
+
+ Sqlite& exec (const char* query);
+ /**
+ * Invokes `exec` on `query.c_str()`.
+ * Example:\code
+ * glim::Sqlite sqlite (":memory:");
+ * for (std::string pv: {"page_size = 4096", "secure_delete = 1"}) sqlite->exec2 ("PRAGMA " + pv); \endcode
+ */
+ template <typename StringLike> Sqlite& exec2 (StringLike query) {return exec (query.c_str());}
+};
+
+/**
+ * A single thread session with Sqlite.
+ * Only a sigle thread at a time can have an SqliteSession,
+ * all other threads will wait, in the SqliteSession constructor,
+ * till the active session is either closed or destructed.
+ */
+class SqliteSession {
+ /// No copying allowed.
+ SqliteSession& operator = (const SqliteSession& other) {return *this;}
+ /// No copying allowed.
+ SqliteSession(SqliteSession& other): db (NULL) {}
+ protected:
+ Sqlite* db;
+ public:
+ /**
+ * Locks the database.
+ * @throws SqliteEx if a mutex error occurs.
+ */
+ SqliteSession (Sqlite* sqlite): db (sqlite) {
+ int err = ::pthread_mutex_lock (&(db->mutex));
+ if (err != 0) throw SqliteEx (std::string ("error locking the mutex: ") + ::strerror(err));
+ }
+ /**
+ * A shorter way to construct query from the session.
+ * Usage example: \code ses.query(S("create table test (i integer)")).step() \endcode
+ * @see SqliteQuery#qstep
+ */
+ template <typename T>
+ SqliteQuery query (T t);
+ /// Automatically unlocks the database.
+ /// @see close
+ ~SqliteSession () {close();}
+ /**
+ * Unlock the database.
+ * It is safe to call this method multiple times.\n
+ * You must not use the session after it was closed.\n
+ * All resources allocated within this session must be released before the session is closed.
+ * @throws SqliteEx if a mutex error occurs.
+ */
+ void close () {
+ if (db == NULL) return;
+ int err = ::pthread_mutex_unlock (&(db->mutex));
+ db = NULL;
+ if (err != 0) throw SqliteEx (std::string ("error unlocking the mutex: ") + ::strerror(err));
+ }
+ /// True if the \c close method has been already called on this SqliteSession.
+ bool isClosed () const {
+ return db == NULL;
+ }
+ /**
+ * This class can be used in place of the SQLite handler.
+ * Make sure you've released any resources thus manually acquired before this SqliteSession is closed.
+ * Usage example:
+ * @code
+ * glim::Sqlite db (":memory:");
+ * glim::SqliteSession ses (&db);
+ * sqlite3_exec (ses, "PRAGMA page_size = 4096;", NULL, NULL, NULL);
+ * @endcode
+ */
+ operator ::sqlite3* () const {return db->handler;}
+};
+
+/**
+ * Execute the given query, throwing SqliteEx on failure.\n
+ * Example:\code
+ * glim::Sqlite sqlite (":memory:");
+ * sqlite.exec ("PRAGMA page_size = 4096") .exec ("PRAGMA secure_delete = 1"); \endcode
+ */
+inline Sqlite& Sqlite::exec (const char* query) {
+ SqliteSession ses (this); // Maintains the locks.
+ char* errmsg = NULL; ::sqlite3_exec (handler, query, NULL, NULL, &errmsg);
+ if (errmsg) throw SqliteEx (std::string ("Sqlite::exec, error in query (") + query + "): " + errmsg);
+ return *this;
+}
+
+/**
+ * Wraps the sqlite3_stmt; will prepare it, bind values, query and finalize.
+ */
+class SqliteQuery {
+ protected:
+ ::sqlite3_stmt* statement;
+ SqliteSession* session;
+ int bindCounter;
+ /// -1 if statement isn't DONE.
+ int mChanges;
+ void prepare (SqliteSession* session, char const* query, int queryLength) {
+ ::sqlite3* handler = *session;
+ if (::sqlite3_prepare_v2 (handler, query, queryLength, &statement, NULL) != SQLITE_OK)
+ throw SqliteEx (std::string(query, queryLength) + ": " + ::sqlite3_errmsg(handler));
+ }
+ /** Shan't copy. */
+ SqliteQuery (const SqliteQuery& other) = delete;
+ public:
+ SqliteQuery (SqliteQuery&& rvalue) {
+ statement = rvalue.statement;
+ session = rvalue.session;
+ bindCounter = rvalue.bindCounter;
+ mChanges = rvalue.mChanges;
+ rvalue.statement = nullptr;
+ }
+ /**
+ * Prepares the query.
+ * @throws SqliteEx if sqlite3_prepare fails; format of the error message is "$query: $errmsg".
+ */
+ SqliteQuery (SqliteSession* session, char const* query, int queryLength)
+ : statement (NULL), session (session), bindCounter (0), mChanges (-1) {
+ prepare (session, query, queryLength);
+ }
+ /**
+ * Prepares the query.
+ * @throws SqliteEx if sqlite3_prepare fails; format of the error message is "$query: $errmsg".
+ */
+ SqliteQuery (SqliteSession* session, std::pair<char const*, int> query)
+ : statement (NULL), session (session), bindCounter (0), mChanges (-1) {
+ prepare (session, query.first, query.second);
+ }
+ /**
+ * Prepares the query.
+ * @throws SqliteEx if sqlite3_prepare fails; format of the error message is "$query: $errmsg".
+ */
+ SqliteQuery (SqliteSession* session, std::string query)
+ : statement (NULL), session (session), bindCounter (0), mChanges (-1) {
+ prepare (session, query.c_str(), query.length());
+ }
+ /**
+ * Release resources.
+ * @see http://sqlite.org/capi3ref.html#sqlite3_finalize
+ */
+ ~SqliteQuery () {
+ if (statement) ::sqlite3_finalize (statement);
+ }
+
+ /// Call this (followed by the #step) if you need the query to be re-executed.
+ /// @see http://sqlite.org/capi3ref.html#sqlite3_reset
+ SqliteQuery& reset () {
+ bindCounter = 0;
+ mChanges = -1;
+ ::sqlite3_reset (statement);
+ return *this;
+ }
+
+ /// Synonym for #step.
+ bool next () {return step();}
+ /**
+ * Invokes sqlite3_step.
+ * @return \c true if there was a row fetched successfully, \c false if there is no more rows.
+ * @see http://sqlite.org/capi3ref.html#sqlite3_step
+ */
+ bool step () {
+ if (mChanges >= 0) {mChanges = 0; return false;}
+ int ret = ::sqlite3_step (statement);
+ if (ret == SQLITE_ROW) return true;
+ if (ret == SQLITE_DONE) {
+ mChanges = ::sqlite3_changes (*session);
+ return false;
+ }
+ throw SqliteEx (std::string(::sqlite3_errmsg(*session)));
+ }
+ /**
+ * Perform #step and throw an exception if #step has returned \c false.
+ * Usage example:
+ * \code (ses.query(S("select count(*) from test where idx = ?")) << 12345).qstep().intAt(1) \endcode
+ */
+ SqliteQuery& qstep () {
+ if (!step())
+ throw SqliteEx (std::string("qstep: no rows returned / affected"));
+ return *this;
+ }
+ /**
+ * Invokes a DML query and returns the number of rows affected.
+ * Example: \code
+ * int affected = (ses.query(S("update test set count = count + ? where id = ?")) << 1 << 9).ustep();
+ * \endcode
+ * @see http://sqlite.org/capi3ref.html#sqlite3_step
+ */
+ int ustep () {
+ int ret = ::sqlite3_step (statement);
+ if (ret == SQLITE_DONE) {
+ mChanges = ::sqlite3_changes (*session);
+ return mChanges;
+ }
+ if (ret == SQLITE_ROW) return 0;
+ throw SqliteEx (std::string(::sqlite3_errmsg(*session)));
+ }
+
+ /**
+ * The number of rows changed by the query.
+ * Providing the query was a DML (Data Modification Language),
+ * returns the number of rows updated.\n
+ * If the query wasn't a DML, returned value is undefined.\n
+ * -1 is returned if the query wasn't executed, or after #reset.\n
+ * Example: \code
+ * SqliteQuery query (&ses, S("update test set count = count + ? where id = ?"));
+ * query.bind (1, 1);
+ * query.bind (2, 9);
+ * query.step ();
+ * int affected = query.changes ();
+ * \endcode
+ * @see #ustep
+ */
+ int changes () {return mChanges;}
+
+ /**
+ * The integer value of the given column.
+ * @param column 1-based.
+ * @see http://sqlite.org/capi3ref.html#sqlite3_column_text
+ */
+ int intAt (int column) {
+ return ::sqlite3_column_int (statement, --column);
+ }
+
+ /**
+ * The integer value of the given column.
+ * @param column 1-based.
+ * @see http://sqlite.org/capi3ref.html#sqlite3_column_text
+ */
+ sqlite3_int64 int64at (int column) {
+ return ::sqlite3_column_int64 (statement, --column);
+ }
+
+ /**
+ * The floating point number from the given column.
+ * @param column 1-based.
+ * @see http://sqlite.org/capi3ref.html#sqlite3_column_text
+ */
+ double doubleAt (int column) {
+ return ::sqlite3_column_double (statement, --column);
+ }
+
+ /**
+ * Return the column as UTF-8 characters, which can be used until the next #step.
+ * @param column 1-based.
+ * @see http://sqlite.org/capi3ref.html#sqlite3_column_text
+ */
+ std::pair<char const*, int> charsAt (int column) {
+ return std::pair<char const*, int> ((char const*) ::sqlite3_column_text (statement, column-1),
+ ::sqlite3_column_bytes (statement, column-1));
+ }
+
+ /**
+ * Return the column as C++ string (UTF-8).
+ * @param column 1-based.
+ */
+ std::string stringAt (int column) {
+ return std::string ((char const*) ::sqlite3_column_text (statement, column-1),
+ ::sqlite3_column_bytes (statement, column-1));
+ }
+
+ /**
+ * The type of the column.
+ * SQLITE_INTEGER, SQLITE_FLOAT, SQLITE_TEXT, SQLITE_BLOB or SQLITE_NULL.
+ * @param column 1-based.
+ * @see http://sqlite.org/capi3ref.html#sqlite3_column_text
+ */
+ int typeAt (int column) {
+ return ::sqlite3_column_type (statement, --column);
+ }
+
+ /**
+ * Binds a value using one of the bind methods.
+ */
+ template<typename T>
+ SqliteQuery& operator << (T value) {
+ return bind (++bindCounter, value);
+ }
+ /**
+ * Binds a value using the named parameter and one of the bind methods.
+ * @throws SqliteEx if the name could not be found.
+ * @see http://sqlite.org/capi3ref.html#sqlite3_bind_parameter_index
+ */
+ template<typename T>
+ SqliteQuery& bind (char const* name, T value) {
+ int index = ::sqlite3_bind_parameter_index (statement, name);
+ if (index == 0)
+ throw SqliteEx (std::string ("No such parameter in the query: ") + name);
+ return bind (index, value);
+ }
+
+ /**
+ * Bind a string to the query.
+ * @param transient must be true, if lifetime of the string might be shorter than that of the query.
+ */
+ SqliteQuery& bind (int index, const char* text, int length, bool transient = false) {
+ if (::sqlite3_bind_text (statement, index, text, length,
+ transient ? SQLITE_TRANSIENT : SQLITE_STATIC) != SQLITE_OK)
+ throw SqliteEx (std::string (::sqlite3_errmsg (*session)));
+ return *this;
+ }
+ /**
+ * Bind a string to the query.
+ * @param transient must be true, if lifetime of the string might be shorter than that of the query.
+ */
+ SqliteQuery& bind (int index, std::pair<const char*, int> text, bool transient = false) {
+ if (::sqlite3_bind_text (statement, index, text.first, text.second,
+ transient ? SQLITE_TRANSIENT : SQLITE_STATIC) != SQLITE_OK)
+ throw SqliteEx (std::string (::sqlite3_errmsg (*session)));
+ return *this;
+ }
+ /**
+ * Bind a string to the query.
+ * @param transient must be true, if lifetime of the string might be shorter than that of the query.
+ */
+ SqliteQuery& bind (int index, const std::string& text, bool transient = true) {
+ if (::sqlite3_bind_text (statement, index, text.data(), text.length(),
+ transient ? SQLITE_TRANSIENT : SQLITE_STATIC) != SQLITE_OK)
+ throw SqliteEx (std::string (::sqlite3_errmsg (*session)));
+ return *this;
+ }
+ /**
+ * Bind an integer to the query.
+ */
+ SqliteQuery& bind (int index, int value) {
+ if (::sqlite3_bind_int (statement, index, value) != SQLITE_OK)
+ throw SqliteEx (std::string (::sqlite3_errmsg (*session)));
+ return *this;
+ }
+ /**
+ * Bind an 64-bit integer to the query.
+ */
+ SqliteQuery& bind (int index, sqlite3_int64 value) {
+ if (::sqlite3_bind_int64 (statement, index, value) != SQLITE_OK)
+ throw SqliteEx (std::string (::sqlite3_errmsg (*session)));
+ return *this;
+ }
+};
+
+/**
+ * Version of SqliteQuery suitable for using SQLite in parallel with other processes.
+ * Will automatically handle the SQLITE_SCHEMA error
+ * and will automatically repeat attempts after SQLITE_BUSY,
+ * but it requires that the query string supplied
+ * is constant and available during the SqliteParQuery lifetime.
+ * Error messages, contained in exceptions, may differ from SqliteQuery by containing the query
+ * (for example, the #step method will throw "$query: $errmsg" instead of just "$errmsg").
+ */
+class SqliteParQuery: public SqliteQuery {
+ protected:
+ char const* query;
+ int queryLength;
+ int repeat;
+ int wait;
+ public:
+ /**
+ * Prepares the query.
+ * @param repeat the number of times we try to repeat the query when SQLITE_BUSY is returned.
+ * @param wait how long, in milliseconds (1/1000 of a second) we are to wait before repeating.
+ * @throws SqliteEx if sqlite3_prepare fails; format of the error message is "$query: $errmsg".
+ */
+ SqliteParQuery (SqliteSession* session, char const* query, int queryLength, int repeat = 90, int wait = 20)
+ : SqliteQuery (session, query, queryLength) {
+ this->query = query;
+ this->queryLength = queryLength;
+ this->repeat = repeat;
+ this->wait = wait;
+ }
+ /**
+ * Prepares the query.
+ * @param query the SQL query together with its length.
+ * @param repeat the number of times we try to repeat the query when SQLITE_BUSY is returned.
+ * @param wait how long, in milliseconds (1/1000 of a second) we are to wait before repeating.
+ * @throws SqliteEx if sqlite3_prepare fails; format of the error message is "$query: $errmsg".
+ */
+ SqliteParQuery (SqliteSession* session, std::pair<char const*, int> query, int repeat = 90, int wait = 20)
+ : SqliteQuery (session, query) {
+ this->query = query.first;
+ this->queryLength = query.second;
+ this->repeat = repeat;
+ this->wait = wait;
+ }
+
+ bool next () {return step();}
+ bool step () {
+ if (mChanges >= 0) {mChanges = 0; return false;}
+ repeat:
+ int ret = ::sqlite3_step (statement);
+ if (ret == SQLITE_ROW) return true;
+ if (ret == SQLITE_DONE) {
+ mChanges = ::sqlite3_changes (*session);
+ return false;
+ }
+ if (ret == SQLITE_SCHEMA) {
+ ::sqlite3_stmt* old = statement;
+ prepare (session, query, queryLength);
+ ::sqlite3_transfer_bindings(old, statement);
+ ::sqlite3_finalize (old);
+ goto repeat;
+ }
+ if (ret == SQLITE_BUSY) for (int repeat = this->repeat; ret == SQLITE_BUSY && repeat >= 0; --repeat) {
+ //struct timespec ts; ts.tv_sec = 0; ts.tv_nsec = wait * 1000000; // nan is 10^-9 of sec.
+ //while (::nanosleep (&ts, &ts) == EINTR);
+ ::sqlite3_sleep (wait);
+ ret = ::sqlite3_step (statement);
+ }
+ throw SqliteEx (std::string(query, queryLength) + ::sqlite3_errmsg(*session));
+ }
+};
+
+template <typename T>
+SqliteQuery SqliteSession::query (T t) {
+ return SqliteQuery (this, t);
+}
+
+}; // namespace glim
+
+#endif // GLIM_SQLITE_HPP_