diff options
Diffstat (limited to '')
-rw-r--r-- | src/sqlitepp.client.hh | 209 |
1 files changed, 209 insertions, 0 deletions
diff --git a/src/sqlitepp.client.hh b/src/sqlitepp.client.hh new file mode 100644 index 0000000..5a82756 --- /dev/null +++ b/src/sqlitepp.client.hh @@ -0,0 +1,209 @@ +/** + * Copyright (c) 2022, Timothy Stack + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * * Neither the name of Timothy Stack nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef lnav_sqlitepp_client_hh +#define lnav_sqlitepp_client_hh + +#include <sqlite3.h> + +#include "base/auto_mem.hh" +#include "base/intern_string.hh" +#include "base/lnav_log.hh" +#include "sql_util.hh" +#include "vtab_module.hh" + +inline int +bind_to_sqlite(sqlite3_stmt* stmt, int index, const struct timeval& tv) +{ + char timestamp[64]; + + sql_strftime(timestamp, sizeof(timestamp), tv, 'T'); + + return sqlite3_bind_text(stmt, index, timestamp, -1, SQLITE_TRANSIENT); +} + +inline int +bind_to_sqlite(sqlite3_stmt* stmt, int index, const char* str) +{ + return sqlite3_bind_text(stmt, index, str, -1, SQLITE_TRANSIENT); +} + +inline int +bind_to_sqlite(sqlite3_stmt* stmt, int index, intern_string_t ist) +{ + return sqlite3_bind_text( + stmt, index, ist.get(), ist.size(), SQLITE_TRANSIENT); +} + +inline int +bind_to_sqlite(sqlite3_stmt* stmt, int index, const std::string& str) +{ + return sqlite3_bind_text( + stmt, index, str.c_str(), str.size(), SQLITE_TRANSIENT); +} + +inline int +bind_to_sqlite(sqlite3_stmt* stmt, int index, int64_t i) +{ + return sqlite3_bind_int64(stmt, index, i); +} + +template<typename... Args, std::size_t... Idx> +int +bind_values_helper(sqlite3_stmt* stmt, + std::index_sequence<Idx...> idxs, + Args... args) +{ + int rcs[] = {bind_to_sqlite(stmt, Idx + 1, args)...}; + + for (size_t lpc = 0; lpc < idxs.size(); lpc++) { + if (rcs[lpc] != SQLITE_OK) { + log_error("Failed to bind column %d in statement: %s", + lpc, + sqlite3_sql(stmt)); + return rcs[lpc]; + } + } + + return SQLITE_OK; +} + +template<typename... Args> +int +bind_values(sqlite3_stmt* stmt, Args... args) +{ + return bind_values_helper( + stmt, std::make_index_sequence<sizeof...(Args)>(), args...); +} + +struct prepared_stmt { + prepared_stmt(auto_mem<sqlite3_stmt> stmt) : ps_stmt(std::move(stmt)) {} + + Result<void, std::string> execute() + { + auto rc = sqlite3_reset(this->ps_stmt.in()); + if (rc != SQLITE_OK) { + return Err(std::string( + sqlite3_errmsg(sqlite3_db_handle(this->ps_stmt.in())))); + } + + rc = sqlite3_step(this->ps_stmt.in()); + if (rc == SQLITE_OK || rc == SQLITE_DONE) { + return Ok(); + } + + auto msg = std::string( + sqlite3_errmsg(sqlite3_db_handle(this->ps_stmt.in()))); + return Err(msg); + } + + struct end_of_rows {}; + struct fetch_error { + std::string fe_msg; + }; + + void reset() { sqlite3_reset(this->ps_stmt.in()); } + + template<typename T> + using fetch_result = mapbox::util::variant<T, end_of_rows, fetch_error>; + + template<typename T> + fetch_result<T> fetch_row() + { + auto rc = sqlite3_step(this->ps_stmt.in()); + if (rc == SQLITE_OK || rc == SQLITE_DONE) { + return end_of_rows{}; + } + + if (rc == SQLITE_ROW) { + const auto argc = sqlite3_column_count(this->ps_stmt.in()); + sqlite3_value* argv[argc]; + + for (int lpc = 0; lpc < argc; lpc++) { + argv[lpc] = sqlite3_column_value(this->ps_stmt.in(), lpc); + } + + return from_sqlite<T>()(argc, argv, 0); + } + + return fetch_error{ + sqlite3_errmsg(sqlite3_db_handle(this->ps_stmt.in())), + }; + } + + template<typename T, typename F> + Result<void, fetch_error> for_each_row(F func) + { + nonstd::optional<fetch_error> err; + auto done = false; + + while (!done) { + done = this->template fetch_row<T>().match( + func, + [](end_of_rows) { return true; }, + [&err](const fetch_error& fe) { + err = fe; + return true; + }); + } + + if (err) { + return Err(err.value()); + } + + return Ok(); + } + + auto_mem<sqlite3_stmt> ps_stmt; +}; + +template<typename... Args> +static Result<prepared_stmt, std::string> +prepare_stmt(sqlite3* db, const char* sql, Args... args) +{ + auto_mem<sqlite3_stmt> retval(sqlite3_finalize); + + if (sqlite3_prepare_v2(db, sql, -1, retval.out(), nullptr) != SQLITE_OK) { + return Err( + fmt::format(FMT_STRING("unable to prepare SQL statement: {}"), + sqlite3_errmsg(db))); + } + + if (bind_values(retval.in(), args...) != SQLITE_OK) { + return Err( + fmt::format(FMT_STRING("unable to prepare SQL statement: {}"), + sqlite3_errmsg(db))); + } + + return Ok(prepared_stmt{ + std::move(retval), + }); +} + +#endif |