From 56b56f46f13a7838e113b529a75771a9fb07bdf7 Mon Sep 17 00:00:00 2001 From: Robin Krahl Date: Sat, 18 Jul 2015 23:30:42 +0200 Subject: Refactoring and update. --- src/sqlitepp.cpp | 240 -------------------------------------- src/sqlitepp/sqlitepp.cc | 264 ++++++++++++++++++++++++++++++++++++++++++ src/sqlitepp/sqlitepp_test.cc | 100 ++++++++++++++++ src/sqlitepptest.cpp | 108 ----------------- 4 files changed, 364 insertions(+), 348 deletions(-) delete mode 100644 src/sqlitepp.cpp create mode 100644 src/sqlitepp/sqlitepp.cc create mode 100644 src/sqlitepp/sqlitepp_test.cc delete mode 100644 src/sqlitepptest.cpp (limited to 'src') diff --git a/src/sqlitepp.cpp b/src/sqlitepp.cpp deleted file mode 100644 index 8cdb357..0000000 --- a/src/sqlitepp.cpp +++ /dev/null @@ -1,240 +0,0 @@ -/* - * (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/sqlitepp/sqlitepp.cc b/src/sqlitepp/sqlitepp.cc new file mode 100644 index 0000000..d64b5ba --- /dev/null +++ b/src/sqlitepp/sqlitepp.cc @@ -0,0 +1,264 @@ +// Copyright (C) 2014--2015 Robin Krahl +// MIT license -- http://opensource.org/licenses/MIT + +#include "sqlitepp/sqlitepp.h" +#include +#include +#include +#include + +namespace sqlitepp { + +Openable::Openable(const bool open, const std::string& name) + : m_open(open), m_name(name) { +} + +bool Openable::isOpen() const { + return m_open; +} + +void Openable::requireOpen() const { + if (!m_open) { + throw std::logic_error(m_name + " is not open."); + } +} + +void Openable::setOpen(const bool open) { + m_open = open; +} + +std::string DatabaseError::getErrorMessage(const int errorCode, + const std::string& errorMessage) { + std::ostringstream stringStream; + stringStream << "Caught SQLite3 error " << errorCode << " meaning: " + << errorMessage; + return stringStream.str(); +} + +DatabaseError::DatabaseError(const int errorCode) + : DatabaseError(errorCode, sqlite3_errstr(errorCode)) { +} + +DatabaseError::DatabaseError(const int errorCode, + const std::string& errorMessage) + : std::runtime_error(getErrorMessage(errorCode, errorMessage)), + m_errorCode(errorCode) { +} + +int DatabaseError::errorCode() const { + return m_errorCode; +} + +Statement::Statement(sqlite3_stmt* handle) + : Openable(true, "Statement"), m_canRead(false), m_handle(handle) { +} + +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 Statement::bind(const int index, const double value) { + requireOpen(); + handleBindResult(index, sqlite3_bind_double(m_handle, index, value)); +} + +void Statement::bind(const std::string& name, const double value) { + bind(getParameterIndex(name), value); +} + +void Statement::bind(const int index, const int value) { + requireOpen(); + handleBindResult(index, sqlite3_bind_int(m_handle, index, value)); +} + +void Statement::bind(const std::string& name, const int value) { + bind(getParameterIndex(name), value); +} + +void Statement::bind(const int index, const std::string& value) { + requireOpen(); + handleBindResult(index, sqlite3_bind_text(m_handle, index, value.c_str(), + value.size(), NULL)); +} + +void Statement::bind(const std::string& name, const std::string& value) { + bind(getParameterIndex(name), value); +} + +ResultSet Statement::execute() { + step(); + return ResultSet(m_instancePointer.lock()); +} + +void Statement::requireCanRead() const { + if (!m_canRead) { + throw std::logic_error("Trying to read from statement without data"); + } +} + +void Statement::setInstancePointer( + const std::weak_ptr& instancePointer) { + m_instancePointer = instancePointer; +} + +bool 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 Statement::close() { + if (isOpen()) { + // errors that could occur during finalizing are ignored as they have + // already been handled! + sqlite3_finalize(m_handle); + setOpen(false); + } +} + +bool Statement::reset() { + requireOpen(); + return sqlite3_reset(m_handle) == SQLITE_OK; +} + +int 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 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); + } +} + +Database::Database() : Openable(false, "Database") { +} + +Database::Database(const std::string & file) : Database() { + open(file); +} + +Database::~Database() { + if (isOpen()) { + sqlite3_close(m_handle); + setOpen(false); + } + // m_handle is deleted by sqlite3_close +} + +void Database::close() { + if (isOpen()) { + int result = sqlite3_close(m_handle); + if (result == SQLITE_OK) { + setOpen(false); + } else { + throw sqlitepp::DatabaseError(result); + } + } +} + +void Database::execute(const std::string& sql) { + requireOpen(); + std::shared_ptr statement = prepare(sql); + statement->step(); +} + +void 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 Database::prepare(const std::string& sql) { + requireOpen(); + sqlite3_stmt* statementHandle; + int result = sqlite3_prepare_v2(m_handle, sql.c_str(), sql.size(), + &statementHandle, NULL); + if (result != SQLITE_OK) { + throw DatabaseError(result, sqlite3_errmsg(m_handle)); + } + if (statementHandle == NULL) { + throw std::runtime_error("Statement handle is NULL"); + } + auto statement = std::shared_ptr(new Statement(statementHandle)); + statement->setInstancePointer(std::weak_ptr(statement)); + return statement; +} + +ResultSet::ResultSet(const std::shared_ptr statement) + : m_statement(statement) { +} + +bool ResultSet::canRead() const { + return m_statement->m_canRead; +} + +int ResultSet::columnCount() const { + m_statement->requireOpen(); + m_statement->requireCanRead(); + return sqlite3_column_count(m_statement->m_handle); +} + +double ResultSet::readDouble(const int column) const { + m_statement->requireOpen(); + m_statement->requireCanRead(); + return sqlite3_column_double(m_statement->m_handle, column); +} + +int ResultSet::readInt(const int column) const { + m_statement->requireOpen(); + m_statement->requireCanRead(); + return sqlite3_column_int(m_statement->m_handle, column); +} + +std::string ResultSet::readString(const int column) const { + m_statement->requireOpen(); + m_statement->requireCanRead(); + return std::string((const char*) sqlite3_column_text(m_statement->m_handle, + column)); +} + +bool ResultSet::next() { + return m_statement->step(); +} + +} // namespace sqlitepp diff --git a/src/sqlitepp/sqlitepp_test.cc b/src/sqlitepp/sqlitepp_test.cc new file mode 100644 index 0000000..b7ea5f6 --- /dev/null +++ b/src/sqlitepp/sqlitepp_test.cc @@ -0,0 +1,100 @@ +// Copyright (C) 2014--2015 Robin Krahl +// MIT license -- http://opensource.org/licenses/MIT + +#include +#include +#include +#include "gtest/gtest.h" +#include "sqlitepp/sqlitepp.h" + +TEST(Database, openClose) { + sqlitepp::Database database; + EXPECT_FALSE(database.isOpen()); + database.open("/tmp/test.db"); + EXPECT_TRUE(database.isOpen()); + database.close(); + EXPECT_FALSE(database.isOpen()); + database.open("/tmp/test2.db"); + EXPECT_TRUE(database.isOpen()); + database.close(); + EXPECT_FALSE(database.isOpen()); + sqlitepp::Database database2("/tmp/test.db"); + EXPECT_TRUE(database2.isOpen()); + EXPECT_THROW(database2.open("/tmp/test2.db"), std::logic_error); + EXPECT_TRUE(database2.isOpen()); + database2.close(); + EXPECT_FALSE(database2.isOpen()); + + std::ifstream testStream("/tmp/test.db"); + EXPECT_TRUE(testStream.good()); + testStream.close(); + testStream.open("/tmp/test2.db"); + EXPECT_TRUE(testStream.good()); + testStream.close(); +} + +TEST(Database, copy) { + sqlitepp::Database database; + // MUST NOT COMPILE: + // sqlitepp::Database database2 = database; + database.close(); + sqlitepp::Database database3; + // MUST NOT COMPILE: + // database3 = database; + database3.close(); +} + +TEST(Database, prepare) { + sqlitepp::Database database("/tmp/test.db"); + std::shared_ptr statement = database.prepare( + "CREATE TABLE IF NOT EXISTS test (id, value);"); + EXPECT_TRUE(statement->isOpen()); + statement->close(); + EXPECT_FALSE(statement->isOpen()); + database.close(); +} + +TEST(Database, execute) { + sqlitepp::Database database("/tmp/test.db"); + database.execute("CREATE TABLE IF NOT EXISTS test (id, value);"); +} + +TEST(Database, insert) { + sqlitepp::Database database("/tmp/test.db"); + std::shared_ptr statement = database.prepare( + "INSERT INTO test (id, value) VALUES (:id, ?)"); + statement->bind(":id", 1); + statement->bind(2, "test value"); + statement->execute(); + statement->reset(); + statement->bind(":id", 2); + statement->bind(2, "other value"); + statement->execute(); +} + +TEST(Database, query) { + sqlitepp::Database database("/tmp/test.db"); + std::shared_ptr statement = database.prepare( + "SELECT id, value FROM test;"); + sqlitepp::ResultSet resultSet = statement->execute(); + EXPECT_TRUE(resultSet.canRead()); + EXPECT_EQ(2, resultSet.columnCount()); + int id = resultSet.readInt(0); + std::string value = resultSet.readString(1); + EXPECT_EQ(1, id); + EXPECT_EQ("test value", value); + EXPECT_TRUE(resultSet.next()); + EXPECT_TRUE(resultSet.canRead()); + id = resultSet.readInt(0); + value = resultSet.readString(1); + EXPECT_EQ(2, id); + EXPECT_EQ("other value", value); + EXPECT_FALSE(resultSet.next()); + EXPECT_FALSE(resultSet.canRead()); +} + +TEST(Database, cleanup) { + sqlitepp::Database database("/tmp/test.db"); + database.execute("DROP TABLE test;"); + database.close(); +} diff --git a/src/sqlitepptest.cpp b/src/sqlitepptest.cpp deleted file mode 100644 index 7974eae..0000000 --- a/src/sqlitepptest.cpp +++ /dev/null @@ -1,108 +0,0 @@ -/* - * (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.3