From f3ef606be551056a56d44c05522ee8739692b947 Mon Sep 17 00:00:00 2001 From: amin roosta Date: Sun, 24 Jan 2016 20:04:29 +0330 Subject: [PATCH 1/2] blob support with std::vector --- hdr/sqlite_modern_cpp.h | 36 +++++++++++++++++++++-- tests/blob_example.cc | 65 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 tests/blob_example.cc diff --git a/hdr/sqlite_modern_cpp.h b/hdr/sqlite_modern_cpp.h index 5e300c1e..f9624fa6 100644 --- a/hdr/sqlite_modern_cpp.h +++ b/hdr/sqlite_modern_cpp.h @@ -6,6 +6,7 @@ #include #include #include +#include #ifdef _MODERN_SQLITE_BOOST_OPTIONAL_SUPPORT #include @@ -188,17 +189,27 @@ namespace sqlite { } template - using is_sqlite_value = std::integral_constant< + 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 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 database_binder::chain_type& operator <<(database_binder::chain_type& db, const std::vector& val); + template friend void get_col_from_db(database_binder& db, int inx, std::vector& val); template friend T operator++(database_binder& db, int); @@ -410,6 +421,27 @@ namespace sqlite { } } + // vector + template inline database_binder::chain_type& operator<<(database_binder::chain_type& db, const std::vector& vec) { + void const* buf = reinterpret_cast(vec.data()); + int bytes = vec.size() * sizeof(T); + int hresult; + if((hresult = sqlite3_bind_blob(db->_stmt.get(), db->_inx, buf, bytes, SQLITE_TRANSIENT)) != SQLITE_OK) { + exceptions::throw_sqlite_error(hresult); + } + ++db->_inx; + return db; + } + template inline void get_col_from_db(database_binder& db, int inx, std::vector& vec) { + if(sqlite3_column_type(db._stmt.get(), inx) == SQLITE_NULL) { + vec.clear(); + } else { + int bytes = sqlite3_column_bytes(db._stmt.get(), inx); + T const* buf = reinterpret_cast(sqlite3_column_blob(db._stmt.get(), inx)); + vec = std::vector(buf, buf + bytes/sizeof(T)); + } + } + // 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) { diff --git a/tests/blob_example.cc b/tests/blob_example.cc new file mode 100644 index 00000000..55442274 --- /dev/null +++ b/tests/blob_example.cc @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include +#include +using namespace sqlite; +using namespace std; + +int main() +{ + try + { + database db(":memory:"); + + db << "CREATE TABLE person (name TEXT, numbers BLOB);"; + db << "INSERT INTO person VALUES (?, ?)" << "bob" << vector { 1, 2, 3, 4}; + db << "INSERT INTO person VALUES (?, ?)" << "jack" << vector { '1', '2', '3', '4'}; + db << "INSERT INTO person VALUES (?, ?)" << "sara" << vector { 1.0, 2.0, 3.0, 4.0}; + + vector numbers_bob; + db << "SELECT numbers from person where name = ?;" << "bob" >> numbers_bob; + + if(numbers_bob.size() != 4 || numbers_bob[0] != 1 + || numbers_bob[1] != 2 || numbers_bob[2] != 3 || numbers_bob[3] != 4 ) { + cout << "Bad result on line " << __LINE__ << endl; + exit(EXIT_FAILURE); + } + //else { for(auto e : numbers_bob) cout << e << ' '; cout << endl; } + + vector numbers_jack; + db << "SELECT numbers from person where name = ?;" << "jack" >> numbers_jack; + + if(numbers_jack.size() != 4 || numbers_jack[0] != '1' + || numbers_jack[1] != '2' || numbers_jack[2] != '3' || numbers_jack[3] != '4' ) { + cout << "Bad result on line " << __LINE__ << endl; + exit(EXIT_FAILURE); + } + //else { for(auto e : numbers_jack) cout << e << ' '; cout << endl; } + + vector numbers_sara; + db << "SELECT numbers from person where name = ?;" << "sara" >> numbers_sara; + + if(numbers_sara.size() != 4 || numbers_sara[0] != 1 + || numbers_sara[1] != 2 || numbers_sara[2] != 3 || numbers_sara[3] != 4 ) { + cout << "Bad result on line " << __LINE__ << endl; + exit(EXIT_FAILURE); + } + //else { for(auto e : numbers_sara) cout << e << ' '; cout << endl; } + + } + 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); +} From e3c9fdf888ee47f045d167d1119a2e4fd8393034 Mon Sep 17 00:00:00 2001 From: amin roosta Date: Sun, 24 Jan 2016 20:11:50 +0330 Subject: [PATCH 2/2] update readme --- README.md | 48 +++++++++++++++++++++++++++++-------------- tests/blob_example.cc | 6 +++++- 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index d7aa1a09..ed3ed2cc 100644 --- a/README.md +++ b/README.md @@ -93,14 +93,14 @@ It is possible to retain and reuse statments this will keep the query plan and i int tmp = 8; ps << tmp; - // now you can execute it with `operator>>` or `execute()`. + // 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! @@ -108,7 +108,7 @@ It is possible to retain and reuse statments this will keep the query plan and i 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 + ps.used(true); // or false if you want it to execute even if it was used // Usage Example: @@ -131,9 +131,9 @@ Take this example on how to deal with a database backup using SQLITEs own functi 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 = + auto state = std::unique_ptr( sqlite3_backup_init(backup.connection().get(), "main", con.get(), "main"), sqlite3_backup_finish @@ -166,7 +166,7 @@ You can use transactions with `begin;`, `commit;` and `rollback;` commands. << u"jack" << 68.5; db << "commit;"; // commit all the changes. - + db << "begin;"; // begin another transaction .... db << "insert into user (age,name,weight) values (?,?,?);" // utf16 string << 19 @@ -176,6 +176,24 @@ You can use transactions with `begin;`, `commit;` and `rollback;` commands. ``` +Blob +===== +Use `std::vector` to store and retrieve blob data. +`T` could be `char,short,int,long,long long, float or double`. + +```c++ + db << "CREATE TABLE person (name TEXT, numbers BLOB);"; + db << "INSERT INTO person VALUES (?, ?)" << "bob" << vector { 1, 2, 3, 4}; + db << "INSERT INTO person VALUES (?, ?)" << "sara" << vector { 1.0, 2.0, 3.0, 4.0}; + + vector numbers_bob; + db << "SELECT numbers from person where name = ?;" << "bob" >> numbers_bob; + + db << "SELECT numbers from person where name = ?;" << "sara" >> [](vector numbers_sara){ + for(auto e : numbers_sara) cout << e << ' '; cout << endl; + }; +``` + Dealing with NULL values ===== If you have databases where some rows may be null, you can use boost::optional to retain the NULL value between C++ variables and the database. Note that you must enable the boost support by defining _MODERN_SQLITE_BOOST_OPTIONAL_SUPPORT befor importing the header. @@ -184,45 +202,45 @@ If you have databases where some rows may be null, you can use boost::optional t #define _MODERN_SQLITE_BOOST_OPTIONAL_SUPPORT #include - + struct User { long long _id; boost::optional age; boost::optional name; boost::optional weight; }; - + { User user; user.name = "bob"; - + // Same database as above database db("dbfile.db"); - + // Here, age and weight will be inserted as NULL in the database. db << "insert into user (age,name,weight) values (?,?,?);" << user.age << user.name << user.weight; - + user._id = db.last_insert_rowid(); } - + { // Here, the User instance will retain the NULL value(s) from the database. db << "select _id,age,name,weight from user where age > ? ;" << 18 >> [&](long long id, - boost::optional age, + boost::optional age, boost::optional name boost::optional weight) { - + User user; user._id = id; user.age = age; user.name = move(name); user.weight = weight; - + cout << "id=" << user._id << " age = " << (user.age ? to_string(*user.age) ? string("NULL")) << " name = " << (user.name ? *user.name : string("NULL")) diff --git a/tests/blob_example.cc b/tests/blob_example.cc index 55442274..c89f5d6a 100644 --- a/tests/blob_example.cc +++ b/tests/blob_example.cc @@ -46,7 +46,11 @@ int main() cout << "Bad result on line " << __LINE__ << endl; exit(EXIT_FAILURE); } - //else { for(auto e : numbers_sara) cout << e << ' '; cout << endl; } + //else { + //db << "SELECT numbers from person where name = ?;" << "sara" >> [](vector numbers_sara){ + //for(auto e : numbers_sara) cout << e << ' '; cout << endl; + //}; + //} } catch(sqlite_exception e)