Skip to content

Blob support with std::vector<T> #32

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 11, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -410,6 +421,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);
}