Browse Source

Refactoring and update.

master
Robin Krahl 4 years ago
parent
commit
56b56f46f1

+ 20
- 0
.travis.yml View File

@@ -0,0 +1,20 @@
language: cpp

sudo: false

compiler:
- gcc
- clang

addons:
apt:
packages:
- libsqlite3-dev
- libgtest-dev

script:
- mkdir bin && cd bin
- cmake ..
- make
- make test
- make check

+ 29
- 14
CMakeLists.txt View File

@@ -1,32 +1,47 @@
cmake_minimum_required(VERSION 2.8)
cmake_minimum_required(VERSION 3.0)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/CMakeModules/")
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
add_definitions(-std=c++11)

project(sqlitepp)
enable_testing()

set(SOURCES src/sqlitepp.cpp)
set(SOURCES src/sqlitepp/sqlitepp.cc)
set(TEST_SOURCES src/sqlitepp/sqlitepp_test.cc)
set(LINT_FILES include/sqlitepp/sqlitepp.h ${SOURCES} ${TEST_SOURCES})
set(INCLUDES include)

include(StyleCheck)

include_directories(${INCLUDES})

add_library(sqlitepp ${SOURCES})
add_executable(sqlitepptest src/sqlitepptest.cpp)

add_custom_target(check COMMAND sqlitepptest)
add_executable(sqlitepp_test ${TEST_SOURCES})

set(Boost_USE_MULTITHREADED OFF)
find_package(Boost 1.54.0 REQUIRED COMPONENTS unit_test_framework)
find_package(Doxygen)
find_package(GTest REQUIRED)
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)
set(TEST_INCLUDE_DIRS ${GTEST_INCLUDE_DIRS})
set(TEST_LIBRARIES ${GTEST_BOTH_LIBRARIES} pthread 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)

target_link_libraries(sqlitepp_test ${TEST_LIBRARIES})

set(GTEST_ARGS "")
gtest_add_tests(sqlitepp_test "${GTEST_ARGS}" ${TEST_SOURCES})

add_style_check_target(check "${LINT_FILES}")

