#ifndef SQLITETOOLS_H #define SQLITETOOLS_H #include #include #include #include #include #include #include #include "Types.h" namespace sqlite { struct ForeignKey { constexpr ForeignKey(unsigned int v) : value(v) {} unsigned int value; }; template struct Traits; template struct Traits::value>::type> { static constexpr int (*Bind)(sqlite3_stmt *, int, int) = &sqlite3_bind_int; static constexpr int (*Load)(sqlite3_stmt *, int) = &sqlite3_column_int; }; template <> struct Traits { static int Bind( sqlite3_stmt *stmt, int pos, ForeignKey fk) { if ( fk.value != 0 ) return Traits::Bind( stmt, pos, fk.value ); return sqlite3_bind_null( stmt, pos ); } }; template <> struct Traits { static int Bind(sqlite3_stmt* stmt, int pos, const std::string& value ) { char* tmp = strdup( value.c_str() ); return sqlite3_bind_text( stmt, pos, tmp, -1, free ); } static std::string Load( sqlite3_stmt* stmt, int pos ) { auto tmp = (const char*)sqlite3_column_text( stmt, pos ); if ( tmp != nullptr ) return std::string( tmp ); return std::string(); } }; template struct Traits::value>::type> { static constexpr int (*Bind)(sqlite3_stmt *, int, double) = &sqlite3_bind_double; static constexpr double (*Load)(sqlite3_stmt *, int) = &sqlite3_column_double; }; template <> struct Traits { static int Bind(sqlite3_stmt* stmt, int idx, std::nullptr_t) { return sqlite3_bind_null( stmt, idx ); } }; class Tools { private: typedef std::unique_ptr StmtPtr; public: /** * Will fetch all records of type IMPL and return them as a shared_ptr to INTF * This WILL add all fetched records to the cache * * @param results A reference to the result vector. All existing elements will * be discarded. */ template static std::vector > fetchAll( DBConnection dbConnectionWeak, const std::string& req, const Args&... args ) { auto dbConnection = dbConnectionWeak.lock(); if ( dbConnection == nullptr ) throw std::runtime_error("Invalid SQlite connection"); std::vector> results; auto stmt = prepareRequest( dbConnection, req, args...); if ( stmt == nullptr ) throw std::runtime_error("Failed to execute SQlite request"); int res = sqlite3_step( stmt.get() ); while ( res == SQLITE_ROW ) { auto row = IMPL::load( dbConnection, stmt.get() ); results.push_back( row ); res = sqlite3_step( stmt.get() ); } return results; } template static std::shared_ptr fetchOne( DBConnection dbConnectionWeak, const std::string& req, const Args&... args ) { auto dbConnection = dbConnectionWeak.lock(); if ( dbConnection == nullptr ) return nullptr; std::shared_ptr result; auto stmt = prepareRequest( dbConnection, req, args... ); if ( stmt == nullptr ) return nullptr; if ( sqlite3_step( stmt.get() ) != SQLITE_ROW ) return result; result = T::load( dbConnection, stmt.get() ); return result; } template static bool executeDelete( DBConnection dbConnectionWeak, const std::string& req, const Args&... args ) { auto dbConnection = dbConnectionWeak.lock(); if ( dbConnection == nullptr ) return false; if ( executeRequest( dbConnection, req, args... ) == false ) return false; return sqlite3_changes( dbConnection.get() ) > 0; } template static bool executeUpdate( DBConnection dbConnectionWeak, const std::string& req, const Args&... args ) { // The code would be exactly the same, do not freak out because it calls delete :) return executeDelete( dbConnectionWeak, req, args... ); } template static bool executeRequest( DBConnection dbConnectionWeak, const std::string& req, const Args&... args ) { auto dbConnection = dbConnectionWeak.lock(); if ( dbConnection == nullptr ) return false; return executeRequest( dbConnection, req, args... ); } /** * Inserts a record to the DB and return the newly created primary key. * Returns 0 (which is an invalid sqlite primary key) when insertion fails. */ template static unsigned int insert( DBConnection dbConnectionWeak, const std::string& req, const Args&... args ) { auto dbConnection = dbConnectionWeak.lock(); if ( dbConnection == nullptr ) return false; if ( executeRequest( dbConnection, req, args... ) == false ) return 0; return sqlite3_last_insert_rowid( dbConnection.get() ); } private: template static StmtPtr prepareRequest( std::shared_ptr dbConnectionPtr, const std::string& req, const Args&... args ) { return _prepareRequest<1>( dbConnectionPtr, req, args... ); } template static StmtPtr _prepareRequest( std::shared_ptr dbConnection, const std::string& req ) { assert( dbConnection != nullptr ); sqlite3_stmt* stmt = nullptr; int res = sqlite3_prepare_v2( dbConnection.get(), req.c_str(), -1, &stmt, NULL ); if ( res != SQLITE_OK ) { std::cerr << "Failed to execute request: " << req << std::endl; std::cerr << sqlite3_errmsg( dbConnection.get() ) << std::endl; } return StmtPtr( stmt, &sqlite3_finalize ); } template static StmtPtr _prepareRequest( std::shared_ptr dbConnectionPtr, const std::string& req, const T& arg, const Args&... args ) { auto stmt = _prepareRequest( dbConnectionPtr, req, args... ); Traits::Bind( stmt.get(), COLIDX, arg ); return stmt; } template static bool executeRequest( std::shared_ptr dbConnection, const std::string& req, const Args&... args ) { auto stmt = prepareRequest( dbConnection, req, args... ); if ( stmt == nullptr ) return false; int res; do { res = sqlite3_step( stmt.get() ); } while ( res == SQLITE_ROW ); if ( res != SQLITE_DONE ) { std::cerr << "Failed to execute <" << req << '>' << std::endl; std::cerr << "Invalid result: " << #if SQLITE_VERSION_NUMBER >= 3007015 sqlite3_errstr( res ) #else res #endif << ": " << sqlite3_errmsg( dbConnection.get() ) << std::endl; return false; } return true; } }; } #endif // SQLITETOOLS_H