Last active
October 6, 2019 12:53
-
-
Save zauguin/7e327d9b5edf5a5002382c933308913c to your computer and use it in GitHub Desktop.
A draft for a virtual table module in sqlite_modern_cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <optional> | |
#include <iostream> | |
#include <string> | |
#include <sqlite_modern_cpp.h> | |
enum class VtabType { | |
regular, | |
eponymous, | |
eponymous_only, | |
}; | |
template <class, class = std::void_t<>> | |
struct has_context_t : std::false_type { }; | |
template <class T> | |
struct has_context_t<T, std::void_t<typename T::Context>> : std::true_type { }; | |
template<class T> constexpr bool has_context = has_context_t<T>::value; | |
namespace { | |
using namespace sqlite; | |
template< | |
std::size_t Count, | |
typename Cursor, | |
typename... Values | |
> | |
inline typename std::enable_if<(sizeof...(Values) < Count), void>::type filter( | |
Cursor& cursor, | |
int count, | |
sqlite3_value** vals, | |
Values&&... values | |
); | |
template< | |
std::size_t Count, | |
typename Cursor, | |
typename... Values | |
> | |
inline typename std::enable_if<(sizeof...(Values) == Count), void>::type filter( | |
Cursor& cursor, | |
int count, | |
sqlite3_value**, | |
Values&&... values | |
); | |
template< | |
std::size_t Count, | |
typename Cursor, | |
typename... Values | |
> | |
inline typename std::enable_if<(sizeof...(Values) < Count), void>::type filter( | |
Cursor& cursor, | |
int count, | |
sqlite3_value** vals, | |
Values&&... values | |
) { | |
using arg_type = typename std::remove_cv< | |
typename std::remove_reference< | |
typename utility::function_traits<decltype(&Cursor::Filter)>::template argument<sizeof...(Values) + 1> | |
>::type | |
>::type; | |
filter<Count>( | |
cursor, | |
count, | |
vals, | |
std::forward<Values>(values)..., | |
count <= sizeof...(Values) - 2 ? arg_type{} : get_val_from_db(vals[sizeof...(Values) - 2], result_type<arg_type>())); | |
} | |
template< | |
std::size_t Count, | |
typename Cursor, | |
typename... Values | |
> | |
inline typename std::enable_if<(sizeof...(Values) == Count), void>::type filter( | |
Cursor& cursor, | |
int count, | |
sqlite3_value**, | |
Values&&... values | |
) { | |
cursor.Filter(count, std::forward<Values>(values)...); | |
} | |
} | |
template<class T, VtabType Type = VtabType::regular> | |
class VtabBase : protected sqlite3_vtab { | |
protected: | |
static void set_error(std::string_view str, char **errmsg) { | |
if (*errmsg) { | |
sqlite3_free(*errmsg); | |
} | |
*errmsg = static_cast<char*>(sqlite3_malloc(str.size() + 1)); | |
std::copy(str.begin(), str.end(), *errmsg); | |
(*errmsg)[str.size()] = 0; | |
} | |
static void set_error(std::string_view str, sqlite3_vtab *vtab) { | |
set_error(str, &vtab->zErrMsg); | |
} | |
struct CreateTag { explicit CreateTag() = default; }; | |
static const sqlite3_module module; | |
void Destroy() {} | |
template<class U = T> | |
static std::enable_if_t<!has_context<U> && std::is_same_v<T, U>> create(sqlite::database &db, const char *name) { | |
auto error = sqlite3_create_module(db.connection().get(), name, &module, &db); | |
if (error != SQLITE_OK) errors::throw_sqlite_error(error); | |
} | |
template<class U = T> | |
static std::enable_if_t<has_context<U> && std::is_same_v<T, U>> create(sqlite::database &db, const char *name, typename U::Context context) { | |
auto *ptr = new std::pair<sqlite::database*, typename T::Context>(&db, std::move(context)); | |
auto error = sqlite3_create_module_v2(db.connection().get(), name, &module, ptr, [](void *p) { | |
delete static_cast<decltype(ptr)>(p); | |
}); | |
if (error != SQLITE_OK) errors::throw_sqlite_error(error); | |
} | |
public: | |
}; | |
template<class T, VtabType Type> | |
const sqlite3_module VtabBase<T, Type>::module = [] { | |
sqlite3_module module; | |
module.iVersion = 3; | |
module.xConnect = [](sqlite3 *db, void *ptr, int argc, const char *const*argv, sqlite3_vtab **ppvtab, char **errmsg) { | |
try { | |
if constexpr(has_context<T>) { | |
auto context = static_cast<std::pair<sqlite::database *, typename T::Context> *>(ptr); | |
auto tvtab = new T(*context->first, context->second, argc, argv); | |
sqlite3_declare_vtab(db, tvtab->CreateTable().c_str()); | |
*ppvtab = tvtab; | |
} else { | |
auto tvtab = new T(*static_cast<sqlite::database*>(ptr), argc, argv); | |
sqlite3_declare_vtab(db, tvtab->CreateTable().c_str()); | |
*ppvtab = tvtab; | |
} | |
return SQLITE_OK; | |
} catch(const sqlite_exception &e) { | |
set_error(e.what(), errmsg); | |
return e.get_code(); | |
} catch(const std::exception &e) { | |
set_error(e.what(), errmsg); | |
return SQLITE_ERROR; | |
} catch(...) { | |
set_error("Unknown error", errmsg); | |
return SQLITE_ERROR; | |
} | |
}; | |
if constexpr(Type == VtabType::regular) | |
module.xCreate = [](sqlite3 *db, void *ptr, int argc, const char *const*argv, sqlite3_vtab **ppvtab, char **errmsg) { | |
try { | |
if constexpr(has_context<T>) { | |
auto context = static_cast<std::pair<sqlite::database *, typename T::Context> *>(ptr); | |
auto tvtab = new T(CreateTag{}, *context->first, context->second, argc, argv); | |
sqlite3_declare_vtab(db, tvtab->CreateTable().c_str()); | |
*ppvtab = tvtab; | |
} else { | |
auto tvtab = new T(CreateTag{}, *static_cast<sqlite::database*>(ptr), argc, argv); | |
sqlite3_declare_vtab(db, tvtab->CreateTable().c_str()); | |
*ppvtab = tvtab; | |
} | |
return SQLITE_OK; | |
} catch(const sqlite_exception &e) { | |
set_error(e.what(), errmsg); | |
return e.get_code(); | |
} catch(const std::exception &e) { | |
set_error(e.what(), errmsg); | |
return SQLITE_ERROR; | |
} catch(...) { | |
set_error("Unknown error", errmsg); | |
return SQLITE_ERROR; | |
} | |
}; | |
else if constexpr(Type == VtabType::eponymous) | |
module.xCreate = module.xConnect; | |
else if constexpr(Type == VtabType::eponymous_only) | |
module.xCreate = nullptr; | |
else | |
assert(false); | |
module.xBestIndex = [](sqlite3_vtab *vtab, sqlite3_index_info *info) { | |
try { | |
static_cast<T*>(vtab)->BestIndex(*info); | |
return SQLITE_OK; | |
} catch(const sqlite_exception &e) { | |
set_error(e.what(), vtab); | |
return e.get_code(); | |
} catch(const std::exception &e) { | |
set_error(e.what(), vtab); | |
return SQLITE_ERROR; | |
} catch(...) { | |
set_error("Unknown error", vtab); | |
return SQLITE_ERROR; | |
} | |
}; | |
module.xDisconnect = [](sqlite3_vtab *vtab) { | |
try { | |
delete static_cast<T*>(vtab); | |
return SQLITE_OK; | |
} catch(const sqlite_exception &e) { | |
set_error(e.what(), vtab); | |
return e.get_code(); | |
} catch(const std::exception &e) { | |
set_error(e.what(), vtab); | |
return SQLITE_ERROR; | |
} catch(...) { | |
set_error("Unknown error", vtab); | |
return SQLITE_ERROR; | |
} | |
}; | |
if (&T::Destroy == &VtabBase<T, Type>::Destroy) | |
module.xDestroy = Type == VtabType::eponymous_only ? nullptr : module.xDisconnect; | |
else | |
module.xDestroy = [](sqlite3_vtab *vtab) { | |
try { | |
auto tvtab = static_cast<T*>(vtab); | |
tvtab->Destroy(); | |
delete tvtab; | |
return SQLITE_OK; | |
} catch(const sqlite_exception &e) { | |
set_error(e.what(), vtab); | |
return e.get_code(); | |
} catch(const std::exception &e) { | |
set_error(e.what(), vtab); | |
return SQLITE_ERROR; | |
} catch(...) { | |
set_error("Unknown error", vtab); | |
return SQLITE_ERROR; | |
} | |
}; | |
module.xOpen = [](sqlite3_vtab *vtab, sqlite3_vtab_cursor **cursor) { | |
try { | |
*cursor = new typename T::Cursor(*static_cast<T*>(vtab)); | |
return SQLITE_OK; | |
} catch(const sqlite_exception &e) { | |
set_error(e.what(), vtab); | |
return e.get_code(); | |
} catch(const std::exception &e) { | |
set_error(e.what(), vtab); | |
return SQLITE_ERROR; | |
} catch(...) { | |
set_error("Unknown error", vtab); | |
return SQLITE_ERROR; | |
} | |
}; | |
module.xClose = [](sqlite3_vtab_cursor *cursor) { | |
try { | |
delete static_cast<typename T::Cursor*>(cursor); | |
return SQLITE_OK; | |
} catch(const sqlite_exception &e) { | |
set_error(e.what(), cursor->pVtab); | |
return e.get_code(); | |
} catch(const std::exception &e) { | |
set_error(e.what(), cursor->pVtab); | |
return SQLITE_ERROR; | |
} catch(...) { | |
set_error("Unknown error", cursor->pVtab); | |
return SQLITE_ERROR; | |
} | |
}; | |
module.xEof = [](sqlite3_vtab_cursor *cursor) noexcept -> int { | |
return static_cast<typename T::Cursor*>(cursor)->Eof(); | |
}; | |
module.xFilter = [](sqlite3_vtab_cursor *cursor, int idxNum, const char *idxStr, int argc, sqlite3_value **arg) { | |
try { | |
using traits = sqlite::utility::function_traits<decltype(&T::Cursor::Filter)>; | |
filter<traits::arity - 1>(*static_cast<typename T::Cursor*>(cursor), argc, arg, idxNum, idxStr); | |
return SQLITE_OK; | |
} catch(const sqlite_exception &e) { | |
set_error(e.what(), cursor->pVtab); | |
return e.get_code(); | |
} catch(const std::exception &e) { | |
set_error(e.what(), cursor->pVtab); | |
return SQLITE_ERROR; | |
} catch(...) { | |
set_error("Unknown error", cursor->pVtab); | |
return SQLITE_ERROR; | |
} | |
}; | |
module.xNext = [](sqlite3_vtab_cursor *cursor) { | |
try { | |
static_cast<typename T::Cursor*>(cursor)->Next(); | |
return SQLITE_OK; | |
} catch(const sqlite_exception &e) { | |
set_error(e.what(), cursor->pVtab); | |
return e.get_code(); | |
} catch(const std::exception &e) { | |
set_error(e.what(), cursor->pVtab); | |
return SQLITE_ERROR; | |
} catch(...) { | |
set_error("Unknown error", cursor->pVtab); | |
return SQLITE_ERROR; | |
} | |
}; | |
module.xColumn = [](sqlite3_vtab_cursor *cursor, sqlite3_context *ctxt, int n) { | |
try { | |
sqlite::store_result_in_db(ctxt, static_cast<typename T::Cursor*>(cursor)->Column(n)); | |
return SQLITE_OK; | |
} catch(const sqlite_exception &e) { | |
sqlite3_result_text(ctxt, e.what(), -1, SQLITE_TRANSIENT); | |
return e.get_code(); | |
} catch(const std::exception &e) { | |
sqlite3_result_text(ctxt, e.what(), -1, SQLITE_TRANSIENT); | |
return SQLITE_ERROR; | |
} catch(...) { | |
sqlite3_result_text(ctxt, "Unknown error", -1, SQLITE_STATIC); | |
return SQLITE_ERROR; | |
} | |
}; | |
module.xRowid = [](sqlite3_vtab_cursor *cursor, sqlite_int64 *rowid) { | |
try { | |
*rowid = static_cast<typename T::Cursor*>(cursor)->Rowid(); | |
return SQLITE_OK; | |
} catch(const sqlite_exception &e) { | |
set_error(e.what(), cursor->pVtab); | |
return e.get_code(); | |
} catch(const std::exception &e) { | |
set_error(e.what(), cursor->pVtab); | |
return SQLITE_ERROR; | |
} catch(...) { | |
set_error("Unknown error", cursor->pVtab); | |
return SQLITE_ERROR; | |
} | |
}; | |
// TODO: update and co | |
return module; | |
}(); | |
class MyVtab : public VtabBase<MyVtab, VtabType::eponymous_only> { | |
sqlite::database &db; | |
public: | |
MyVtab(sqlite::database &db, int argc, const char *const *argv): db(db) { } | |
std::string CreateTable() { | |
return "CREATE TABLE xxx(value, string TEXT HIDDEN, separator TEXT HIDDEN);"; | |
} | |
void BestIndex(sqlite3_index_info &info) { | |
auto begin = info.aConstraint; | |
auto end = begin + info.nConstraint; | |
auto found_string = std::find_if(begin, end, [](const auto &con) { | |
return con.iColumn == 1 && con.op == SQLITE_INDEX_CONSTRAINT_EQ && con.usable; | |
}); | |
if (found_string != end) { | |
int i = found_string - begin; | |
info.aConstraintUsage[i].argvIndex = 1; | |
info.aConstraintUsage[i].omit = true; | |
auto found_sep = std::find_if(begin, end, [](const auto &con) { | |
return con.iColumn == 2 && con.op == SQLITE_INDEX_CONSTRAINT_EQ && con.usable; | |
}); | |
if (found_sep != end) { | |
int i = found_sep - begin; | |
info.aConstraintUsage[i].argvIndex = 2; | |
info.aConstraintUsage[i].omit = true; | |
info.idxNum = 2; | |
info.estimatedCost = 100; | |
} else { | |
info.idxNum = 1; | |
info.estimatedCost = 1000; | |
} | |
} | |
} | |
class Cursor : public sqlite3_vtab_cursor { | |
std::string arg, separator; | |
std::default_searcher<std::string::iterator> searcher{separator.begin(), separator.end()}; | |
MyVtab &vtab; | |
std::string::iterator current, next, end; | |
int rowid = 0; | |
public: | |
Cursor(MyVtab &vtab): vtab(vtab) {} | |
bool Eof() {return current == arg.end();} | |
void Next() { | |
current = next; | |
std::tie(end, next) = searcher(next, arg.end()); | |
++rowid; | |
} | |
std::string Column(int n) { | |
switch(n) { | |
case 1: return arg; | |
case 2: return separator; | |
default: return std::string(current, end); | |
} | |
} | |
long long int Rowid() {return rowid;} | |
void Filter(int argc, int idxNum, const char *idxStr, std::optional<std::string> str, std::optional<std::string> sep) { | |
if (idxNum == 0 || !str) { current = arg.end(); return; } | |
arg = *str, separator = sep.value_or(","); | |
searcher = decltype(searcher){separator.begin(), separator.end()}; | |
next = arg.begin(); | |
rowid = -1; | |
Next(); | |
} | |
}; | |
using VtabBase<MyVtab, VtabType::eponymous_only>::create; | |
}; | |
int main(int argc, char const* argv[]) { | |
sqlite::database db("testdb"); | |
MyVtab::create(db, "hallo"); | |
db << "SELECT rowid, * FROM hallo('Hallo, Hello, World, Welt', ', ');" >> [](int i, std::string str) { | |
std::cout << "* " << i << ": " << str << '\n'; | |
}; | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment