Skip to content
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

std::variant support #95

Merged
merged 5 commits into from
Feb 22, 2017
Merged
Show file tree
Hide file tree
Changes from 3 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
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -336,6 +336,41 @@ You can enable boost support by defining _MODERN_SQLITE_BOOST_OPTIONAL_SUPPORT b
}
```

Variant type support (C++17)
----
If your columns may have flexible types, you can use C++17's `std::variant` to extract the value.

```c++
db << "CREATE TABLE tbl (id integer, data);";
db << "INSERT INTO tbl VALUES (?, ?, ?);" << 1 << vector<int> { 1, 2, 3};
unique_ptr<string> ptr_null; // you can even bind empty unique_ptr<T>
db << "INSERT INTO tbl VALUES (?, ?, ?);" << 2 << 2.5;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zauguin Here It should be two question marks, right? INSERT INTO tbl values (?, ?)


db << "select data from tbl where id = 1"
>> [](std::variant<vector<int>, double> data) {
if(data.index() != 1) {
cerr << "ERROR: we expected a blob" << std::endl;
}

for(auto i : get<vector<int>>(data)) cout << i << ","; cout << endl;
};

db << "select age,name,img from tbl where id = 2"
>> [](std::variant<vector<int>, double> data) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zauguin age, name, img are not defined in the table and the lambda is only taking one argument.

if(data.index() != 2) {
cerr << "ERROR: we expected a real number" << std::endl;
}

cout << get<double>(data) << endl;
};
```

If you read a specific type and this type does not match the actual type in the SQlite database, yor data will be converted.
This does not happen if you use a `variant`.
If the `variant` does an alternative of the same value type, an `mismatch` exception will be thrown.
The value types are NULL, integer, real number, text and BLOB.
To support all possible values, you can use `variant<nullptr_t, sqlite_int64, double, string, vector<char>`.

Errors
----

Expand Down
75 changes: 60 additions & 15 deletions hdr/sqlite_modern_cpp.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@
#endif
#endif

#ifdef __has_include
#if __cplusplus > 201402 && __has_include(<variant>)
#define MODERN_SQLITE_STD_VARIANT_SUPPORT
#endif
#endif

#ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT
#include <optional>
#endif
Expand All @@ -36,10 +42,10 @@ namespace sqlite {
sqlite_exception(const char* msg, std::string sql, int code = -1): runtime_error(msg), code(code), sql(sql) {}
sqlite_exception(int code, std::string sql): runtime_error(sqlite3_errstr(code)), code(code), sql(sql) {}
int get_code() {return code;}
std::string get_sql() {return sql;}
std::string get_sql() {return sql;}
private:
int code;
std::string sql;
std::string sql;
};

namespace exceptions {
Expand Down Expand Up @@ -109,7 +115,13 @@ namespace sqlite {
else throw sqlite_exception(error_code, sql);
}
}
}

#ifdef MODERN_SQLITE_STD_VARIANT_SUPPORT
#include "sqlite_modern_cpp/utility/variant.h"
#endif

namespace sqlite {
class database;
class database_binder;

Expand Down Expand Up @@ -159,19 +171,19 @@ namespace sqlite {
used(true); /* prevent from executing again when goes out of scope */
}

std::string sql() {
std::string sql() {
#if SQLITE_VERSION_NUMBER >= 3014000
auto sqlite_deleter = [](void *ptr) {sqlite3_free(ptr);};
std::unique_ptr<char, decltype(sqlite_deleter)> str(sqlite3_expanded_sql(_stmt.get()), sqlite_deleter);
return str ? str.get() : original_sql();
auto sqlite_deleter = [](void *ptr) {sqlite3_free(ptr);};
std::unique_ptr<char, decltype(sqlite_deleter)> str(sqlite3_expanded_sql(_stmt.get()), sqlite_deleter);
return str ? str.get() : original_sql();
#else
return original_sql();
return original_sql();
#endif
}
}

std::string original_sql() {
return sqlite3_sql(_stmt.get());
}
std::string original_sql() {
return sqlite3_sql(_stmt.get());
}

void used(bool state) { execution_started = state; }
bool used() const { return execution_started; }
Expand Down Expand Up @@ -253,6 +265,13 @@ namespace sqlite {
|| std::is_integral<Type>::value
|| std::is_same<sqlite_int64, Type>::value
> { };
#ifdef MODERN_SQLITE_STD_VARIANT_SUPPORT
template <typename ...Args>
struct is_sqlite_value< std::variant<Args...> > : public std::integral_constant<
bool,
true
> { };
#endif


template<typename T> friend database_binder& operator <<(database_binder& db, const T& val);
Expand All @@ -264,6 +283,10 @@ namespace sqlite {
friend database_binder& operator <<(database_binder& db, std::nullptr_t);
template<typename T> friend database_binder& operator <<(database_binder& db, const std::unique_ptr<T>& val);
template<typename T> friend void get_col_from_db(database_binder& db, int inx, std::unique_ptr<T>& val);
#ifdef MODERN_SQLITE_STD_VARIANT_SUPPORT
template<typename ...Args> friend database_binder& operator <<(database_binder& db, const std::variant<Args...>& val);
template<typename ...Args> friend void get_col_from_db(database_binder& db, int inx, std::variant<Args...>& val);
#endif
template<typename T> friend T operator++(database_binder& db, int);
// Overload instead of specializing function templates (http://www.gotw.ca/publications/mill17.htm)
friend database_binder& operator<<(database_binder& db, const int& val);
Expand Down Expand Up @@ -896,6 +919,28 @@ namespace sqlite {
}
#endif

#ifdef MODERN_SQLITE_STD_VARIANT_SUPPORT
template <typename ...Args> inline database_binder& operator <<(database_binder& db, const std::variant<Args...>& val) {
std::visit([&](auto &&opt) {db << std::forward<decltype(opt)>(opt);}, val);
return db;
}
template <typename ...Args> inline void store_result_in_db(sqlite3_context* db, const std::variant<Args...>& val) {
std::visit([&](auto &&opt) {store_result_in_db(db, std::forward<decltype(opt)>(opt));}, val);
}
template <typename ...Args> inline void get_col_from_db(database_binder& db, int inx, std::variant<Args...>& val) {
utility::variant_select<Args...>(sqlite3_column_type(db._stmt.get(), inx))([&](auto v) {
get_col_from_db(db, inx, v);
val = std::move(v);
});
}
template <typename ...Args> inline void get_val_from_db(sqlite3_value *value, std::variant<Args...>& val) {
utility::variant_select<Args...>(sqlite3_value_type(value))([&](auto v) {
get_val_from_db(value, v);
val = std::move(v);
});
}
#endif

// Some ppl are lazy so we have a operator for proper prep. statemant handling.
void inline operator++(database_binder& db, int) { db.execute(); db.reset(); }

Expand Down Expand Up @@ -923,7 +968,7 @@ namespace sqlite {
if(!ctxt) return;
try {
if(!ctxt->constructed) new(ctxt) AggregateCtxt<ContextType>();
step<Count, Functions>(db, count, vals, ctxt->obj);
step<Count, Functions>(db, count, vals, ctxt->obj);
return;
} catch(sqlite_exception &e) {
sqlite3_result_error_code(db, e.get_code());
Expand All @@ -934,7 +979,7 @@ namespace sqlite {
sqlite3_result_error(db, "Unknown error", -1);
}
if(ctxt && ctxt->constructed)
ctxt->~AggregateCtxt();
ctxt->~AggregateCtxt();
}

template<
Expand Down Expand Up @@ -994,7 +1039,7 @@ namespace sqlite {
sqlite3_result_error(db, "Unknown error", -1);
}
if(ctxt && ctxt->constructed)
ctxt->~AggregateCtxt();
ctxt->~AggregateCtxt();
}

template<
Expand All @@ -1020,7 +1065,7 @@ namespace sqlite {

template<
std::size_t Count,
typename Function,
typename Function,
typename... Values
>
inline typename std::enable_if<(sizeof...(Values) == Count), void>::type scalar(
Expand Down
Loading