diff --git a/README.md b/README.md index f56869c4..c99fd27d 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ int main() { cout << "The new record got assigned id " << db.last_insert_rowid() << endl; - // slects from user table on a condition ( age > 18 ) and executes + // selects from user table on a condition ( age > 18 ) and executes // the lambda for each row returned . db << "select age,name,weight from user where age > ? ;" << 18 @@ -56,6 +56,18 @@ int main() { cout << age << ' ' << name << ' ' << weight << endl; }; + // a for loop can be used too: + // with named variables + for(auto &&row : db << "select age,name,weight from user where age > ? ;" << 18) { + int age; string name; double weight; + row >> age >> name >> weight; + cout << age << ' ' << name << ' ' << weight << endl; + } + // or with a tuple + for(tuple row : db << "select age,name,weight from user where age > ? ;" << 18) { + cout << get<0>(row) << ' ' << get<1>(row) << ' ' << get<2>(row) << endl; + } + // selects the count(*) from user table // note that you can extract a single culumn single row result only to : int,long,long,float,double,string,u16string int count = 0; diff --git a/hdr/sqlite_modern_cpp.h b/hdr/sqlite_modern_cpp.h index 0d328470..a3ff93d9 100644 --- a/hdr/sqlite_modern_cpp.h +++ b/hdr/sqlite_modern_cpp.h @@ -70,17 +70,7 @@ namespace sqlite { typedef std::shared_ptr connection_type; - 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); - } - }; - - template struct tuple_iterate { - static void iterate(Tuple&, database_binder&) {} - }; - + class row_iterator; class database_binder { public: @@ -94,16 +84,7 @@ namespace sqlite { _stmt(std::move(other._stmt)), _inx(other._inx), execution_started(other.execution_started) { } - void execute() { - _start_execute(); - int hresult; - - while((hresult = sqlite3_step(_stmt.get())) == SQLITE_ROW) {} - - if(hresult != SQLITE_DONE) { - errors::throw_sqlite_error(hresult, sql()); - } - } + void execute(); std::string sql() { #if SQLITE_VERSION_NUMBER >= 3014000 @@ -128,6 +109,8 @@ namespace sqlite { execution_started = state; } bool used() const { return execution_started; } + row_iterator begin(); + row_iterator end(); private: std::shared_ptr _db; @@ -145,43 +128,6 @@ namespace sqlite { } return ++_inx; } - void _start_execute() { - _next_index(); - _inx = 0; - used(true); - } - - void _extract(std::function call_back) { - int hresult; - _start_execute(); - - while((hresult = sqlite3_step(_stmt.get())) == SQLITE_ROW) { - call_back(); - } - - if(hresult != SQLITE_DONE) { - errors::throw_sqlite_error(hresult, sql()); - } - } - - void _extract_single_value(std::function call_back) { - int hresult; - _start_execute(); - - if((hresult = sqlite3_step(_stmt.get())) == SQLITE_ROW) { - call_back(); - } else if(hresult == SQLITE_DONE) { - throw errors::no_rows("no rows to extract: exactly 1 row expected", sql(), SQLITE_DONE); - } - - if((hresult = sqlite3_step(_stmt.get())) == SQLITE_ROW) { - throw errors::more_rows("not all rows extracted", sql(), SQLITE_ROW); - } - - if(hresult != SQLITE_DONE) { - errors::throw_sqlite_error(hresult, sql()); - } - } #ifdef _MSC_VER sqlite3_stmt* _prepare(const std::u16string& sql) { @@ -204,31 +150,6 @@ namespace sqlite { return tmp; } - template - struct is_sqlite_value : public 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 - struct is_sqlite_value< std::vector > : public std::integral_constant< - bool, - std::is_floating_point::value - || std::is_integral::value - || std::is_same::value - > { }; -#ifdef MODERN_SQLITE_STD_VARIANT_SUPPORT - template - struct is_sqlite_value< std::variant > : public std::integral_constant< - bool, - true - > { }; -#endif - - /* for vector support */ template friend database_binder& operator <<(database_binder& db, const std::vector& val); template friend void get_col_from_db(database_binder& db, int inx, std::vector& val); @@ -288,31 +209,191 @@ namespace sqlite { } } - template - typename std::enable_if::value, void>::type operator>>( - Result& value) { - this->_extract_single_value([&value, this] { - get_col_from_db(*this, 0, value); - }); + friend class row_iterator; + }; + namespace detail { + template + struct is_sqlite_value : public 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 + struct is_sqlite_value< std::vector > : public std::integral_constant< + bool, + std::is_floating_point::value + || std::is_integral::value + || std::is_same::value + > { }; + template + struct is_sqlite_value< std::unique_ptr > : public is_sqlite_value {}; +#ifdef MODERN_SQLITE_STD_VARIANT_SUPPORT + template + struct is_sqlite_value< std::variant > : public std::integral_constant< + bool, + true + > { }; +#endif +#ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT + template + struct is_sqlite_value< optional > : public is_sqlite_value {}; +#endif + +#ifdef _MODERN_SQLITE_BOOST_OPTIONAL_SUPPORT + template + struct is_sqlite_value< boost::optional > : public is_sqlite_value {}; +#endif + } + + class row_iterator { + public: + class value_type { + public: + value_type(database_binder *_binder): _binder(_binder) {}; + template + typename std::enable_if::value, value_type &>::type operator >>(T &result) { + get_col_from_db(*_binder, next_index++, result); + return *this; + } + template + value_type &operator >>(std::tuple& values); + template + value_type &operator >>(std::tuple&& values) { + return *this >> values; + } + template + operator std::tuple() { + std::tuple value; + *this >> value; + return value; + } + explicit operator bool() { + return sqlite3_column_count(_binder->_stmt.get()) >= next_index; + } + private: + database_binder *_binder; + int next_index = 0; + }; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + using iterator_category = std::input_iterator_tag; + + row_iterator() = default; + explicit row_iterator(database_binder &binder): _binder(&binder) { + _binder->_next_index(); + _binder->_inx = 0; + _binder->used(true); + ++*this; + } + + reference operator*() const { return value;} + pointer operator->() const { return std::addressof(**this); } + row_iterator &operator++() { + switch(int result = sqlite3_step(_binder->_stmt.get())) { + case SQLITE_ROW: + value = {_binder}; + break; + case SQLITE_DONE: + _binder = nullptr; + break; + default: + exceptions::throw_sqlite_error(result, _binder->sql()); + } + return *this; } - template - void operator>>(std::tuple&& values) { - this->_extract_single_value([&values, this] { - tuple_iterate>::iterate(values, *this); - }); + friend inline bool operator ==(const row_iterator &a, const row_iterator &b) { + return a._binder == b._binder; + } + friend inline bool operator !=(const row_iterator &a, const row_iterator &b) { + return !(a==b); } - template - typename std::enable_if::value, void>::type operator>>( - Function&& func) { - typedef utility::function_traits traits; + private: + database_binder *_binder = nullptr; + mutable value_type value{_binder}; // mutable, because `changing` the value is just reading it + }; + + namespace detail { + template::value == Element)> struct tuple_iterate { + static void iterate(Tuple& t, row_iterator::value_type& row) { + row >> std::get(t); + tuple_iterate::iterate(t, row); + } + }; + + template struct tuple_iterate { + static void iterate(Tuple&, row_iterator::value_type&) {} + }; + } + + template + row_iterator::value_type &row_iterator::value_type::operator >>(std::tuple& values) { + assert(!next_index); + detail::tuple_iterate>::iterate(values, *this); + next_index = sizeof...(Types) + 1; + return *this; + } + + inline row_iterator database_binder::begin() { + return row_iterator(*this); + } + + inline row_iterator database_binder::end() { + return row_iterator(); + } + + namespace detail { + template + void _extract_single_value(database_binder &binder, Callback call_back) { + auto iter = binder.begin(); + if(iter == binder.end()) + throw errors::no_rows("no rows to extract: exactly 1 row expected", binder.sql(), SQLITE_DONE); - this->_extract([&func, this]() { - binder::run(*this, func); - }); + call_back(*iter); + + if(++iter != binder.end()) + throw errors::more_rows("not all rows extracted", binder.sql(), SQLITE_ROW); } - }; + } + inline void database_binder::execute() { + for(auto &&row : *this) + (void)row; + } + namespace detail { + template using void_t = void; + template + struct sqlite_direct_result : std::false_type {}; + template + struct sqlite_direct_result< + T, + void_t().operator>>(std::declval()))> + > : std::true_type {}; + } + template + inline typename std::enable_if::value>::type operator>>(database_binder &binder, Result&& value) { + detail::_extract_single_value(binder, [&value] (row_iterator::value_type &row) { + row >> std::forward(value); + }); + } + + template + inline typename std::enable_if::value>::type operator>>(database_binder &db_binder, Function&& func) { + using traits = utility::function_traits; + + for(auto &&row : db_binder) { + binder::run(row, func); + } + } + + template + inline decltype(auto) operator>>(database_binder &&binder, Result&& value) { + return binder >> std::forward(value); + } namespace sql_function_binder { template< @@ -516,14 +597,13 @@ namespace sqlite { std::size_t Boundary = Count > static typename std::enable_if<(sizeof...(Values) < Boundary), void>::type run( - database_binder& db, - Function&& function, - Values&&... values + row_iterator::value_type& row, + Function&& function, + Values&&... values ) { - typename std::remove_cv>::type>::type value{}; - get_col_from_db(db, sizeof...(Values), value); - - run(db, function, std::forward(values)..., std::move(value)); + typename std::decay>::type value; + row >> value; + run(row, function, std::forward(values)..., std::move(value)); } template< @@ -532,9 +612,9 @@ namespace sqlite { std::size_t Boundary = Count > static typename std::enable_if<(sizeof...(Values) == Boundary), void>::type run( - database_binder&, - Function&& function, - Values&&... values + row_iterator::value_type&, + Function&& function, + Values&&... values ) { function(std::move(values)...); } @@ -913,7 +993,7 @@ namespace sqlite { void inline operator++(database_binder& db, int) { db.execute(); } // Convert the rValue binder to a reference and call first op<<, its needed for the call that creates the binder (be carefull of recursion here!) - template database_binder&& operator << (database_binder&& db, const T& val) { db << val; return std::move(db); } + template database_binder operator << (database_binder&& db, const T& val) { db << val; return std::move(db); } namespace sql_function_binder { template diff --git a/hdr/sqlite_modern_cpp/utility/function_traits.h b/hdr/sqlite_modern_cpp/utility/function_traits.h index cd8fab09..f629aa09 100644 --- a/hdr/sqlite_modern_cpp/utility/function_traits.h +++ b/hdr/sqlite_modern_cpp/utility/function_traits.h @@ -42,10 +42,11 @@ namespace sqlite { > { typedef ReturnType result_type; + using argument_tuple = std::tuple; template using argument = typename std::tuple_element< Index, - std::tuple + argument_tuple >::type; static const std::size_t arity = sizeof...(Arguments); diff --git a/tests/readme_example.cc b/tests/readme_example.cc index e903a85f..00523341 100644 --- a/tests/readme_example.cc +++ b/tests/readme_example.cc @@ -45,10 +45,25 @@ int main() { db << "select age,name,weight from user where age > ? ;" << 21 >> [&](int _age, string _name, double _weight) { - if(_age != age || _name != name) + if(_age != age || _name != name) exit(EXIT_FAILURE); cout << _age << ' ' << _name << ' ' << _weight << endl; }; + for(auto &&row : db << "select age,name,weight from user where age > ? ;" << 21) { + int _age; + string _name; + double _weight; + row >> _age >> _name >> _weight; + if(_age != age || _name != name) + exit(EXIT_FAILURE); + cout << _age << ' ' << _name << ' ' << _weight << endl; + } + + for(std::tuple row : db << "select age,name,weight from user where age > ? ;" << 21) { + if(std::get(row) != age || std::get(row) != name) + exit(EXIT_FAILURE); + cout << std::get(row) << ' ' << std::get(row) << ' ' << std::get(row) << endl; + } // selects the count(*) from user table // note that you can extract a single culumn single row result only to : int,long,long,float,double,string,u16string