Skip to content

Commit

Permalink
Merge pull request #32 from aminroosta/blob
Browse files Browse the repository at this point in the history
Blob support with `std::vector<T>`
  • Loading branch information
aminroosta committed Feb 11, 2016
2 parents 471f8af + e3c9fdf commit ee1654e
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 17 deletions.
48 changes: 33 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,22 +93,22 @@ 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!

// 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
ps.used(true); // or false if you want it to execute even if it was used

// Usage Example:

Expand All @@ -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,decltype(&sqlite3_backup_finish)>(
sqlite3_backup_init(backup.connection().get(), "main", con.get(), "main"),
sqlite3_backup_finish
Expand Down Expand Up @@ -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
Expand All @@ -176,6 +176,24 @@ You can use transactions with `begin;`, `commit;` and `rollback;` commands.

```

Blob
=====
Use `std::vector<T>` 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<int> { 1, 2, 3, 4};
db << "INSERT INTO person VALUES (?, ?)" << "sara" << vector<double> { 1.0, 2.0, 3.0, 4.0};

vector<int> numbers_bob;
db << "SELECT numbers from person where name = ?;" << "bob" >> numbers_bob;

db << "SELECT numbers from person where name = ?;" << "sara" >> [](vector<double> 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.
Expand All @@ -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 <sqlite_modern_cpp.h>
struct User {
long long _id;
boost::optional<int> age;
boost::optional<string> name;
boost::optional<real> 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<int> age,
boost::optional<int> age,
boost::optional<string> name
boost::optional<real> 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"))
Expand Down
36 changes: 34 additions & 2 deletions hdr/sqlite_modern_cpp.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <ctime>
#include <tuple>
#include <memory>
#include <vector>

#ifdef _MODERN_SQLITE_BOOST_OPTIONAL_SUPPORT
#include <boost/optional.hpp>
Expand Down Expand Up @@ -188,17 +189,27 @@ namespace sqlite {
}

template <typename Type>
using is_sqlite_value = std::integral_constant<
struct is_sqlite_value : public std::integral_constant<
bool,
std::is_floating_point<Type>::value
|| std::is_integral<Type>::value
|| std::is_same<std::string, Type>::value
|| std::is_same<std::u16string, Type>::value
|| std::is_same<sqlite_int64, Type>::value
>;
> { };
template <typename Type>
struct is_sqlite_value< std::vector<Type> > : public std::integral_constant<
bool,
std::is_floating_point<Type>::value
|| std::is_integral<Type>::value
|| std::is_same<sqlite_int64, Type>::value
> { };


template<typename T> friend database_binder::chain_type& operator <<(database_binder::chain_type& db, const T& val);
template<typename T> friend void get_col_from_db(database_binder& db, int inx, T& val);
template<typename T> friend database_binder::chain_type& operator <<(database_binder::chain_type& db, const std::vector<T>& val);
template<typename T> friend void get_col_from_db(database_binder& db, int inx, std::vector<T>& val);
template<typename T> friend T operator++(database_binder& db, int);


Expand Down Expand Up @@ -414,6 +425,27 @@ namespace sqlite {
}
}

// vector<T>
template<typename T> inline database_binder::chain_type& operator<<(database_binder::chain_type& db, const std::vector<T>& vec) {
void const* buf = reinterpret_cast<void const *>(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<typename T> inline void get_col_from_db(database_binder& db, int inx, std::vector<T>& 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<T const *>(sqlite3_column_blob(db._stmt.get(), inx));
vec = std::vector<T>(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) {
Expand Down
69 changes: 69 additions & 0 deletions tests/blob_example.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <vector>
#include <string>
#include <sqlite_modern_cpp.h>
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<int> { 1, 2, 3, 4};
db << "INSERT INTO person VALUES (?, ?)" << "jack" << vector<char> { '1', '2', '3', '4'};
db << "INSERT INTO person VALUES (?, ?)" << "sara" << vector<double> { 1.0, 2.0, 3.0, 4.0};

vector<int> 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<char> 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<double> 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 {
//db << "SELECT numbers from person where name = ?;" << "sara" >> [](vector<double> numbers_sara){
//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);
}

0 comments on commit ee1654e

Please sign in to comment.