diff options
-rw-r--r-- | .gitignore | 6 | ||||
-rw-r--r-- | CMakeLists.txt | 32 | ||||
-rw-r--r-- | CMakeModules/FindSqlite3.cmake | 56 | ||||
-rw-r--r-- | LICENSE | 22 | ||||
-rw-r--r-- | include/sqlitepp.h | 91 | ||||
-rw-r--r-- | src/sqlitepp.cpp | 240 | ||||
-rw-r--r-- | src/sqlitepptest.cpp | 108 |
7 files changed, 555 insertions, 0 deletions
@@ -26,3 +26,9 @@ *.exe *.out *.app + +# build directory +build + +# IDE project files +.idea diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..3991c61 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 2.8) +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/CMakeModules/") +SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") + +project(sqlitepp) + +set(SOURCES src/sqlitepp.cpp) +set(INCLUDES include) + +include_directories(${INCLUDES}) + +add_library(sqlitepp ${SOURCES}) +add_executable(sqlitepptest src/sqlitepptest.cpp) + +add_custom_target(check COMMAND sqlitepptest) + +set(Boost_USE_MULTITHREADED OFF) +find_package(Boost 1.54.0 REQUIRED COMPONENTS unit_test_framework) +find_package(Sqlite3 REQUIRED) + +set(DEP_INCLUDE_DIRS ${SQLITE3_INCLUDE_DIRS}) +set(DEP_LIBRARIES ${SQLITE3_LIBRARIES}) +set(TEST_INCLUDE_DIRS ${Boost_UNIT_TEST_FRAMEWORK_INCLUDE_DIRS}) +set(TEST_LIBRARIES ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES} sqlitepp) + +include_directories(${DEP_INCLUDE_DIRS}) +include_directories(${TEST_INCLUDE_DIRS}) +target_link_libraries(sqlitepp ${DEP_LIBRARIES}) +target_link_libraries(sqlitepptest ${TEST_LIBRARIES}) + +# add_subdirectory(tests) + diff --git a/CMakeModules/FindSqlite3.cmake b/CMakeModules/FindSqlite3.cmake new file mode 100644 index 0000000..0eccec2 --- /dev/null +++ b/CMakeModules/FindSqlite3.cmake @@ -0,0 +1,56 @@ +# - find Sqlite 3 +# SQLITE3_INCLUDE_DIR - Where to find Sqlite 3 header files (directory) +# SQLITE3_LIBRARIES - Sqlite 3 libraries +# SQLITE3_LIBRARY_RELEASE - Where the release library is +# SQLITE3_LIBRARY_DEBUG - Where the debug library is +# SQLITE3_FOUND - Set to TRUE if we found everything (library, includes and executable) + +# Copyright (c) 2010 Pau Garcia i Quiles, <pgquiles@elpauer.org> +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# +# Generated by CModuler, a CMake Module Generator - http://gitorious.org/cmoduler + +IF( SQLITE3_INCLUDE_DIR AND SQLITE3_LIBRARY_RELEASE AND SQLITE3_LIBRARY_DEBUG ) + SET(SQLITE3_FIND_QUIETLY TRUE) +ENDIF( SQLITE3_INCLUDE_DIR AND SQLITE3_LIBRARY_RELEASE AND SQLITE3_LIBRARY_DEBUG ) + +FIND_PATH( SQLITE3_INCLUDE_DIR sqlite3.h ) + +FIND_LIBRARY(SQLITE3_LIBRARY_RELEASE NAMES sqlite3 ) + +FIND_LIBRARY(SQLITE3_LIBRARY_DEBUG NAMES sqlite3 sqlite3d HINTS /usr/lib/debug/usr/lib/ ) + +IF( SQLITE3_LIBRARY_RELEASE OR SQLITE3_LIBRARY_DEBUG AND SQLITE3_INCLUDE_DIR ) + SET( SQLITE3_FOUND TRUE ) +ENDIF( SQLITE3_LIBRARY_RELEASE OR SQLITE3_LIBRARY_DEBUG AND SQLITE3_INCLUDE_DIR ) + +IF( SQLITE3_LIBRARY_DEBUG AND SQLITE3_LIBRARY_RELEASE ) + # if the generator supports configuration types then set + # optimized and debug libraries, or if the CMAKE_BUILD_TYPE has a value + IF( CMAKE_CONFIGURATION_TYPES OR CMAKE_BUILD_TYPE ) + SET( SQLITE3_LIBRARIES optimized ${SQLITE3_LIBRARY_RELEASE} debug ${SQLITE3_LIBRARY_DEBUG} ) + ELSE( CMAKE_CONFIGURATION_TYPES OR CMAKE_BUILD_TYPE ) + # if there are no configuration types and CMAKE_BUILD_TYPE has no value + # then just use the release libraries + SET( SQLITE3_LIBRARIES ${SQLITE3_LIBRARY_RELEASE} ) + ENDIF( CMAKE_CONFIGURATION_TYPES OR CMAKE_BUILD_TYPE ) +ELSEIF( SQLITE3_LIBRARY_RELEASE ) + SET( SQLITE3_LIBRARIES ${SQLITE3_LIBRARY_RELEASE} ) +ELSE( SQLITE3_LIBRARY_DEBUG AND SQLITE3_LIBRARY_RELEASE ) + SET( SQLITE3_LIBRARIES ${SQLITE3_LIBRARY_DEBUG} ) +ENDIF( SQLITE3_LIBRARY_DEBUG AND SQLITE3_LIBRARY_RELEASE ) + +IF( SQLITE3_FOUND ) + IF( NOT SQLITE3_FIND_QUIETLY ) + MESSAGE( STATUS "Found Sqlite3 header file in ${SQLITE3_INCLUDE_DIR}") + MESSAGE( STATUS "Found Sqlite3 libraries: ${SQLITE3_LIBRARIES}") + ENDIF( NOT SQLITE3_FIND_QUIETLY ) +ELSE(SQLITE3_FOUND) + IF( SQLITE3_FIND_REQUIRED) + MESSAGE( FATAL_ERROR "Could not find Sqlite3" ) + ELSE( SQLITE3_FIND_REQUIRED) + MESSAGE( STATUS "Optional package Sqlite3 was not found" ) + ENDIF( SQLITE3_FIND_REQUIRED) +ENDIF(SQLITE3_FOUND) @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2014 Robin Krahl + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/include/sqlitepp.h b/include/sqlitepp.h new file mode 100644 index 0000000..db932d2 --- /dev/null +++ b/include/sqlitepp.h @@ -0,0 +1,91 @@ +/* + * (C) 2014 Robin Krahl + * MIT license -- http://opensource.org/licenses/MIT + */ + +#ifndef __SQLITEPP_H +#define __SQLITEPP_H + +#include <memory> +#include <string> + +#include <boost/noncopyable.hpp> +#include <sqlite3.h> + +namespace sqlitepp { + class Openable { + public: + const bool isOpen() const; + + protected: + Openable(const bool open, const std::string & name); + + void requireOpen() const; + void setOpen(const bool open); + + private: + bool m_open; + const std::string & m_name; + }; + + class DatabaseError : public std::runtime_error { + public: + DatabaseError(const int errorCode); + DatabaseError(const int errorCode, const std::string & errorMessage); + + const int errorCode() const; + private: + const int m_errorCode; + + static const std::string getErrorMessage(const int errorCode, const std::string & errorMessage); + }; + + class Statement; + + class Database : private boost::noncopyable, public Openable { + friend class Statement; + public: + Database(); + Database(const std::string & file); + ~Database(); + + void close(); + void execute(const std::string & sql); + void open(const std::string & file); + std::shared_ptr<Statement> prepare(const std::string & sql); + + private: + sqlite3 * m_handle; + }; + + class Statement : private boost::noncopyable, public Openable { + public: + Statement(Database & database, const std::string & statement); + ~Statement(); + + void bindDouble(const int index, const double value); + void bindDouble(const std::string & name, const double value); + void bindInt(const int index, const int value); + void bindInt(const std::string & name, const int value); + void bindString(const int index, const std::string & value); + void bindString(const std::string & name, const std::string & value); + const bool canRead() const; + const int columnCount() const; + const double readDouble(const int column) const; + const int readInt(const int column) const; + const std::string readString(const int column) const; + const bool step(); + void finalize(); + const bool reset(); + + private: + sqlite3_stmt * m_handle; + bool m_canRead; + + int getParameterIndex(const std::string & name) const; + void handleBindResult(const int index, const int result) const; + void requireCanRead() const; + }; +} + +#endif
\ No newline at end of file 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 <exception> +#include <iostream> +#include <sstream> + +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::Statement> sqlitepp::Database::prepare(const std::string & sql) { + return std::shared_ptr<sqlitepp::Statement>(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 <boost/test/unit_test.hpp> + +#include <fstream> +#include <iostream> +#include <stdexcept> + +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 |