From c0ed59e61e65bc91b245d57a4c248b342255ee9d Mon Sep 17 00:00:00 2001 From: Timothy Stack Date: Sun, 28 Mar 2021 13:50:26 -0700 Subject: [PATCH] [sqlite] implement .dump SQL command --- NEWS | 1 + aminclude_static.am | 2 +- cmake/Hunter/config.cmake | 7 + configure.ac | 8 +- src/CMakeLists.txt | 5 + src/Makefile.am | 4 +- src/base/Makefile.am | 2 + src/base/humanize.time.cc | 121 +++++ src/base/humanize.time.hh | 17 + src/base/injector.bind.hh | 3 - src/base/injector.hh | 7 + src/base/time_util.hh | 12 + src/bound_tags.hh | 2 + src/command_executor.cc | 18 +- src/field_overlay_source.cc | 6 +- src/help_text.hh | 6 + src/help_text_formatter.cc | 35 ++ src/lnav.cc | 27 +- src/lnav_config.cc | 10 +- src/lnav_util.cc | 116 +---- src/lnav_util.hh | 40 +- src/log_format_loader.cc | 16 +- src/readline_callbacks.cc | 6 +- src/readline_curses.cc | 4 +- src/readline_curses.hh | 2 +- src/readline_highlighters.cc | 17 +- src/sql_commands.cc | 257 ++++++++++ src/sql_util.cc | 32 +- src/sql_util.hh | 1 + src/sqlite-extension-func.hh | 10 + src/third-party/sqlite/ext/dbdump.c | 726 ++++++++++++++++++++++++++++ test/CMakeLists.txt | 14 +- test/Makefile.am | 6 + test/drive_data_scanner.cc | 9 - test/drive_logfile.cc | 9 - test/drive_sql.cc | 34 +- test/drive_sql_anno.cc | 32 -- test/file_for_dot_read.sql | 4 + test/test_sql.sh | 20 + test/test_stubs.cc | 71 +++ test/test_top_status.cc | 9 - 41 files changed, 1436 insertions(+), 292 deletions(-) create mode 100644 src/base/humanize.time.cc create mode 100644 src/base/humanize.time.hh create mode 100644 src/sql_commands.cc create mode 100644 src/third-party/sqlite/ext/dbdump.c create mode 100644 test/file_for_dot_read.sql create mode 100644 test/test_stubs.cc diff --git a/NEWS b/NEWS index 1a96da0f..7f39d981 100644 --- a/NEWS +++ b/NEWS @@ -53,6 +53,7 @@ lnav v0.9.1: * The "generate_series()" SQLite extension is now included by default. One change from the standard implementation is that both the start and stop are required parameters. + * Added the ";.read" SQL command for executing a plain SQL file. Interface Changes: * When copying log lines, the file name and time offset will be included diff --git a/aminclude_static.am b/aminclude_static.am index 209c8bb5..1a3c5df4 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 Mar 23 22:11:31 PDT 2021 +# from AX_AM_MACROS_STATIC on Sun Mar 28 13:05:16 PDT 2021 # Code coverage diff --git a/cmake/Hunter/config.cmake b/cmake/Hunter/config.cmake index 77399da7..f731b2d9 100644 --- a/cmake/Hunter/config.cmake +++ b/cmake/Hunter/config.cmake @@ -5,3 +5,10 @@ hunter_config( CMAKE_ARGS EXTRA_FLAGS=--enable-unicode-properties --enable-jit --enable-utf ) + +hunter_config( + readline + VERSION 6.3 + CMAKE_ARGS + EXTRA_FLAGS=CFLAGS=-Wno-implicit-function-declaration +) diff --git a/configure.ac b/configure.ac index bc238961..e535c6cd 100644 --- a/configure.ac +++ b/configure.ac @@ -90,8 +90,8 @@ AC_CHECK_SIZEOF(size_t) AC_STRUCT_TIMEZONE AC_ARG_ENABLE([static], - AS_HELP_STRING([--disable-static], - [Disable static linking])) + AS_HELP_STRING([--enable-static], + [Enable static linking])) AC_SEARCH_LIBS(openpty, util) AC_SEARCH_LIBS(gzseek, z, [], [AC_MSG_ERROR([libz required to build])]) @@ -101,7 +101,7 @@ AC_SEARCH_LIBS(BZ2_bzopen, bz2, AC_SUBST(BZIP2_SUPPORT) AC_SEARCH_LIBS(dlopen, dl) AC_SEARCH_LIBS(backtrace, execinfo) -LIBCURL_CHECK_CONFIG([], [7.23.0], [], [], [test x"${enable_static}" != x"no"]) +LIBCURL_CHECK_CONFIG([], [7.23.0], [], []) # Sometimes, curses depends on these libraries being linked in... AC_ARG_ENABLE([tinfo], @@ -193,7 +193,7 @@ AS_VAR_SET(static_lib_list, AS_VAR_SET(static_lib_list, ["$static_lib_list libarchive.a"]) -if test x"${enable_static}" != x"no"; then +if test x"${enable_static}" = x"yes"; then case "$host_os" in darwin*) STATIC_LDFLAGS="$STATIC_LDFLAGS -Wl,-search_paths_first" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 9757c845..a09036a4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -266,6 +266,7 @@ add_library(diag STATIC hist_source.cc hotkeys.cc base/humanize.cc + base/humanize.time.cc input_dispatcher.cc base/intern_string.cc base/is_utf8.cc @@ -314,6 +315,7 @@ add_library(diag STATIC pcrepp/pcrepp.cc piper_proc.cc spectro_source.cc + sql_commands.cc sql_util.cc state-extension-functions.cc styling.cc @@ -358,6 +360,7 @@ add_library(diag STATIC spookyhash/SpookyV2.cpp third-party/sqlite/ext/series.c + third-party/sqlite/ext/dbdump.c all_logs_vtab.hh archive_manager.hh @@ -394,6 +397,7 @@ add_library(diag STATIC highlighter.hh hotkeys.hh base/humanize.hh + base/humanize.time.hh input_dispatcher.hh base/injector.hh base/injector.bind.hh @@ -440,6 +444,7 @@ add_library(diag STATIC shlex.hh simdutf8check.h spectro_source.hh + sql_util.hh strong_int.hh string_attr_type.hh sysclip.hh diff --git a/src/Makefile.am b/src/Makefile.am index f7df5d17..f3d292e8 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -248,9 +248,9 @@ noinst_HEADERS = \ shlex.hh \ simdutf8check.h \ spectro_source.hh \ - styling.hh \ sql_util.hh \ sqlite-extension-func.hh \ + styling.hh \ statusview_curses.hh \ string_attr_type.hh \ strnatcmp.h \ @@ -293,6 +293,7 @@ nodist_libdiag_a_SOURCES = \ $(LNAV_BUILT_FILES) THIRD_PARTY_SRCS = \ + third-party/sqlite/ext/dbdump.c \ third-party/sqlite/ext/series.c libdiag_a_SOURCES = \ @@ -372,6 +373,7 @@ libdiag_a_SOURCES = \ textfile_sub_source.cc \ timer.cc \ piper_proc.cc \ + sql_commands.cc \ sql_util.cc \ state-extension-functions.cc \ strnatcmp.c \ diff --git a/src/base/Makefile.am b/src/base/Makefile.am index a2d90f4e..eeb8b33f 100644 --- a/src/base/Makefile.am +++ b/src/base/Makefile.am @@ -20,6 +20,7 @@ noinst_HEADERS = \ func_util.hh \ future_util.hh \ humanize.hh \ + humanize.time.hh \ injector.hh \ injector.bind.hh \ intern_string.hh \ @@ -37,6 +38,7 @@ noinst_HEADERS = \ libbase_a_SOURCES = \ date_time_scanner.cc \ humanize.cc \ + humanize.time.cc \ intern_string.cc \ is_utf8.cc \ isc.cc \ diff --git a/src/base/humanize.time.cc b/src/base/humanize.time.cc new file mode 100644 index 00000000..d4f23cdf --- /dev/null +++ b/src/base/humanize.time.cc @@ -0,0 +1,121 @@ +/** + * Copyright (c) 2021, 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. + */ + +#include "config.h" + +#include "time_util.hh" +#include "humanize.time.hh" + +namespace humanize { +namespace time { + +std::string time_ago(time_t last_time, bool convert_local) +{ + time_t delta, current_time = ::time(nullptr); + const char *fmt; + char buffer[64]; + int amount; + + if (convert_local) { + current_time = convert_log_time_to_local(current_time); + } + + delta = current_time - last_time; + if (delta < 0) { + return "in the future"; + } else if (delta < 60) { + return "just now"; + } else if (delta < (60 * 2)) { + return "one minute ago"; + } else if (delta < (60 * 60)) { + fmt = "%d minutes ago"; + amount = delta / 60; + } else if (delta < (2 * 60 * 60)) { + return "one hour ago"; + } else if (delta < (24 * 60 * 60)) { + fmt = "%d hours ago"; + amount = delta / (60 * 60); + } else if (delta < (2 * 24 * 60 * 60)) { + return "one day ago"; + } else if (delta < (365 * 24 * 60 * 60)) { + fmt = "%d days ago"; + amount = delta / (24 * 60 * 60); + } else if (delta < (2 * 365 * 24 * 60 * 60)) { + return "over a year ago"; + } else { + fmt = "over %d years ago"; + amount = delta / (365 * 24 * 60 * 60); + } + + snprintf(buffer, sizeof(buffer), fmt, amount); + + return std::string(buffer); +} + +std::string precise_time_ago(const struct timeval &tv, bool convert_local) +{ + struct timeval now, diff; + + gettimeofday(&now, nullptr); + if (convert_local) { + now.tv_sec = convert_log_time_to_local(now.tv_sec); + } + + timersub(&now, &tv, &diff); + if (diff.tv_sec < 0) { + return time_ago(tv.tv_sec); + } else if (diff.tv_sec <= 1) { + return "a second ago"; + } else if (diff.tv_sec < (10 * 60)) { + char buf[64]; + + if (diff.tv_sec < 60) { + snprintf(buf, sizeof(buf), + "%2ld seconds ago", + diff.tv_sec); + } else { + time_t seconds = diff.tv_sec % 60; + time_t minutes = diff.tv_sec / 60; + + snprintf(buf, sizeof(buf), + "%2ld minute%s and %2ld second%s ago", + minutes, + minutes > 1 ? "s" : "", + seconds, + seconds == 1 ? "" : "s"); + } + + return std::string(buf); + } else { + return time_ago(tv.tv_sec, convert_local); + } +} + +} +} diff --git a/src/base/humanize.time.hh b/src/base/humanize.time.hh new file mode 100644 index 00000000..79d79d06 --- /dev/null +++ b/src/base/humanize.time.hh @@ -0,0 +1,17 @@ + +#ifndef lnav_humanize_time_hh +#define lnav_humanize_time_hh + +#include + +namespace humanize { +namespace time { + +std::string time_ago(time_t last_time, bool convert_local = false); + +std::string precise_time_ago(const struct timeval &tv, bool convert_local = false); + +} +} + +#endif diff --git a/src/base/injector.bind.hh b/src/base/injector.bind.hh index fb05e787..6ab493db 100644 --- a/src/base/injector.bind.hh +++ b/src/base/injector.bind.hh @@ -118,9 +118,6 @@ struct bind_multiple : multiple_storage { return *this; } - -private: - }; } diff --git a/src/base/injector.hh b/src/base/injector.hh index b868faad..4818c535 100644 --- a/src/base/injector.hh +++ b/src/base/injector.hh @@ -43,6 +43,9 @@ namespace injector { +template +void force_linking(Annotation anno); + template using void_t = void; @@ -56,11 +59,15 @@ struct has_injectable> : std::true_type template struct singleton_storage { static T *get() { + static int _[] = {0, (force_linking(Annotations{}), 0)...}; + (void)_; assert(ss_data != nullptr); return ss_data; } static std::shared_ptr create() { + static int _[] = {0, (force_linking(Annotations{}), 0)...}; + (void)_; return ss_factory(); } protected: diff --git a/src/base/time_util.hh b/src/base/time_util.hh index 52901f69..2d97e103 100644 --- a/src/base/time_util.hh +++ b/src/base/time_util.hh @@ -45,6 +45,18 @@ struct tm *secs2tm(time_t *tim_p, struct tm *res); time_t tm2sec(const struct tm *t); void secs2wday(const struct timeval &tv, struct tm *res); +inline +time_t convert_log_time_to_local(time_t value) { + struct tm tm; + + localtime_r(&value, &tm); +#ifdef HAVE_STRUCT_TM_TM_ZONE + tm.tm_zone = NULL; +#endif + tm.tm_isdst = 0; + return tm2sec(&tm); +} + constexpr time_t MAX_TIME_T = 4000000000LL; enum exttm_bits_t { diff --git a/src/bound_tags.hh b/src/bound_tags.hh index 05088f72..19097af4 100644 --- a/src/bound_tags.hh +++ b/src/bound_tags.hh @@ -34,4 +34,6 @@ struct last_relative_time_tag {}; +struct sql_cmd_map_tag {}; + #endif diff --git a/src/command_executor.cc b/src/command_executor.cc index 7b4a9862..84d8c608 100644 --- a/src/command_executor.cc +++ b/src/command_executor.cc @@ -40,6 +40,7 @@ #include "sql_util.hh" #include "lnav_config.hh" #include "service_tags.hh" +#include "bound_tags.hh" #include "command_executor.hh" #include "db_sub_source.hh" @@ -138,15 +139,20 @@ Result execute_sql(exec_context &ec, const string &sql, string & lnav_data.ld_bottom_source.grep_error(""); - if (stmt_str == ".schema") { - alt_msg = ""; + if (startswith(stmt_str, ".")) { + vector args; + split_ws(stmt_str, args); - ensure_view(&lnav_data.ld_views[LNV_SCHEMA]); + auto sql_cmd_map = injector::get< + readline_context::command_map_t *, sql_cmd_map_tag>(); + auto cmd_iter = sql_cmd_map->find(args[0]); - lnav_data.ld_mode = LNM_PAGING; - return Ok(string()); + if (cmd_iter != sql_cmd_map->end()) { + return cmd_iter->second->c_func(ec, stmt_str, args); + } } - else if (stmt_str == ".msgformats") { + + if (stmt_str == ".msgformats") { stmt_str = MSG_FORMAT_STMT; } diff --git a/src/field_overlay_source.cc b/src/field_overlay_source.cc index 252ad19d..de6c2cd4 100644 --- a/src/field_overlay_source.cc +++ b/src/field_overlay_source.cc @@ -29,6 +29,7 @@ #include "config.h" +#include "base/humanize.time.hh" #include "lnav_util.hh" #include "ansi_scrubber.hh" #include "vtab_module.hh" @@ -75,7 +76,8 @@ void field_overlay_source::build_summary_lines(const listview_curses &lv) first_line = lss.find_line(lss.at(vis_line_t(0))); last_line = lss.find_line(lss.at(lv.get_bottom())); - last_time = "Last message: " ANSI_BOLD_START + precise_time_ago( + last_time = "Last message: " ANSI_BOLD_START + + humanize::time::precise_time_ago( last_line->get_timeval(), true) + ANSI_NORM; duration2str(last_line->get_time_in_millis() - first_line->get_time_in_millis(), @@ -283,7 +285,7 @@ void field_overlay_source::build_field_lines(const listview_curses &lv) time_line.with_attr(string_attr(time_lr, &view_curses::VC_STYLE, A_BOLD)); time_str.append(" -- "); time_lr.lr_start = time_str.length(); - time_str.append(precise_time_ago(ll->get_timeval(), true)); + time_str.append(humanize::time::precise_time_ago(ll->get_timeval(), true)); time_lr.lr_end = time_str.length(); time_line.with_attr(string_attr(time_lr, &view_curses::VC_STYLE, A_BOLD)); diff --git a/src/help_text.hh b/src/help_text.hh index 63ba28bf..c714686a 100644 --- a/src/help_text.hh +++ b/src/help_text.hh @@ -39,6 +39,7 @@ enum class help_context_t { HC_PARAMETER, HC_RESULT, HC_COMMAND, + HC_SQL_COMMAND, HC_SQL_KEYWORD, HC_SQL_INFIX, HC_SQL_FUNCTION, @@ -122,6 +123,11 @@ struct help_text { return *this; }; + help_text &sql_command() noexcept { + this->ht_context = help_context_t::HC_SQL_COMMAND; + return *this; + }; + help_text &sql_keyword() noexcept { this->ht_context = help_context_t::HC_SQL_KEYWORD; return *this; diff --git a/src/help_text_formatter.cc b/src/help_text_formatter.cc index aa3c8ba2..797703e4 100644 --- a/src/help_text_formatter.cc +++ b/src/help_text_formatter.cc @@ -174,6 +174,38 @@ void format_help_text_for_term(const help_text &ht, size_t width, attr_line_t &o .append("\n"); break; } + case help_context_t::HC_SQL_COMMAND: { + out.append("Synopsis", &view_curses::VC_STYLE, A_UNDERLINE) + .append("\n") + .append(body_indent, ' ') + .append(";") + .append(ht.ht_name, &view_curses::VC_STYLE, A_BOLD); + for (auto ¶m : ht.ht_parameters) { + out.append(" "); + if (param.ht_nargs == help_nargs_t::HN_OPTIONAL) { + out.append("["); + } + out.append(param.ht_name, &view_curses::VC_STYLE, A_UNDERLINE); + if (param.ht_nargs == help_nargs_t::HN_OPTIONAL) { + out.append("]"); + } + if (param.ht_nargs == help_nargs_t::HN_ONE_OR_MORE) { + out.append("1", &view_curses::VC_STYLE, A_UNDERLINE); + out.append(" ["); + out.append("...", &view_curses::VC_STYLE, A_UNDERLINE); + out.append(" "); + out.append(param.ht_name, &view_curses::VC_STYLE, + A_UNDERLINE); + out.append("N", &view_curses::VC_STYLE, A_UNDERLINE); + out.append("]"); + } + } + out.append(" - ") + .append(attr_line_t::from_ansi_str(ht.ht_summary), + &tws.with_indent(body_indent + 2)) + .append("\n"); + break; + } case help_context_t::HC_SQL_INFIX: case help_context_t::HC_SQL_KEYWORD: { size_t line_start = body_indent; @@ -501,6 +533,9 @@ void format_help_text_for_rst(const help_text &ht, case help_context_t::HC_COMMAND: prefix = ":"; break; + case help_context_t::HC_SQL_COMMAND: + prefix = ";"; + break; case help_context_t::HC_SQL_FUNCTION: case help_context_t::HC_SQL_TABLE_VALUED_FUNCTION: is_sql = is_sql_func = true; diff --git a/src/lnav.cc b/src/lnav.cc index 928f4327..8ac1aa4f 100644 --- a/src/lnav.cc +++ b/src/lnav.cc @@ -80,6 +80,7 @@ #include "init-sql.h" #include "logfile.hh" #include "base/func_util.hh" +#include "base/humanize.time.hh" #include "base/injector.bind.hh" #include "base/isc.hh" #include "base/string_util.hh" @@ -252,6 +253,16 @@ static auto bound_curl = injector::bind_multiple() .add_singleton(); +template<> +void injector::force_linking(last_relative_time_tag anno) +{ +} + +template<> +void injector::force_linking(services::curl_streamer_t anno) +{ +} + bool setup_logline_table(exec_context &ec) { // Hidden columns don't show up in the table_info pragma. @@ -264,13 +275,6 @@ bool setup_logline_table(exec_context &ec) nullptr }; - static const char *commands[] = { - ".schema", - ".msgformats", - - nullptr - }; - textview_curses &log_view = lnav_data.ld_views[LNV_LOG]; bool retval = false; bool update_possibilities = ( @@ -326,7 +330,6 @@ bool setup_logline_table(exec_context &ec) lnav_data.ld_rl_view->add_possibility(LNM_SQL, "*", sql_function_names); lnav_data.ld_rl_view->add_possibility(LNM_SQL, "*", hidden_table_columns); - lnav_data.ld_rl_view->add_possibility(LNM_SQL, "*", commands); for (int lpc = 0; sqlite_registration_funcs[lpc]; lpc++) { struct FuncDef *basic_funcs; @@ -1133,7 +1136,9 @@ static void wait_for_pipers() static void looper() { try { - exec_context &ec = lnav_data.ld_exec_context; + auto sql_cmd_map = injector::get< + readline_context::command_map_t*, sql_cmd_map_tag>(); + auto& ec = lnav_data.ld_exec_context; readline_context command_context("cmd", &lnav_commands); @@ -1141,7 +1146,7 @@ static void looper() readline_context search_filters_context("search-filters", nullptr, false); readline_context search_files_context("search-files", nullptr, false); readline_context index_context("capture"); - readline_context sql_context("sql", nullptr, false); + readline_context sql_context("sql", sql_cmd_map, false); readline_context exec_context("exec"); readline_context user_context("user"); readline_curses rlc; @@ -1430,7 +1435,7 @@ static void looper() if (session_data.sd_save_time) { std::string ago; - ago = time_ago(session_data.sd_save_time); + ago = humanize::time::time_ago(session_data.sd_save_time); lnav_data.ld_rl_view->set_value( ("restored session from " ANSI_BOLD_START) + ago + diff --git a/src/lnav_config.cc b/src/lnav_config.cc index 97cbe8ad..e097bcce 100644 --- a/src/lnav_config.cc +++ b/src/lnav_config.cc @@ -979,11 +979,15 @@ Result detect_config_file_type(const ghc::filesystem::path &path) { static const char *id_path[] = {"$schema", nullptr}; - string content; - if (!read_file(path.string(), content)) { - return Err(fmt::format("unable to open file: {}", path.string())); + auto read_res = read_file(path); + + if (read_res.isErr()) { + return Err(fmt::format("unable to open file: {} -- {}", + path.string(), read_res.unwrapErr())); } + + auto content = read_res.unwrap(); if (startswith(content, "#")) { content.insert(0, "//"); } diff --git a/src/lnav_util.cc b/src/lnav_util.cc index c85f7413..bce2ce60 100644 --- a/src/lnav_util.cc +++ b/src/lnav_util.cc @@ -45,101 +45,6 @@ using namespace std; -std::string time_ago(time_t last_time, bool convert_local) -{ - time_t delta, current_time = time(nullptr); - const char *fmt; - char buffer[64]; - int amount; - - if (convert_local) { - current_time = convert_log_time_to_local(current_time); - } - - delta = current_time - last_time; - if (delta < 0) { - return "in the future"; - } - else if (delta < 60) { - return "just now"; - } - else if (delta < (60 * 2)) { - return "one minute ago"; - } - else if (delta < (60 * 60)) { - fmt = "%d minutes ago"; - amount = delta / 60; - } - else if (delta < (2 * 60 * 60)) { - return "one hour ago"; - } - else if (delta < (24 * 60 * 60)) { - fmt = "%d hours ago"; - amount = delta / (60 * 60); - } - else if (delta < (2 * 24 * 60 * 60)) { - return "one day ago"; - } - else if (delta < (365 * 24 * 60 * 60)) { - fmt = "%d days ago"; - amount = delta / (24 * 60 * 60); - } - else if (delta < (2 * 365 * 24 * 60 * 60)) { - return "over a year ago"; - } - else { - fmt = "over %d years ago"; - amount = delta / (365 * 24 * 60 * 60); - } - - snprintf(buffer, sizeof(buffer), fmt, amount); - - return std::string(buffer); -} - -std::string precise_time_ago(const struct timeval &tv, bool convert_local) -{ - struct timeval now, diff; - - gettimeofday(&now, nullptr); - if (convert_local) { - now.tv_sec = convert_log_time_to_local(now.tv_sec); - } - - timersub(&now, &tv, &diff); - if (diff.tv_sec < 0) { - return time_ago(tv.tv_sec); - } - else if (diff.tv_sec <= 1) { - return "a second ago"; - } - else if (diff.tv_sec < (10 * 60)) { - char buf[64]; - - if (diff.tv_sec < 60) { - snprintf(buf, sizeof(buf), - "%2ld seconds ago", - diff.tv_sec); - } - else { - time_t seconds = diff.tv_sec % 60; - time_t minutes = diff.tv_sec / 60; - - snprintf(buf, sizeof(buf), - "%2ld minute%s and %2ld second%s ago", - minutes, - minutes > 1 ? "s" : "", - seconds, - seconds == 1 ? "" : "s"); - } - - return string(buf); - } - else { - return time_ago(tv.tv_sec, convert_local); - } -} - bool change_to_parent_dir() { bool retval = false; @@ -211,17 +116,22 @@ string build_path(const vector &paths) return retval; } -bool read_file(const ghc::filesystem::path &filename, string &out) +Result read_file(const ghc::filesystem::path &path) { - std::ifstream sql_file(filename.string()); + try { + ghc::filesystem::ifstream file_stream(path); - if (sql_file) { - out.assign((std::istreambuf_iterator(sql_file)), - std::istreambuf_iterator()); - return true; + if (!file_stream) { + return Err(std::string(strerror(errno))); + } + + std::string retval; + retval.assign((std::istreambuf_iterator(file_stream)), + std::istreambuf_iterator()); + return Ok(retval); + } catch (const std::exception& e) { + return Err(std::string(e.what())); } - - return false; } Result, std::string> diff --git a/src/lnav_util.hh b/src/lnav_util.hh index eeb13739..3ecf13c6 100644 --- a/src/lnav_util.hh +++ b/src/lnav_util.hh @@ -57,10 +57,6 @@ #include "fmt/format.h" #include "ghc/filesystem.hpp" -std::string time_ago(time_t last_time, bool convert_local = false); - -std::string precise_time_ago(const struct timeval &tv, bool convert_local = false); - #if SIZEOF_OFF_T == 8 #define FORMAT_OFF_T "%qd" #elif SIZEOF_OFF_T == 4 @@ -111,28 +107,6 @@ private: SpookyHash h_context; }; -template -struct object_field_t { - object_field_t(UnaryFunction &func, Member &mem) - : of_func(func), of_mem(mem) {}; - - template - void operator()(Object obj) - { - this->of_func(obj.*(this->of_mem)); - }; - - UnaryFunction &of_func; - Member of_mem; -}; - -template -object_field_t object_field(UnaryFunction &func, - Member mem) -{ - return object_field_t(func, mem); -} - bool change_to_parent_dir(); bool next_format(const char * const fmt[], int &index, int &locked_index); @@ -151,19 +125,7 @@ inline bool is_glob(const char *fn) std::string build_path(const std::vector &paths); -bool read_file(const ghc::filesystem::path &path, std::string &out); - -inline -time_t convert_log_time_to_local(time_t value) { - struct tm tm; - - localtime_r(&value, &tm); -#ifdef HAVE_STRUCT_TM_TM_ZONE - tm.tm_zone = NULL; -#endif - tm.tm_isdst = 0; - return tm2sec(&tm); -} +Result read_file(const ghc::filesystem::path &path); template size_t strtonum(T &num_out, const char *data, size_t len); diff --git a/src/log_format_loader.cc b/src/log_format_loader.cc index f9f6aa62..1b60e11b 100644 --- a/src/log_format_loader.cc +++ b/src/log_format_loader.cc @@ -1058,19 +1058,19 @@ static void exec_sql_in_path(sqlite3 *db, const ghc::filesystem::path &path, std log_info("executing SQL files in path: %s", format_path.c_str()); if (glob(format_path.c_str(), 0, nullptr, gl.inout()) == 0) { for (int lpc = 0; lpc < (int)gl->gl_pathc; lpc++) { - string filename(gl->gl_pathv[lpc]); - string content; + auto filename = ghc::filesystem::path(gl->gl_pathv[lpc]); + auto read_res = read_file(filename); - if (read_file(filename, content)) { + if (read_res.isOk()) { log_info("Executing SQL file: %s", filename.c_str()); + auto content = read_res.unwrap(); + sql_execute_script(db, filename.c_str(), content.c_str(), errors); } else { - errors.push_back( - "error:unable to read file '" + - filename + - "' -- " + - string(strerror(errno))); + errors.push_back(fmt::format( + "error:unable to read file '{}' -- {}", + filename.string(), read_res.unwrapErr())); } } } diff --git a/src/readline_callbacks.cc b/src/readline_callbacks.cc index 27cfc284..76d2266e 100644 --- a/src/readline_callbacks.cc +++ b/src/readline_callbacks.cc @@ -183,10 +183,8 @@ bool rl_sql_help(readline_curses *rc) lnav_data.ld_doc_source.replace_with(doc_al); dtc.reload_data(); - if (!ex_al.empty()) { - lnav_data.ld_example_source.replace_with(ex_al); - etc.reload_data(); - } + lnav_data.ld_example_source.replace_with(ex_al); + etc.reload_data(); has_doc = true; } diff --git a/src/readline_curses.cc b/src/readline_curses.cc index c71ea269..c239abfb 100644 --- a/src/readline_curses.cc +++ b/src/readline_curses.cc @@ -502,6 +502,8 @@ readline_curses::readline_curses() readline_curses::~readline_curses() { + this->rc_pty[RCF_MASTER].reset(); + this->rc_command_pipe[RCF_MASTER].reset(); if (this->rc_child == 0) { _exit(0); } @@ -509,7 +511,7 @@ readline_curses::~readline_curses() int status; log_debug("term child %d", this->rc_child); - kill(this->rc_child, SIGTERM); + log_perror(kill(this->rc_child, SIGTERM)); this->rc_child = -1; while (wait(&status) < 0 && (errno == EINTR)) { diff --git a/src/readline_curses.hh b/src/readline_curses.hh index b925aaa8..8fe1c27f 100644 --- a/src/readline_curses.hh +++ b/src/readline_curses.hh @@ -86,7 +86,7 @@ public: _command_t(const char *name, command_func_t func, - help_text help, + help_text help = {}, prompt_func_t prompt = nullptr) noexcept : c_name(name), c_func(func), c_help(std::move(help)), c_prompt(prompt) {}; diff --git a/src/readline_highlighters.cc b/src/readline_highlighters.cc index 6beea286..46db8f88 100644 --- a/src/readline_highlighters.cc +++ b/src/readline_highlighters.cc @@ -439,7 +439,7 @@ void readline_command_highlighter(attr_line_t &al, int x) static void readline_sqlite_highlighter_int(attr_line_t &al, int x, int skip) { - static string keyword_re_str = sql_keyword_re() + "|\\.schema|\\.msgformats"; + static string keyword_re_str = sql_keyword_re(); static pcrepp keyword_pcre(keyword_re_str.c_str(), PCRE_CASELESS); static pcrepp string_literal_pcre("'[^']*('(?:'[^']*')*|$)"); static pcrepp ident_pcre("(?:\\$|:)?(\\b[a-z_]\\w*)|\"([^\"]+)\"|\\[([^\\]]+)]", PCRE_CASELESS); @@ -451,7 +451,7 @@ static void readline_sqlite_highlighter_int(attr_line_t &al, int x, int skip) nullptr }; - view_colors &vc = view_colors::singleton(); + auto &vc = view_colors::singleton(); int keyword_attrs = vc.attrs_for_role(view_colors::VCR_KEYWORD); int symbol_attrs = vc.attrs_for_role(view_colors::VCR_SYMBOL); @@ -460,7 +460,18 @@ static void readline_sqlite_highlighter_int(attr_line_t &al, int x, int skip) pcre_context_static<30> pc; pcre_input pi(al.get_string(), skip); - string &line = al.get_string(); + auto &line = al.get_string(); + + if (startswith(line, ";.")) { + auto space = line.find(' '); + struct line_range lr{2, -1}; + + if (space != std::string::npos) { + lr.lr_end = space; + } + al.get_attrs().emplace_back(lr, &view_curses::VC_STYLE, keyword_attrs); + return; + } while (ident_pcre.match(pc, pi)) { pcre_context::capture_t *cap = pc.first_valid(); diff --git a/src/sql_commands.cc b/src/sql_commands.cc new file mode 100644 index 00000000..c04efd19 --- /dev/null +++ b/src/sql_commands.cc @@ -0,0 +1,257 @@ +/** + * Copyright (c) 2021, 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. + */ + +#include "config.h" + +#include "base/lnav_log.hh" +#include "lnav.hh" +#include "lnav_util.hh" +#include "bound_tags.hh" +#include "base/injector.bind.hh" +#include "readline_curses.hh" +#include "sqlite-extension-func.hh" + +static +Result sql_cmd_dump( + exec_context &ec, std::string cmdline, std::vector &args) +{ + std::string retval; + + if (args.empty()) { + args.emplace_back("filename"); + args.emplace_back("tables"); + return Ok(retval); + } + + if (args.size() < 2) { + return ec.make_error("expecting a file name to write to"); + } + + auto_mem file(fclose); + + if ((file = fopen(args[1].c_str(), "w+")) == nullptr) { + return ec.make_error("unable to open '{}' for writing: {}", + args[1], strerror(errno)); + } + + for (size_t lpc = 2; lpc < args.size(); lpc++) { + sqlite3_db_dump(lnav_data.ld_db.in(), + "main", + args[lpc].c_str(), + (int (*)(const char *, void*)) fputs, + file.in()); + } + + retval = "generated"; + return Ok(retval); +} + +static +Result sql_cmd_read( + exec_context &ec, std::string cmdline, std::vector &args) +{ + std::string retval; + + if (args.empty()) { + args.emplace_back("filename"); + return Ok(retval); + } + + std::vector split_args; + shlex lexer(cmdline); + + if (!lexer.split(split_args, ec.create_resolver())) { + return ec.make_error("unable to parse arguments"); + } + + for (size_t lpc = 1; lpc < split_args.size(); lpc++) { + auto read_res = read_file(split_args[lpc]); + + if (read_res.isErr()) { + return ec.make_error("unable to read script file: {} -- {}", + split_args[lpc], + read_res.unwrapErr()); + } + + auto script = read_res.unwrap(); + auto_mem stmt(sqlite3_finalize); + const char *start = script.c_str(); + + do { + const char *tail; + auto rc = sqlite3_prepare_v2(lnav_data.ld_db.in(), + start, + -1, + stmt.out(), + &tail); + + if (rc != SQLITE_OK) { + const char *errmsg = sqlite3_errmsg(lnav_data.ld_db); + + return ec.make_error("{}", errmsg); + } + + if (stmt.in() != nullptr) { + std::string alt_msg; + auto exec_res = execute_sql(ec, + std::string(start, tail - start), + alt_msg); + if (exec_res.isErr()) { + return exec_res; + } + } + + start = tail; + } while (start[0]); + } + + if (lnav_data.ld_flags & LNF_HEADLESS) { + if (ec.ec_local_vars.size() == 1) { + ensure_view(&lnav_data.ld_views[LNV_DB]); + } + } else if (lnav_data.ld_db_row_source.dls_rows.size() > 1) { + ensure_view(&lnav_data.ld_views[LNV_DB]); + } + return Ok(retval); +} + +static +Result sql_cmd_schema( + exec_context &ec, std::string cmdline, std::vector &args) +{ + std::string retval; + + if (args.empty()) { + return Ok(retval); + } + + ensure_view(&lnav_data.ld_views[LNV_SCHEMA]); + + return Ok(retval); +} + +static +Result sql_cmd_generic( + exec_context &ec, std::string cmdline, std::vector &args) +{ + std::string retval; + + if (args.empty()) { + args.emplace_back("*"); + return Ok(retval); + } + + return Ok(retval); +} + +static readline_context::command_t sql_commands[] = { + { + ".dump", + sql_cmd_dump, + help_text(".dump", + "Dump the contents of the database") + .sql_command() + .with_parameter({"path", "The path to the file to write"}) + .with_tags({"io",}), + }, + { + ".msgformats", + sql_cmd_schema, + help_text(".msgformats", "df") + .sql_command(), + }, + { + ".read", + sql_cmd_read, + help_text(".read", + "Switch to the SCHEMA view that contains a dump of the " + "current database schema") + .sql_command(), + }, + { + ".schema", + sql_cmd_schema, + help_text(".schema", + "Switch to the SCHEMA view that contains a dump of the " + "current database schema") + .sql_command(), + }, + { + "ATTACH", + sql_cmd_generic, + }, + { + "CREATE", + sql_cmd_generic, + }, + { + "DELETE", + sql_cmd_generic, + }, + { + "DETACH", + sql_cmd_generic, + }, + { + "DROP", + sql_cmd_generic, + }, + { + "INSERT", + sql_cmd_generic, + }, + { + "SELECT", + sql_cmd_generic, + }, + { + "UPDATE", + sql_cmd_generic, + }, + { + "WITH", + sql_cmd_generic, + }, +}; + +static readline_context::command_map_t sql_cmd_map; + +static auto bound_sql_cmd_map = injector::bind< + readline_context::command_map_t, sql_cmd_map_tag>::to_instance(+[]() { + for (auto& cmd : sql_commands) { + sql_cmd_map[cmd.c_name] = &cmd; + } + + return &sql_cmd_map; +}); + +template<> +void injector::force_linking(sql_cmd_map_tag anno) +{ +} diff --git a/src/sql_util.cc b/src/sql_util.cc index e4d6b36c..3ba30eaa 100644 --- a/src/sql_util.cc +++ b/src/sql_util.cc @@ -41,10 +41,13 @@ #include "auto_mem.hh" #include "sql_util.hh" +#include "base/injector.hh" #include "base/string_util.hh" #include "base/lnav_log.hh" #include "base/time_util.hh" #include "pcrepp/pcrepp.hh" +#include "readline_curses.hh" +#include "bound_tags.hh" #include "sqlite-extension-func.hh" using namespace std; @@ -886,6 +889,7 @@ string sql_keyword_re(void) return retval; } +string_attr_type SQL_COMMAND_ATTR("sql_command"); string_attr_type SQL_KEYWORD_ATTR("sql_keyword"); string_attr_type SQL_IDENTIFIER_ATTR("sql_ident"); string_attr_type SQL_FUNCTION_ATTR("sql_func"); @@ -897,16 +901,16 @@ string_attr_type SQL_GARBAGE_ATTR("sql_garbage"); void annotate_sql_statement(attr_line_t &al) { - static string keyword_re_str = - R"(\A)" + sql_keyword_re() + R"(|\.schema|\.msgformats)"; + static string keyword_re_str = R"(\A)" + sql_keyword_re(); static struct { pcrepp re; string_attr_type_t type; } PATTERNS[] = { + { pcrepp{R"(^(\.\w+))"}, &SQL_COMMAND_ATTR }, { pcrepp{R"(\A,)"}, &SQL_COMMA_ATTR }, { pcrepp{R"(\A\(|\A\))"}, &SQL_PAREN_ATTR }, - { pcrepp{keyword_re_str.c_str(), PCRE_CASELESS}, &SQL_KEYWORD_ATTR }, + { pcrepp{keyword_re_str, PCRE_CASELESS}, &SQL_KEYWORD_ATTR }, { pcrepp{R"(\A'[^']*('(?:'[^']*')*|$))"}, &SQL_STRING_ATTR }, { pcrepp{R"(\A(\$?\b[a-z_]\w*)|\"([^\"]+)\"|\[([^\]]+)])", PCRE_CASELESS}, &SQL_IDENTIFIER_ATTR }, { pcrepp{R"(\A(\*|<|>|=|!|\-|\+|\|\|))"}, &SQL_OPERATOR_ATTR }, @@ -917,14 +921,14 @@ void annotate_sql_statement(attr_line_t &al) pcre_context_static<30> pc; pcre_input pi(al.get_string()); - string &line = al.get_string(); - string_attrs_t &sa = al.get_attrs(); + auto &line = al.get_string(); + auto &sa = al.get_attrs(); while (pi.pi_next_offset < line.length()) { if (ws_pattern.match(pc, pi, PCRE_ANCHORED)) { continue; } - for (auto &pat : PATTERNS) { + for (const auto &pat : PATTERNS) { if (pat.re.match(pc, pi, PCRE_ANCHORED)) { pcre_context::capture_t *cap = pc.all(); struct line_range lr(cap->c_begin, cap->c_end); @@ -990,8 +994,24 @@ vector find_sql_help_for_line(const attr_line_t &al, size_t x x = al.nearest_text(x); + { + auto sa_opt = get_string_attr(al.get_attrs(), &SQL_COMMAND_ATTR); + + if (sa_opt) { + auto sql_cmd_map = injector::get< + readline_context::command_map_t*, sql_cmd_map_tag>(); + auto cmd_name = al.get_substring((*sa_opt)->sa_range); + auto cmd_iter = sql_cmd_map->find(cmd_name); + + if (cmd_iter != sql_cmd_map->end()) { + return {&cmd_iter->second->c_help}; + } + } + } + vector kw; auto iter = rfind_string_attr_if(sa, x, [&al, &name, &kw, x](auto sa) { + if (sa.sa_type != &SQL_FUNCTION_ATTR && sa.sa_type != &SQL_KEYWORD_ATTR) { return false; diff --git a/src/sql_util.hh b/src/sql_util.hh index 13577f9c..56e7163c 100644 --- a/src/sql_util.hh +++ b/src/sql_util.hh @@ -108,6 +108,7 @@ int sqlite_authorizer(void* pUserData, int action_code, const char *detail1, const char *detail2, const char *detail3, const char *detail4); +extern string_attr_type SQL_COMMAND_ATTR; extern string_attr_type SQL_KEYWORD_ATTR; extern string_attr_type SQL_IDENTIFIER_ATTR; extern string_attr_type SQL_FUNCTION_ATTR; diff --git a/src/sqlite-extension-func.hh b/src/sqlite-extension-func.hh index db09c2e6..e877f288 100644 --- a/src/sqlite-extension-func.hh +++ b/src/sqlite-extension-func.hh @@ -91,4 +91,14 @@ extern sqlite_registration_func_t sqlite_registration_funcs[]; int register_sqlite_funcs(sqlite3 *db, sqlite_registration_func_t *reg_funcs); +extern "C" { +int sqlite3_db_dump( + sqlite3 *db, /* The database connection */ + const char *zSchema, /* Which schema to dump. Usually "main". */ + const char *zTable, /* Which table to dump. NULL means everything. */ + int (*xCallback)(const char*,void*), /* Output sent to this callback */ + void *pArg /* Second argument of the callback */ +); +} + #endif diff --git a/src/third-party/sqlite/ext/dbdump.c b/src/third-party/sqlite/ext/dbdump.c new file mode 100644 index 00000000..fb45c6a8 --- /dev/null +++ b/src/third-party/sqlite/ext/dbdump.c @@ -0,0 +1,726 @@ +/* +** 2016-03-13 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This file implements a C-language subroutine that converts the content +** of an SQLite database into UTF-8 text SQL statements that can be used +** to exactly recreate the original database. ROWID values are preserved. +** +** A prototype of the implemented subroutine is this: +** +** int sqlite3_db_dump( +** sqlite3 *db, +** const char *zSchema, +** const char *zTable, +** void (*xCallback)(void*, const char*), +** void *pArg +** ); +** +** The db parameter is the database connection. zSchema is the schema within +** that database which is to be dumped. Usually the zSchema is "main" but +** can also be "temp" or any ATTACH-ed database. If zTable is not NULL, then +** only the content of that one table is dumped. If zTable is NULL, then all +** tables are dumped. +** +** The generate text is passed to xCallback() in multiple calls. The second +** argument to xCallback() is a copy of the pArg parameter. The first +** argument is some of the output text that this routine generates. The +** signature to xCallback() is designed to make it compatible with fputs(). +** +** The sqlite3_db_dump() subroutine returns SQLITE_OK on success or some error +** code if it encounters a problem. +** +** If this file is compiled with -DDBDUMP_STANDALONE then a "main()" routine +** is included so that this routine becomes a command-line utility. The +** command-line utility takes two or three arguments which are the name +** of the database file, the schema, and optionally the table, forming the +** first three arguments of a single call to the library routine. +*/ +#include "sqlite3.h" +#include +#include +#include + +/* +** The state of the dump process. +*/ +typedef struct DState DState; +struct DState { + sqlite3 *db; /* The database connection */ + int nErr; /* Number of errors seen so far */ + int rc; /* Error code */ + int writableSchema; /* True if in writable_schema mode */ + int (*xCallback)(const char*,void*); /* Send output here */ + void *pArg; /* Argument to xCallback() */ +}; + +/* +** A variable length string to which one can append text. +*/ +typedef struct DText DText; +struct DText { + char *z; /* The text */ + int n; /* Number of bytes of content in z[] */ + int nAlloc; /* Number of bytes allocated to z[] */ +}; + +/* +** Initialize and destroy a DText object +*/ +static void initText(DText *p){ + memset(p, 0, sizeof(*p)); +} +static void freeText(DText *p){ + sqlite3_free(p->z); + initText(p); +} + +/* zIn is either a pointer to a NULL-terminated string in memory obtained +** from malloc(), or a NULL pointer. The string pointed to by zAppend is +** added to zIn, and the result returned in memory obtained from malloc(). +** zIn, if it was not NULL, is freed. +** +** If the third argument, quote, is not '\0', then it is used as a +** quote character for zAppend. +*/ +static void appendText(DText *p, char const *zAppend, char quote){ + int len; + int i; + int nAppend = (int)(strlen(zAppend) & 0x3fffffff); + + len = nAppend+p->n+1; + if( quote ){ + len += 2; + for(i=0; in+len>=p->nAlloc ){ + char *zNew; + p->nAlloc = p->nAlloc*2 + len + 20; + zNew = sqlite3_realloc(p->z, p->nAlloc); + if( zNew==0 ){ + freeText(p); + return; + } + p->z = zNew; + } + + if( quote ){ + char *zCsr = p->z+p->n; + *zCsr++ = quote; + for(i=0; in = (int)(zCsr - p->z); + *zCsr = '\0'; + }else{ + memcpy(p->z+p->n, zAppend, nAppend); + p->n += nAppend; + p->z[p->n] = '\0'; + } +} + +/* +** Attempt to determine if identifier zName needs to be quoted, either +** because it contains non-alphanumeric characters, or because it is an +** SQLite keyword. Be conservative in this estimate: When in doubt assume +** that quoting is required. +** +** Return '"' if quoting is required. Return 0 if no quoting is required. +*/ +static char quoteChar(const char *zName){ + int i; + if( !isalpha((unsigned char)zName[0]) && zName[0]!='_' ) return '"'; + for(i=0; zName[i]; i++){ + if( !isalnum((unsigned char)zName[i]) && zName[i]!='_' ) return '"'; + } + return sqlite3_keyword_check(zName, i) ? '"' : 0; +} + + +/* +** Release memory previously allocated by tableColumnList(). +*/ +static void freeColumnList(char **azCol){ + int i; + for(i=1; azCol[i]; i++){ + sqlite3_free(azCol[i]); + } + /* azCol[0] is a static string */ + sqlite3_free(azCol); +} + +/* +** Return a list of pointers to strings which are the names of all +** columns in table zTab. The memory to hold the names is dynamically +** allocated and must be released by the caller using a subsequent call +** to freeColumnList(). +** +** The azCol[0] entry is usually NULL. However, if zTab contains a rowid +** value that needs to be preserved, then azCol[0] is filled in with the +** name of the rowid column. +** +** The first regular column in the table is azCol[1]. The list is terminated +** by an entry with azCol[i]==0. +*/ +static char **tableColumnList(DState *p, const char *zTab){ + char **azCol = 0; + sqlite3_stmt *pStmt = 0; + char *zSql; + int nCol = 0; + int nAlloc = 0; + int nPK = 0; /* Number of PRIMARY KEY columns seen */ + int isIPK = 0; /* True if one PRIMARY KEY column of type INTEGER */ + int preserveRowid = 1; + int rc; + + zSql = sqlite3_mprintf("PRAGMA table_info=%Q", zTab); + if( zSql==0 ) return 0; + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + if( rc ) return 0; + while( sqlite3_step(pStmt)==SQLITE_ROW ){ + if( nCol>=nAlloc-2 ){ + char **azNew; + nAlloc = nAlloc*2 + nCol + 10; + azNew = sqlite3_realloc64(azCol, nAlloc*sizeof(azCol[0])); + if( azNew==0 ) goto col_oom; + azCol = azNew; + azCol[0] = 0; + } + azCol[++nCol] = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 1)); + if( azCol[nCol]==0 ) goto col_oom; + if( sqlite3_column_int(pStmt, 5) ){ + nPK++; + if( nPK==1 + && sqlite3_stricmp((const char*)sqlite3_column_text(pStmt,2), + "INTEGER")==0 + ){ + isIPK = 1; + }else{ + isIPK = 0; + } + } + } + sqlite3_finalize(pStmt); + pStmt = 0; + azCol[nCol+1] = 0; + + /* The decision of whether or not a rowid really needs to be preserved + ** is tricky. We never need to preserve a rowid for a WITHOUT ROWID table + ** or a table with an INTEGER PRIMARY KEY. We are unable to preserve + ** rowids on tables where the rowid is inaccessible because there are other + ** columns in the table named "rowid", "_rowid_", and "oid". + */ + if( isIPK ){ + /* If a single PRIMARY KEY column with type INTEGER was seen, then it + ** might be an alise for the ROWID. But it might also be a WITHOUT ROWID + ** table or a INTEGER PRIMARY KEY DESC column, neither of which are + ** ROWID aliases. To distinguish these cases, check to see if + ** there is a "pk" entry in "PRAGMA index_list". There will be + ** no "pk" index if the PRIMARY KEY really is an alias for the ROWID. + */ + zSql = sqlite3_mprintf("SELECT 1 FROM pragma_index_list(%Q)" + " WHERE origin='pk'", zTab); + if( zSql==0 ) goto col_oom; + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + sqlite3_free(zSql); + if( rc ){ + freeColumnList(azCol); + return 0; + } + rc = sqlite3_step(pStmt); + sqlite3_finalize(pStmt); + pStmt = 0; + preserveRowid = rc==SQLITE_ROW; + } + if( preserveRowid ){ + /* Only preserve the rowid if we can find a name to use for the + ** rowid */ + static char *azRowid[] = { "rowid", "_rowid_", "oid" }; + int i, j; + for(j=0; j<3; j++){ + for(i=1; i<=nCol; i++){ + if( sqlite3_stricmp(azRowid[j],azCol[i])==0 ) break; + } + if( i>nCol ){ + /* At this point, we know that azRowid[j] is not the name of any + ** ordinary column in the table. Verify that azRowid[j] is a valid + ** name for the rowid before adding it to azCol[0]. WITHOUT ROWID + ** tables will fail this last check */ + rc = sqlite3_table_column_metadata(p->db,0,zTab,azRowid[j],0,0,0,0,0); + if( rc==SQLITE_OK ) azCol[0] = azRowid[j]; + break; + } + } + } + return azCol; + +col_oom: + sqlite3_finalize(pStmt); + freeColumnList(azCol); + p->nErr++; + p->rc = SQLITE_NOMEM; + return 0; +} + +/* +** Send mprintf-formatted content to the output callback. +*/ +static void output_formatted(DState *p, const char *zFormat, ...){ + va_list ap; + char *z; + va_start(ap, zFormat); + z = sqlite3_vmprintf(zFormat, ap); + va_end(ap); + p->xCallback(z, p->pArg); + sqlite3_free(z); +} + +/* +** Find a string that is not found anywhere in z[]. Return a pointer +** to that string. +** +** Try to use zA and zB first. If both of those are already found in z[] +** then make up some string and store it in the buffer zBuf. +*/ +static const char *unused_string( + const char *z, /* Result must not appear anywhere in z */ + const char *zA, const char *zB, /* Try these first */ + char *zBuf /* Space to store a generated string */ +){ + unsigned i = 0; + if( strstr(z, zA)==0 ) return zA; + if( strstr(z, zB)==0 ) return zB; + do{ + sqlite3_snprintf(20,zBuf,"(%s%u)", zA, i++); + }while( strstr(z,zBuf)!=0 ); + return zBuf; +} + +/* +** Output the given string as a quoted string using SQL quoting conventions. +** Additionallly , escape the "\n" and "\r" characters so that they do not +** get corrupted by end-of-line translation facilities in some operating +** systems. +*/ +static void output_quoted_escaped_string(DState *p, const char *z){ + int i; + char c; + for(i=0; (c = z[i])!=0 && c!='\'' && c!='\n' && c!='\r'; i++){} + if( c==0 ){ + output_formatted(p,"'%s'",z); + }else{ + const char *zNL = 0; + const char *zCR = 0; + int nNL = 0; + int nCR = 0; + char zBuf1[20], zBuf2[20]; + for(i=0; z[i]; i++){ + if( z[i]=='\n' ) nNL++; + if( z[i]=='\r' ) nCR++; + } + if( nNL ){ + p->xCallback("replace(", p->pArg); + zNL = unused_string(z, "\\n", "\\012", zBuf1); + } + if( nCR ){ + p->xCallback("replace(", p->pArg); + zCR = unused_string(z, "\\r", "\\015", zBuf2); + } + p->xCallback("'", p->pArg); + while( *z ){ + for(i=0; (c = z[i])!=0 && c!='\n' && c!='\r' && c!='\''; i++){} + if( c=='\'' ) i++; + if( i ){ + output_formatted(p, "%.*s", i, z); + z += i; + } + if( c=='\'' ){ + p->xCallback("'", p->pArg); + continue; + } + if( c==0 ){ + break; + } + z++; + if( c=='\n' ){ + p->xCallback(zNL, p->pArg); + continue; + } + p->xCallback(zCR, p->pArg); + } + p->xCallback("'", p->pArg); + if( nCR ){ + output_formatted(p, ",'%s',char(13))", zCR); + } + if( nNL ){ + output_formatted(p, ",'%s',char(10))", zNL); + } + } +} + +/* +** This is an sqlite3_exec callback routine used for dumping the database. +** Each row received by this callback consists of a table name, +** the table type ("index" or "table") and SQL to create the table. +** This routine should print text sufficient to recreate the table. +*/ +static int dump_callback(void *pArg, int nArg, char **azArg, char **azCol){ + int rc; + const char *zTable; + const char *zType; + const char *zSql; + DState *p = (DState*)pArg; + sqlite3_stmt *pStmt; + + (void)azCol; + if( nArg!=3 ) return 1; + zTable = azArg[0]; + zType = azArg[1]; + zSql = azArg[2]; + + if( strcmp(zTable, "sqlite_sequence")==0 ){ + p->xCallback("DELETE FROM sqlite_sequence;\n", p->pArg); + }else if( sqlite3_strglob("sqlite_stat?", zTable)==0 ){ + p->xCallback("ANALYZE sqlite_schema;\n", p->pArg); + }else if( strncmp(zTable, "sqlite_", 7)==0 ){ + return 0; + }else if( strncmp(zSql, "CREATE VIRTUAL TABLE", 20)==0 ){ +#if 0 + if( !p->writableSchema ){ + p->xCallback("PRAGMA writable_schema=ON;\n", p->pArg); + p->writableSchema = 1; + } + output_formatted(p, + "INSERT INTO sqlite_schema(type,name,tbl_name,rootpage,sql)" + "VALUES('table','%q','%q',0,'%q');", + zTable, zTable, zSql); + return 0; +#endif + }else{ + if( sqlite3_strglob("CREATE TABLE ['\"]*", zSql)==0 ){ + p->xCallback("CREATE TABLE IF NOT EXISTS ", p->pArg); + p->xCallback(zSql+13, p->pArg); + }else{ + p->xCallback(zSql, p->pArg); + } + p->xCallback(";\n", p->pArg); + } + + if( strcmp(zType, "table")==0 ){ + DText sSelect; + DText sTable; + char **azTCol; + int i; + int nCol; + + azTCol = tableColumnList(p, zTable); + if( azTCol==0 ) return 0; + + initText(&sTable); + appendText(&sTable, "INSERT INTO ", 0); + + /* Always quote the table name, even if it appears to be pure ascii, + ** in case it is a keyword. Ex: INSERT INTO "table" ... */ + appendText(&sTable, zTable, quoteChar(zTable)); + + /* If preserving the rowid, add a column list after the table name. + ** In other words: "INSERT INTO tab(rowid,a,b,c,...) VALUES(...)" + ** instead of the usual "INSERT INTO tab VALUES(...)". + */ + if( azTCol[0] ){ + appendText(&sTable, "(", 0); + appendText(&sTable, azTCol[0], 0); + for(i=1; azTCol[i]; i++){ + appendText(&sTable, ",", 0); + appendText(&sTable, azTCol[i], quoteChar(azTCol[i])); + } + appendText(&sTable, ")", 0); + } + appendText(&sTable, " VALUES(", 0); + + /* Build an appropriate SELECT statement */ + initText(&sSelect); + appendText(&sSelect, "SELECT ", 0); + if( azTCol[0] ){ + appendText(&sSelect, azTCol[0], 0); + appendText(&sSelect, ",", 0); + } + for(i=1; azTCol[i]; i++){ + appendText(&sSelect, azTCol[i], quoteChar(azTCol[i])); + if( azTCol[i+1] ){ + appendText(&sSelect, ",", 0); + } + } + nCol = i; + if( azTCol[0]==0 ) nCol--; + freeColumnList(azTCol); + appendText(&sSelect, " FROM ", 0); + appendText(&sSelect, zTable, quoteChar(zTable)); + + rc = sqlite3_prepare_v2(p->db, sSelect.z, -1, &pStmt, 0); + if( rc!=SQLITE_OK ){ + p->nErr++; + if( p->rc==SQLITE_OK ) p->rc = rc; + }else{ + while( SQLITE_ROW==sqlite3_step(pStmt) ){ + p->xCallback(sTable.z, p->pArg); + for(i=0; ixCallback(",", p->pArg); + switch( sqlite3_column_type(pStmt,i) ){ + case SQLITE_INTEGER: { + output_formatted(p, "%lld", sqlite3_column_int64(pStmt,i)); + break; + } + case SQLITE_FLOAT: { + double r = sqlite3_column_double(pStmt,i); + sqlite3_uint64 ur; + memcpy(&ur,&r,sizeof(r)); + if( ur==0x7ff0000000000000LL ){ + p->xCallback("1e999", p->pArg); + }else if( ur==0xfff0000000000000LL ){ + p->xCallback("-1e999", p->pArg); + }else{ + output_formatted(p, "%!.20g", r); + } + break; + } + case SQLITE_NULL: { + p->xCallback("NULL", p->pArg); + break; + } + case SQLITE_TEXT: { + output_quoted_escaped_string(p, + (const char*)sqlite3_column_text(pStmt,i)); + break; + } + case SQLITE_BLOB: { + int nByte = sqlite3_column_bytes(pStmt,i); + unsigned char *a = (unsigned char*)sqlite3_column_blob(pStmt,i); + int j; + p->xCallback("x'", p->pArg); + for(j=0; j>4)&15]; + zWord[1] = "0123456789abcdef"[a[j]&15]; + zWord[2] = 0; + p->xCallback(zWord, p->pArg); + } + p->xCallback("'", p->pArg); + break; + } + } + } + p->xCallback(");\n", p->pArg); + } + } + sqlite3_finalize(pStmt); + freeText(&sTable); + freeText(&sSelect); + } + return 0; +} + + +/* +** Execute a query statement that will generate SQL output. Print +** the result columns, comma-separated, on a line and then add a +** semicolon terminator to the end of that line. +** +** If the number of columns is 1 and that column contains text "--" +** then write the semicolon on a separate line. That way, if a +** "--" comment occurs at the end of the statement, the comment +** won't consume the semicolon terminator. +*/ +static void output_sql_from_query( + DState *p, /* Query context */ + const char *zSelect, /* SELECT statement to extract content */ + ... +){ + sqlite3_stmt *pSelect; + int rc; + int nResult; + int i; + const char *z; + char *zSql; + va_list ap; + va_start(ap, zSelect); + zSql = sqlite3_vmprintf(zSelect, ap); + va_end(ap); + if( zSql==0 ){ + p->rc = SQLITE_NOMEM; + p->nErr++; + return; + } + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pSelect, 0); + sqlite3_free(zSql); + if( rc!=SQLITE_OK || !pSelect ){ + output_formatted(p, "/**** ERROR: (%d) %s *****/\n", rc, + sqlite3_errmsg(p->db)); + p->nErr++; + return; + } + rc = sqlite3_step(pSelect); + nResult = sqlite3_column_count(pSelect); + while( rc==SQLITE_ROW ){ + z = (const char*)sqlite3_column_text(pSelect, 0); + p->xCallback(z, p->pArg); + for(i=1; ixCallback(",", p->pArg); + p->xCallback((const char*)sqlite3_column_text(pSelect,i), p->pArg); + } + if( z==0 ) z = ""; + while( z[0] && (z[0]!='-' || z[1]!='-') ) z++; + if( z[0] ){ + p->xCallback("\n;\n", p->pArg); + }else{ + p->xCallback(";\n", p->pArg); + } + rc = sqlite3_step(pSelect); + } + rc = sqlite3_finalize(pSelect); + if( rc!=SQLITE_OK ){ + output_formatted(p, "/**** ERROR: (%d) %s *****/\n", rc, + sqlite3_errmsg(p->db)); + if( (rc&0xff)!=SQLITE_CORRUPT ) p->nErr++; + } +} + +/* +** Run zQuery. Use dump_callback() as the callback routine so that +** the contents of the query are output as SQL statements. +** +** If we get a SQLITE_CORRUPT error, rerun the query after appending +** "ORDER BY rowid DESC" to the end. +*/ +static void run_schema_dump_query( + DState *p, + const char *zQuery, + ... +){ + char *zErr = 0; + char *z; + va_list ap; + va_start(ap, zQuery); + z = sqlite3_vmprintf(zQuery, ap); + va_end(ap); + sqlite3_exec(p->db, z, dump_callback, p, &zErr); + sqlite3_free(z); + if( zErr ){ + output_formatted(p, "/****** %s ******/\n", zErr); + sqlite3_free(zErr); + p->nErr++; + zErr = 0; + } +} + +/* +** Convert an SQLite database into SQL statements that will recreate that +** database. +*/ +int sqlite3_db_dump( + sqlite3 *db, /* The database connection */ + const char *zSchema, /* Which schema to dump. Usually "main". */ + const char *zTable, /* Which table to dump. NULL means everything. */ + int (*xCallback)(const char*,void*), /* Output sent to this callback */ + void *pArg /* Second argument of the callback */ +){ + DState x; + memset(&x, 0, sizeof(x)); + x.rc = sqlite3_exec(db, "BEGIN", 0, 0, 0); + if( x.rc ) return x.rc; + x.db = db; + x.xCallback = xCallback; + x.pArg = pArg; + xCallback("PRAGMA foreign_keys=OFF;\nBEGIN TRANSACTION;\n", pArg); + if( zTable==0 ){ + run_schema_dump_query(&x, + "SELECT name, type, sql FROM \"%w\".sqlite_schema " + "WHERE sql NOT NULL AND type=='table' AND name!='sqlite_sequence'", + zSchema + ); + run_schema_dump_query(&x, + "SELECT name, type, sql FROM \"%w\".sqlite_schema " + "WHERE name=='sqlite_sequence'", zSchema + ); + output_sql_from_query(&x, + "SELECT sql FROM sqlite_schema " + "WHERE sql NOT NULL AND type IN ('index','trigger','view')", 0 + ); + }else{ + run_schema_dump_query(&x, + "SELECT name, type, sql FROM \"%w\".sqlite_schema " + "WHERE tbl_name=%Q COLLATE nocase AND type=='table'" + " AND sql NOT NULL", + zSchema, zTable + ); + output_sql_from_query(&x, + "SELECT sql FROM \"%w\".sqlite_schema " + "WHERE sql NOT NULL" + " AND type IN ('index','trigger','view')" + " AND tbl_name=%Q COLLATE nocase", + zSchema, zTable + ); + } + if( x.writableSchema ){ + xCallback("PRAGMA writable_schema=OFF;\n", pArg); + } + xCallback(x.nErr ? "ROLLBACK; -- due to errors\n" : "COMMIT;\n", pArg); + sqlite3_exec(db, "COMMIT", 0, 0, 0); + return x.rc; +} + + + +/* The generic subroutine is above. The code the follows implements +** the command-line interface. +*/ +#ifdef DBDUMP_STANDALONE +#include + +/* +** Command-line interface +*/ +int main(int argc, char **argv){ + sqlite3 *db; + const char *zDb; + const char *zSchema; + const char *zTable = 0; + int rc; + + if( argc<2 || argc>4 ){ + fprintf(stderr, "Usage: %s DATABASE ?SCHEMA? ?TABLE?\n", argv[0]); + return 1; + } + zDb = argv[1]; + zSchema = argc>=3 ? argv[2] : "main"; + zTable = argc==4 ? argv[3] : 0; + + rc = sqlite3_open(zDb, &db); + if( rc ){ + fprintf(stderr, "Cannot open \"%s\": %s\n", zDb, sqlite3_errmsg(db)); + sqlite3_close(db); + return 1; + } + rc = sqlite3_db_dump(db, zSchema, zTable, + (int(*)(const char*,void*))fputs, (void*)stdout); + if( rc ){ + fprintf(stderr, "Error: sqlite3_db_dump() returns %d\n", rc); + } + sqlite3_close(db); + return rc!=SQLITE_OK; +} +#endif /* DBDUMP_STANDALONE */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 53770281..fd77fda6 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -3,10 +3,16 @@ enable_testing() include_directories( . + ${CMAKE_SOURCE_DIR}/src + ${CMAKE_SOURCE_DIR}/src/fmtlib ${CMAKE_CURRENT_BINARY_DIR}/../src ${CMAKE_CURRENT_BINARY_DIR} ) +add_library(testdummy STATIC + test_stubs.cc) +target_link_libraries(testdummy PkgConfig::libpcre) + add_executable(test_abbrev test_abbrev.cc) target_link_libraries(test_abbrev diag PkgConfig::libpcre) add_test(NAME test_abbrev COMMAND test_abbrev) @@ -59,20 +65,20 @@ target_link_libraries(test_reltime diag PkgConfig::libpcre) add_test(NAME test_reltime COMMAND test_reltime) add_executable(test_top_status test_top_status.cc) -target_link_libraries(test_top_status diag PkgConfig::libpcre) +target_link_libraries(test_top_status diag testdummy PkgConfig::libpcre) add_test(NAME test_top_status COMMAND test_top_status) add_executable(drive_view_colors drive_view_colors.cc) -target_link_libraries(drive_view_colors diag PkgConfig::ncursesw) +target_link_libraries(drive_view_colors diag testdummy PkgConfig::ncursesw) add_executable(drive_vt52_curses drive_vt52_curses.cc) target_link_libraries(drive_vt52_curses diag PkgConfig::ncursesw) add_executable(drive_sql_anno drive_sql_anno.cc) -target_link_libraries(drive_sql_anno diag PkgConfig::libpcre) +target_link_libraries(drive_sql_anno diag testdummy PkgConfig::libpcre) add_executable(drive_data_scanner drive_data_scanner.cc) -target_link_libraries(drive_data_scanner diag PkgConfig::libpcre) +target_link_libraries(drive_data_scanner diag testdummy PkgConfig::libpcre) add_executable(scripty scripty.cc) target_link_libraries(scripty diag PkgConfig::ncursesw) diff --git a/test/Makefile.am b/test/Makefile.am index 702ae604..1596bbdf 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -24,6 +24,11 @@ AM_CPPFLAGS = \ # AM_CFLAGS = -fprofile-arcs -ftest-coverage # AM_CXXFLAGS = -fprofile-arcs -ftest-coverage +noinst_LIBRARIES = libtestdummy.a + +libtestdummy_a_SOURCES = \ + test_stubs.cc + check_PROGRAMS = \ drive_data_scanner \ drive_line_buffer \ @@ -75,6 +80,7 @@ TEXT2C_OBJS = \ LDADD = \ -lz \ + libtestdummy.a \ $(CONFIG_OBJS) \ $(TEXT2C_OBJS) \ $(top_builddir)/src/libdiag.a \ diff --git a/test/drive_data_scanner.cc b/test/drive_data_scanner.cc index d7f0c749..207c6d75 100644 --- a/test/drive_data_scanner.cc +++ b/test/drive_data_scanner.cc @@ -50,15 +50,6 @@ using namespace std; const char *TMP_NAME = "scanned.tmp"; -string execute_any(exec_context &ec, const string &cmdline_with_mode) -{ - return ""; -} - -void add_global_vars(exec_context &ec) -{ -} - int main(int argc, char *argv[]) { int c, retval = EXIT_SUCCESS; diff --git a/test/drive_logfile.cc b/test/drive_logfile.cc index b2f7c308..71da8583 100644 --- a/test/drive_logfile.cc +++ b/test/drive_logfile.cc @@ -58,15 +58,6 @@ time_t time(time_t *_unused) return 1194107018; } -string execute_any(exec_context &ec, const string &cmdline_with_mode) -{ - return ""; -} - -void add_global_vars(exec_context &ec) -{ -} - int main(int argc, char *argv[]) { int c, retval = EXIT_SUCCESS; diff --git a/test/drive_sql.cc b/test/drive_sql.cc index 1446964c..741d1186 100644 --- a/test/drive_sql.cc +++ b/test/drive_sql.cc @@ -7,7 +7,7 @@ #include -#include "lnav.hh" +#include "base/injector.hh" #include "auto_mem.hh" #include "sqlite-extension-func.hh" #include "regexp_vtab.hh" @@ -17,8 +17,6 @@ struct callback_state { int cs_row; }; -struct lnav_data_t lnav_data; - static int sql_callback(void *ptr, int ncols, char **colvalues, @@ -36,36 +34,6 @@ static int sql_callback(void *ptr, return 0; } -void rebuild_hist() -{ -} - -bool setup_logline_table(exec_context &ec) -{ - return false; -} - -bool rescan_files(bool required) -{ - return false; -} - -void wait_for_children() -{ - -} - -void rebuild_indexes() -{ -} - -textview_curses *get_textview_for_mode(ln_mode_t mode) -{ - return nullptr; -} - -readline_context::command_map_t lnav_commands; - int main(int argc, char *argv[]) { int retval = EXIT_SUCCESS; diff --git a/test/drive_sql_anno.cc b/test/drive_sql_anno.cc index 783b6f98..ab9f49d0 100644 --- a/test/drive_sql_anno.cc +++ b/test/drive_sql_anno.cc @@ -38,38 +38,6 @@ using namespace std; -struct lnav_data_t lnav_data; - -void rebuild_hist() -{ -} - -bool setup_logline_table(exec_context &ec) -{ - return false; -} - -bool rescan_files(bool required) -{ - return false; -} - -void wait_for_children() -{ - -} - -void rebuild_indexes() -{ -} - -textview_curses *get_textview_for_mode(ln_mode_t mode) -{ - return nullptr; -} - -readline_context::command_map_t lnav_commands; - int main(int argc, char *argv[]) { int retval = EXIT_SUCCESS; diff --git a/test/file_for_dot_read.sql b/test/file_for_dot_read.sql new file mode 100644 index 00000000..fca253c9 --- /dev/null +++ b/test/file_for_dot_read.sql @@ -0,0 +1,4 @@ + +INSERT INTO environ VALUES ('SEARCH_TERM', '%mount%'); + +SELECT log_line, log_body FROM syslog_log WHERE log_body LIKE $SEARCH_TERM diff --git a/test/test_sql.sh b/test/test_sql.sh index 0acb7b09..f863e52f 100644 --- a/test/test_sql.sh +++ b/test/test_sql.sh @@ -2,6 +2,26 @@ lnav_test="${top_builddir}/src/lnav-test" + +run_test ${lnav_test} -n \ + -c ";.read nonexistent-file" \ + ${test_dir}/logfile_empty.0 + +check_error_output "read worked with a nonexistent file?" < +void injector::force_linking(services::curl_streamer_t anno) +{ +} diff --git a/test/test_top_status.cc b/test/test_top_status.cc index 8bd1bedc..ddec9bf5 100644 --- a/test/test_top_status.cc +++ b/test/test_top_status.cc @@ -48,15 +48,6 @@ int gettimeofday(struct timeval * tp, void * tzp) return 0; } -Result execute_any(exec_context &ec, const string &cmdline_with_mode) -{ - return Ok(string()); -} - -void add_global_vars(exec_context &ec) -{ -} - int main(int argc, char *argv[]) { int retval = EXIT_SUCCESS;