// Copyright (C) 2016 xaizek <xaizek@posteo.net>
//
// This file is part of uncov.
//
// uncov is free software: you can redistribute it and/or modify
// it under the terms of version 3 of the GNU Affero General Public License as
// published by the Free Software Foundation.
//
// uncov is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with uncov.  If not, see <http://www.gnu.org/licenses/>.
#ifndef UNCOV_DB_HPP_
#define UNCOV_DB_HPP_
/**
 * @file DB.hpp
 *
 * @brief This unit provides basic facilities for interacting with a database.
 *
 * At the moment SQLite is the only supported database.
 */
#include <boost/range.hpp>
#include <boost/variant.hpp>
#include <cstdint>
#include <functional>
#include <iterator>
#include <memory>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
#include "utils/integer_seq.hpp"
struct sqlite3;
struct sqlite3_stmt;
class Binding;
class Transaction;
/**
 * @brief Represents databaes connection.
 */
class DB
{
    class Row;
    class SingleRow;
    class RowIterator;
    class RowsData;
    class Rows;
    //! Type of smart handle to database statement, which is RAII-friendly.
    using stmtPtr = std::unique_ptr<sqlite3_stmt,
                                    std::function<void(sqlite3_stmt *)>>;
    //! Iterator facade base for the Rows class.
    using RowsBase = boost::iterator_range<RowIterator>;
public:
    /**
     * @brief Opens a database.
     *
     * @param path Path to the database.
     *
     * @throws std::runtime_error if database connection can't be opened.
     */
    explicit DB(const std::string &path);
    /**
     * @brief Closes database connection.
     */
    ~DB();
public:
    /**
     * @brief Performs a statement and discards result.
     *
     * @param stmt  Statement to be performed.
     * @param binds Bindings to be applied.
     *
     * @throws std::runtime_error on failure to make a statement or apply binds.
     */
    void execute(const std::string &stmt,
                 const std::vector<Binding> &binds = {});
    /**
     * @brief Queries single row as a result of performing a statement.
     *
     * @param stmt  Statement to be performed.
     * @param binds Bindings to be applied.
     *
     * @returns An object representing row, which is convertible to a tuple.
     *
     * @throws std::runtime_error on failure to make a statement or apply binds.
     */
    SingleRow queryOne(const std::string &stmt,
                       const std::vector<Binding> &binds = {});
    /**
     * @brief Queries multiple rows as a result of performing a statement.
     *
     * @param stmt  Statement to be performed.
     * @param binds Bindings to be applied.
     *
     * @returns Range-friendly object that generates tuples.
     *
     * @throws std::runtime_error on failure to make a statement or apply binds.
     */
    Rows queryAll(const std::string &stmt,
                  const std::vector<Binding> &binds = {});
    /**
     * @brief Retrieves id of the last inserted row.
     *
     * @returns The id.
     */
    std::int64_t getLastRowId();
    /**
     * @brief Starts a transaction.
     *
     * Usage:
     * @code
     * Transaction transaction = db.makeTransaction();
     * // db.execute(...);
     * // db.execute(...);
     * transaction.commit();
     * @endcode
     *
     * @returns RAII transaction object.
     */
    Transaction makeTransaction();
private:
    /**
     * @brief Builds a prepared statement.
     *
     * @param stmt  Statement to be performed.
     * @param binds Bindings to be applied.
     *
     * @returns Smart pointer to just created prepared statement.
     *
     * @throws std::runtime_error on failure to make a statement or apply binds.
     */
    stmtPtr prepare(const std::string &stmt, const std::vector<Binding> &binds);
private:
    sqlite3 *conn; //!< Connection to the database.
};
/**
 * @brief Wrapper for a single database row.
 */