if(DOXYGEN_FOUND)
configure_file(${CMAKE_MODULE_PATH}/sqlitepp.doxyfile
${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
add_custom_target(doc ${DOXYGEN_EXECUTABLE}
${CMAKE_CURRENT_BINARY_DIR}/Doxyfile
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
COMMENT "Generating API documentation with Doxygen"
VERBATIM)
endif(DOXYGEN_FOUND)

+ 53
- 0
CMakeModules/StyleCheck.cmake View File

@@ -0,0 +1,53 @@

# Copyright (C) 2013 Daniel Scharrer
#
# This software is provided 'as-is', without any express or implied
# warranty. In no event will the author(s) be held liable for any damages
# arising from the use of this software.
#
# Permission is granted to anyone to use this software for any purpose,
# including commercial applications, and to alter it and redistribute it
# freely, subject to the following restrictions:
#
# 1. The origin of this software must not be misrepresented; you must not
# claim that you wrote the original software. If you use this software
# in a product, an acknowledgment in the product documentation would be
# appreciated but is not required.
# 2. Altered source versions must be plainly marked as such, and must not be
# misrepresented as being the original software.
# 3. This notice may not be removed or altered from any source distribution.
#
#
# Modified 2015 by Robin Krahl -- removed "--filter" and "--project", added
# "--root"
# Original source: https://github.com/dscharrer/innoextract/blob/master/cmake/StyleCheck.cmake

find_package(PythonInterp)

# Add a target that runs cpplint.py
#
# Parameters:
# - TARGET_NAME the name of the target to add
# - SOURCES_LIST a complete list of source and include files to check
function(add_style_check_target TARGET_NAME SOURCES_LIST)
if(NOT PYTHONINTERP_FOUND)
return()
endif()
list(SORT SOURCES_LIST)
list(REMOVE_DUPLICATES SOURCES_LIST)
add_custom_target(${TARGET_NAME}
COMMAND "${CMAKE_COMMAND}" -E chdir
"${CMAKE_SOURCE_DIR}"
"${PYTHON_EXECUTABLE}"
"${CMAKE_MODULE_PATH}/cpplint.py"
"--root=include"
${SOURCES_LIST}
DEPENDS ${SOURCES_LIST}
COMMENT "Checking code style."
VERBATIM
)
endfunction(add_style_check_target)

+ 6323
- 0
CMakeModules/cpplint.py
File diff suppressed because it is too large
View File


+ 2385
- 0
CMakeModules/sqlitepp.doxyfile
File diff suppressed because it is too large
View File


+ 1
- 1
LICENSE View File

@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2014 Robin Krahl
Copyright (c) 2014--2015 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

+ 0
- 91
include/sqlitepp.h View File

@@ -1,91 +0,0 @@
/*
* (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

+ 491
- 0
include/sqlitepp/sqlitepp.h View File

@@ -0,0 +1,491 @@
// Copyright (C) 2014--2015 Robin Krahl <robin.krahl@ireas.org>
// MIT license -- http://opensource.org/licenses/MIT

#ifndef SQLITEPP_SQLITEPP_H_
#define SQLITEPP_SQLITEPP_H_

#include <sqlite3.h>
#include <stdexcept>
#include <memory>
#include <string>

/// \file
/// \brief Defines all classes of the sqlitepp library in the namespace
/// sqlitepp.

/// \mainpage sqlitepp -- C++ wrapper for SQLite3
/// **sqlitepp** is a C++ wrapper for the official SQLite3 C API.
///
/// \section compile Compiling sqlitepp
/// sqlitepp uses CMake as a build tool. To build sqlitepp from source,
/// download the source from GitHub and then execute these commands:
/// \code
/// $ mkdir bin && cd bin
/// $ cmake .. && make
/// \endcode
///
/// \section using Using sqlitepp
///
/// \subsection connect Connecting to a database
/// To connect to a SQLite database, you just have to create a new
/// sqlitepp::Database object.
/// \code{.cpp}
/// sqlitepp::Database database("/path/to/database.sqlite");
/// \endcode
/// This snippet is equivalent to:
/// \code{.cpp}
/// sqlitepp::Database database;
/// database.open("/path/to/database.sqlite");
/// \endcode
/// If the database file does not already exist, it is created. If an error
/// occurs during the creation of the database, a sqlitepp::DatabaseError
/// is thrown.
///
/// \subsection execute Executing a simple statement
/// To execute a simple statement, use sqlitepp::Database::execute:
/// \code{.cpp}
/// sqlitepp::Database database("/path/to/database.sqlite");
/// database.execute("CREATE TABLE test (id, value);");
/// \endcode
///
/// \subsection prepare Executing complex statements
/// If you want to execute more complex statements, for example selection or
/// insertion, use prepared statements. You can prepare a statement using
/// sqlitepp::Database::prepare. You can then bind values (if necessary) using
/// the `bind` methods of sqlitepp::Statement and execute the statement using
/// sqlitepp::Statement::execute. `execute` returns a sqlitepp::ResultSet that
/// stores the returned values (if any).
///
/// \subsubsection insert Example 1: insert
/// The recommended way to handle insertions are named bindings:
/// \code{.cpp}
/// sqlitepp::Database database("/path/to/database.sqlite");
/// std::shared_ptr<sqlitepp::Statement> statement = database.prepare(
/// "INSERT INTO test (id, value) VALUES (:id, :value);");
///
/// // insert (1, "test value")
/// statement->bind(":id", 1);
/// statement->bind(":value", "test value");
/// statement->execute();
/// statement->reset();
///
/// // insert (2, "other value")
/// statement->bind(":id", 2);
/// statement->bind(":value", "other value");
/// statement->execute();
/// \endcode
///
/// \subsubsection select Example 2: select
/// \code{.cpp}
/// sqlitepp::Database database("/path/to/database.sqlite");
/// std::shared_ptr<sqlitepp::Statement> statement = database.prepare(
/// "SELECT id, value FROM test;");
/// ResultSet resultSet = statement.execute();
/// while (resultSet.canRead()) {
/// std::cout << "ID: " << resultSet.readInt(0) << "\tvalue: "
/// << resultSet.readString(1) << std::endl;
/// resultSet.next();
/// }
/// \endcode
///
/// \section concepts Concepts
/// \subsection error Error handling
/// If an error occurs during an operation, an exception is thrown. All
/// SQLite3 database errors are wrapped in sqlitepp::DatabaseError. If a
/// method returns, it was successful (if not stated otherwise in the method
/// documentation).
///
/// \subsection resources Resources
/// sqlitepp uses RAII. This means that the destructors of sqlitepp::Database
/// and sqlitepp::Statement take care of freeing their resources once they
/// are destroyed. You can force them to free their resources using the
/// `close` methods.

/// \brief Contains all classes of the sqlitepp library.
namespace sqlitepp {

/// \brief A class that forbids copying and assignments for all subclasses.
///
/// This class defines a private, unimplemented copy constructor and assignment
/// method so that copies and assignments fail at compile-time. This class is
/// inspired by Scott Meyers, <em>Effective C++</em>, 3rd Edition, Item 6.
class Uncopyable {
protected:
Uncopyable() {}
~Uncopyable() {}

private:
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
};

/// \brief An element that has the two states *open* and *closed*.
///
/// Subclasses of this class may define methods that require the object to be
/// in a specific state. Refer to the implementing class&rsquo;s documentation
/// for more information about the methods that require a specific state.
///
/// The default state depends on the implementation. You can check the state of
/// an object using the isOpen() method.
///
/// Implementing classes may use setOpen() to change the state and
/// requireOpen() to throw a std::logic_error if the object is currently not
/// open.
class Openable {
public:
/// \brief Checks whether this object is open.
///
/// \returns `true` if this object is open; `false` if it is closed
bool isOpen() const;

protected:
/// \brief Creates a new Openable.
///
/// \param open `true` if the objet should be open per default; `false` if
/// it shoukd be closed per defaut
/// \param name the name of the implementing class used in error messages
Openable(const bool open, const std::string& name);

/// \brief Requires this object to be open and throws an exception if it is
/// not.
///
/// This method should be used at the beginning of other subclass methods
/// that require this object to be open. The error message of the exception
/// will contain the class name passed to the constructor.
///
/// \throws std::logic_error if this object is not open
void requireOpen() const;

/// \brief Changes the state of this object.
///
/// \param open the new state of this object (`true` if it should be opened;
/// `false` if it should be closed)
void setOpen(const bool open);

private:
bool m_open;
const std::string& m_name;
};

/// \brief An error that occurred during a database operation.
///
/// This error class is only used for errors that occured in the SQLite3
/// library and that are related to database operations. If there are other
/// problems, for example wrong states or illegal arguments, appropriate other
/// exceptions are thrown.
///
/// This exception class stores the SQLite3 error code and the error message.
///
/// \sa [SQLite Result Codes](https://www.sqlite.org/c3ref/c_abort.html)
class DatabaseError : public std::runtime_error {
public:
/// \brief Creates a new DatabaseError with the given code and the default
/// message.
///
/// The message is retrieved from the default SQLite3 error messages.
///
/// \param errorCode the SQLite3 error code
/// \sa [SQLite Result Codes](https://www.sqlite.org/c3ref/c_abort.html)
explicit DatabaseError(const int errorCode);

/// \brief Creates a new DatabaseError with the given code and message.
///
/// \param errorCode the SQLite3 error code
/// \param errorMessage the according error message
/// \sa [SQLite Result Codes](https://www.sqlite.org/c3ref/c_abort.html)
DatabaseError(const int errorCode, const std::string& errorMessage);

/// \brief Returns the SQLite3 error code for this error.
///
/// \sa [SQLite Result Codes](https://www.sqlite.org/c3ref/c_abort.html)
int errorCode() const;

private:
const int m_errorCode;

static std::string getErrorMessage(const int errorCode,
const std::string& errorMessage);
};

class Database;
class ResultSet;

/// \brief A handle for a SQLite3 statement.
///
/// This class stores a reference to a prepared SQLite3 statement and provides
/// methods to bind parameters to the query, execute it and read the results.
/// If a database operation fails, a DatabaseError is thrown.
///
/// Use Database::prepare to obtain instances of this class.
class Statement : private Uncopyable, public Openable {
public:
/// \brief Deconstructs this object and finalizes the statement.
///
/// Errors that occur when the statement is finalized are ignored as they
/// already occured during the last operation.
~Statement();

/// \brief Binds the given double value to the column with the given index.
///
/// \param index the index of the column to bind the value to
/// \param value the value to bind to that column
/// \throws std::logic_error if the statement is not open
/// \throws std::out_of_range if the given index is out of range
/// \throws std::runtime_error if there is not enough memory to bind the
/// value
/// \throws DatabaseError if an database error occured during the binding
void bind(const int index, const double value);

/// \brief Binds the given double value to the column with the given name.
///
/// \param index the name of the column to bind the value to
/// \param value the value to bind to that column
/// \throws std::logic_error if the statement is not open
/// \throws std::invalid_argument if there is no column witht the given name
/// \throws std::runtime_error if there is not enough memory to bind the
/// value
/// \throws DatabaseError if an database error occured during the binding
void bind(const std::string& name, const double value);

/// \brief Binds the given integer value to the column with the given index.
///
/// \param index the index of the column to bind the value to
/// \param value the value to bind to that column
/// \throws std::logic_error if the statement is not open
/// \throws std::out_of_range if the given index is out of range
/// \throws std::runtime_error if there is not enough memory to bind the
/// value
/// \throws DatabaseError if an database error occured during the binding
void bind(const int index, const int value);

/// \brief Binds the given integer value to the column with the given name.
///
/// \param index the name of the column to bind the value to
/// \param value the value to bind to that column
/// \throws std::logic_error if the statement is not open
/// \throws std::invalid_argument if there is no column witht the given name
/// \throws std::runtime_error if there is not enough memory to bind the
/// value
/// \throws DatabaseError if an database error occured during the binding
void bind(const std::string& name, const int value);

/// \brief Binds the given string value to the column with the given index.
///
/// \param index the index of the column to bind the value to
/// \param value the value to bind to that column
/// \throws std::logic_error if the statement is not open
/// \throws std::out_of_range if the given index is out of range
/// \throws std::runtime_error if there is not enough memory to bind the
/// value
/// \throws DatabaseError if an database error occured during the binding
void bind(const int index, const std::string& value);

/// \brief Binds the given string value to the column with the given name.
///
/// \param index the name of the column to bind the value to
/// \param value the value to bind to that column
/// \throws std::logic_error if the statement is not open
/// \throws std::invalid_argument if there is no column witht the given name
/// \throws std::runtime_error if there is not enough memory to bind the
/// value
/// \throws DatabaseError if an database error occured during the binding
void bind(const std::string& name, const std::string& value);

/// \brief Closes this statement.
///
/// Once you closed this statement, you may no longer access it. Any errors
/// that occur during finalization are ignored as they already occurred
/// during the last operation.
void close();

/// \brief Executes this statement and returns the result (if any).
///
/// \returns the result returned from the query (empty if there was no result)
/// \throws std::logic_error if the statement is not open
/// \throws DatabaseError if a database error occurs during the query
/// execution
ResultSet execute();

/// \brief Resets the statement.
///
/// Resets the statement so that it can be re-executed. Bindings are not
/// resetted.
///
/// \returns `true` if the reset was successful; otherwise `false`
/// \throws std::logic_error if the statement is not open
bool reset();

private:
explicit Statement(sqlite3_stmt* handle);

int getParameterIndex(const std::string& name) const;
void handleBindResult(const int index, const int result) const;
void requireCanRead() const;
void setInstancePointer(const std::weak_ptr<Statement>& instancePointer);
bool step();

sqlite3_stmt* m_handle;
bool m_canRead;
std::weak_ptr<Statement> m_instancePointer;

friend class Database;
friend class ResultSet;
};

/// \brief A handle for a SQLite3 database.
///
/// This class stores a reference to a SQLite3 database and provides methods
/// to open, query and change this database. After you successfully opened a
/// database (using the constructor Database(const std::string&) or using the
/// open(const std::string&) method), you can execute and prepare statements.
/// You can check whether the database is currently open using isOpen().
///
/// If you try to call a method that queries or updates the database and the
/// database is not open, a std::logic_error is thrown. If a database operation
/// fails, a DatabaseError is thrown.
class Database : private Uncopyable, public Openable {
public:
/// \brief Creates a new closed database.
///
/// Before you can access this database, you have to open a database file
/// using open(std::string&).
Database();

/// \brief Creates a new database and opens the given file.
///
/// The given file must either be a valid SQLite3 database file or may not
/// exist yet. This constructor is an abbreviation for:
/// \code{.cpp}
/// Database database;
/// database.open(file);
/// \endcode
///
/// \param file the name of the database file (not required to exist)
/// \throws std::runtime_error if there is not enough memory to create a
/// database connection
/// \throws DatabaseError if the SQLite3 database could not be opened
explicit Database(const std::string& file);

/// \brief Destructs this object and closes the database connection.
///
/// Errors that occur closing the database are ignored.
~Database();

/// \brief Closes the database if it is open.
///
/// \throws DatabaseError if the database cannot be closed
void close();

/// \brief Executes the given SQL string.
///
/// You can only call this method if there is an open database connection.
/// If you want to access the values returned by a SQL statement, use
/// prepare(const std::string&) instead.
///
/// \param sql the SQL statement to execute
/// \throws std::logic_error if the database is not open
/// \throws DatabaseError if an error occurred during the execution
void execute(const std::string& sql);

/// \brief Opens the given database file.
///
/// The given file must either be a valid SQLite3 database file or may not
/// exist yet. You can only open a new connection when the previous
/// connection has been closed (if any).
///
/// \param file the name of the database file (not required to exist)
/// \throws std::logic_error if the database is already open
/// \throws std::runtime_error if there is not enough memory to create a
/// database connection
/// \throws DatabaseError if the SQLite3 database could not be opened
void open(const std::string& file);

/// \brief Prepares a statement and returns a pointer to it.
///
/// You can either pass a complete SQL statement or a statement with
/// wildcards. If you use wildcards, you can bind them to a value using the
/// returned Statement.
///
/// \param sql the SQL statement to prepare (may contain wildcards)
/// \returns a pointer to the prepared statement
/// \throws std::logic_error if the database is not open
/// \throws DatabaseError if an error occurred during the preparation
std::shared_ptr<Statement> prepare(const std::string& sql);

private:
sqlite3* m_handle;
};

/// \brief A result set returned from a SQL query.
///
/// As long as there is data (`canRead()`), you can read it using the
/// `read*Type*` methods. To advance to the next row, use `next()`.
class ResultSet {
public:
/// \brief Checks whether there is data to read.
///
/// \returns `true` if there is data to read; otherwise `false`
bool canRead() const;

/// \brief Returns the column count of the result data.
///
/// You may only call this method when there is data to read (canRead()).
///
/// \returns the column count of the result
/// \throws std::logic_error if the statement is not open or there is no
/// data to read
int columnCount() const;

/// \brief Steps to the next row of the result (if there is one).
///
/// \returns `true` if there is new data to read or `false` if there are
/// no more results
/// \throws std::logic_error if the statement is not open
/// \throws DatabaseError if a database error occurs during the query
/// execution
bool next();

/// \brief Returns the current double value of the result column with the
/// given index.
///
/// You may only call this metod when there is data to read (canRead()).
///
/// \param column the index of the column to read from
/// \returns the current value of the result column with the given index
/// \throws std::logic_error if the statement is not open or there is no
/// data to read
double readDouble(const int column) const;

/// \brief Returns the current integer value of the result column with the
/// given index.
///
/// You may only call this metod when there is data to read (canRead()).
///
/// \param column the index of the column to read from
/// \returns the current value of the result column with the given index
/// \throws std::logic_error if the statement is not open or there is no
/// data to read
int readInt(const int column) const;

/// \brief Returns the current string value of the result column with the
/// given index.
///
/// You may only call this metod when there is data to read (canRead()).
///
/// \param column the index of the column to read from
/// \returns the current value of the result column with the given index
/// \throws std::logic_error if the statement is not open or there is no
/// data to read
std::string readString(const int column) const;

private:
explicit ResultSet(const std::shared_ptr<Statement> statement);

const std::shared_ptr<Statement> m_statement;

friend class Statement;
};

} // namespace sqlitepp

#endif // SQLITEPP_SQLITEPP_H_

+ 0
- 240
src/sqlitepp.cpp View File

@@ -1,240 +0,0 @@
/*
* (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));
}

+ 264
- 0
src/sqlitepp/sqlitepp.cc View File

@@ -0,0 +1,264 @@
// Copyright (C) 2014--2015 Robin Krahl <robin.krahl@ireas.org>
// MIT license -- http://opensource.org/licenses/MIT

#include "sqlitepp/sqlitepp.h"
#include <exception>
#include <iostream>
#include <sstream>
#include <string>

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<Statement>& 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> 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<Statement> 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<Statement>(new Statement(statementHandle));
statement->setInstancePointer(std::weak_ptr<Statement>(statement));
return statement;
}

ResultSet::ResultSet(const std::shared_ptr<Statement> 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

+ 100
- 0
src/sqlitepp/sqlitepp_test.cc View File

@@ -0,0 +1,100 @@
// Copyright (C) 2014--2015 Robin Krahl <robin.krahl@ireas.org>
// MIT license -- http://opensource.org/licenses/MIT

#include <stdexcept>
#include <fstream>
#include <iostream>
#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<sqlitepp::Statement> 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<sqlitepp::Statement> 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<sqlitepp::Statement> 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();
}

+ 0
- 108
src/sqlitepptest.cpp View File

@@ -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 <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();
}

Loading…
Cancel
Save