// Copyright (c) 2023 Klemens D. Morgenstern // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) #ifndef BOOST_SQLITE_DETAIL_VTABLE_HPP #define BOOST_SQLITE_DETAIL_VTABLE_HPP #include #include #include BOOST_SQLITE_BEGIN_NAMESPACE namespace detail { struct vtab_impl { template static int connect(sqlite3 * db, void * pAux, int argc, const char * const * argv, sqlite3_vtab **ppVTab, char** errMsg) { using table_type = typename Module::table_type; auto &impl = *static_cast(pAux); BOOST_SQLITE_TRY { result rtab = impl.connect( sqlite::connection(db, false), argc, argv); if (rtab.has_error()) return extract_error(*errMsg, rtab); auto tab = make_unique(std::move(*rtab)); tab->db_ = db; auto code = sqlite3_declare_vtab(db, tab->declaration()); if (code != SQLITE_OK) return code; sqlite::vtab::module_config cfg{db}; auto r = tab->config(cfg); if (r.has_error()) return extract_error(*errMsg, r); *ppVTab = tab.release(); return SQLITE_OK; } BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(*errMsg) } template static int create(sqlite3 * db, void * pAux, int argc, const char * const * argv, sqlite3_vtab **ppVTab, char** errMsg) { using table_type = typename Module::table_type; auto &impl = *static_cast(pAux); BOOST_SQLITE_TRY { result rtab = impl.create( sqlite::connection(db, false), argc, argv); if (rtab.has_error()) return extract_error(*errMsg, rtab); auto tab = make_unique(std::move(*rtab)); tab->db_ = db; auto code = sqlite3_declare_vtab(db, tab->declaration()); if (code != SQLITE_OK) return code; sqlite::vtab::module_config mc{db}; auto r = tab->config(mc); if (r.has_error()) return extract_error(*errMsg, r); *ppVTab = tab.release(); return SQLITE_OK; } BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(*errMsg) } template static int disconnect(sqlite3_vtab * tab) { BOOST_SQLITE_TRY { auto tb = static_cast(tab); tb->~Table(); sqlite3_free(tb); return SQLITE_OK; } BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(tab->zErrMsg); } template static int destroy(sqlite3_vtab * tab) { BOOST_SQLITE_TRY { auto tb = static_cast(tab); auto res = tb->destroy(); tb->~Table(); sqlite3_free(tb); if (res.has_error()) return std::move(res).error().code; return SQLITE_OK; } BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(tab->zErrMsg); } template static void assign_create(sqlite3_module & md, const Module &, const sqlite::vtab::eponymous_module & base) { md.xConnect = md.xCreate = &connect; md.xDisconnect = md.xDestroy = &disconnect
; if (base.eponymous_only()) md.xCreate = nullptr; } template static void assign_create(sqlite3_module & md, const Module &, const sqlite::vtab::module
&) { md.xConnect = &connect; md.xDisconnect = &disconnect
; md.xCreate = &create; md.xDestroy = &destroy
; } template static int open(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor) { auto tab = static_cast
(pVTab); BOOST_SQLITE_TRY { auto res = tab->open(); if (res.has_error()) return extract_error(pVTab->zErrMsg, res); *ppCursor = new (memory_tag{}) typename Table::cursor_type(std::move(*res)); return SQLITE_OK; } BOOST_SQLITE_CATCH_AND_RETURN(); } template static int close(sqlite3_vtab_cursor * cursor) { auto p = static_cast(cursor); BOOST_SQLITE_TRY { p->~Cursor(); } BOOST_SQLITE_CATCH_AND_RETURN(); sqlite3_free(p); return SQLITE_OK; } template static int best_index(sqlite3_vtab *pVTab, sqlite3_index_info* info) { BOOST_SQLITE_TRY { auto tb = static_cast(pVTab); sqlite::vtab::index_info ii(tb->db_, info); auto r = tb->best_index(ii); if (r.has_error()) return extract_error(pVTab->zErrMsg, r); return SQLITE_OK; } BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVTab->zErrMsg); } template static int filter(sqlite3_vtab_cursor* pCursor, int idxNum, const char *idxStr, int argc, sqlite3_value **argv) { BOOST_SQLITE_TRY { auto cr = static_cast(pCursor); auto r = cr->filter(idxNum, idxStr, boost::span{reinterpret_cast(argv), static_cast(argc)}); if (r.has_error()) return extract_error(pCursor->pVtab->zErrMsg, r); return SQLITE_OK; } BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pCursor->pVtab->zErrMsg); } template static int next(sqlite3_vtab_cursor* pCursor) { BOOST_SQLITE_TRY { auto cr = static_cast(pCursor); auto r = cr->next(); if (r.has_error()) return extract_error(pCursor->pVtab->zErrMsg, r); return SQLITE_OK; } BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pCursor->pVtab->zErrMsg); } template static int eof(sqlite3_vtab_cursor* pCursor) { return static_cast(pCursor)->eof() ? 1 : 0; } template static auto column(sqlite3_vtab_cursor* pCursor, sqlite3_context * ctx, int idx) -> typename std::enable_if::value, int>::type { #if SQLITE_VERSION_NUMBER >= 3032000 bool no_change = sqlite3_vtab_nochange(ctx) != 0; #else bool no_change = false; #endif auto cr = static_cast(pCursor); execute_context_function( ctx, [&]{ return cr->column(idx, no_change); }); return SQLITE_OK; } template static auto column(sqlite3_vtab_cursor* pCursor, sqlite3_context * ctx, int idx) -> typename std::enable_if::value, int>::type { #if SQLITE_VERSION_NUMBER >= 3032000 bool no_change = sqlite3_vtab_nochange(ctx) != 0; #else bool no_change = false; #endif auto cr = static_cast(pCursor); BOOST_SQLITE_TRY { cr->column(context<>{ctx}, idx, no_change); } BOOST_SQLITE_CATCH_RESULT(ctx); return SQLITE_OK; } template static int row_id(sqlite3_vtab_cursor* pCursor, sqlite3_int64 *pRowid) { BOOST_SQLITE_TRY { auto cr = static_cast(pCursor); auto r = cr->row_id(); if (r.has_error()) return extract_error(pCursor->pVtab->zErrMsg, r); *pRowid = *r; return SQLITE_OK; } BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pCursor->pVtab->zErrMsg); } template static int update(sqlite3_vtab * pVTab, int argc, sqlite3_value ** argv, sqlite3_int64 * pRowid) { using table_type = Table; BOOST_SQLITE_TRY { auto & mod = *static_cast(pVTab); auto db = mod.db_; if (argc == 1 && sqlite3_value_type(argv[0]) != SQLITE_NULL) { auto r = mod.delete_(sqlite::value(*argv)); if (r.has_error()) return extract_error(pVTab->zErrMsg, r); } else if (argc > 1 && sqlite3_value_type(argv[0]) == SQLITE_NULL) { auto r = mod.insert(value{argv[1]}, boost::span{reinterpret_cast(argv + 2), static_cast(argc - 2)}, sqlite3_vtab_on_conflict(db)); if (r.has_error()) return extract_error(pVTab->zErrMsg, r); *pRowid = r.value(); } else if (argc > 1 && sqlite3_value_type(argv[0]) != SQLITE_NULL) { auto r = mod.update(sqlite::value(*argv), value{argv[1]}, // ID boost::span{reinterpret_cast(argv + 2), static_cast(argc - 2)}, sqlite3_vtab_on_conflict(db)); if (r.has_error()) return extract_error(pVTab->zErrMsg, r); *pRowid = r.value(); } else { pVTab->zErrMsg = sqlite3_mprintf("Misuse of update api"); return SQLITE_MISUSE; } } BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVTab->zErrMsg) return SQLITE_OK; } template static void assign_update(sqlite3_module & md, const Module &, std::true_type /* modifiable */) { md.xUpdate = &update; } template static void assign_update(sqlite3_module & /*md*/, const Module &, std::false_type /* modifiable */) { } template static int begin(sqlite3_vtab* pVTab) { BOOST_SQLITE_TRY { auto cr = static_cast(pVTab); auto r = cr->begin(); if (r.has_error()) return extract_error(pVTab->zErrMsg, r); return SQLITE_OK; } BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVTab->zErrMsg); } template static int sync(sqlite3_vtab* pVTab) { BOOST_SQLITE_TRY { auto cr = static_cast(pVTab); auto r = cr->sync(); if (r.has_error()) return extract_error(pVTab->zErrMsg, r); return SQLITE_OK; } BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVTab->zErrMsg); } template static int commit(sqlite3_vtab* pVTab) { BOOST_SQLITE_TRY { auto cr = static_cast(pVTab); auto r = cr->commit(); if (r.has_error()) return extract_error(pVTab->zErrMsg, r); return SQLITE_OK; } BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVTab->zErrMsg); } template static int rollback(sqlite3_vtab* pVTab) { BOOST_SQLITE_TRY { auto cr = static_cast(pVTab); auto r = cr->rollback(); if (r.has_error()) return extract_error(pVTab->zErrMsg, r); return SQLITE_OK; } BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVTab->zErrMsg); } template static void assign_transaction(sqlite3_module & /*md*/, const Module &, std::false_type /* modifiable */) { } template static void assign_transaction(sqlite3_module & md, const Module &, std::true_type /* modifiable */) { md.xBegin = &begin ; md.xSync = &sync ; md.xCommit = &commit ; md.xRollback = &rollback; } template static int find_function(sqlite3_vtab *pVtab, int nArg, const char *zName, void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), void **ppArg) { BOOST_SQLITE_TRY { auto cr = static_cast(pVtab); auto r = cr->find_function( nArg, zName, sqlite::vtab::function_setter(pxFunc, ppArg)); if (r.has_error()) return extract_error(pVtab->zErrMsg, r); return SQLITE_OK; } BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVtab->zErrMsg); } template static void assign_find_function(sqlite3_module & /*md*/, const Module &, std::false_type /* overloadable */) { } template static void assign_find_function(sqlite3_module & md, const Module &, std::true_type /* overloadable */) { md.xFindFunction = &find_function; } template static int rename(sqlite3_vtab* pVTab, const char * name) { BOOST_SQLITE_TRY { auto cr = static_cast(pVTab); auto r = cr->rename(name); if (r.has_error()) return extract_error(pVTab->zErrMsg, r); return SQLITE_OK; } BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVTab->zErrMsg); } template static void assign_rename(sqlite3_module & /*md*/, const Module &, std::false_type /* renamable */) { } template static void assign_rename(sqlite3_module & md, const Module &, std::true_type /* renamable */) { md.xRename = &rename; } #if SQLITE_VERSION_NUMBER >= 3007007 template static int savepoint(sqlite3_vtab* pVTab, int i) { BOOST_SQLITE_TRY { auto cr = static_cast(pVTab); auto r = cr->savepoint(i); if (r.has_error()) return extract_error(pVTab->zErrMsg, r); return SQLITE_OK; } BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVTab->zErrMsg); } template static int release(sqlite3_vtab* pVTab, int i) { BOOST_SQLITE_TRY { auto cr = static_cast(pVTab); auto r = cr->release(i); if (r.has_error()) return extract_error(pVTab->zErrMsg, r); return SQLITE_OK; } BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVTab->zErrMsg); } template static int rollback_to(sqlite3_vtab* pVTab, int i) { BOOST_SQLITE_TRY { auto cr = static_cast(pVTab); auto r = cr->rollback_to(i); if (r.has_error()) return extract_error(pVTab->zErrMsg, r); return SQLITE_OK; } BOOST_SQLITE_CATCH_ASSIGN_STR_AND_RETURN(pVTab->zErrMsg); } template static void assign_recursive_transaction(sqlite3_module & /*md*/, const Module &, std::false_type /* recursive_transaction */) { } template static void assign_recursive_transaction(sqlite3_module & md, const Module &, std::true_type /* recursive_transaction */) { md.xSavepoint = &savepoint ; md.xRelease = &release ; md.xRollbackTo = &rollback_to; } #endif #if SQLITE_VERSION_NUMBER >= 3026000 template static void assign_shadow_name(sqlite3_module & /*md*/, const sqlite::vtab::module
&) {} template static void assign_shadow_name(sqlite3_module & /*md*/, const sqlite::vtab::eponymous_module
&) {} template static void assign_shadow_name(sqlite3_module & md, const Module & mod) { md.xShadowName = +[](const char * name){return Func(name) != 0;}; } #endif }; template const sqlite3_module make_module(const Module & mod) { sqlite3_module md; std::memset(&md, 0, sizeof(sqlite3_module)); #if SQLITE_VERSION_NUMBER < 3007007 md.iVersion = 1; #elif SQLITE_VERSION_NUMBER < 3026000 md.iVersion = 2; #else md.iVersion = 3; #endif using table_type = typename Module::table_type; using cursor_type = typename table_type::cursor_type; vtab_impl::assign_create(md, mod, mod); md.xBestIndex = &vtab_impl::best_index; md.xOpen = &vtab_impl::open ; md.xClose = &vtab_impl::close ; md.xFilter = &vtab_impl::filter ; md.xNext = &vtab_impl::next ; md.xEof = &vtab_impl::eof ; md.xColumn = &vtab_impl::column ; md.xRowid = &vtab_impl::row_id ; vtab_impl::assign_update (md, mod, std::is_base_of{}); vtab_impl::assign_transaction (md, mod, std::is_base_of{}); vtab_impl::assign_find_function(md, mod, std::is_base_of{}); vtab_impl::assign_rename (md, mod, std::is_base_of{}); #if SQLITE_VERSION_NUMBER >= 3007007 vtab_impl::assign_recursive_transaction(md, mod, std::is_base_of{}); #endif #if SQLITE_VERSION_NUMBER >= 3026000 vtab_impl::assign_shadow_name(md, mod); #endif return md; } } BOOST_SQLITE_END_NAMESPACE #endif //BOOST_SQLITE_DETAIL_VTABLE_HPP