class DB::Row
{
    /**
     * @brief Type marker for overload resolution.
     *
     * @tparam T Marked type.
     */
    template <typename T> struct Marker {};
public:
    /**
     * @brief Initializes row from database statement.
     *
     * @param ps @copybrief ps
     */
    Row(sqlite3_stmt *ps) : ps(ps)
    {
    }
public:
    /**
     * @brief Retrieves contents of the row as a tuple.
     *
     * @tparam Types Column types.
     *
     * @returns Row columns.
     *
     * @throws std::runtime_error if tuple size or types don't match.
     */
    template <typename... Types>
    operator std::tuple<Types...>()
    {
        const int nCols = getColumnCount();
        if (nCols != sizeof...(Types)) {
            throw std::runtime_error {
                "Expected " + std::to_string(nCols) + " columns, got " +
                std::to_string(sizeof...(Types))
            };
        }
        return makeTuple<Types...>(index_sequence_for<Types...>());
    }
private:
    /**
     * @brief Does the actual job of converting columns into a tuple.
     *
     * @tparam Types Column types.
     * @tparam Is    Index sequence that numbers columns.
     *
     * @param is "Container" for integer sequence.
     *
     * @returns Row columns.
     *
     * @throws std::runtime_error if tuple types don't match.
     */
    template <typename... Types, std::size_t... Is>
    std::tuple<Types...> makeTuple(integer_sequence<Is...> is)
    {
        static_cast<void>(is);
        return std::make_tuple(makeTupleItem(Is, Marker<Types>())...);
    }
    /**
     * @brief Retrieves number of columns in the row.
     *
     * @returns The number.
     */
    int getColumnCount() const;
    /**
     * @brief Reads contents of a column as a string.
     *
     * @param idx    Index of the column.
     * @param marker Overload resolution marker.
     *
     * @returns The string.
     */
    std::string makeTupleItem(std::size_t idx, Marker<std::string> marker);
    /**
     * @brief Reads contents of a column as an integer.
     *
     * @param idx    Index of the column.
     * @param marker Overload resolution marker.
     *
     * @returns The integer.
     */
    int makeTupleItem(std::size_t idx, Marker<int> marker);
    /**
     * @brief Reads contents of a column as a vector of integers.
     *
     * @param idx    Index of the column.
     * @param marker Overload resolution marker.
     *
     * @returns The vector of integers.
     */
    std::vector<int> makeTupleItem(std::size_t idx,
                                   Marker<std::vector<int>> marker);
private:
    sqlite3_stmt *ps; //!< Handle to the database statement.
};
/**
 * @brief A wrapper for reading single row from database statement.
 */
class DB::SingleRow : public Row
{
public:
    /**
     * @brief Takes ownership of the argument and reads single row.
     *
     * @param ps @copybrief ps
     */
    explicit SingleRow(stmtPtr ps);
private:
    stmtPtr ps; //!< Smart handle to database statement.
};
/**
 * @brief Table row iterator implementation.
 */
class DB::RowIterator
  : public boost::iterator_facade<RowIterator, Row, std::input_iterator_tag>
{
    friend class boost::iterator_core_access;
public:
    /**
     * @brief Initializes empty row iterator (end-iterator).
     */
    RowIterator() : ps(nullptr), row(ps)
    {
    }
    /**
     * @brief Initializes non-empty row iterator (begin-iterator).
     *
     * @param ps @copybrief ps
     */
    RowIterator(sqlite3_stmt *ps) : ps(ps), row(ps)
    {
        increment();
    }
private:
    /**
     * @brief Advances iterator position to the next row.
     */
    void increment();
    /**
     * @brief Compares the iterator against another one for equality.
     *
     * @param rhs Iterator to compare against.
     *
     * @returns @c true when equal, @c false otherwise.
     */
    bool equal(const RowIterator &rhs) const
    {
        return ps == rhs.ps;
    }
    /**
     * @brief Dereferences iterator.
     *
     * @returns Row object that allows accessing columns.
     */
    Row & dereference() const
    {
        return row;
    }
private:
    sqlite3_stmt *ps; //!< Handle to the database statement.
    mutable Row row;  //!< Row iterator object.
};
/**
 * @brief Data class for base-from-member idiom for Rows.
 */
class DB::RowsData
{
protected:
    /**
     * @brief Just moves argument into a member.
     *
     * @param ps @copybrief ps
     */
    RowsData(stmtPtr ps) : ps(std::move(ps))
    {
    }
protected:
    stmtPtr ps; //!< Smart handle to database statement.
};
/**
 * @brief Iterator range for rows.
 */
