diff --git a/Makefile.am b/Makefile.am index e06014b2..d3f34d56 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,7 +1,7 @@ ACLOCAL_AMFLAGS = -I . -SUBDIRS = src test +SUBDIRS = tools src test noinst_SCRIPTS = TESTS_ENVIRONMENT diff --git a/aminclude_static.am b/aminclude_static.am index 860d2189..cff354f0 100644 --- a/aminclude_static.am +++ b/aminclude_static.am @@ -1,6 +1,6 @@ # aminclude_static.am generated automatically by Autoconf -# from AX_AM_MACROS_STATIC on Tue Jul 12 09:53:25 PDT 2022 +# from AX_AM_MACROS_STATIC on Fri Jul 15 10:37:15 PDT 2022 # Code coverage diff --git a/configure.ac b/configure.ac index 1821975b..f2069957 100644 --- a/configure.ac +++ b/configure.ac @@ -314,6 +314,7 @@ AC_SUBST(USER_CXXFLAGS) AC_CONFIG_HEADERS([src/config.h]) AC_CONFIG_FILES([Makefile]) AC_CONFIG_FILES([TESTS_ENVIRONMENT]) +AC_CONFIG_FILES([tools/Makefile]) AC_CONFIG_FILES([src/Makefile]) AC_CONFIG_FILES([src/base/Makefile]) AC_CONFIG_FILES([src/formats/logfmt/Makefile]) @@ -321,7 +322,6 @@ AC_CONFIG_FILES([src/fmtlib/Makefile]) AC_CONFIG_FILES([src/pcrepp/Makefile]) AC_CONFIG_FILES([src/pugixml/Makefile]) AC_CONFIG_FILES([src/tailer/Makefile]) -AC_CONFIG_FILES([src/tools/Makefile]) AC_CONFIG_FILES([src/yajl/Makefile]) AC_CONFIG_FILES([src/yajlpp/Makefile]) AC_CONFIG_FILES([src/third-party/base64/lib/Makefile]) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index ad6490ef..56a5c065 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -22,7 +22,7 @@ add_subdirectory(formats/logfmt) add_subdirectory(yajl) add_subdirectory(yajlpp) -add_executable(bin2c bin2c.hh tools/bin2c.c) +add_executable(bin2c bin2c.hh ../tools/bin2c.c) target_link_libraries(bin2c ZLIB::ZLIB) add_executable(ptimec ptimec.hh ptimec.c) diff --git a/src/Makefile.am b/src/Makefile.am index ff24fd70..75e0d653 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -3,7 +3,7 @@ include $(top_srcdir)/aminclude_static.am CXXFLAGS = -SUBDIRS = tools fmtlib third-party/base64/lib pcrepp base tailer pugixml yajl yajlpp formats/logfmt . +SUBDIRS = fmtlib third-party/base64/lib pcrepp base tailer pugixml yajl yajlpp formats/logfmt . bin_PROGRAMS = lnav @@ -23,10 +23,12 @@ RE2C_V = $(RE2C_V_@AM_V@) RE2C_V_ = $(RE2C_V_@AM_DEFAULT_V@) RE2C_V_0 = @echo " RE2C " $@; +BIN2C_PATH = ../tools/bin2c$(BUILD_EXEEXT) + include formats/formats.am -default-formats.h default-formats.cc: tools/bin2c$(BUILD_EXEEXT) $(FORMAT_FILES) - $(BIN2C_V)tools/bin2c$(BUILD_EXEEXT) -n lnav_format_json default-formats $(FORMAT_FILES) +default-formats.cc: $(BIN2C_PATH) $(FORMAT_FILES) + $(BIN2C_V)$(BIN2C_PATH) -n lnav_format_json default-formats $(FORMAT_FILES) include keymaps/keymaps.am include themes/themes.am @@ -37,34 +39,34 @@ CONFIG_FILES = \ $(THEME_FILES) \ $() -default-config.h default-config.cc: tools/bin2c$(BUILD_EXEEXT) $(CONFIG_FILES) - $(BIN2C_V)tools/bin2c$(BUILD_EXEEXT) -n lnav_config_json default-config $(CONFIG_FILES) +default-config.cc: $(BIN2C_PATH) $(CONFIG_FILES) + $(BIN2C_V)$(BIN2C_PATH) -n lnav_config_json default-config $(CONFIG_FILES) include scripts/scripts.am -builtin-scripts.h builtin-scripts.cc: tools/bin2c$(BUILD_EXEEXT) $(BUILTIN_LNAVSCRIPTS) - $(BIN2C_V)tools/bin2c$(BUILD_EXEEXT) -n lnav_scripts builtin-scripts $(BUILTIN_LNAVSCRIPTS) +builtin-scripts.cc: $(BIN2C_PATH) $(BUILTIN_LNAVSCRIPTS) + $(BIN2C_V)$(BIN2C_PATH) -n lnav_scripts builtin-scripts $(BUILTIN_LNAVSCRIPTS) -builtin-sh-scripts.h builtin-sh-scripts.cc: tools/bin2c$(BUILD_EXEEXT) $(BUILTIN_SHSCRIPTS) - $(BIN2C_V)tools/bin2c$(BUILD_EXEEXT) -n lnav_sh_scripts builtin-sh-scripts $(BUILTIN_SHSCRIPTS) +builtin-sh-scripts.cc: $(BIN2C_PATH) $(BUILTIN_SHSCRIPTS) + $(BIN2C_V)$(BIN2C_PATH) -n lnav_sh_scripts builtin-sh-scripts $(BUILTIN_SHSCRIPTS) -%-sh.cc: $(srcdir)/%.sh tools/bin2c$(BUILD_EXEEXT) - $(BIN2C_V)tools/bin2c$(BUILD_EXEEXT) $(*)-sh $< +%-sh.cc: $(srcdir)/%.sh $(BIN2C_PATH) + $(BIN2C_V)$(BIN2C_PATH) $(*)-sh $< -%-txt.cc %-txt.h: $(srcdir)/%.txt tools/bin2c$(BUILD_EXEEXT) - $(BIN2C_V)tools/bin2c$(BUILD_EXEEXT) $(*)-txt $< +%-txt.cc: $(srcdir)/%.txt $(BIN2C_PATH) + $(BIN2C_V)$(BIN2C_PATH) $(*)-txt $< -%-md.cc %-md.h: $(srcdir)/%.md tools/bin2c$(BUILD_EXEEXT) - $(BIN2C_V)tools/bin2c$(BUILD_EXEEXT) $(*)-md $< +%-md.cc: $(srcdir)/%.md $(BIN2C_PATH) + $(BIN2C_V)$(BIN2C_PATH) $(*)-md $< -%-sql.cc %-sql.h: $(srcdir)/%.sql tools/bin2c$(BUILD_EXEEXT) - $(BIN2C_V)tools/bin2c$(BUILD_EXEEXT) $(*)-sql $< +%-sql.cc: $(srcdir)/%.sql $(BIN2C_PATH) + $(BIN2C_V)$(BIN2C_PATH) $(*)-sql $< -%-lnav.cc %-lnav.h: $(srcdir)/%.lnav tools/bin2c$(BUILD_EXEEXT) - $(BIN2C_V)tools/bin2c$(BUILD_EXEEXT) $(*)-lnav $< +%-lnav.cc: $(srcdir)/%.lnav $(BIN2C_PATH) + $(BIN2C_V)$(BIN2C_PATH) $(*)-lnav $< -%-json.cc %-json.h: $(srcdir)/%.json tools/bin2c$(BUILD_EXEEXT) - $(BIN2C_V)tools/bin2c$(BUILD_EXEEXT) $(*)-json $< +%-json.cc: $(srcdir)/%.json $(BIN2C_PATH) + $(BIN2C_V)$(BIN2C_PATH) $(*)-json $< include time_formats.am @@ -77,44 +79,21 @@ if HAVE_RE2C $(REC2_V)test $@ -ef $(srcdir)/$*.cc || cp $@ $(srcdir)/$*.cc endif -lnav_config.$(OBJEXT): default-config.h - -log_format_loader.$(OBJEXT): \ - builtin-scripts.h \ - builtin-sh-scripts.h \ - default-formats.h - -styling.$(OBJEXT): ansi-palette-json.h xterm-palette-json.h - -view_helpers.$(OBJEXT): help-txt.h help-md.h - -md4cpp.$(OBJEXT): xml-entities-json.h emojis-json.h - LNAV_BUILT_FILES = \ - ansi-palette-json.h \ ansi-palette-json.cc \ - builtin-scripts.h \ builtin-scripts.cc \ - builtin-sh-scripts.h \ builtin-sh-scripts.cc \ - default-config.h \ default-config.cc \ - default-formats.h \ default-formats.cc \ - emojis-json.h \ emojis-json.cc \ - help-txt.h \ - help-txt.cc \ - help-md.h \ help-md.cc \ - init-sql.h \ init-sql.cc \ time_fmts.cc \ - xml-entities-json.h \ xml-entities-json.cc \ - xterm-palette-json.h \ xterm-palette-json.cc +BUILT_SOURCES = $(LNAV_BUILT_FILES) + AM_LIBS = $(CODE_COVERAGE_LIBS) AM_CFLAGS = $(CODE_COVERAGE_CFLAGS) AM_CXXFLAGS = $(CODE_COVERAGE_CXXFLAGS) $(USER_CXXFLAGS) @@ -287,6 +266,7 @@ noinst_HEADERS = \ spectro_impls.hh \ spectro_source.hh \ sqlitepp.hh \ + sqlitepp.client.hh \ sql_help.hh \ sql_util.hh \ sqlite-extension-func.hh \ @@ -474,8 +454,6 @@ libdiag_a_SOURCES = \ PLUGIN_SRCS = \ file_vtab.cc -lnav.$(OBJEXT): help-txt.h init-sql.h - lnav_SOURCES = \ lnav.cc \ lnav.events.cc \ @@ -516,11 +494,13 @@ uncrusty: $(HEADERS)) if !DISABLE_DOCUMENTATION -all-local: lnav +all-local: $(LNAV_BUILT_FILES) lnav if test -w $(srcdir)/internals; then \ env DUMP_INTERNALS_DIR=$(srcdir)/internals DUMP_CRASH=1 ./lnav Makefile; \ mv $(srcdir)/internals/*.schema.json $(top_srcdir)/docs/schemas; \ fi +else +all-local: $(LNAV_BUILT_FILES) endif install-exec-hook: diff --git a/src/init.sql b/src/init.sql index 8ba02262..ef75775d 100644 --- a/src/init.sql +++ b/src/init.sql @@ -109,13 +109,30 @@ VALUES (0, null, '2017-02-03T04:05:06.100', '2017-02-03T04:05:06.100', 0, CREATE TABLE lnav_user_notifications ( - id TEXT NOT NULL DEFAULT 'org.lnav.unknown', + -- A unique identifier for the notification. + id TEXT NOT NULL DEFAULT 'org.lnav.user' PRIMARY KEY, + -- The priority of this message relative to others, the highest priority + -- message will be shown in the top-right corner. priority INTEGER NOT NULL DEFAULT 0, + -- The time when this notification was created. created DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + -- The time when this notification is no longer applicable. expiration DATETIME DEFAULT NULL, - message TEXT + -- A JSON array with the names of the views where this notification is + -- applicable. Use NULL to show it in all views. + views JSON, + -- The message to display, can be null to clear the message. + message TEXT, + + CHECK (views IS NULL OR json_type(views) = 'array') ); INSERT INTO lnav_user_notifications (id, priority, expiration, message) -VALUES ('org.lnav.breadcrumb.help.focus', -1, datetime('now', '+1 minute'), +VALUES ('org.lnav.breadcrumb.focus', -1, datetime('now', '+1 minute'), 'Press ENTER to focus on the breadcrumb bar'); + +CREATE TABLE lnav_views_echo AS +SELECT name, top, "left", height, inner_height, top_time, search +FROM lnav_views; + +CREATE UNIQUE INDEX lnav_views_echo_index ON lnav_views_echo (name); diff --git a/src/json-extension-functions.cc b/src/json-extension-functions.cc index 27b82733..a64a14fe 100644 --- a/src/json-extension-functions.cc +++ b/src/json-extension-functions.cc @@ -67,7 +67,7 @@ null_or_default(sqlite3_context* context, int argc, sqlite3_value* argv[]) } struct contains_userdata { - util::variant cu_match_value{false}; + util::variant cu_match_value{false}; size_t cu_depth{0}; bool cu_result{false}; }; @@ -75,12 +75,10 @@ struct contains_userdata { static int contains_string(void* ctx, const unsigned char* str, size_t len) { + auto sf = string_fragment{(const char*) str, 0, (int) len}; auto& cu = *((contains_userdata*) ctx); - if (cu.cu_depth <= 1 - && strncmp((const char*) str, cu.cu_match_value.get(), len) - == 0) - { + if (cu.cu_depth <= 1 && cu.cu_match_value.get() == sf) { cu.cu_result = true; } @@ -158,7 +156,11 @@ json_contains(vtab_types::nullable nullable_json_in, switch (sqlite3_value_type(value)) { case SQLITE3_TEXT: cb.yajl_string = contains_string; - cu.cu_match_value = (const char*) sqlite3_value_text(value); + cu.cu_match_value = string_fragment{ + (const char*) sqlite3_value_text(value), + 0, + sqlite3_value_bytes(value), + }; break; case SQLITE_INTEGER: cb.yajl_integer = contains_integer; diff --git a/src/lnav.cc b/src/lnav.cc index a70a7963..cad4cfbc 100644 --- a/src/lnav.cc +++ b/src/lnav.cc @@ -96,7 +96,6 @@ #include "filter_sub_source.hh" #include "fstat_vtab.hh" #include "grep_proc.hh" -#include "help-txt.h" #include "hist_source.hh" #include "init-sql.h" #include "listview_curses.hh" @@ -124,6 +123,7 @@ #include "sql_help.hh" #include "sql_util.hh" #include "sqlite-extension-func.hh" +#include "sqlitepp.client.hh" #include "tailer/tailer.looper.hh" #include "term_extra.hh" #include "termios_guard.hh" @@ -1329,6 +1329,19 @@ looper() auto next_status_update_time = next_rebuild_time; auto next_rescan_time = next_rebuild_time; + auto echo_views_stmt = prepare_stmt(lnav_data.ld_db, R"( +UPDATE lnav_views_echo + SET top = orig.top, + left = orig.left, + height = orig.height, + inner_height = orig.inner_height, + top_time = orig.top_time, + search = orig.search + FROM (SELECT * FROM lnav_views) AS orig + WHERE orig.name = lnav_views_echo.name +)") + .unwrap(); + while (lnav_data.ld_looping) { auto loop_deadline = ui_clock::now() + (session_stage == 0 ? 3s : 50ms); @@ -1447,6 +1460,7 @@ looper() lnav_data.ld_spectro_details_view.do_update(); lnav_data.ld_user_message_view.do_update(); if (ui_clock::now() >= next_status_update_time) { + echo_views_stmt.execute(); lnav_data.ld_top_source.update_user_msg(); for (auto& sc : lnav_data.ld_status) { sc.do_update(); diff --git a/src/lnav_commands.cc b/src/lnav_commands.cc index ad2513c4..c8dce8a2 100644 --- a/src/lnav_commands.cc +++ b/src/lnav_commands.cc @@ -2214,8 +2214,8 @@ com_create_search_table(exec_context& ec, } auto re = re_res.unwrap(); - auto lst = std::make_shared( - re, intern_string::lookup(args[1])); + auto tab_name = intern_string::lookup(args[1]); + auto lst = std::make_shared(re, tab_name); if (ec.ec_dry_run) { textview_curses* tc = &lnav_data.ld_views[LNV_LOG]; auto& hm = tc->get_highlights(); @@ -2238,6 +2238,11 @@ com_create_search_table(exec_context& ec, return Ok(std::string()); } + auto tab_iter = custom_search_tables.find(args[1]); + if (tab_iter != custom_search_tables.end()) { + lnav_data.ld_vtab_manager->unregister_vtab(tab_name); + } + std::string errmsg; errmsg = lnav_data.ld_vtab_manager->register_vtab(lst); @@ -2268,7 +2273,8 @@ com_delete_search_table(exec_context& ec, if (args.empty()) { args.emplace_back("search-table"); } else if (args.size() == 2) { - if (custom_search_tables.find(args[1]) == custom_search_tables.end()) { + auto tab_iter = custom_search_tables.find(args[1]); + if (tab_iter == custom_search_tables.end()) { return ec.make_error("unknown search table -- {}", args[1]); } @@ -2276,7 +2282,8 @@ com_delete_search_table(exec_context& ec, return Ok(std::string()); } - std::string rc = lnav_data.ld_vtab_manager->unregister_vtab( + custom_search_tables.erase(tab_iter); + auto rc = lnav_data.ld_vtab_manager->unregister_vtab( intern_string::lookup(args[1])); if (rc.empty()) { diff --git a/src/log_search_table.cc b/src/log_search_table.cc index b2dd4809..8ef93cd1 100644 --- a/src/log_search_table.cc +++ b/src/log_search_table.cc @@ -127,21 +127,20 @@ log_search_table::next(log_cursor& lc, logfile_sub_source& lss) return true; } - lc.lc_curr_line += 1_vl; - lc.lc_sub_index = 0; + this->lst_match_index = -1; + return false; } this->lst_match_index = -1; - while (!lc.is_eof() && !this->is_valid(lc, lss)) { - lc.lc_curr_line += 1_vl; - lc.lc_sub_index = 0; - } - if (lc.is_eof()) { return true; } + if (!this->is_valid(lc, lss)) { + return false; + } + auto cl = lss.at(lc.lc_curr_line); auto* lf = lss.find_file_ptr(cl); @@ -177,7 +176,9 @@ log_search_table::extract(logfile* lf, shared_buffer_ref& line, std::vector& values) { - values = this->lst_line_values_cache; + if (this->lst_format != nullptr) { + values = this->lst_line_values_cache; + } values.emplace_back(this->lst_column_metas[this->lst_format_column_count], this->lst_match_index); for (int lpc = 0; lpc < this->lst_regex.get_capture_count(); lpc++) { diff --git a/src/log_vtab_impl.cc b/src/log_vtab_impl.cc index ad75782c..afee0c06 100644 --- a/src/log_vtab_impl.cc +++ b/src/log_vtab_impl.cc @@ -2023,7 +2023,7 @@ log_vtab_manager::unregister_vtab(intern_string_t name) std::string retval; if (this->vm_impls.find(name) == this->vm_impls.end()) { - retval = fmt::format(FMT_STRING("unknown log line table -- {}"), name); + retval = fmt::format(FMT_STRING("unknown table -- {}"), name); } else { auto_mem sql; __attribute((unused)) int rc; diff --git a/src/readline_callbacks.cc b/src/readline_callbacks.cc index fc2797c9..61d8eda3 100644 --- a/src/readline_callbacks.cc +++ b/src/readline_callbacks.cc @@ -520,7 +520,7 @@ rl_search_internal(readline_curses* rc, ln_mode_t mode, bool complete = false) void rl_search(readline_curses* rc) { - textview_curses* tc = get_textview_for_mode(lnav_data.ld_mode); + auto* tc = get_textview_for_mode(lnav_data.ld_mode); rl_search_internal(rc, lnav_data.ld_mode); tc->set_follow_search_for(0, {}); @@ -878,6 +878,8 @@ rl_focus(readline_curses* rc) .get_overlay_source(); fos->fos_contexts.emplace("", false, true); + + get_textview_for_mode(lnav_data.ld_mode)->save_current_search(); } void diff --git a/src/readline_curses.cc b/src/readline_curses.cc index b5995f9a..a28d0158 100644 --- a/src/readline_curses.cc +++ b/src/readline_curses.cc @@ -67,6 +67,7 @@ #include "spookyhash/SpookyV2.h" static int got_line = 0; +static int got_abort = 0; static bool alt_done = 0; static sig_atomic_t got_timeout = 0; static sig_atomic_t got_winch = 0; @@ -499,6 +500,8 @@ rubout_char_or_abort(int count, int key) { if (rl_line_buffer[0] == '\0') { rl_done = true; + got_abort = 1; + got_line = 0; return 0; } else { return rl_rubout(count, '\b'); @@ -885,6 +888,7 @@ readline_curses::start() = this->rc_contexts.find(context)) != this->rc_contexts.end()) { + got_abort = 0; current_context->second->load(); rl_callback_handler_install(&msg[prompt_start], line_ready_tramp); @@ -1027,7 +1031,7 @@ readline_curses::line_ready(const char* line) const char* cmd_ch = alt_done ? "D" : "d"; alt_done = false; - if (line == nullptr) { + if (got_abort || line == nullptr) { snprintf(msg, sizeof(msg), "a"); if (sendstring(this->rc_command_pipe[RCF_SLAVE], msg, strlen(msg)) diff --git a/src/session_data.cc b/src/session_data.cc index 61e3b38c..5c3e05c1 100644 --- a/src/session_data.cc +++ b/src/session_data.cc @@ -52,6 +52,7 @@ #include "logfile.hh" #include "service_tags.hh" #include "sql_util.hh" +#include "sqlitepp.client.hh" #include "tailer/tailer.looper.hh" #include "vtab_module.hh" #include "yajlpp/yajlpp.hh" @@ -131,143 +132,6 @@ static const size_t MAX_SESSION_FILE_COUNT = 256; static std::vector marked_session_lines; static std::vector offset_session_lines; -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); -} - -int -bind_to_sqlite(sqlite3_stmt* stmt, int index, const char* str) -{ - return sqlite3_bind_text(stmt, index, str, -1, SQLITE_TRANSIENT); -} - -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); -} - -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); -} - -int -bind_to_sqlite(sqlite3_stmt* stmt, int index, int64_t i) -{ - return sqlite3_bind_int64(stmt, index, i); -} - -template -int -bind_values_helper(sqlite3_stmt* stmt, - std::index_sequence 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 -int -bind_values(sqlite3_stmt* stmt, Args... args) -{ - return bind_values_helper( - stmt, std::make_index_sequence(), args...); -} - -struct prepared_stmt { - prepared_stmt(auto_mem stmt) : ps_stmt(std::move(stmt)) {} - - Result execute() - { - auto 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; - }; - - template - using fetch_result = mapbox::util::variant; - - template - fetch_result 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()(argc, argv, 0); - } - - return fetch_error{ - sqlite3_errmsg(sqlite3_db_handle(this->ps_stmt.in())), - }; - } - - auto_mem ps_stmt; -}; - -template -static Result -prepare_stmt(sqlite3* db, const char* sql, Args... args) -{ - auto_mem 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), - }); -} - static bool bind_line(sqlite3* db, sqlite3_stmt* stmt, diff --git a/src/sqlitepp.client.hh b/src/sqlitepp.client.hh new file mode 100644 index 00000000..5928fb3d --- /dev/null +++ b/src/sqlitepp.client.hh @@ -0,0 +1,186 @@ +/** + * 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 + +#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 +int +bind_values_helper(sqlite3_stmt* stmt, + std::index_sequence 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 +int +bind_values(sqlite3_stmt* stmt, Args... args) +{ + return bind_values_helper( + stmt, std::make_index_sequence(), args...); +} + +struct prepared_stmt { + prepared_stmt(auto_mem stmt) : ps_stmt(std::move(stmt)) {} + + Result 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 + using fetch_result = mapbox::util::variant; + + template + fetch_result 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()(argc, argv, 0); + } + + return fetch_error{ + sqlite3_errmsg(sqlite3_db_handle(this->ps_stmt.in())), + }; + } + + auto_mem ps_stmt; +}; + +template +static Result +prepare_stmt(sqlite3* db, const char* sql, Args... args) +{ + auto_mem 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 diff --git a/src/tailer/Makefile.am b/src/tailer/Makefile.am index 8ad9c89e..751b89f8 100644 --- a/src/tailer/Makefile.am +++ b/src/tailer/Makefile.am @@ -46,7 +46,7 @@ libtailerpp_a_CPPFLAGS = \ libtailerpp_a_SOURCES = \ tailerpp.cc -tailerbin.h tailerbin.cc: tailer tailer.ape ../tools/bin2c$(BUILD_EXEEXT) +tailerbin.cc: tailer tailer.ape ../tools/bin2c$(BUILD_EXEEXT) ../tools/bin2c$(BUILD_EXEEXT) -n tailer_bin tailerbin $(srcdir)/tailer.ape libtailerservice_a_CPPFLAGS = \ diff --git a/src/textview_curses.cc b/src/textview_curses.cc index cc37e40f..66d46b69 100644 --- a/src/textview_curses.cc +++ b/src/textview_curses.cc @@ -570,7 +570,6 @@ textview_curses::execute_search(const std::string& regex_orig) const char* errptr; int eoff; - this->tc_previous_search = this->tc_current_search; this->match_reset(); this->tc_search_child.reset(); diff --git a/src/textview_curses.hh b/src/textview_curses.hh index 8dffea9b..70f4bf2e 100644 --- a/src/textview_curses.hh +++ b/src/textview_curses.hh @@ -676,15 +676,14 @@ public: } } - std::string get_current_search() const + std::string get_current_search() const { return this->tc_current_search; } + + void save_current_search() { - return this->tc_current_search; + this->tc_previous_search = this->tc_current_search; } - void revert_search() - { - this->execute_search(this->tc_previous_search); - } + void revert_search() { this->execute_search(this->tc_previous_search); } void invoke_scroll() { diff --git a/src/third-party/base64/include/libbase64.h b/src/third-party/base64/include/libbase64.h index d470a82f..f3e5abb2 100644 --- a/src/third-party/base64/include/libbase64.h +++ b/src/third-party/base64/include/libbase64.h @@ -4,21 +4,9 @@ #include /* size_t */ -#if defined(_WIN32) || defined(__CYGWIN__) -#define BASE64_SYMBOL_IMPORT __declspec(dllimport) -#define BASE64_SYMBOL_EXPORT __declspec(dllexport) -#define BASE64_SYMBOL_PRIVATE - -#elif __GNUC__ >= 4 -#define BASE64_SYMBOL_IMPORT __attribute__ ((visibility ("default"))) -#define BASE64_SYMBOL_EXPORT __attribute__ ((visibility ("default"))) -#define BASE64_SYMBOL_PRIVATE __attribute__ ((visibility ("hidden"))) - -#else #define BASE64_SYMBOL_IMPORT #define BASE64_SYMBOL_EXPORT #define BASE64_SYMBOL_PRIVATE -#endif #if defined(BASE64_STATIC_DEFINE) #define BASE64_EXPORT diff --git a/src/top_status_source.cc b/src/top_status_source.cc index eb5a2fde..34670cf9 100644 --- a/src/top_status_source.cc +++ b/src/top_status_source.cc @@ -37,6 +37,7 @@ #include "lnav_config.hh" #include "logfile_sub_source.hh" #include "sql_util.hh" +#include "sqlitepp.client.hh" top_status_source::top_status_source() { @@ -70,76 +71,48 @@ top_status_source::update_time() this->update_time(tv); } -struct user_msg_stmt { - user_msg_stmt() - { - static const char* MSG_QUERY = R"( +static const char* MSG_QUERY = R"( SELECT message FROM lnav_user_notifications - WHERE expiration IS NULL OR expiration > datetime('now') + WHERE message IS NOT NULL AND + (expiration IS NULL OR expiration > datetime('now')) AND + (views IS NULL OR + json_contains(views, (SELECT name FROM lnav_top_view))) ORDER BY priority DESC LIMIT 1 )"; - auto& lnav_db = injector::get&, - sqlite_db_tag>(); - - auto retcode = sqlite3_prepare_v2( - lnav_db, MSG_QUERY, -1, this->ums_stmt.out(), nullptr); - - ensure(retcode == SQLITE_OK); +struct user_msg_stmt { + user_msg_stmt() + : ums_stmt( + prepare_stmt(injector::get&, + sqlite_db_tag>() + .in(), + MSG_QUERY) + .unwrap()) + { } - auto_mem ums_stmt{sqlite3_finalize}; + prepared_stmt ums_stmt; }; void top_status_source::update_user_msg() { static user_msg_stmt um_stmt; - static auto& lnav_db - = injector::get&, - sqlite_db_tag>(); auto& al = this->tss_fields[TSF_USER_MSG].get_value(); al.clear(); - auto* stmt = um_stmt.ums_stmt.in(); - sqlite3_reset(stmt); - - auto count = sqlite3_bind_parameter_count(stmt); - for (int lpc = 0; lpc < count; lpc++) { - const auto* name = sqlite3_bind_parameter_name(stmt, lpc + 1); - - if (name[0] == '$') { - const char* env_value; - - if ((env_value = getenv(&name[1])) != nullptr) { - sqlite3_bind_text(stmt, lpc + 1, env_value, -1, SQLITE_STATIC); - } - continue; - } - } - - auto step_res = sqlite3_step(stmt); - - switch (step_res) { - case SQLITE_OK: - case SQLITE_DONE: - break; - case SQLITE_ROW: { - int ncols = sqlite3_column_count(stmt); - for (int lpc = 0; lpc < ncols; lpc++) { - const auto* text = (const char*) sqlite3_column_text(stmt, lpc); - - al.with_ansi_string(text); - al.append(" "); - } - break; - } - default: { + um_stmt.ums_stmt.reset(); + auto fetch_res = um_stmt.ums_stmt.fetch_row(); + fetch_res.match( + [&al](const std::string& value) { + al.with_ansi_string(value); + al.append(" "); + }, + [](const prepared_stmt::end_of_rows&) {}, + [](const prepared_stmt::fetch_error& fe) { log_error("failed to execute user-message expression: %s", - sqlite3_errmsg(lnav_db)); - break; - } - } + fe.fe_msg.c_str()); + }); } diff --git a/src/view_helpers.cc b/src/view_helpers.cc index ba34ffc0..66c4a535 100644 --- a/src/view_helpers.cc +++ b/src/view_helpers.cc @@ -35,7 +35,6 @@ #include "document.sections.hh" #include "environ_vtab.hh" #include "help-md.h" -#include "help-txt.h" #include "intervaltree/IntervalTree.h" #include "lnav.hh" #include "lnav.indexing.hh" diff --git a/src/views_vtab.cc b/src/views_vtab.cc index 6cdf9de0..798c20e5 100644 --- a/src/views_vtab.cc +++ b/src/views_vtab.cc @@ -243,7 +243,7 @@ CREATE TABLE lnav_views ( sql_strftime(timestamp, sizeof(timestamp), top_time_opt.value(), - 'T'); + ' '); sqlite3_result_text( ctx, timestamp, -1, SQLITE_TRANSIENT); } else { @@ -304,7 +304,7 @@ CREATE TABLE lnav_views ( sql_strftime(timestamp, sizeof(timestamp), top_time_opt.value(), - 'T'); + ' '); tlm.tlm_time = timestamp; } } diff --git a/test/Makefile.am b/test/Makefile.am index 2be04c9e..1481ae6d 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -92,7 +92,6 @@ TEXT2C_OBJS = \ ../src/builtin-scripts.$(OBJEXT) \ ../src/builtin-sh-scripts.$(OBJEXT) \ ../src/default-formats.$(OBJEXT) \ - ../src/help-txt.$(OBJEXT) \ ../src/time_fmts.$(OBJEXT) LDADD = \ diff --git a/test/expected/expected.am b/test/expected/expected.am index 2693cf8a..1ae30a66 100644 --- a/test/expected/expected.am +++ b/test/expected/expected.am @@ -662,6 +662,8 @@ EXPECTED_FILES = \ $(srcdir)/%reldir%/test_sql_json_func.sh_7c01aaf09078aaa3f23d127f9e03a317dca066de.out \ $(srcdir)/%reldir%/test_sql_json_func.sh_80c97b22084a06fd765ad22c935616c578968d07.err \ $(srcdir)/%reldir%/test_sql_json_func.sh_80c97b22084a06fd765ad22c935616c578968d07.out \ + $(srcdir)/%reldir%/test_sql_json_func.sh_83d8615c9ce5dfab5e4373570c1b68b8608155f5.err \ + $(srcdir)/%reldir%/test_sql_json_func.sh_83d8615c9ce5dfab5e4373570c1b68b8608155f5.out \ $(srcdir)/%reldir%/test_sql_json_func.sh_8cae9740ddfd6ba4c865fca0117b7bea3bb556e5.err \ $(srcdir)/%reldir%/test_sql_json_func.sh_8cae9740ddfd6ba4c865fca0117b7bea3bb556e5.out \ $(srcdir)/%reldir%/test_sql_json_func.sh_8e229f1b5fa3d3803e9db2f295a8d1a490e1b3db.err \ diff --git a/test/expected/test_sql_json_func.sh_83d8615c9ce5dfab5e4373570c1b68b8608155f5.err b/test/expected/test_sql_json_func.sh_83d8615c9ce5dfab5e4373570c1b68b8608155f5.err new file mode 100644 index 00000000..e69de29b diff --git a/test/expected/test_sql_json_func.sh_83d8615c9ce5dfab5e4373570c1b68b8608155f5.out b/test/expected/test_sql_json_func.sh_83d8615c9ce5dfab5e4373570c1b68b8608155f5.out new file mode 100644 index 00000000..375f86a5 --- /dev/null +++ b/test/expected/test_sql_json_func.sh_83d8615c9ce5dfab5e4373570c1b68b8608155f5.out @@ -0,0 +1,2 @@ +Row 0: + Column json_contains('"hi"', 'hi there'): 0 diff --git a/test/test_sql_json_func.sh b/test/test_sql_json_func.sh index a13819d1..ead747f3 100644 --- a/test/test_sql_json_func.sh +++ b/test/test_sql_json_func.sh @@ -24,6 +24,10 @@ run_cap_test env TEST_COMMENT='contains1' ./drive_sql <