diff --git a/README.md b/README.md index 4d049a91..d7aa1a09 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,79 @@ int main() { } ``` +Prepared Statements +===== +It is possible to retain and reuse statments this will keep the query plan and in case of an complex query or many uses might increase the performance significantly. + +```c++ + database db(":memory:"); + + // if you use << on a sqlite::database you get a prepared statment back + // this will not be executed till it gets destroyed or you execute it explicitly + auto ps = db << "select a,b from table where something = ? and anotherthing = ?"; // get a prepared parsed and ready statment + + // first if needed bind values to it + ps << 5; + int tmp = 8; + ps << tmp; + + // now you can execute it with `operator>>` or `execute()`. + // If the statment was executed once it will not be executed again when it goes out of scope. + // But beware that it will execute on destruction if it wasn't executed! + ps >> [&](int a,int b){ ... }; + + // after a successfull execution the statment needs to be reset to be execute again. This will reset the bound values too! + ps.reset(); + + // If you dont need the returned values you can execute it like this + ps.execute(); // the statment will not be reset! + + // there is a convinience operator to execute and reset in one go + ps++; + + // To disable the execution of a statment when it goes out of scope and wasn't used + ps.used(true); // or false if you want it to execute even if it was used + + // Usage Example: + + auto ps = db << "insert into complex_table_with_lots_of_indices values (?,?,?)"; + int i = 0; + while( i < 100000 ){ + ps << long_list[i++] << long_list[i++] << long_list[i++]; + ps++; + } +``` + +Shared Connections +===== +If you need the handle to the database connection to execute sqlite3 commands directly you can get a managed shared_ptr to it, so it will not close as long as you have a referenc to it. + +Take this example on how to deal with a database backup using SQLITEs own functions in a save and modern way. +```c++ + try { + database backup("backup"); //Open the database file we want to backup to + + auto con = db.connection(); // get a handle to the DB we want to backup in our scope + // this way we are sure the DB is open and ok while we backup + + // Init Backup and make sure its freed on exit or exceptions! + auto state = + std::unique_ptr( + sqlite3_backup_init(backup.connection().get(), "main", con.get(), "main"), + sqlite3_backup_finish + ); + + if(state) { + int rc; + // Each iteration of this loop copies 500 database pages from database db to the backup database. + do { + rc = sqlite3_backup_step(state.get(), 500); + std::cout << "Remaining " << sqlite3_backup_remaining(state.get()) << "/" << sqlite3_backup_pagecount(state.get()) << "\n"; + } while(rc == SQLITE_OK || rc == SQLITE_BUSY || rc == SQLITE_LOCKED); + } + } // Release allocated resources. +``` + Transactions ===== You can use transactions with `begin;`, `commit;` and `rollback;` commands. diff --git a/hdr/sqlite_modern_cpp.h b/hdr/sqlite_modern_cpp.h index 6c991e92..5e300c1e 100644 --- a/hdr/sqlite_modern_cpp.h +++ b/hdr/sqlite_modern_cpp.h @@ -5,9 +5,10 @@ #include #include #include +#include #ifdef _MODERN_SQLITE_BOOST_OPTIONAL_SUPPORT - #include +#include #endif #include @@ -16,515 +17,476 @@ namespace sqlite { -struct sqlite_exception: public std::runtime_error { - sqlite_exception(const char* msg):runtime_error(msg) {} -}; - -namespace exceptions -{ - //One more or less trivial derived error class for each SQLITE error. - //Note the following are not errors so have no classes: - //SQLITE_OK, SQLITE_NOTICE, SQLITE_WARNING, SQLITE_ROW, SQLITE_DONE - // - //Note these names are exact matches to the names of the SQLITE error codes. - class error : public sqlite_exception { using sqlite_exception::sqlite_exception;}; - class internal : public sqlite_exception { using sqlite_exception::sqlite_exception;}; - class perm : public sqlite_exception { using sqlite_exception::sqlite_exception;}; - class abort : public sqlite_exception { using sqlite_exception::sqlite_exception;}; - class busy : public sqlite_exception { using sqlite_exception::sqlite_exception;}; - class locked : public sqlite_exception { using sqlite_exception::sqlite_exception;}; - class nomem : public sqlite_exception { using sqlite_exception::sqlite_exception;}; - class readonly : public sqlite_exception { using sqlite_exception::sqlite_exception;}; - class interrupt : public sqlite_exception { using sqlite_exception::sqlite_exception;}; - class ioerr : public sqlite_exception { using sqlite_exception::sqlite_exception;}; - class corrupt : public sqlite_exception { using sqlite_exception::sqlite_exception;}; - class notfound : public sqlite_exception { using sqlite_exception::sqlite_exception;}; - class full : public sqlite_exception { using sqlite_exception::sqlite_exception;}; - class cantopen : public sqlite_exception { using sqlite_exception::sqlite_exception;}; - class protocol : public sqlite_exception { using sqlite_exception::sqlite_exception;}; - class empty : public sqlite_exception { using sqlite_exception::sqlite_exception;}; - class schema : public sqlite_exception { using sqlite_exception::sqlite_exception;}; - class toobig : public sqlite_exception { using sqlite_exception::sqlite_exception;}; - class constraint: public sqlite_exception { using sqlite_exception::sqlite_exception;}; - class mismatch : public sqlite_exception { using sqlite_exception::sqlite_exception;}; - class misuse : public sqlite_exception { using sqlite_exception::sqlite_exception;}; - class nolfs : public sqlite_exception { using sqlite_exception::sqlite_exception;}; - class auth : public sqlite_exception { using sqlite_exception::sqlite_exception;}; - class format : public sqlite_exception { using sqlite_exception::sqlite_exception;}; - class range : public sqlite_exception { using sqlite_exception::sqlite_exception;}; - class notadb : public sqlite_exception { using sqlite_exception::sqlite_exception;}; - - //Some additional errors are here for the C++ interface - class more_rows : public sqlite_exception { using sqlite_exception::sqlite_exception;}; - class no_rows : public sqlite_exception { using sqlite_exception::sqlite_exception;}; -} + struct sqlite_exception: public std::runtime_error { + sqlite_exception(const char* msg):runtime_error(msg) {} + }; + + namespace exceptions { + //One more or less trivial derived error class for each SQLITE error. + //Note the following are not errors so have no classes: + //SQLITE_OK, SQLITE_NOTICE, SQLITE_WARNING, SQLITE_ROW, SQLITE_DONE + // + //Note these names are exact matches to the names of the SQLITE error codes. + class error: public sqlite_exception { using sqlite_exception::sqlite_exception; }; + class internal: public sqlite_exception{ using sqlite_exception::sqlite_exception; }; + class perm: public sqlite_exception { using sqlite_exception::sqlite_exception; }; + class abort: public sqlite_exception { using sqlite_exception::sqlite_exception; }; + class busy: public sqlite_exception { using sqlite_exception::sqlite_exception; }; + class locked: public sqlite_exception { using sqlite_exception::sqlite_exception; }; + class nomem: public sqlite_exception { using sqlite_exception::sqlite_exception; }; + class readonly: public sqlite_exception { using sqlite_exception::sqlite_exception; }; + class interrupt: public sqlite_exception { using sqlite_exception::sqlite_exception; }; + class ioerr: public sqlite_exception { using sqlite_exception::sqlite_exception; }; + class corrupt: public sqlite_exception { using sqlite_exception::sqlite_exception; }; + class notfound: public sqlite_exception { using sqlite_exception::sqlite_exception; }; + class full: public sqlite_exception { using sqlite_exception::sqlite_exception; }; + class cantopen: public sqlite_exception { using sqlite_exception::sqlite_exception; }; + class protocol: public sqlite_exception { using sqlite_exception::sqlite_exception; }; + class empty: public sqlite_exception { using sqlite_exception::sqlite_exception; }; + class schema: public sqlite_exception { using sqlite_exception::sqlite_exception; }; + class toobig: public sqlite_exception { using sqlite_exception::sqlite_exception; }; + class constraint: public sqlite_exception { using sqlite_exception::sqlite_exception; }; + class mismatch: public sqlite_exception { using sqlite_exception::sqlite_exception; }; + class misuse: public sqlite_exception { using sqlite_exception::sqlite_exception; }; + class nolfs: public sqlite_exception { using sqlite_exception::sqlite_exception; }; + class auth: public sqlite_exception { using sqlite_exception::sqlite_exception; }; + class format: public sqlite_exception { using sqlite_exception::sqlite_exception; }; + class range: public sqlite_exception { using sqlite_exception::sqlite_exception; }; + class notadb: public sqlite_exception { using sqlite_exception::sqlite_exception; }; + + //Some additional errors are here for the C++ interface + class more_rows: public sqlite_exception { using sqlite_exception::sqlite_exception; }; + class no_rows: public sqlite_exception { using sqlite_exception::sqlite_exception; }; + + static void throw_sqlite_error(const int& error_code) { + if(error_code == SQLITE_ERROR) throw exceptions::error(sqlite3_errstr(error_code)); + else if(error_code == SQLITE_INTERNAL) throw exceptions::internal (sqlite3_errstr(error_code)); + else if(error_code == SQLITE_PERM) throw exceptions::perm(sqlite3_errstr(error_code)); + else if(error_code == SQLITE_ABORT) throw exceptions::abort(sqlite3_errstr(error_code)); + else if(error_code == SQLITE_BUSY) throw exceptions::busy(sqlite3_errstr(error_code)); + else if(error_code == SQLITE_LOCKED) throw exceptions::locked(sqlite3_errstr(error_code)); + else if(error_code == SQLITE_NOMEM) throw exceptions::nomem(sqlite3_errstr(error_code)); + else if(error_code == SQLITE_READONLY) throw exceptions::readonly(sqlite3_errstr(error_code)); + else if(error_code == SQLITE_INTERRUPT) throw exceptions::interrupt(sqlite3_errstr(error_code)); + else if(error_code == SQLITE_IOERR) throw exceptions::ioerr(sqlite3_errstr(error_code)); + else if(error_code == SQLITE_CORRUPT) throw exceptions::corrupt(sqlite3_errstr(error_code)); + else if(error_code == SQLITE_NOTFOUND) throw exceptions::notfound(sqlite3_errstr(error_code)); + else if(error_code == SQLITE_FULL) throw exceptions::full(sqlite3_errstr(error_code)); + else if(error_code == SQLITE_CANTOPEN) throw exceptions::cantopen(sqlite3_errstr(error_code)); + else if(error_code == SQLITE_PROTOCOL) throw exceptions::protocol(sqlite3_errstr(error_code)); + else if(error_code == SQLITE_EMPTY) throw exceptions::empty(sqlite3_errstr(error_code)); + else if(error_code == SQLITE_SCHEMA) throw exceptions::schema(sqlite3_errstr(error_code)); + else if(error_code == SQLITE_TOOBIG) throw exceptions::toobig(sqlite3_errstr(error_code)); + else if(error_code == SQLITE_CONSTRAINT) throw exceptions::constraint(sqlite3_errstr(error_code)); + else if(error_code == SQLITE_MISMATCH) throw exceptions::mismatch(sqlite3_errstr(error_code)); + else if(error_code == SQLITE_MISUSE) throw exceptions::misuse(sqlite3_errstr(error_code)); + else if(error_code == SQLITE_NOLFS) throw exceptions::nolfs(sqlite3_errstr(error_code)); + else if(error_code == SQLITE_AUTH) throw exceptions::auth(sqlite3_errstr(error_code)); + else if(error_code == SQLITE_FORMAT) throw exceptions::format(sqlite3_errstr(error_code)); + else if(error_code == SQLITE_RANGE) throw exceptions::range(sqlite3_errstr(error_code)); + else if(error_code == SQLITE_NOTADB) throw exceptions::notadb(sqlite3_errstr(error_code)); + else throw sqlite_exception(sqlite3_errstr(error_code)); + } -class database; -class database_binder; + static void throw_custom_error(const char* str) { + throw std::runtime_error(str); + } -template class binder; + } -template database_binder&& operator <<(database_binder&& db,T const&& val); -template void get_col_from_db(database_binder& db, int inx, T& val); + class database; + class database_binder; -class sqlite3_statement -{ - private: - sqlite3_stmt* _stmt; + template class binder; - public: - - sqlite3_stmt** operator&() - { - return &_stmt; - } + typedef std::shared_ptr connection_type; - operator sqlite3_stmt*() - { - return _stmt; + template::value == Element)> struct tuple_iterate { + static void iterate(Tuple& t, database_binder& db) { + get_col_from_db(db, Element, std::get(t)); + tuple_iterate::iterate(t, db); } + }; - sqlite3_statement(sqlite3_stmt* s) - :_stmt(s) - { - } + template struct tuple_iterate { + static void iterate(Tuple&, database_binder&) {} + }; + + class database_binder { + + public: + typedef std::unique_ptr chain_type; - ~sqlite3_statement() - { - //Do not check for errors: an error code means that the - //*execution* of the statement failed somehow. We deal with errors - //at that point so we don't need to know about errors here. - // - //Also, this is an RAII class to make sure we don't leak during exceptions - //so there's a reasonably chance we're already in an exception here. - - sqlite3_finalize(_stmt); + void reset() { + sqlite3_reset(_stmt.get()); + sqlite3_clear_bindings(_stmt.get()); + _inx = 1; } -}; -template::value == Element)> struct tuple_iterate -{ - static void iterate(Tuple& t, database_binder& db) - { - get_col_from_db(db,Element, std::get(t)); - tuple_iterate::iterate(t, db); - } -}; + void execute() { + int hresult; -template struct tuple_iterate -{ - static void iterate(Tuple&, database_binder&) - { - } -}; - -class database_binder { -private: - sqlite3* const _db; - std::u16string _sql; - sqlite3_statement _stmt; - int _inx; - - bool execution_started = false; - bool throw_exceptions = true; - bool error_occured = false; - - void _extract(std::function call_back) { - execution_started = true; - int hresult; + while((hresult = sqlite3_step(_stmt.get())) == SQLITE_ROW) {} - while ((hresult = sqlite3_step(_stmt)) == SQLITE_ROW) { - call_back(); + if(hresult != SQLITE_DONE) { + exceptions::throw_sqlite_error(hresult); + } } - if (hresult != SQLITE_DONE) { - throw_sqlite_error(hresult); - } + void used(bool state) { execution_started = state; } + bool used() const { return execution_started; } - _stmt = nullptr; - } - void _extract_single_value(std::function call_back) { - execution_started = true; - int hresult; + private: + std::shared_ptr _db; + std::u16string _sql; + std::unique_ptr _stmt; - if ((hresult = sqlite3_step(_stmt)) == SQLITE_ROW) { - call_back(); - } - else if(hresult == SQLITE_DONE) - { - if(throw_exceptions) - throw exceptions::no_rows("no rows to extract: exactly 1 row expected"); - } + int _inx; - if ((hresult = sqlite3_step(_stmt)) == SQLITE_ROW) { - if(throw_exceptions) - throw exceptions::more_rows("not all rows extracted"); - } + bool execution_started = false; - if (hresult != SQLITE_DONE) { - throw_sqlite_error(hresult); + void _extract(std::function call_back) { + execution_started = true; + int hresult; + + while((hresult = sqlite3_step(_stmt.get())) == SQLITE_ROW) { + call_back(); + } + + if(hresult != SQLITE_DONE) { + exceptions::throw_sqlite_error(hresult); + } + reset(); } - _stmt = nullptr; - } + void _extract_single_value(std::function call_back) { + execution_started = true; + int hresult; - void _prepare() { - int hresult; - if ((hresult = sqlite3_prepare16_v2(_db, _sql.data(), -1, &_stmt, nullptr)) != SQLITE_OK) { - throw_sqlite_error(hresult); + if((hresult = sqlite3_step(_stmt.get())) == SQLITE_ROW) { + call_back(); + } else if(hresult == SQLITE_DONE) { + exceptions::throw_custom_error("no rows to extract: exactly 1 row expected"); + } + + if((hresult = sqlite3_step(_stmt.get())) == SQLITE_ROW) { + exceptions::throw_custom_error("not all rows extracted"); + } + + if(hresult != SQLITE_DONE) { + exceptions::throw_sqlite_error(hresult); + } + reset(); } - } - template - using is_sqlite_value = std::integral_constant< - bool, - std::is_floating_point::value - || std::is_integral::value - || std::is_same::value - || std::is_same::value - || std::is_same::value - >; + sqlite3_stmt* _prepare(const std::u16string& sql) { + int hresult; + sqlite3_stmt* tmp = nullptr; + hresult = sqlite3_prepare16_v2(_db.get(), sql.data(), -1, &tmp, nullptr); + if((hresult) != SQLITE_OK) exceptions::throw_sqlite_error(hresult); + return tmp; + } + + template + using is_sqlite_value = std::integral_constant< + bool, + std::is_floating_point::value + || std::is_integral::value + || std::is_same::value + || std::is_same::value + || std::is_same::value + >; + + template friend database_binder::chain_type& operator <<(database_binder::chain_type& db, const T& val); + template friend void get_col_from_db(database_binder& db, int inx, T& val); + template friend T operator++(database_binder& db, int); - template friend database_binder&& operator <<(database_binder&& ddb,T const&& val); - template friend void get_col_from_db(database_binder& ddb, int inx, T& val); #ifdef _MODERN_SQLITE_BOOST_OPTIONAL_SUPPORT - template friend database_binder&& operator <<(database_binder&& db, const boost::optional&& val); - template friend void get_col_from_db(database_binder& db, int inx, boost::optional& o); + template friend database_binder::chain_type& operator <<(database_binder::chain_type& db, const boost::optional& val); + template friend void get_col_from_db(database_binder& db, int inx, boost::optional& o); #endif -protected: - database_binder(sqlite3* db, std::u16string const & sql): - _db(db), - _sql(sql), - _stmt(nullptr), - _inx(1) { - _prepare(); - } - database_binder(sqlite3* db, std::string const & sql): - database_binder(db, std::u16string(sql.begin(), sql.end())) { } + database_binder() = delete; + database_binder(const database_binder& other) = delete; + database_binder& operator=(const database_binder&) = delete; + database_binder(const database_binder&& other) = delete; -public: - friend class database; - ~database_binder() noexcept(false){ + public: - /* Will be executed if no >>op is found, but not if an exception - is in mud flight */ - if (!execution_started && !std::uncaught_exception()) { - int hresult; + database_binder(std::shared_ptr db, std::u16string const & sql): + _db(db), + _sql(sql), + _stmt(_prepare(sql), sqlite3_finalize), + _inx(1) { + } - while ((hresult = sqlite3_step(_stmt)) == SQLITE_ROW) { } + database_binder(std::shared_ptr db, std::string const & sql): + database_binder(db, std::u16string(sql.begin(), sql.end())) {} - if (hresult != SQLITE_DONE) { - throw_sqlite_error(hresult); + ~database_binder() noexcept(false) { + /* Will be executed if no >>op is found, but not if an exception + is in mid flight */ + if(!execution_started && !std::uncaught_exception()) { + execute(); } } - } - void throw_sqlite_error(int error_code) { - if(throw_exceptions) { - if(error_code == SQLITE_ERROR ) throw exceptions::error (sqlite3_errmsg(_db)); - else if(error_code == SQLITE_INTERNAL ) throw exceptions::internal (sqlite3_errmsg(_db)); - else if(error_code == SQLITE_PERM ) throw exceptions::perm (sqlite3_errmsg(_db)); - else if(error_code == SQLITE_ABORT ) throw exceptions::abort (sqlite3_errmsg(_db)); - else if(error_code == SQLITE_BUSY ) throw exceptions::busy (sqlite3_errmsg(_db)); - else if(error_code == SQLITE_LOCKED ) throw exceptions::locked (sqlite3_errmsg(_db)); - else if(error_code == SQLITE_NOMEM ) throw exceptions::nomem (sqlite3_errmsg(_db)); - else if(error_code == SQLITE_READONLY ) throw exceptions::readonly (sqlite3_errmsg(_db)); - else if(error_code == SQLITE_INTERRUPT ) throw exceptions::interrupt (sqlite3_errmsg(_db)); - else if(error_code == SQLITE_IOERR ) throw exceptions::ioerr (sqlite3_errmsg(_db)); - else if(error_code == SQLITE_CORRUPT ) throw exceptions::corrupt (sqlite3_errmsg(_db)); - else if(error_code == SQLITE_NOTFOUND ) throw exceptions::notfound (sqlite3_errmsg(_db)); - else if(error_code == SQLITE_FULL ) throw exceptions::full (sqlite3_errmsg(_db)); - else if(error_code == SQLITE_CANTOPEN ) throw exceptions::cantopen (sqlite3_errmsg(_db)); - else if(error_code == SQLITE_PROTOCOL ) throw exceptions::protocol (sqlite3_errmsg(_db)); - else if(error_code == SQLITE_EMPTY ) throw exceptions::empty (sqlite3_errmsg(_db)); - else if(error_code == SQLITE_SCHEMA ) throw exceptions::schema (sqlite3_errmsg(_db)); - else if(error_code == SQLITE_TOOBIG ) throw exceptions::toobig (sqlite3_errmsg(_db)); - else if(error_code == SQLITE_CONSTRAINT) throw exceptions::constraint(sqlite3_errmsg(_db)); - else if(error_code == SQLITE_MISMATCH ) throw exceptions::mismatch (sqlite3_errmsg(_db)); - else if(error_code == SQLITE_MISUSE ) throw exceptions::misuse (sqlite3_errmsg(_db)); - else if(error_code == SQLITE_NOLFS ) throw exceptions::nolfs (sqlite3_errmsg(_db)); - else if(error_code == SQLITE_AUTH ) throw exceptions::auth (sqlite3_errmsg(_db)); - else if(error_code == SQLITE_FORMAT ) throw exceptions::format (sqlite3_errmsg(_db)); - else if(error_code == SQLITE_RANGE ) throw exceptions::range (sqlite3_errmsg(_db)); - else if(error_code == SQLITE_NOTADB ) throw exceptions::notadb (sqlite3_errmsg(_db)); - else throw sqlite_exception(sqlite3_errmsg(_db)); - } - error_occured = true; - } + template + typename std::enable_if::value, void>::type operator>>( + Result& value) { + this->_extract_single_value([&value, this] { + get_col_from_db(*this, 0, value); + }); + } - void throw_custom_error(const char* str) { - if(throw_exceptions) { - throw std::runtime_error(str); + template + void operator>>(std::tuple&& values) { + this->_extract_single_value([&values, this] { + tuple_iterate>::iterate(values, *this); + }); } - error_occured = true; - } - template - typename std::enable_if::value, void>::type operator>>( - Result& value) { - this->_extract_single_value([&value, this]{ - get_col_from_db(*this,0, value); - }); - } + template + typename std::enable_if::value, void>::type operator>>( + Function func) { + typedef utility::function_traits traits; + this->_extract([&func, this]() { + binder::run(*this, func); + }); + } + }; + class database { + private: + std::shared_ptr _db; - template - void operator>>(std::tuple&& values){ - this->_extract_single_value([&values, this]{ - tuple_iterate>::iterate(values, *this); - }); - } + public: + database(std::u16string const & db_name): _db(nullptr) { + sqlite3* tmp = nullptr; + auto ret = sqlite3_open16(db_name.data(), &tmp); + if(ret != SQLITE_OK) exceptions::throw_sqlite_error(ret); + _db = std::shared_ptr(tmp, [=](sqlite3* ptr) { sqlite3_close_v2(ptr); ptr = nullptr; }); // close and null to be sure + //_db.reset(tmp, sqlite3_close); // alternative close. (faster?) + } - template - typename std::enable_if::value, void>::type operator>>( - Function func) { - typedef utility::function_traits traits; + database(std::string const & db_name): + database(std::u16string(db_name.begin(), db_name.end())) {} - this->_extract([&func, this]() { - binder::run(*this, func); - }); - } -}; - -class database { -private: - sqlite3 * _db; - bool _connected; - bool _ownes_db; - -public: - database(std::u16string const & db_name): - _db(nullptr), - _connected(false), - _ownes_db(true) { - _connected = sqlite3_open16(db_name.data(), &_db) == SQLITE_OK; - } + database(std::shared_ptr db): + _db(db) {} - database(std::string const & db_name): - database(std::u16string(db_name.begin(), db_name.end())) { } + database_binder::chain_type operator<<(const char* sql) { + return database_binder::chain_type(new database_binder(_db, std::string(sql))); + } - database(sqlite3* db): - _db(db), - _connected(SQLITE_OK), - _ownes_db(false) { } + connection_type connection() const { return _db; } - ~database() { - if (_db && _ownes_db) { - sqlite3_close_v2(_db); - _db = nullptr; + sqlite3_int64 last_insert_rowid() const { + return sqlite3_last_insert_rowid(_db.get()); } - } - database_binder operator<<(std::string const& sql) const { - return database_binder(_db, sql); - } - database_binder operator<<(std::u16string const& sql) const { - return database_binder(_db, sql); - } + }; + + template + class binder { + private: + template < + typename Function, + std::size_t Index + > + using nth_argument_type = typename utility::function_traits< + Function + >::template argument; - operator bool() const { - return _connected; + public: + // `Boundary` needs to be defaulted to `Count` so that the `run` function + // template is not implicitly instantiated on class template instantiation. + // Look up section 14.7.1 _Implicit instantiation_ of the ISO C++14 Standard + // and the [dicussion](https://github.com/aminroosta/sqlite_modern_cpp/issues/8) + // on Github. + + template< + typename Function, + typename... Values, + std::size_t Boundary = Count + > + static typename std::enable_if<(sizeof...(Values) < Boundary), void>::type run( + database_binder& db, + Function& function, + Values&&... values + ) { + nth_argument_type value{}; + get_col_from_db(db, sizeof...(Values), value); + + run(db, function, std::forward(values)..., std::move(value)); + } + + template< + typename Function, + typename... Values, + std::size_t Boundary = Count + > + static typename std::enable_if<(sizeof...(Values) == Boundary), void>::type run( + database_binder&, + Function& function, + Values&&... values + ) { + function(std::move(values)...); + } + }; + + // int + template<> inline database_binder::chain_type& operator<<(database_binder::chain_type& db, const int& val) { + int hresult; + if((hresult = sqlite3_bind_int(db->_stmt.get(), db->_inx, val)) != SQLITE_OK) { + exceptions::throw_sqlite_error(hresult); + } + ++db->_inx; + return db; + } + template<> inline void get_col_from_db(database_binder& db, int inx, int& val) { + if(sqlite3_column_type(db._stmt.get(), inx) == SQLITE_NULL) { + val = 0; + } else { + val = sqlite3_column_int(db._stmt.get(), inx); + } } - sqlite3_int64 last_insert_rowid() const { - return sqlite3_last_insert_rowid(_db); + // sqlite_int64 + template<> inline database_binder::chain_type& operator <<(database_binder::chain_type& db, const sqlite_int64& val) { + int hresult; + if((hresult = sqlite3_bind_int64(db->_stmt.get(), db->_inx, val)) != SQLITE_OK) { + exceptions::throw_sqlite_error(hresult); + } + + ++db->_inx; + return db; } -}; - -template -class binder { -private: - template < - typename Function, - std::size_t Index - > - using nth_argument_type = typename utility::function_traits< - Function - >::template argument; - -public: - // `Boundary` needs to be defaulted to `Count` so that the `run` function - // template is not implicitly instantiated on class template instantiation. - // Look up section 14.7.1 _Implicit instantiation_ of the ISO C++14 Standard - // and the [dicussion](https://github.com/aminroosta/sqlite_modern_cpp/issues/8) - // on Github. - - template< - typename Function, - typename... Values, - std::size_t Boundary = Count - > - static typename std::enable_if<(sizeof...(Values) < Boundary), void>::type run( - database_binder& db, - Function& function, - Values&&... values - ) { - nth_argument_type value{}; - get_col_from_db(db,sizeof...(Values), value); - - run(db, function, std::forward(values)..., std::move(value)); + template<> inline void get_col_from_db(database_binder& db, int inx, sqlite3_int64& i) { + if(sqlite3_column_type(db._stmt.get(), inx) == SQLITE_NULL) { + i = 0; + } else { + i = sqlite3_column_int64(db._stmt.get(), inx); + } } - template< - typename Function, - typename... Values, - std::size_t Boundary = Count - > - static typename std::enable_if<(sizeof...(Values) == Boundary), void>::type run( - database_binder&, - Function& function, - Values&&... values - ) { - function(std::move(values)...); - } -}; + // float + template<> inline database_binder::chain_type& operator <<(database_binder::chain_type& db, const float& val) { + int hresult; + if((hresult = sqlite3_bind_double(db->_stmt.get(), db->_inx, double(val))) != SQLITE_OK) { + exceptions::throw_sqlite_error(hresult); + } -// int -template<> inline database_binder&& operator <<(database_binder&& db,int const&& val) { - int hresult; - if((hresult = sqlite3_bind_int(db._stmt, db._inx, val)) != SQLITE_OK) { - db.throw_sqlite_error(hresult); + ++db->_inx; + return db; } - ++db._inx; - return std::move(db); -} -template<> inline void get_col_from_db(database_binder& db, int inx, int& val) { - if(sqlite3_column_type(db._stmt, inx) == SQLITE_NULL) { - val = 0; - } else { - val = sqlite3_column_int(db._stmt, inx); + template<> inline void get_col_from_db(database_binder& db, int inx, float& f) { + if(sqlite3_column_type(db._stmt.get(), inx) == SQLITE_NULL) { + f = 0; + } else { + f = float(sqlite3_column_double(db._stmt.get(), inx)); + } } -} -// sqlite_int64 -template<> inline database_binder&& operator <<(database_binder&& db, sqlite_int64 const&& val) { - int hresult; - if((hresult = sqlite3_bind_int64(db._stmt, db._inx, val)) != SQLITE_OK) { - db.throw_sqlite_error(hresult); - } + // double + template<> inline database_binder::chain_type& operator <<(database_binder::chain_type& db, const double& val) { + int hresult; + if((hresult = sqlite3_bind_double(db->_stmt.get(), db->_inx, val)) != SQLITE_OK) { + exceptions::throw_sqlite_error(hresult); + } - ++db._inx; - return std::move(db); -} -template<> inline void get_col_from_db(database_binder& db,int inx, sqlite3_int64& i) { - if(sqlite3_column_type(db._stmt, inx) == SQLITE_NULL) { - i = 0; - } else { - i = sqlite3_column_int64(db._stmt, inx); + ++db->_inx; + return db; } -} - -// float -template<> inline database_binder&& operator <<(database_binder&& db,float const&& val) { - int hresult; - if((hresult = sqlite3_bind_double(db._stmt, db._inx, double(val))) != SQLITE_OK) { - db.throw_sqlite_error(hresult); + template<> inline void get_col_from_db(database_binder& db, int inx, double& d) { + if(sqlite3_column_type(db._stmt.get(), inx) == SQLITE_NULL) { + d = 0; + } else { + d = sqlite3_column_double(db._stmt.get(), inx); + } } - ++db._inx; - return std::move(db); -} -template<> inline void get_col_from_db(database_binder& db, int inx, float& f) { - if(sqlite3_column_type(db._stmt, inx) == SQLITE_NULL) { - f = 0; - } else { - f = float(sqlite3_column_double(db._stmt, inx)); + // std::string + template<> inline void get_col_from_db(database_binder& db, int inx, std::string & s) { + if(sqlite3_column_type(db._stmt.get(), inx) == SQLITE_NULL) { + s = std::string(); + } else { + sqlite3_column_bytes(db._stmt.get(), inx); + s = std::string(reinterpret_cast(sqlite3_column_text(db._stmt.get(), inx))); + } } -} -// double -template<> inline database_binder&& operator <<(database_binder&& db,double const&& val) { - int hresult; - if((hresult = sqlite3_bind_double(db._stmt, db._inx, val)) != SQLITE_OK) { - db.throw_sqlite_error(hresult); - } + // Convert char* to string to trigger op<<(..., const std::string ) + template inline database_binder::chain_type& operator <<(database_binder::chain_type& db, const char(&STR)[N]) { return db << std::string(STR); } + template inline database_binder::chain_type& operator <<(database_binder::chain_type& db, const char16_t(&STR)[N]) { return db << std::u16string(STR); } - ++db._inx; - return std::move(db); -} -template<> inline void get_col_from_db(database_binder& db, int inx, double& d) { - if(sqlite3_column_type(db._stmt, inx) == SQLITE_NULL) { - d = 0; - } else { - d = sqlite3_column_double(db._stmt, inx); - } -} + template<> database_binder::chain_type& operator <<(database_binder::chain_type& db, const std::string& txt) { + int hresult; + if((hresult = sqlite3_bind_text(db->_stmt.get(), db->_inx, txt.data(), -1, SQLITE_TRANSIENT)) != SQLITE_OK) { + exceptions::throw_sqlite_error(hresult); + } -// std::string -template<> inline void get_col_from_db(database_binder& db, int inx,std::string & s) { - if(sqlite3_column_type(db._stmt, inx) == SQLITE_NULL) { - s = std::string(); - } else { - sqlite3_column_bytes(db._stmt, inx); - s = std::string(reinterpret_cast(sqlite3_column_text(db._stmt, inx))); + ++db->_inx; + return db; } -} -template<> inline database_binder&& operator <<(database_binder&& db, std::string const&& txt) { - int hresult; - if((hresult = sqlite3_bind_text(db._stmt, db._inx, txt.data(), -1, SQLITE_TRANSIENT)) != SQLITE_OK) { - db.throw_sqlite_error(hresult); - } - - ++db._inx; - return std::move(db); -} -// std::u16string -template<> inline void get_col_from_db(database_binder& db, int inx, std::u16string & w) { - if(sqlite3_column_type(db._stmt, inx) == SQLITE_NULL) { - w = std::u16string(); - } else { - sqlite3_column_bytes16(db._stmt, inx); - w = std::u16string(reinterpret_cast(sqlite3_column_text16(db._stmt, inx))); + // std::u16string + template<> void get_col_from_db(database_binder& db, int inx, std::u16string & w) { + if(sqlite3_column_type(db._stmt.get(), inx) == SQLITE_NULL) { + w = std::u16string(); + } else { + sqlite3_column_bytes16(db._stmt.get(), inx); + w = std::u16string(reinterpret_cast(sqlite3_column_text16(db._stmt.get(), inx))); + } } -} -// boost::optinal support for NULL values -template<> inline database_binder&& operator <<(database_binder&& db, std::u16string const&& txt) { - int hresult; - if((hresult = sqlite3_bind_text16(db._stmt, db._inx, txt.data(), -1, SQLITE_TRANSIENT)) != SQLITE_OK) { - db.throw_sqlite_error(hresult); - } - ++db._inx; - return std::move(db); -} + template<> inline database_binder::chain_type& operator <<(database_binder::chain_type& db, const std::u16string& txt) { + int hresult; + if((hresult = sqlite3_bind_text16(db->_stmt.get(), db->_inx, txt.data(), -1, SQLITE_TRANSIENT)) != SQLITE_OK) { + exceptions::throw_sqlite_error(hresult); + } -#ifdef _MODERN_SQLITE_BOOST_OPTIONAL_SUPPORT -template database_binder&& operator <<(database_binder&& db, const boost::optional&& val) { - if(val) { - return operator << (std::move(db), std::move(*val)); - } - int hresult; - if((hresult = sqlite3_bind_null(db._stmt, db._inx)) != SQLITE_OK) { - db.throw_sqlite_error(hresult); + ++db->_inx; + return db; } + // boost::optinal support for NULL values +#ifdef _MODERN_SQLITE_BOOST_OPTIONAL_SUPPORT + template database_binder::chain_type& operator <<(database_binder::chain_type& db, const boost::optional& val) { + if(val) { + return operator << (std::move(db), std::move(*val)); + } + int hresult; + if((hresult = sqlite3_bind_null(db->_stmt.get(), db->_inx)) != SQLITE_OK) { + exceptions::throw_sqlite_error(hresult); + } - ++db._inx; - return std::move(db); -} + ++db->_inx; + return db; + } -template void get_col_from_db(database_binder& db, int inx, boost::optional& o) { - if(sqlite3_column_type(db._stmt, inx) == SQLITE_NULL) { - o.reset(); - } else { - BoostOptionalT v; - get_col_from_db(db, inx, v); - o = std::move(v); + template void get_col_from_db(database_binder& db, int inx, boost::optional& o) { + if(sqlite3_column_type(db._stmt.get(), inx) == SQLITE_NULL) { + o.reset(); + } else { + BoostOptionalT v; + get_col_from_db(db, inx, v); + o = std::move(v); + } } -} #endif -/* call the rvalue functions */ -template database_binder&& operator <<(database_binder&& db, T const& val) { return std::move(db) << std::move(val); } + // there is too much magic here, val might be rValue or lValue + template void operator >> (database_binder::chain_type& db, T&& val) { *db >> std::forward(val); } + template void operator >> (database_binder::chain_type&& db, T&& val) { db >> std::forward(val); } + + // Some ppl are lazy so we have a operator for proper prep. statemant handling. + void operator++(database_binder::chain_type& db, int) { db->execute(); db->reset(); } + + // Convert the rValue binder to a reference and call first op<<, its needed for the call that creates the binder (be carfull of recursion here!) + template database_binder::chain_type& operator << (database_binder::chain_type&& db, const T& val) { return db << val; } -/*special case for string literals*/ -template database_binder&& operator <<(database_binder&& db, const char(&STR)[N]) - { return std::move(db) << std::string(STR); } -template database_binder&& operator <<(database_binder&& db, const char16_t(&STR)[N]) - { return std::move(db) << std::u16string(STR); } } diff --git a/tests/prepared_statment.cc b/tests/prepared_statment.cc new file mode 100644 index 00000000..5ad896fa --- /dev/null +++ b/tests/prepared_statment.cc @@ -0,0 +1,90 @@ +#include +#include +#include +#include +using namespace sqlite; +using namespace std; + +int main() { + try { + + database db(":memory:"); + + auto pps = db << "select ?"; // get a prepared parsed and ready statment + + int test = 4; + pps << test; // set a bound var + + pps >> test; // execute and reset statment + + pps << 4; // bind a rvalue + pps >> test; // and execute again + + pps << 8 >> test; + + auto pps2 = db << "select 1,2,3,4,5"; // multiple extract test + + pps2 >> [](int a, int b, int c, int d, int e) { + std::cout << "L " << a << b << c << d << e << "\n"; // still works as intended + }; + + auto pps3 = db << "select ?,?,?"; + + pps3 << 1 << test << 5 >> [](int a, int b, int, int c) { + std::cout << "L2 " << a << b << c << "\n"; // still works as intended + }; + + db << "select ?,?" << test << 5 >> test; // and mow everything together + + db << "select ?, ?, ?" << 1 << test << 1 >> [](int a, int b, int, int c) { + std::cout << "L3 " << a << b << c << "\n"; // still works as intended + }; + + db << "select ?" << test; // noVal + db << "select ?,?" << test << 1; + db << "select ?,?" << 1 << test; + db << "select ?,?" << 1 << 1; + db << "select ?,?" << test << test; + + db << "select ?" << test >> test; // lVal + db << "select ?,?" << test << 1 >> test; + db << "select ?,?" << 1 << test >> test; + db << "select ?,?" << 1 << 1 >> test; + db << "select ?,?" << test << test >> test; + + int q = 0; + + db << "select ?" << test >> [&](int t) { q = t++; }; // rVal + db << "select ?,?" << test << 1 >> [&](int t, int p) { q = t + p; }; + db << "select ?,?" << 1 << test >> [&](int t, int p) { q = t + p; }; + db << "select ?,?" << 1 << 1 >> [&](int t, int p) { q = t + p; }; + db << "select ?,?" << test << test >> [&](int t, int p) { q = t + p; }; + + db << "select ?,?,?" << test << 1 << test; // mix + db << "select ?,?,?" << 1 << test << 1; + db << "select ?,?,?" << 1 << 1 << test; + db << "select ?,?,?" << 1 << 1 << 1; + db << "select ?,?,?" << test << test << test; + + { + auto pps4 = db << "select ?,?,?"; // reuse + + (pps4 << test << 1 << test)++; + (pps4 << 1 << test << 1)++; + (pps4 << 1 << 1 << test)++; + (pps4 << 1 << 1 << 1)++; + (pps4 << test << test << test)++; + } + + + } catch(sqlite_exception e) { + cout << "Unexpected error " << e.what() << endl; + exit(EXIT_FAILURE); + } catch(...) { + cout << "Unknown error\n"; + exit(EXIT_FAILURE); + } + + cout << "OK\n"; + exit(EXIT_SUCCESS); +} diff --git a/tests/shared_connection.cc b/tests/shared_connection.cc new file mode 100644 index 00000000..a75c04e5 --- /dev/null +++ b/tests/shared_connection.cc @@ -0,0 +1,59 @@ +#include +#include +#include +#include + +using namespace sqlite; +using namespace std; + +struct TmpFile { + string fname; + + TmpFile() { + char f[] = "/tmp/sqlite_modern_cpp_test_XXXXXX"; + int fid = mkstemp(f); + close(fid); + + fname = f; + } + + ~TmpFile() { + unlink(fname.c_str()); + } +}; + +int main() { + try { + + TmpFile file; + database db(file.fname); + + { + + auto con = db.connection(); + + { + database db2(con); + int test = 0; + db2 << "select 1" >> test; + if(test != 1) exit(EXIT_FAILURE); + } + + int test = 0; + db << "select 1" >> test; + if(test != 1) exit(EXIT_FAILURE); + + } + + + } catch(sqlite_exception e) { + cout << "Unexpected error " << e.what() << endl; + exit(EXIT_FAILURE); + } catch(...) { + cout << "Unknown error\n"; + exit(EXIT_FAILURE); + } + + cout << "OK\n"; + exit(EXIT_SUCCESS); +}