aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore6
-rw-r--r--CMakeLists.txt32
-rw-r--r--CMakeModules/FindSqlite3.cmake56
-rw-r--r--LICENSE22
-rw-r--r--include/sqlitepp.h91
-rw-r--r--src/sqlitepp.cpp240
-rw-r--r--src/sqlitepptest.cpp108
7 files changed, 555 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index b8bd026..172c96d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..54eb9d1
--- /dev/null
+++ b/LICENSE
@@ -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