From f634bafc38bb948e41f829b16f03e2f0b8ee8f93 Mon Sep 17 00:00:00 2001 From: Dan Goodliffe Date: Thu, 30 Apr 2015 00:27:27 +0100 Subject: Adds support for SQLite, SQLite mocking and some basic tests --- libsqlitepp/Jamfile.jam | 19 +++++++ libsqlitepp/command.cpp | 100 +++++++++++++++++++++++++++++++++ libsqlitepp/command.h | 39 +++++++++++++ libsqlitepp/connection.cpp | 128 ++++++++++++++++++++++++++++++++++++++++++ libsqlitepp/connection.h | 39 +++++++++++++ libsqlitepp/error.cpp | 29 ++++++++++ libsqlitepp/error.h | 24 ++++++++ libsqlitepp/modifycommand.cpp | 31 ++++++++++ libsqlitepp/modifycommand.h | 25 +++++++++ libsqlitepp/selectcommand.cpp | 80 ++++++++++++++++++++++++++ libsqlitepp/selectcommand.h | 21 +++++++ 11 files changed, 535 insertions(+) create mode 100644 libsqlitepp/Jamfile.jam create mode 100644 libsqlitepp/command.cpp create mode 100644 libsqlitepp/command.h create mode 100644 libsqlitepp/connection.cpp create mode 100644 libsqlitepp/connection.h create mode 100644 libsqlitepp/error.cpp create mode 100644 libsqlitepp/error.h create mode 100644 libsqlitepp/modifycommand.cpp create mode 100644 libsqlitepp/modifycommand.h create mode 100644 libsqlitepp/selectcommand.cpp create mode 100644 libsqlitepp/selectcommand.h (limited to 'libsqlitepp') diff --git a/libsqlitepp/Jamfile.jam b/libsqlitepp/Jamfile.jam new file mode 100644 index 0000000..cac3186 --- /dev/null +++ b/libsqlitepp/Jamfile.jam @@ -0,0 +1,19 @@ +alias glibmm : : : : + "`pkg-config --cflags glibmm-2.4`" + "`pkg-config --libs glibmm-2.4`" + ; + +lib libsqlite : : sqlite3 ; + +lib sqlitepp : + [ glob *.cpp ] : + -fPIC + glibmm + libsqlite + ../libdbpp + ../libmisc + : : + . + glibmm + ../libdbpp + ; diff --git a/libsqlitepp/command.cpp b/libsqlitepp/command.cpp new file mode 100644 index 0000000..ce2758b --- /dev/null +++ b/libsqlitepp/command.cpp @@ -0,0 +1,100 @@ +#include "command.h" +#include "connection.h" +#include +#include + +SQLite::Command::Command(const Connection * conn, const std::string & sql) : + DB::Command(sql), + c(conn) +{ + if (sqlite3_prepare_v2(conn->db, sql.c_str(), sql.length(), &stmt, NULL) != SQLITE_OK) { + throw Error(sqlite3_errmsg(conn->db)); + } +} + +SQLite::Command::~Command() +{ + sqlite3_finalize(stmt); +} + +void +SQLite::Command::bindParamI(unsigned int n, int v) +{ + if (sqlite3_bind_int(stmt, n + 1, v) != SQLITE_OK) { + throw Error(sqlite3_errmsg(c->db)); + } +} +void +SQLite::Command::bindParamI(unsigned int n, long int v) +{ + if (sqlite3_bind_int64(stmt, n + 1, v) != SQLITE_OK) { + throw Error(sqlite3_errmsg(c->db)); + } +} +void +SQLite::Command::bindParamI(unsigned int n, long long int v) +{ + if (sqlite3_bind_int64(stmt, n + 1, v) != SQLITE_OK) { + throw Error(sqlite3_errmsg(c->db)); + } +} +void +SQLite::Command::bindParamI(unsigned int n, unsigned int v) +{ + if (sqlite3_bind_int64(stmt, n + 1, v) != SQLITE_OK) { + throw Error(sqlite3_errmsg(c->db)); + } +} +void +SQLite::Command::bindParamI(unsigned int n, long unsigned int v) +{ + if (sqlite3_bind_int64(stmt, n + 1, v) != SQLITE_OK) { + throw Error(sqlite3_errmsg(c->db)); + } +} +void +SQLite::Command::bindParamI(unsigned int n, long long unsigned int v) +{ + if (sqlite3_bind_int64(stmt, n + 1, v) != SQLITE_OK) { + throw Error(sqlite3_errmsg(c->db)); + } +} +void +SQLite::Command::bindParamF(unsigned int n, double v) +{ + if (sqlite3_bind_double(stmt, n + 1, v) != SQLITE_OK) { + throw Error(sqlite3_errmsg(c->db)); + } +} +void +SQLite::Command::bindParamF(unsigned int n, float v) +{ + if (sqlite3_bind_double(stmt, n + 1, v) != SQLITE_OK) { + throw Error(sqlite3_errmsg(c->db)); + } +} +void +SQLite::Command::bindParamS(unsigned int n, const Glib::ustring & s) +{ + if (sqlite3_bind_text(stmt, n + 1, s.c_str(), s.length(), SQLITE_STATIC) != SQLITE_OK) { + throw Error(sqlite3_errmsg(c->db)); + } +} +void +SQLite::Command::bindParamT(unsigned int, const boost::posix_time::time_duration &) +{ + throw Error("Not supported"); +} +void +SQLite::Command::bindParamT(unsigned int, const boost::posix_time::ptime &) +{ + throw Error("Not supported"); +} +void +SQLite::Command::bindNull(unsigned int n) +{ + if (sqlite3_bind_null(stmt, n + 1) != SQLITE_OK) { + throw Error(sqlite3_errmsg(c->db)); + } +} + diff --git a/libsqlitepp/command.h b/libsqlitepp/command.h new file mode 100644 index 0000000..ee099e1 --- /dev/null +++ b/libsqlitepp/command.h @@ -0,0 +1,39 @@ +#ifndef SQLITE_COMMAND_H +#define SQLITE_COMMAND_H + +#include "../libdbpp/command.h" +#include + +namespace SQLite { + class Connection; + class Command : public virtual DB::Command { + public: + Command(const Connection *, const std::string & sql); + virtual ~Command() = 0; + + void bindParamI(unsigned int, int) override; + void bindParamI(unsigned int, long int) override; + void bindParamI(unsigned int, long long int) override; + void bindParamI(unsigned int, unsigned int) override; + void bindParamI(unsigned int, long unsigned int) override; + void bindParamI(unsigned int, long long unsigned int) override; + + void bindParamF(unsigned int, double) override; + void bindParamF(unsigned int, float) override; + + void bindParamS(unsigned int, const Glib::ustring&) override; + + void bindParamT(unsigned int, const boost::posix_time::time_duration &) override; + void bindParamT(unsigned int, const boost::posix_time::ptime &) override; + + void bindNull(unsigned int) override; + + protected: + const Connection * c; + sqlite3_stmt * stmt; + }; +} + +#endif + + diff --git a/libsqlitepp/connection.cpp b/libsqlitepp/connection.cpp new file mode 100644 index 0000000..50df13e --- /dev/null +++ b/libsqlitepp/connection.cpp @@ -0,0 +1,128 @@ +#include "connection.h" +#include "error.h" +#include "selectcommand.h" +#include "modifycommand.h" + +SQLite::Connection::Connection(const std::string & str) : + txDepth(0), + rolledback(false) +{ + if (sqlite3_open(str.c_str(), &db) != SQLITE_OK) { + if (db) { + std::string err(sqlite3_errmsg(db)); + sqlite3_close(db); + throw Error(err.c_str()); + } + throw Error("Unknown error opening database"); + } +} + +SQLite::Connection::~Connection() +{ + sqlite3_close(db); +} + +void +SQLite::Connection::finish() const +{ + if (txDepth != 0) { + rollbackTx(); + throw Error("Transaction still open"); + } +} + +int +SQLite::Connection::beginTx() const +{ + if (txDepth == 0) { + if (sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, NULL) != SQLITE_OK) { + throw Error(sqlite3_errmsg(db)); + } + rolledback = false; + } + return ++txDepth; +} + +int +SQLite::Connection::commitTx() const +{ + if (rolledback) { + return rollbackTx(); + } + if (--txDepth == 0) { + if (sqlite3_exec(db, "COMMIT TRANSACTION", NULL, NULL, NULL) != SQLITE_OK) { + throw Error(sqlite3_errmsg(db)); + } + } + return txDepth; +} + +int +SQLite::Connection::rollbackTx() const +{ + if (--txDepth == 0) { + if (sqlite3_exec(db, "ROLLBACK TRANSACTION", NULL, NULL, NULL) != SQLITE_OK) { + throw Error(sqlite3_errmsg(db)); + } + } + else { + rolledback = true; + } + return txDepth; +} + +bool +SQLite::Connection::inTx() const +{ + return txDepth; +} + +DB::BulkDeleteStyle +SQLite::Connection::bulkDeleteStyle() const +{ + return DB::BulkDeleteUsingUsingAlias; +} + +DB::BulkUpdateStyle +SQLite::Connection::bulkUpdateStyle() const +{ + return DB::BulkUpdateUsingJoin; +} + +void +SQLite::Connection::ping() const +{ + // Can this fail? +} + + +DB::SelectCommand * +SQLite::Connection::newSelectCommand(const std::string & sql) const +{ + return new SelectCommand(this, sql); +} + +DB::ModifyCommand * +SQLite::Connection::newModifyCommand(const std::string & sql) const +{ + return new ModifyCommand(this, sql); +} + +void +SQLite::Connection::beginBulkUpload(const char *, const char *) const +{ + throw Error("Not implemented"); +} + +void +SQLite::Connection::endBulkUpload(const char *) const +{ + throw Error("Not implemented"); +} + +size_t +SQLite::Connection::bulkUploadData(const char *, size_t) const +{ + throw Error("Not implemented"); +} + diff --git a/libsqlitepp/connection.h b/libsqlitepp/connection.h new file mode 100644 index 0000000..aa73036 --- /dev/null +++ b/libsqlitepp/connection.h @@ -0,0 +1,39 @@ +#ifndef SQLITE_CONNECTION_H +#define SQLITE_CONNECTION_H + +#include "../libdbpp/connection.h" +#include "error.h" +#include + +namespace SQLite { + class Connection : public DB::Connection { + public: + Connection(const std::string & info); + ~Connection(); + + void finish() const; + int beginTx() const; + int commitTx() const; + int rollbackTx() const; + bool inTx() const; + void ping() const; + DB::BulkDeleteStyle bulkDeleteStyle() const; + DB::BulkUpdateStyle bulkUpdateStyle() const; + + DB::SelectCommand * newSelectCommand(const std::string & sql) const; + DB::ModifyCommand * newModifyCommand(const std::string & sql) const; + + void beginBulkUpload(const char *, const char *) const; + void endBulkUpload(const char *) const; + size_t bulkUploadData(const char *, size_t) const; + + sqlite3 * db; + + private: + mutable unsigned int txDepth; + mutable bool rolledback; + }; +} + +#endif + diff --git a/libsqlitepp/error.cpp b/libsqlitepp/error.cpp new file mode 100644 index 0000000..9bdf80b --- /dev/null +++ b/libsqlitepp/error.cpp @@ -0,0 +1,29 @@ +#include "error.h" +#include + +SQLite::Error::Error() : + msg(NULL) +{ +} + +SQLite::Error::Error(const SQLite::Error & e) : + msg(e.msg ? strdup(e.msg) : NULL) +{ +} + +SQLite::Error::Error(const char * e) : + msg(e ? strdup(e) : NULL) +{ +} + +SQLite::Error::~Error() throw() +{ + free(msg); +} + +const char * +SQLite::Error::what() const throw() +{ + return msg ? msg : "No message"; +} + diff --git a/libsqlitepp/error.h b/libsqlitepp/error.h new file mode 100644 index 0000000..a7f0ae1 --- /dev/null +++ b/libsqlitepp/error.h @@ -0,0 +1,24 @@ +#ifndef SQLITE_ERROR_H +#define SQLITE_ERROR_H + +#include "../libdbpp/error.h" + +namespace SQLite { + class Error : public DB::Error { + public: + Error(); + Error(const Error &); + Error(const char *); + ~Error() throw(); + + const char * what() const throw(); + + private: + char * msg; + }; + class ConnectionError : public Error, public virtual DB::ConnectionError { + }; +} + +#endif + diff --git a/libsqlitepp/modifycommand.cpp b/libsqlitepp/modifycommand.cpp new file mode 100644 index 0000000..1b44cf4 --- /dev/null +++ b/libsqlitepp/modifycommand.cpp @@ -0,0 +1,31 @@ +#include "modifycommand.h" +#include "error.h" +#include +#include "connection.h" + +SQLite::ModifyCommand::ModifyCommand(const Connection * conn, const std::string & sql) : + DB::Command(sql), + DB::ModifyCommand(sql), + SQLite::Command(conn, sql) +{ +} + +SQLite::ModifyCommand::~ModifyCommand() +{ +} + +unsigned int +SQLite::ModifyCommand::execute(bool anc) +{ + if (sqlite3_step(stmt) != SQLITE_DONE) { + sqlite3_reset(stmt); + throw Error(sqlite3_errmsg(c->db)); + } + unsigned int rows = sqlite3_changes(c->db); + sqlite3_reset(stmt); + if (rows == 0 && !anc) { + throw Error("No rows affected"); + } + return rows; +} + diff --git a/libsqlitepp/modifycommand.h b/libsqlitepp/modifycommand.h new file mode 100644 index 0000000..ffb1205 --- /dev/null +++ b/libsqlitepp/modifycommand.h @@ -0,0 +1,25 @@ +#ifndef SQLITE_MODIFYCOMMAND_H +#define SQLITE_MODIFYCOMMAND_H + +#include "../libdbpp/modifycommand.h" +#include "command.h" + +namespace SQLite { + class Connection; + class ModifyCommand : public DB::ModifyCommand, public Command { + public: + ModifyCommand(const Connection *, const std::string & sql); + virtual ~ModifyCommand(); + + unsigned int execute(bool); + + private: + void prepare() const; + mutable bool prepared; + }; +} + +#endif + + + diff --git a/libsqlitepp/selectcommand.cpp b/libsqlitepp/selectcommand.cpp new file mode 100644 index 0000000..a3d5ac7 --- /dev/null +++ b/libsqlitepp/selectcommand.cpp @@ -0,0 +1,80 @@ +#include "selectcommand.h" +#include "connection.h" +#include "error.h" +#include + +class Column : public DB::Column { + public: + Column(const Glib::ustring & n, unsigned int i, sqlite3_stmt * s) : + DB::Column(n, i), + stmt(s) + { + } + + bool isNull() const { + return (SQLITE_NULL == sqlite3_column_type(stmt, colNo)); + } + + void apply(DB::HandleField & h) const { + switch (sqlite3_column_type(stmt, colNo)) { + case SQLITE_INTEGER: + h.integer(sqlite3_column_int64(stmt, colNo)); + return; + case SQLITE_FLOAT: + h.floatingpoint(sqlite3_column_double(stmt, colNo)); + return; + case SQLITE_TEXT: + { + auto t = sqlite3_column_text(stmt, colNo); + auto l = sqlite3_column_bytes(stmt, colNo); + h.string(reinterpret_cast(t), l); + return; + } + case SQLITE_NULL: + h.null(); + return; + case SQLITE_BLOB: + throw std::runtime_error("Blobs not supported"); + } + + } + + void rebind(DB::Command*, unsigned int) const { + throw std::runtime_error("Not implemented"); + } + + private: + sqlite3_stmt * const stmt; +}; + +SQLite::SelectCommand::SelectCommand(const Connection * conn, const std::string & sql) : + DB::Command(sql), + DB::SelectCommand(sql), + SQLite::Command(conn, sql) +{ +} + +void +SQLite::SelectCommand::execute() +{ + // No explicit execute required +} + +bool +SQLite::SelectCommand::fetch() +{ + switch (sqlite3_step(stmt)) { + case SQLITE_ROW: + if (columns.empty()) { + for (int c = sqlite3_data_count(stmt) - 1; c >= 0; c -= 1) { + columns.insert(DB::ColumnPtr(new Column(sqlite3_column_name(stmt, c), c, stmt))); + } + } + return true; + case SQLITE_DONE: + return false; + default: + throw Error(sqlite3_errmsg(c->db)); + } +} + diff --git a/libsqlitepp/selectcommand.h b/libsqlitepp/selectcommand.h new file mode 100644 index 0000000..be8b02b --- /dev/null +++ b/libsqlitepp/selectcommand.h @@ -0,0 +1,21 @@ +#ifndef SQLITE_SELECTCOMMAND_H +#define SQLITE_SELECTCOMMAND_H + +#include "../libdbpp/selectcommand.h" +#include "command.h" + +namespace SQLite { + class Connection; + class ColumnBase; + class SelectCommand : public DB::SelectCommand, public Command { + public: + SelectCommand(const Connection *, const std::string & sql); + + bool fetch(); + void execute(); + }; +} + +#endif + + -- cgit v1.2.3