From 6228b0a15c86d2105a29cbccb48db6bd83b82f71 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Fri, 17 Oct 2014 03:20:43 +0200 Subject: Initial commit --- src/sqlitepp.cpp | 240 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/sqlitepptest.cpp | 108 +++++++++++++++++++++++ 2 files changed, 348 insertions(+) create mode 100644 src/sqlitepp.cpp create mode 100644 src/sqlitepptest.cpp (limited to 'src') diff --git a/src/sqlitepp.cpp b/src/sqlitepp.cpp new file mode 100644 index 0000000..8cdb357 --- /dev/null +++ b/src/sqlitepp.cpp @@ -0,0 +1,240 @@ +/* + * (C) 2014 Robin Krahl + * MIT license -- http://opensource.org/licenses/MIT + */ + +#include "sqlitepp.h" + +#include +#include +#include + +sqlitepp::Openable::Openable(const bool open, const std::string & name) : + m_open(open), m_name(name) { +} + +const bool sqlitepp::Openable::isOpen() const { + return m_open; +} + +void sqlitepp::Openable::requireOpen() const { + if (!m_open) { + throw std::logic_error(m_name + " is not open."); + } +} + +void sqlitepp::Openable::setOpen(const bool open) { + m_open = open; +} + +const std::string sqlitepp::DatabaseError::getErrorMessage(const int errorCode, const std::string & errorMessage) { + std::ostringstream stringStream; + stringStream << "Caught SQLite3 error " << errorCode << " meaning: " << errorMessage; + return stringStream.str(); +} + +sqlitepp::DatabaseError::DatabaseError(const int errorCode) : + std::runtime_error(getErrorMessage(errorCode, sqlite3_errstr(errorCode))), m_errorCode(errorCode) { +} + +sqlitepp::DatabaseError::DatabaseError(const int errorCode, const std::string & errorMessage) : + std::runtime_error(getErrorMessage(errorCode, errorMessage)), m_errorCode(errorCode) +{ +} + +const int sqlitepp::DatabaseError::errorCode() const { + return m_errorCode; +} + +sqlitepp::Statement::Statement(sqlitepp::Database & database, const std::string & statement) : + Openable(true, "Statement"), m_canRead(false) { + database.requireOpen(); + int result = sqlite3_prepare_v2(database.m_handle, statement.c_str(), -1, &m_handle, NULL); + if (result != SQLITE_OK) { + throw DatabaseError(result, sqlite3_errmsg(database.m_handle)); + } + if (m_handle == NULL) { + throw std::runtime_error("Statement handle is NULL"); + } +} + +sqlitepp::Statement::~Statement() { + if (isOpen()) { + // errors that could occur during finalizing are ignored as they have + // already been handled! + sqlite3_finalize(m_handle); + setOpen(false); + } +} + +void sqlitepp::Statement::bindDouble(const int index, const double value) { + requireOpen(); + handleBindResult(index, sqlite3_bind_double(m_handle, index, value)); +} + +void sqlitepp::Statement::bindDouble(const std::string & name, const double value) { + bindDouble(getParameterIndex(name), value); +} + +void sqlitepp::Statement::bindInt(const int index, const int value) { + requireOpen(); + handleBindResult(index, sqlite3_bind_int(m_handle, index, value)); +} + +void sqlitepp::Statement::bindInt(const std::string & name, const int value) { + bindInt(getParameterIndex(name), value); +} + +void sqlitepp::Statement::bindString(const int index, const std::string & value) { + requireOpen(); + handleBindResult(index, sqlite3_bind_text(m_handle, index, value.c_str(), -1, NULL)); +} + +void sqlitepp::Statement::bindString(const std::string & name, const std::string & value) { + bindString(getParameterIndex(name), value); +} + +const bool sqlitepp::Statement::canRead() const { + return m_canRead; +} + +const int sqlitepp::Statement::columnCount() const { + requireOpen(); + requireCanRead(); + return sqlite3_column_count(m_handle); +} + +const double sqlitepp::Statement::readDouble(const int column) const { + requireOpen(); + requireCanRead(); + return sqlite3_column_double(m_handle, column); +} + +const int sqlitepp::Statement::readInt(const int column) const { + requireOpen(); + requireCanRead(); + return sqlite3_column_int(m_handle, column); +} + +const std::string sqlitepp::Statement::readString(const int column) const { + requireOpen(); + requireCanRead(); + return std::string((const char *) sqlite3_column_text(m_handle, column)); +} + +void sqlitepp::Statement::requireCanRead() const { + if (!m_canRead) { + throw std::logic_error("Trying to read from statement without data"); + } +} + +const bool sqlitepp::Statement::step() { + requireOpen(); + int result = sqlite3_step(m_handle); + if (result == SQLITE_ROW) { + m_canRead = true; + } else if (result == SQLITE_DONE) { + m_canRead = false; + } else { + throw DatabaseError(result); + } + return m_canRead; +} + +void sqlitepp::Statement::finalize() { + if (isOpen()) { + // errors that could occur during finalizing are ignored as they have + // already been handled! + sqlite3_finalize(m_handle); + setOpen(false); + } +} + +const bool sqlitepp::Statement::reset() { + requireOpen(); + return sqlite3_reset(m_handle) == SQLITE_OK; +} + +int sqlitepp::Statement::getParameterIndex(const std::string & name) const { + requireOpen(); + int index = sqlite3_bind_parameter_index(m_handle, name.c_str()); + if (index == 0) { + throw std::invalid_argument("No such parameter: " + name); + } + return index; +} + +void sqlitepp::Statement::handleBindResult(const int index, const int result) const { + switch (result) { + case SQLITE_OK: + break; + case SQLITE_RANGE: + throw std::out_of_range("Bind index out of range: " + index); + case SQLITE_NOMEM: + throw std::runtime_error("No memory to bind parameter"); + default: + throw DatabaseError(result); + } +} + +sqlitepp::Database::Database() : Openable(false, "Database") { +} + +sqlitepp::Database::Database(const std::string & file) : Openable(false, "Database") { + open(file); +} + +sqlitepp::Database::~Database() { + if (isOpen()) { + int result = sqlite3_close(m_handle); + if (result != SQLITE_OK) { + std::cerr << "sqlitepp::Database::~Database(): Close failed with code " + << result << " meaning: " << sqlite3_errstr(result); + std::abort(); + } else { + setOpen(false); + } + } + // m_handle is deleted by sqlite3_close +} + +void sqlitepp::Database::close() { + if (isOpen()) { + int result = sqlite3_close(m_handle); + if (result == SQLITE_OK) { + setOpen(false); + } else { + throw sqlitepp::DatabaseError(result); + } + } +} + +void sqlitepp::Database::execute(const std::string & sql) { + requireOpen(); + Statement statement(*this, sql); + statement.step(); + statement.finalize(); +} + +void sqlitepp::Database::open(const std::string & file) { + if (isOpen()) { + throw std::logic_error("sqlitepp::Database::open(std::string&): Database already open"); + } + int result = sqlite3_open(file.c_str(), & m_handle); + + if (m_handle == NULL) { + throw std::runtime_error("sqlitepp::Database::open(std::string&): Can't allocate memory"); + } + + if (result == SQLITE_OK) { + setOpen(true); + } else { + std::string errorMessage = sqlite3_errmsg(m_handle); + sqlite3_close(m_handle); + throw sqlitepp::DatabaseError(result, errorMessage); + } +} + +std::shared_ptr sqlitepp::Database::prepare(const std::string & sql) { + return std::shared_ptr(new sqlitepp::Statement(*this, sql)); +} \ No newline at end of file diff --git a/src/sqlitepptest.cpp b/src/sqlitepptest.cpp new file mode 100644 index 0000000..7974eae --- /dev/null +++ b/src/sqlitepptest.cpp @@ -0,0 +1,108 @@ +/* + * (C) 2014 Robin Krahl + * MIT license -- http://opensource.org/licenses/MIT + */ + +#include "sqlitepp.h" + +#define BOOST_TEST_DYN_LINK +#define BOOST_TEST_MODULE SQLitePPTest +#include + +#include +#include +#include + +BOOST_AUTO_TEST_CASE(openClose) { + sqlitepp::Database database; + BOOST_CHECK(!database.isOpen()); + database.open("/tmp/test.db"); + BOOST_CHECK(database.isOpen()); + database.close(); + BOOST_CHECK(!database.isOpen()); + database.open("/tmp/test2.db"); + BOOST_CHECK(database.isOpen()); + database.close(); + BOOST_CHECK(!database.isOpen()); + sqlitepp::Database database2("/tmp/test.db"); + BOOST_CHECK(database2.isOpen()); + try { + database2.open("/tmp/test2.db"); + BOOST_ERROR("Calling open() to an open database does not throw an exception."); + } catch (std::logic_error &) { + // everything fine + } + BOOST_CHECK(database2.isOpen()); + database2.close(); + BOOST_CHECK(!database2.isOpen()); + + std::ifstream testStream("/tmp/test.db"); + BOOST_CHECK(testStream.good()); + testStream.close(); + testStream.open("/tmp/test2.db"); + BOOST_CHECK(testStream.good()); + testStream.close(); +} + +BOOST_AUTO_TEST_CASE(copy) { + sqlitepp::Database database; + // MUST NOT COMPILE: + // sqlitepp::Database database2 = database; + database.close(); + sqlitepp::Database database3; + // MUST NOT COMPILE: + // database3 = database; + database3.close(); +} + +BOOST_AUTO_TEST_CASE(prepare) { + sqlitepp::Database database("/tmp/test.db"); + sqlitepp::Statement statement(database, "CREATE TABLE IF NOT EXISTS test (id, value);"); + // TODO check std::logic_error + BOOST_CHECK(statement.isOpen()); + statement.finalize(); + BOOST_CHECK(!statement.isOpen()); + database.close(); +} + +BOOST_AUTO_TEST_CASE(execute) { + sqlitepp::Database database("/tmp/test.db"); + database.execute("CREATE TABLE IF NOT EXISTS test (id, value);"); + sqlitepp::Statement statement(database, "INSERT INTO test (id, value) VALUES (:id, ?)"); + statement.bindInt(":id", 1); + statement.bindString(2, "test value"); + statement.step(); + statement.reset(); + statement.bindInt(":id", 2); + statement.bindString(2, "other value"); + statement.step(); + statement.finalize(); +} + +BOOST_AUTO_TEST_CASE(query) { + sqlitepp::Database database("/tmp/test.db"); + sqlitepp::Statement statement(database, "SELECT id, value FROM test;"); + bool hasNext = statement.step(); + BOOST_CHECK(hasNext); + BOOST_CHECK_EQUAL(statement.columnCount(), 2); + int id = statement.readInt(0); + std::string value = statement.readString(1); + BOOST_CHECK_EQUAL(id, 1); + BOOST_CHECK_EQUAL(value, "test value"); + hasNext = statement.step(); + BOOST_CHECK(hasNext); + id = statement.readInt(0); + value = statement.readString(1); + BOOST_CHECK_EQUAL(id, 2); + BOOST_CHECK_EQUAL(value, "other value"); + hasNext = statement.step(); + BOOST_CHECK(!hasNext); + statement.finalize(); + database.close(); +} + +BOOST_AUTO_TEST_CASE(cleanup) { + sqlitepp::Database database("/tmp/test.db"); + database.execute("DROP TABLE test;"); + database.close(); +} \ No newline at end of file -- cgit v1.2.1