Skip to content

Instantly share code, notes, and snippets.

@zauguin
Last active October 6, 2019 12:53
Show Gist options
  • Save zauguin/7e327d9b5edf5a5002382c933308913c to your computer and use it in GitHub Desktop.
Save zauguin/7e327d9b5edf5a5002382c933308913c to your computer and use it in GitHub Desktop.
A draft for a virtual table module in sqlite_modern_cpp
#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