Skip to content

std::variant support #95

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 5 commits into from
Feb 22, 2017
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
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,40 @@ If you do not have C++17 support, you can use boost optional instead by defining

**Note: boost support is deprecated and will be removed in future versions.**

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};
db << "INSERT INTO tbl VALUES (?, ?);" << 2 << 2.5;

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 data from tbl where id = 2"
>> [](std::variant<vector<int>, double> data) {
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
73 changes: 59 additions & 14 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 Down
200 changes: 200 additions & 0 deletions hdr/sqlite_modern_cpp/utility/variant.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
#pragma once

#include <sqlite3.h>
#include <optional>
#include <variant>

namespace sqlite::utility {
template<typename ...Options>
struct VariantFirstNullable {
using type = void;
};
template<typename T, typename ...Options>
struct VariantFirstNullable<T, Options...> {
using type = typename VariantFirstNullable<Options...>::type;
};
#ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT
template<typename T, typename ...Options>
struct VariantFirstNullable<std::optional<T>, Options...> {
using type = std::optional<T>;
};
#endif
template<typename T, typename ...Options>
struct VariantFirstNullable<std::unique_ptr<T>, Options...> {
using type = std::unique_ptr<T>;
};
template<typename ...Options>
struct VariantFirstNullable<std::nullptr_t, Options...> {
using type = std::nullptr_t;
};
template<typename Callback, typename ...Options>
inline void variant_select_null(Callback&&callback) {
if constexpr(std::is_same_v<typename VariantFirstNullable<Options...>::type, void>) {
throw exceptions::mismatch("NULL is unsupported by this variant.", "", SQLITE_MISMATCH);
} else {
std::forward<Callback>(callback)(typename VariantFirstNullable<Options...>::type());
}
}

template<typename ...Options>
struct VariantFirstIntegerable {
using type = void;
};
template<typename T, typename ...Options>
struct VariantFirstIntegerable<T, Options...> {
using type = typename VariantFirstIntegerable<Options...>::type;
};
#ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT
template<typename T, typename ...Options>
struct VariantFirstIntegerable<std::optional<T>, Options...> {
using type = std::conditional_t<std::is_same_v<typename VariantFirstIntegerable<T, Options...>::type, T>, std::optional<T>, typename VariantFirstIntegerable<Options...>::type>;
};
#endif
template<typename T, typename ...Options>
struct VariantFirstIntegerable<std::enable_if_t<std::is_same_v<typename VariantFirstIntegerable<T, Options...>::type, T>>, std::unique_ptr<T>, Options...> {
using type = std::conditional_t<std::is_same_v<typename VariantFirstIntegerable<T, Options...>::type, T>, std::unique_ptr<T>, typename VariantFirstIntegerable<Options...>::type>;
};
template<typename ...Options>
struct VariantFirstIntegerable<int, Options...> {
using type = int;
};
template<typename ...Options>
struct VariantFirstIntegerable<sqlite_int64, Options...> {
using type = sqlite_int64;
};
template<typename Callback, typename ...Options>
inline auto variant_select_integer(Callback&&callback) {
if constexpr(std::is_same_v<typename VariantFirstIntegerable<Options...>::type, void>) {
throw exceptions::mismatch("Integer is unsupported by this variant.", "", SQLITE_MISMATCH);
} else {
std::forward<Callback>(callback)(typename VariantFirstIntegerable<Options...>::type());
}
}

template<typename ...Options>
struct VariantFirstFloatable {
using type = void;
};
template<typename T, typename ...Options>
struct VariantFirstFloatable<T, Options...> {
using type = typename VariantFirstFloatable<Options...>::type;
};
#ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT
template<typename T, typename ...Options>
struct VariantFirstFloatable<std::optional<T>, Options...> {
using type = std::conditional_t<std::is_same_v<typename VariantFirstFloatable<T, Options...>::type, T>, std::optional<T>, typename VariantFirstFloatable<Options...>::type>;
};
#endif
template<typename T, typename ...Options>
struct VariantFirstFloatable<std::unique_ptr<T>, Options...> {
using type = std::conditional_t<std::is_same_v<typename VariantFirstFloatable<T, Options...>::type, T>, std::unique_ptr<T>, typename VariantFirstFloatable<Options...>::type>;
};
template<typename ...Options>
struct VariantFirstFloatable<float, Options...> {
using type = float;
};
template<typename ...Options>
struct VariantFirstFloatable<double, Options...> {
using type = double;
};
template<typename Callback, typename ...Options>
inline auto variant_select_float(Callback&&callback) {
if constexpr(std::is_same_v<typename VariantFirstFloatable<Options...>::type, void>) {
throw exceptions::mismatch("Real is unsupported by this variant.", "", SQLITE_MISMATCH);
} else {
std::forward<Callback>(callback)(typename VariantFirstFloatable<Options...>::type());
}
}

template<typename ...Options>
struct VariantFirstTextable {
using type = void;
};
template<typename T, typename ...Options>
struct VariantFirstTextable<T, Options...> {
using type = typename VariantFirstTextable<void, Options...>::type;
};
#ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT
template<typename T, typename ...Options>
struct VariantFirstTextable<std::optional<T>, Options...> {
using type = std::conditional_t<std::is_same_v<typename VariantFirstTextable<T, Options...>::type, T>, std::optional<T>, typename VariantFirstTextable<Options...>::type>;
};
#endif
template<typename T, typename ...Options>
struct VariantFirstTextable<std::unique_ptr<T>, Options...> {
using type = std::conditional_t<std::is_same_v<typename VariantFirstTextable<T, Options...>::type, T>, std::unique_ptr<T>, typename VariantFirstTextable<Options...>::type>;
};
template<typename ...Options>
struct VariantFirstTextable<std::string, Options...> {
using type = std::string;
};
template<typename ...Options>
struct VariantFirstTextable<std::u16string, Options...> {
using type = std::u16string;
};
template<typename Callback, typename ...Options>
inline void variant_select_text(Callback&&callback) {
if constexpr(std::is_same_v<typename VariantFirstTextable<Options...>::type, void>) {
throw exceptions::mismatch("Text is unsupported by this variant.", "", SQLITE_MISMATCH);
} else {
std::forward<Callback>(callback)(typename VariantFirstTextable<Options...>::type());
}
}

template<typename ...Options>
struct VariantFirstBlobable {
using type = void;
};
template<typename T, typename ...Options>
struct VariantFirstBlobable<T, Options...> {
using type = typename VariantFirstBlobable<Options...>::type;
};
#ifdef MODERN_SQLITE_STD_OPTIONAL_SUPPORT
template<typename T, typename ...Options>
struct VariantFirstBlobable<std::optional<T>, Options...> {
using type = std::conditional_t<std::is_same_v<typename VariantFirstBlobable<T, Options...>::type, T>, std::optional<T>, typename VariantFirstBlobable<Options...>::type>;
};
#endif
template<typename T, typename ...Options>
struct VariantFirstBlobable<std::unique_ptr<T>, Options...> {
using type = std::conditional_t<std::is_same_v<typename VariantFirstBlobable<T, Options...>::type, T>, std::unique_ptr<T>, typename VariantFirstBlobable<Options...>::type>;
};
template<typename T, typename ...Options>
struct VariantFirstBlobable<std::enable_if_t<std::is_pod_v<T>>, std::vector<T>, Options...> {
using type = std::vector<T>;
};
template<typename Callback, typename ...Options>
inline auto variant_select_blob(Callback&&callback) {
if constexpr(std::is_same_v<typename VariantFirstBlobable<Options...>::type, void>) {
throw exceptions::mismatch("Blob is unsupported by this variant.", "", SQLITE_MISMATCH);
} else {
std::forward<Callback>(callback)(typename VariantFirstBlobable<Options...>::type());
}
}

template<typename ...Options>
inline auto variant_select(int type) {
return [type](auto &&callback) {
using Callback = decltype(callback);
switch(type) {
case SQLITE_NULL:
variant_select_null<Callback, Options...>(std::forward<Callback>(callback));
break;
case SQLITE_INTEGER:
variant_select_integer<Callback, Options...>(std::forward<Callback>(callback));
break;
case SQLITE_FLOAT:
variant_select_float<Callback, Options...>(std::forward<Callback>(callback));
break;
case SQLITE_TEXT:
variant_select_text<Callback, Options...>(std::forward<Callback>(callback));
break;
case SQLITE_BLOB:
variant_select_blob<Callback, Options...>(std::forward<Callback>(callback));
break;
default:;
/* assert(false); */
}
};
}
}
Loading