class DB::Rows : private RowsData, public RowsBase
{
public:
    /**
     * @brief Initializes the range.
     *
     * @param ps Smart handle to database statement.
     */
    explicit Rows(stmtPtr ps)
        : RowsData(std::move(ps)),
          RowsBase(RowIterator(RowsData::ps.get()), RowIterator())
    {
    }
};
/**
 * @brief RAII class for managing transactions.
 *
 * Transaction is started in the constructor and automatically rolled back in
 * the destructor unless commit() was called.
 */
class Transaction
{
public:
    /**
     * Starts the transaction.
     *
     * @param conn @copybrief conn
     */
    Transaction(sqlite3 *conn);
    //! Not copyable.
    Transaction(const Transaction &rhs) = delete;
    //! Moveable.
    Transaction(Transaction &&rhs) = default;
    //! Not copy-assignable.
    Transaction & operator=(const Transaction &rhs) = delete;
    //! Move-assignable.
    Transaction & operator=(Transaction &&rhs) = default;
    /**
     * Rolls back the transaction if commit() wasn't called.
     */
    ~Transaction();
public:
    /**
     * @brief Commits the transaction.
     *
     * @throws std::logic_error if transaction has already been committed.
     * @throws std::runtime_error if transaction doesn't commit.
     */
    void commit();
private:
    sqlite3 *conn;  //!< Connection on which transaction is performed.
    bool committed; //!< Whether transaction has been committed.
};
/**
 * @brief A name-value pair that represents a binding for prepared statements.
 */
class Binding
{
    friend class BlankBinding;
    /**
     * @brief Initializes the binding.
     *
     * Only BlankBinding can do this.
     *
     * @param name  @copybrief name
     * @param value @copybrief value
     */
    Binding(std::string name,
            boost::variant<std::string, int, std::vector<int>> value)
        : name(std::move(name)), value(value)
    {
    }
public:
    /**
     * @brief Retrieves name of the binding.
     *
     * @returns The name.
     */
    const std::string & getName() const
    {
        return name;
    }
    /**
     * @brief Retrieves value of the binding.
     *
     * @returns The value.
     */
    const boost::variant<std::string, int, std::vector<int>> & getValue() const
    {
        return value;
    }
private:
    //! Name of the argument that is being bound.
    const std::string name;
    //! Value that is bound.
    const boost::variant<std::string, int, std::vector<int>> value;
};
/**
 * @brief A temporary object which is a result of user-defined literal _b.
 */
class BlankBinding
{
    friend BlankBinding operator ""_b(const char name[], std::size_t len);
    /**
     * @brief Initializes blank binding with a name.
     *
     * @param name @copybrief name
     */
    explicit BlankBinding(std::string name) : name(std::move(name))
    {
    }
public:
    /**
     * @brief Completes binding with a string.
     *
     * @param val Value for the binding.
     *
     * @returns Fully initialized binding.
     */
    Binding operator=(std::string val) &&
    {
        return Binding(name, std::move(val));
    }
    /**
     * @brief Completes binding with an integer.
     *
     * @param val Value for the binding.
     *
     * @returns Fully initialized binding.
     */
    Binding operator=(int val) &&
    {
        return Binding(name, val);
    }
    /**
     * @brief Completes binding with vector of integers.
     *
     * @param val Value for the binding.
     *
     * @returns Fully initialized binding.
     */
    Binding operator=(std::vector<int> val) &&
    {
        return Binding(name, std::move(val));
    }
private:
    const std::string name; //!< Name of the argument that is being bound.
};
/**
 * @brief Partially creates a binding that holds its name only.
 *
 * @param name Name of the binding.
 * @param len  Length of the name of the binding.
 *
 * @returns Half-formed binding which should be completed with assignment.
 */
inline BlankBinding
operator ""_b(const char name[], std::size_t len)
{
    return BlankBinding(std::string(name, len));
}
#endif // UNCOV_DB_HPP_
 
  
Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"
Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/uncov
Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/uncov
You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a
pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master