[sqlite] implement .dump SQL command

pull/857/head
Timothy Stack 3 years ago
parent 6f4791ec35
commit c0ed59e61e

@ -53,6 +53,7 @@ lnav v0.9.1:
* The "generate_series()" SQLite extension is now included by default. * The "generate_series()" SQLite extension is now included by default.
One change from the standard implementation is that both the start and One change from the standard implementation is that both the start and
stop are required parameters. stop are required parameters.
* Added the ";.read" SQL command for executing a plain SQL file.
Interface Changes: Interface Changes:
* When copying log lines, the file name and time offset will be included * When copying log lines, the file name and time offset will be included

@ -1,6 +1,6 @@
# aminclude_static.am generated automatically by Autoconf # 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 # Code coverage

@ -5,3 +5,10 @@ hunter_config(
CMAKE_ARGS CMAKE_ARGS
EXTRA_FLAGS=--enable-unicode-properties --enable-jit --enable-utf EXTRA_FLAGS=--enable-unicode-properties --enable-jit --enable-utf
) )
hunter_config(
readline
VERSION 6.3
CMAKE_ARGS
EXTRA_FLAGS=CFLAGS=-Wno-implicit-function-declaration
)

@ -90,8 +90,8 @@ AC_CHECK_SIZEOF(size_t)
AC_STRUCT_TIMEZONE AC_STRUCT_TIMEZONE
AC_ARG_ENABLE([static], AC_ARG_ENABLE([static],
AS_HELP_STRING([--disable-static], AS_HELP_STRING([--enable-static],
[Disable static linking])) [Enable static linking]))
AC_SEARCH_LIBS(openpty, util) AC_SEARCH_LIBS(openpty, util)
AC_SEARCH_LIBS(gzseek, z, [], [AC_MSG_ERROR([libz required to build])]) 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_SUBST(BZIP2_SUPPORT)
AC_SEARCH_LIBS(dlopen, dl) AC_SEARCH_LIBS(dlopen, dl)
AC_SEARCH_LIBS(backtrace, execinfo) 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... # Sometimes, curses depends on these libraries being linked in...
AC_ARG_ENABLE([tinfo], AC_ARG_ENABLE([tinfo],
@ -193,7 +193,7 @@ AS_VAR_SET(static_lib_list,
AS_VAR_SET(static_lib_list, AS_VAR_SET(static_lib_list,
["$static_lib_list libarchive.a"]) ["$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 case "$host_os" in
darwin*) darwin*)
STATIC_LDFLAGS="$STATIC_LDFLAGS -Wl,-search_paths_first" STATIC_LDFLAGS="$STATIC_LDFLAGS -Wl,-search_paths_first"

@ -266,6 +266,7 @@ add_library(diag STATIC
hist_source.cc hist_source.cc
hotkeys.cc hotkeys.cc
base/humanize.cc base/humanize.cc
base/humanize.time.cc
input_dispatcher.cc input_dispatcher.cc
base/intern_string.cc base/intern_string.cc
base/is_utf8.cc base/is_utf8.cc
@ -314,6 +315,7 @@ add_library(diag STATIC
pcrepp/pcrepp.cc pcrepp/pcrepp.cc
piper_proc.cc piper_proc.cc
spectro_source.cc spectro_source.cc
sql_commands.cc
sql_util.cc sql_util.cc
state-extension-functions.cc state-extension-functions.cc
styling.cc styling.cc
@ -358,6 +360,7 @@ add_library(diag STATIC
spookyhash/SpookyV2.cpp spookyhash/SpookyV2.cpp
third-party/sqlite/ext/series.c third-party/sqlite/ext/series.c
third-party/sqlite/ext/dbdump.c
all_logs_vtab.hh all_logs_vtab.hh
archive_manager.hh archive_manager.hh
@ -394,6 +397,7 @@ add_library(diag STATIC
highlighter.hh highlighter.hh
hotkeys.hh hotkeys.hh
base/humanize.hh base/humanize.hh
base/humanize.time.hh
input_dispatcher.hh input_dispatcher.hh
base/injector.hh base/injector.hh
base/injector.bind.hh base/injector.bind.hh
@ -440,6 +444,7 @@ add_library(diag STATIC
shlex.hh shlex.hh
simdutf8check.h simdutf8check.h
spectro_source.hh spectro_source.hh
sql_util.hh
strong_int.hh strong_int.hh
string_attr_type.hh string_attr_type.hh
sysclip.hh sysclip.hh

@ -248,9 +248,9 @@ noinst_HEADERS = \
shlex.hh \ shlex.hh \
simdutf8check.h \ simdutf8check.h \
spectro_source.hh \ spectro_source.hh \
styling.hh \
sql_util.hh \ sql_util.hh \
sqlite-extension-func.hh \ sqlite-extension-func.hh \
styling.hh \
statusview_curses.hh \ statusview_curses.hh \
string_attr_type.hh \ string_attr_type.hh \
strnatcmp.h \ strnatcmp.h \
@ -293,6 +293,7 @@ nodist_libdiag_a_SOURCES = \
$(LNAV_BUILT_FILES) $(LNAV_BUILT_FILES)
THIRD_PARTY_SRCS = \ THIRD_PARTY_SRCS = \
third-party/sqlite/ext/dbdump.c \
third-party/sqlite/ext/series.c third-party/sqlite/ext/series.c
libdiag_a_SOURCES = \ libdiag_a_SOURCES = \
@ -372,6 +373,7 @@ libdiag_a_SOURCES = \
textfile_sub_source.cc \ textfile_sub_source.cc \
timer.cc \ timer.cc \
piper_proc.cc \ piper_proc.cc \
sql_commands.cc \
sql_util.cc \ sql_util.cc \
state-extension-functions.cc \ state-extension-functions.cc \
strnatcmp.c \ strnatcmp.c \

@ -20,6 +20,7 @@ noinst_HEADERS = \
func_util.hh \ func_util.hh \
future_util.hh \ future_util.hh \
humanize.hh \ humanize.hh \
humanize.time.hh \
injector.hh \ injector.hh \
injector.bind.hh \ injector.bind.hh \
intern_string.hh \ intern_string.hh \
@ -37,6 +38,7 @@ noinst_HEADERS = \
libbase_a_SOURCES = \ libbase_a_SOURCES = \
date_time_scanner.cc \ date_time_scanner.cc \
humanize.cc \ humanize.cc \
humanize.time.cc \
intern_string.cc \ intern_string.cc \
is_utf8.cc \ is_utf8.cc \
isc.cc \ isc.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);
}
}
}
}

@ -0,0 +1,17 @@
#ifndef lnav_humanize_time_hh
#define lnav_humanize_time_hh
#include <string>
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

@ -118,9 +118,6 @@ struct bind_multiple : multiple_storage<T> {
return *this; return *this;
} }
private:
}; };
} }

@ -43,6 +43,9 @@
namespace injector { namespace injector {
template<typename Annotation>
void force_linking(Annotation anno);
template<class ...> template<class ...>
using void_t = void; using void_t = void;
@ -56,11 +59,15 @@ struct has_injectable<T, void_t<typename T::injectable>> : std::true_type
template<typename T, typename...Annotations> template<typename T, typename...Annotations>
struct singleton_storage { struct singleton_storage {
static T *get() { static T *get() {
static int _[] = {0, (force_linking(Annotations{}), 0)...};
(void)_;
assert(ss_data != nullptr); assert(ss_data != nullptr);
return ss_data; return ss_data;
} }
static std::shared_ptr<T> create() { static std::shared_ptr<T> create() {
static int _[] = {0, (force_linking(Annotations{}), 0)...};
(void)_;
return ss_factory(); return ss_factory();
} }
protected: protected:

@ -45,6 +45,18 @@ struct tm *secs2tm(time_t *tim_p, struct tm *res);
time_t tm2sec(const struct tm *t); time_t tm2sec(const struct tm *t);
void secs2wday(const struct timeval &tv, struct tm *res); 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; constexpr time_t MAX_TIME_T = 4000000000LL;
enum exttm_bits_t { enum exttm_bits_t {

@ -34,4 +34,6 @@
struct last_relative_time_tag {}; struct last_relative_time_tag {};
struct sql_cmd_map_tag {};
#endif #endif

@ -40,6 +40,7 @@
#include "sql_util.hh" #include "sql_util.hh"
#include "lnav_config.hh" #include "lnav_config.hh"
#include "service_tags.hh" #include "service_tags.hh"
#include "bound_tags.hh"
#include "command_executor.hh" #include "command_executor.hh"
#include "db_sub_source.hh" #include "db_sub_source.hh"
@ -138,15 +139,20 @@ Result<string, string> execute_sql(exec_context &ec, const string &sql, string &
lnav_data.ld_bottom_source.grep_error(""); lnav_data.ld_bottom_source.grep_error("");
if (stmt_str == ".schema") { if (startswith(stmt_str, ".")) {
alt_msg = ""; vector<string> 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; if (cmd_iter != sql_cmd_map->end()) {
return Ok(string()); return cmd_iter->second->c_func(ec, stmt_str, args);
}
} }
else if (stmt_str == ".msgformats") {
if (stmt_str == ".msgformats") {
stmt_str = MSG_FORMAT_STMT; stmt_str = MSG_FORMAT_STMT;
} }

@ -29,6 +29,7 @@
#include "config.h" #include "config.h"
#include "base/humanize.time.hh"
#include "lnav_util.hh" #include "lnav_util.hh"
#include "ansi_scrubber.hh" #include "ansi_scrubber.hh"
#include "vtab_module.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))); first_line = lss.find_line(lss.at(vis_line_t(0)));
last_line = lss.find_line(lss.at(lv.get_bottom())); 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; last_line->get_timeval(), true) + ANSI_NORM;
duration2str(last_line->get_time_in_millis() - duration2str(last_line->get_time_in_millis() -
first_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_line.with_attr(string_attr(time_lr, &view_curses::VC_STYLE, A_BOLD));
time_str.append(" -- "); time_str.append(" -- ");
time_lr.lr_start = time_str.length(); 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_lr.lr_end = time_str.length();
time_line.with_attr(string_attr(time_lr, &view_curses::VC_STYLE, A_BOLD)); time_line.with_attr(string_attr(time_lr, &view_curses::VC_STYLE, A_BOLD));

@ -39,6 +39,7 @@ enum class help_context_t {
HC_PARAMETER, HC_PARAMETER,
HC_RESULT, HC_RESULT,
HC_COMMAND, HC_COMMAND,
HC_SQL_COMMAND,
HC_SQL_KEYWORD, HC_SQL_KEYWORD,
HC_SQL_INFIX, HC_SQL_INFIX,
HC_SQL_FUNCTION, HC_SQL_FUNCTION,
@ -122,6 +123,11 @@ struct help_text {
return *this; return *this;
}; };
help_text &sql_command() noexcept {
this->ht_context = help_context_t::HC_SQL_COMMAND;
return *this;
};
help_text &sql_keyword() noexcept { help_text &sql_keyword() noexcept {
this->ht_context = help_context_t::HC_SQL_KEYWORD; this->ht_context = help_context_t::HC_SQL_KEYWORD;
return *this; return *this;

@ -174,6 +174,38 @@ void format_help_text_for_term(const help_text &ht, size_t width, attr_line_t &o
.append("\n"); .append("\n");
break; 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 &param : 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_INFIX:
case help_context_t::HC_SQL_KEYWORD: { case help_context_t::HC_SQL_KEYWORD: {
size_t line_start = body_indent; 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: case help_context_t::HC_COMMAND:
prefix = ":"; prefix = ":";
break; break;
case help_context_t::HC_SQL_COMMAND:
prefix = ";";
break;
case help_context_t::HC_SQL_FUNCTION: case help_context_t::HC_SQL_FUNCTION:
case help_context_t::HC_SQL_TABLE_VALUED_FUNCTION: case help_context_t::HC_SQL_TABLE_VALUED_FUNCTION:
is_sql = is_sql_func = true; is_sql = is_sql_func = true;

@ -80,6 +80,7 @@
#include "init-sql.h" #include "init-sql.h"
#include "logfile.hh" #include "logfile.hh"
#include "base/func_util.hh" #include "base/func_util.hh"
#include "base/humanize.time.hh"
#include "base/injector.bind.hh" #include "base/injector.bind.hh"
#include "base/isc.hh" #include "base/isc.hh"
#include "base/string_util.hh" #include "base/string_util.hh"
@ -252,6 +253,16 @@ static auto bound_curl =
injector::bind_multiple<isc::service>() injector::bind_multiple<isc::service>()
.add_singleton<curl_looper, services::curl_streamer_t>(); .add_singleton<curl_looper, services::curl_streamer_t>();
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) bool setup_logline_table(exec_context &ec)
{ {
// Hidden columns don't show up in the table_info pragma. // Hidden columns don't show up in the table_info pragma.
@ -264,13 +275,6 @@ bool setup_logline_table(exec_context &ec)
nullptr nullptr
}; };
static const char *commands[] = {
".schema",
".msgformats",
nullptr
};
textview_curses &log_view = lnav_data.ld_views[LNV_LOG]; textview_curses &log_view = lnav_data.ld_views[LNV_LOG];
bool retval = false; bool retval = false;
bool update_possibilities = ( 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, "*", sql_function_names);
lnav_data.ld_rl_view->add_possibility(LNM_SQL, "*", lnav_data.ld_rl_view->add_possibility(LNM_SQL, "*",
hidden_table_columns); hidden_table_columns);
lnav_data.ld_rl_view->add_possibility(LNM_SQL, "*", commands);
for (int lpc = 0; sqlite_registration_funcs[lpc]; lpc++) { for (int lpc = 0; sqlite_registration_funcs[lpc]; lpc++) {
struct FuncDef *basic_funcs; struct FuncDef *basic_funcs;
@ -1133,7 +1136,9 @@ static void wait_for_pipers()
static void looper() static void looper()
{ {
try { 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); 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_filters_context("search-filters", nullptr, false);
readline_context search_files_context("search-files", nullptr, false); readline_context search_files_context("search-files", nullptr, false);
readline_context index_context("capture"); 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 exec_context("exec");
readline_context user_context("user"); readline_context user_context("user");
readline_curses rlc; readline_curses rlc;
@ -1430,7 +1435,7 @@ static void looper()
if (session_data.sd_save_time) { if (session_data.sd_save_time) {
std::string ago; 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( lnav_data.ld_rl_view->set_value(
("restored session from " ANSI_BOLD_START) + ("restored session from " ANSI_BOLD_START) +
ago + ago +

@ -979,11 +979,15 @@ Result<config_file_type, std::string>
detect_config_file_type(const ghc::filesystem::path &path) detect_config_file_type(const ghc::filesystem::path &path)
{ {
static const char *id_path[] = {"$schema", nullptr}; static const char *id_path[] = {"$schema", nullptr};
string content;
if (!read_file(path.string(), content)) { auto read_res = read_file(path);
return Err(fmt::format("unable to open file: {}", path.string()));
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, "#")) { if (startswith(content, "#")) {
content.insert(0, "//"); content.insert(0, "//");
} }

@ -45,101 +45,6 @@
using namespace std; 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 change_to_parent_dir()
{ {
bool retval = false; bool retval = false;
@ -211,17 +116,22 @@ string build_path(const vector<ghc::filesystem::path> &paths)
return retval; return retval;
} }
bool read_file(const ghc::filesystem::path &filename, string &out) Result<std::string, std::string> read_file(const ghc::filesystem::path &path)
{ {
std::ifstream sql_file(filename.string()); try {
ghc::filesystem::ifstream file_stream(path);
if (sql_file) { if (!file_stream) {
out.assign((std::istreambuf_iterator<char>(sql_file)), return Err(std::string(strerror(errno)));
std::istreambuf_iterator<char>()); }
return true;
}
return false; std::string retval;
retval.assign((std::istreambuf_iterator<char>(file_stream)),
std::istreambuf_iterator<char>());
return Ok(retval);
} catch (const std::exception& e) {
return Err(std::string(e.what()));
}
} }
Result<std::pair<ghc::filesystem::path, int>, std::string> Result<std::pair<ghc::filesystem::path, int>, std::string>

@ -57,10 +57,6 @@
#include "fmt/format.h" #include "fmt/format.h"
#include "ghc/filesystem.hpp" #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 #if SIZEOF_OFF_T == 8
#define FORMAT_OFF_T "%qd" #define FORMAT_OFF_T "%qd"
#elif SIZEOF_OFF_T == 4 #elif SIZEOF_OFF_T == 4
@ -111,28 +107,6 @@ private:
SpookyHash h_context; SpookyHash h_context;
}; };
template<typename UnaryFunction, typename Member>
struct object_field_t {
object_field_t(UnaryFunction &func, Member &mem)
: of_func(func), of_mem(mem) {};
template<typename Object>
void operator()(Object obj)
{
this->of_func(obj.*(this->of_mem));
};
UnaryFunction &of_func;
Member of_mem;
};
template<typename UnaryFunction, typename Member>
object_field_t<UnaryFunction, Member> object_field(UnaryFunction &func,
Member mem)
{
return object_field_t<UnaryFunction, Member>(func, mem);
}
bool change_to_parent_dir(); bool change_to_parent_dir();
bool next_format(const char * const fmt[], int &index, int &locked_index); 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<ghc::filesystem::path> &paths); std::string build_path(const std::vector<ghc::filesystem::path> &paths);
bool read_file(const ghc::filesystem::path &path, std::string &out); Result<std::string, std::string> read_file(const ghc::filesystem::path &path);
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);
}
template<typename T> template<typename T>
size_t strtonum(T &num_out, const char *data, size_t len); size_t strtonum(T &num_out, const char *data, size_t len);

@ -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()); log_info("executing SQL files in path: %s", format_path.c_str());
if (glob(format_path.c_str(), 0, nullptr, gl.inout()) == 0) { if (glob(format_path.c_str(), 0, nullptr, gl.inout()) == 0) {
for (int lpc = 0; lpc < (int)gl->gl_pathc; lpc++) { for (int lpc = 0; lpc < (int)gl->gl_pathc; lpc++) {
string filename(gl->gl_pathv[lpc]); auto filename = ghc::filesystem::path(gl->gl_pathv[lpc]);
string content; auto read_res = read_file(filename);
if (read_file(filename, content)) { if (read_res.isOk()) {
log_info("Executing SQL file: %s", filename.c_str()); 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); sql_execute_script(db, filename.c_str(), content.c_str(), errors);
} }
else { else {
errors.push_back( errors.push_back(fmt::format(
"error:unable to read file '" + "error:unable to read file '{}' -- {}",
filename + filename.string(), read_res.unwrapErr()));
"' -- " +
string(strerror(errno)));
} }
} }
} }

@ -183,10 +183,8 @@ bool rl_sql_help(readline_curses *rc)
lnav_data.ld_doc_source.replace_with(doc_al); lnav_data.ld_doc_source.replace_with(doc_al);
dtc.reload_data(); dtc.reload_data();
if (!ex_al.empty()) { lnav_data.ld_example_source.replace_with(ex_al);
lnav_data.ld_example_source.replace_with(ex_al); etc.reload_data();
etc.reload_data();
}
has_doc = true; has_doc = true;
} }

@ -502,6 +502,8 @@ readline_curses::readline_curses()
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) { if (this->rc_child == 0) {
_exit(0); _exit(0);
} }
@ -509,7 +511,7 @@ readline_curses::~readline_curses()
int status; int status;
log_debug("term child %d", this->rc_child); log_debug("term child %d", this->rc_child);
kill(this->rc_child, SIGTERM); log_perror(kill(this->rc_child, SIGTERM));
this->rc_child = -1; this->rc_child = -1;
while (wait(&status) < 0 && (errno == EINTR)) { while (wait(&status) < 0 && (errno == EINTR)) {

@ -86,7 +86,7 @@ public:
_command_t(const char *name, _command_t(const char *name,
command_func_t func, command_func_t func,
help_text help, help_text help = {},
prompt_func_t prompt = nullptr) noexcept prompt_func_t prompt = nullptr) noexcept
: c_name(name), c_func(func), c_help(std::move(help)), c_prompt(prompt) {}; : c_name(name), c_func(func), c_help(std::move(help)), c_prompt(prompt) {};

@ -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 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 keyword_pcre(keyword_re_str.c_str(), PCRE_CASELESS);
static pcrepp string_literal_pcre("'[^']*('(?:'[^']*')*|$)"); static pcrepp string_literal_pcre("'[^']*('(?:'[^']*')*|$)");
static pcrepp ident_pcre("(?:\\$|:)?(\\b[a-z_]\\w*)|\"([^\"]+)\"|\\[([^\\]]+)]", PCRE_CASELESS); 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 nullptr
}; };
view_colors &vc = view_colors::singleton(); auto &vc = view_colors::singleton();
int keyword_attrs = vc.attrs_for_role(view_colors::VCR_KEYWORD); int keyword_attrs = vc.attrs_for_role(view_colors::VCR_KEYWORD);
int symbol_attrs = vc.attrs_for_role(view_colors::VCR_SYMBOL); 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_context_static<30> pc;
pcre_input pi(al.get_string(), skip); 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)) { while (ident_pcre.match(pc, pi)) {
pcre_context::capture_t *cap = pc.first_valid(); pcre_context::capture_t *cap = pc.first_valid();

@ -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<std::string, std::string> sql_cmd_dump(
exec_context &ec, std::string cmdline, std::vector<std::string> &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> 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<std::string, std::string> sql_cmd_read(
exec_context &ec, std::string cmdline, std::vector<std::string> &args)
{
std::string retval;
if (args.empty()) {
args.emplace_back("filename");
return Ok(retval);
}
std::vector<std::string> 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<sqlite3_stmt> 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<std::string, std::string> sql_cmd_schema(
exec_context &ec, std::string cmdline, std::vector<std::string> &args)
{
std::string retval;
if (args.empty()) {
return Ok(retval);
}
ensure_view(&lnav_data.ld_views[LNV_SCHEMA]);
return Ok(retval);
}
static
Result<std::string, std::string> sql_cmd_generic(
exec_context &ec, std::string cmdline, std::vector<std::string> &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)
{
}

@ -41,10 +41,13 @@
#include "auto_mem.hh" #include "auto_mem.hh"
#include "sql_util.hh" #include "sql_util.hh"
#include "base/injector.hh"
#include "base/string_util.hh" #include "base/string_util.hh"
#include "base/lnav_log.hh" #include "base/lnav_log.hh"
#include "base/time_util.hh" #include "base/time_util.hh"
#include "pcrepp/pcrepp.hh" #include "pcrepp/pcrepp.hh"
#include "readline_curses.hh"
#include "bound_tags.hh"
#include "sqlite-extension-func.hh" #include "sqlite-extension-func.hh"
using namespace std; using namespace std;
@ -886,6 +889,7 @@ string sql_keyword_re(void)
return retval; return retval;
} }
string_attr_type SQL_COMMAND_ATTR("sql_command");
string_attr_type SQL_KEYWORD_ATTR("sql_keyword"); string_attr_type SQL_KEYWORD_ATTR("sql_keyword");
string_attr_type SQL_IDENTIFIER_ATTR("sql_ident"); string_attr_type SQL_IDENTIFIER_ATTR("sql_ident");
string_attr_type SQL_FUNCTION_ATTR("sql_func"); 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) void annotate_sql_statement(attr_line_t &al)
{ {
static string keyword_re_str = static string keyword_re_str = R"(\A)" + sql_keyword_re();
R"(\A)" + sql_keyword_re() + R"(|\.schema|\.msgformats)";
static struct { static struct {
pcrepp re; pcrepp re;
string_attr_type_t type; string_attr_type_t type;
} PATTERNS[] = { } PATTERNS[] = {
{ pcrepp{R"(^(\.\w+))"}, &SQL_COMMAND_ATTR },
{ pcrepp{R"(\A,)"}, &SQL_COMMA_ATTR }, { pcrepp{R"(\A,)"}, &SQL_COMMA_ATTR },
{ pcrepp{R"(\A\(|\A\))"}, &SQL_PAREN_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'[^']*('(?:'[^']*')*|$))"}, &SQL_STRING_ATTR },
{ pcrepp{R"(\A(\$?\b[a-z_]\w*)|\"([^\"]+)\"|\[([^\]]+)])", PCRE_CASELESS}, &SQL_IDENTIFIER_ATTR }, { pcrepp{R"(\A(\$?\b[a-z_]\w*)|\"([^\"]+)\"|\[([^\]]+)])", PCRE_CASELESS}, &SQL_IDENTIFIER_ATTR },
{ pcrepp{R"(\A(\*|<|>|=|!|\-|\+|\|\|))"}, &SQL_OPERATOR_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_context_static<30> pc;
pcre_input pi(al.get_string()); pcre_input pi(al.get_string());
string &line = al.get_string(); auto &line = al.get_string();
string_attrs_t &sa = al.get_attrs(); auto &sa = al.get_attrs();
while (pi.pi_next_offset < line.length()) { while (pi.pi_next_offset < line.length()) {
if (ws_pattern.match(pc, pi, PCRE_ANCHORED)) { if (ws_pattern.match(pc, pi, PCRE_ANCHORED)) {
continue; continue;
} }
for (auto &pat : PATTERNS) { for (const auto &pat : PATTERNS) {
if (pat.re.match(pc, pi, PCRE_ANCHORED)) { if (pat.re.match(pc, pi, PCRE_ANCHORED)) {
pcre_context::capture_t *cap = pc.all(); pcre_context::capture_t *cap = pc.all();
struct line_range lr(cap->c_begin, cap->c_end); struct line_range lr(cap->c_begin, cap->c_end);
@ -990,8 +994,24 @@ vector<const help_text *> find_sql_help_for_line(const attr_line_t &al, size_t x
x = al.nearest_text(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<string> kw; vector<string> kw;
auto iter = rfind_string_attr_if(sa, x, [&al, &name, &kw, x](auto sa) { auto iter = rfind_string_attr_if(sa, x, [&al, &name, &kw, x](auto sa) {
if (sa.sa_type != &SQL_FUNCTION_ATTR && if (sa.sa_type != &SQL_FUNCTION_ATTR &&
sa.sa_type != &SQL_KEYWORD_ATTR) { sa.sa_type != &SQL_KEYWORD_ATTR) {
return false; return false;

@ -108,6 +108,7 @@ int sqlite_authorizer(void* pUserData, int action_code, const char *detail1,
const char *detail2, const char *detail3, const char *detail2, const char *detail3,
const char *detail4); const char *detail4);
extern string_attr_type SQL_COMMAND_ATTR;
extern string_attr_type SQL_KEYWORD_ATTR; extern string_attr_type SQL_KEYWORD_ATTR;
extern string_attr_type SQL_IDENTIFIER_ATTR; extern string_attr_type SQL_IDENTIFIER_ATTR;
extern string_attr_type SQL_FUNCTION_ATTR; extern string_attr_type SQL_FUNCTION_ATTR;

@ -91,4 +91,14 @@ extern sqlite_registration_func_t sqlite_registration_funcs[];
int register_sqlite_funcs(sqlite3 *db, sqlite_registration_func_t *reg_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 #endif

@ -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 <stdarg.h>
#include <string.h>
#include <ctype.h>
/*
** 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; i<nAppend; i++){
if( zAppend[i]==quote ) len++;
}
}
if( p->n+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; i<nAppend; i++){
*zCsr++ = zAppend[i];
if( zAppend[i]==quote ) *zCsr++ = quote;
}
*zCsr++ = quote;
p->n = (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; i<nCol; i++){
if( i ) p->xCallback(",", 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<nByte; j++){
char zWord[3];
zWord[0] = "0123456789abcdef"[(a[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; i<nResult; i++){
p->xCallback(",", 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 <stdio.h>
/*
** 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 */

@ -3,10 +3,16 @@ enable_testing()
include_directories( include_directories(
. .
${CMAKE_SOURCE_DIR}/src
${CMAKE_SOURCE_DIR}/src/fmtlib
${CMAKE_CURRENT_BINARY_DIR}/../src ${CMAKE_CURRENT_BINARY_DIR}/../src
${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}
) )
add_library(testdummy STATIC
test_stubs.cc)
target_link_libraries(testdummy PkgConfig::libpcre)
add_executable(test_abbrev test_abbrev.cc) add_executable(test_abbrev test_abbrev.cc)
target_link_libraries(test_abbrev diag PkgConfig::libpcre) target_link_libraries(test_abbrev diag PkgConfig::libpcre)
add_test(NAME test_abbrev COMMAND test_abbrev) 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_test(NAME test_reltime COMMAND test_reltime)
add_executable(test_top_status test_top_status.cc) 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_test(NAME test_top_status COMMAND test_top_status)
add_executable(drive_view_colors drive_view_colors.cc) 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) add_executable(drive_vt52_curses drive_vt52_curses.cc)
target_link_libraries(drive_vt52_curses diag PkgConfig::ncursesw) target_link_libraries(drive_vt52_curses diag PkgConfig::ncursesw)
add_executable(drive_sql_anno drive_sql_anno.cc) 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) 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) add_executable(scripty scripty.cc)
target_link_libraries(scripty diag PkgConfig::ncursesw) target_link_libraries(scripty diag PkgConfig::ncursesw)

@ -24,6 +24,11 @@ AM_CPPFLAGS = \
# AM_CFLAGS = -fprofile-arcs -ftest-coverage # AM_CFLAGS = -fprofile-arcs -ftest-coverage
# AM_CXXFLAGS = -fprofile-arcs -ftest-coverage # AM_CXXFLAGS = -fprofile-arcs -ftest-coverage
noinst_LIBRARIES = libtestdummy.a
libtestdummy_a_SOURCES = \
test_stubs.cc
check_PROGRAMS = \ check_PROGRAMS = \
drive_data_scanner \ drive_data_scanner \
drive_line_buffer \ drive_line_buffer \
@ -75,6 +80,7 @@ TEXT2C_OBJS = \
LDADD = \ LDADD = \
-lz \ -lz \
libtestdummy.a \
$(CONFIG_OBJS) \ $(CONFIG_OBJS) \
$(TEXT2C_OBJS) \ $(TEXT2C_OBJS) \
$(top_builddir)/src/libdiag.a \ $(top_builddir)/src/libdiag.a \

@ -50,15 +50,6 @@ using namespace std;
const char *TMP_NAME = "scanned.tmp"; 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 main(int argc, char *argv[])
{ {
int c, retval = EXIT_SUCCESS; int c, retval = EXIT_SUCCESS;

@ -58,15 +58,6 @@ time_t time(time_t *_unused)
return 1194107018; 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 main(int argc, char *argv[])
{ {
int c, retval = EXIT_SUCCESS; int c, retval = EXIT_SUCCESS;

@ -7,7 +7,7 @@
#include <iostream> #include <iostream>
#include "lnav.hh" #include "base/injector.hh"
#include "auto_mem.hh" #include "auto_mem.hh"
#include "sqlite-extension-func.hh" #include "sqlite-extension-func.hh"
#include "regexp_vtab.hh" #include "regexp_vtab.hh"
@ -17,8 +17,6 @@ struct callback_state {
int cs_row; int cs_row;
}; };
struct lnav_data_t lnav_data;
static int sql_callback(void *ptr, static int sql_callback(void *ptr,
int ncols, int ncols,
char **colvalues, char **colvalues,
@ -36,36 +34,6 @@ static int sql_callback(void *ptr,
return 0; 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 main(int argc, char *argv[])
{ {
int retval = EXIT_SUCCESS; int retval = EXIT_SUCCESS;

@ -38,38 +38,6 @@
using namespace std; 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 main(int argc, char *argv[])
{ {
int retval = EXIT_SUCCESS; int retval = EXIT_SUCCESS;

@ -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

@ -2,6 +2,26 @@
lnav_test="${top_builddir}/src/lnav-test" 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?" <<EOF
command-option:1: error: unable to read script file: nonexistent-file -- No such file or directory
EOF
run_test ${lnav_test} -n \
-c ";.read ${test_dir}/file_for_dot_read.sql" \
-c ':write-csv-to -' \
${test_dir}/logfile_syslog.0
check_output ".read did not work?" <<EOF
log_line,log_body
1, attempting to mount entry /auto/opt
EOF
run_test ${lnav_test} -n \ run_test ${lnav_test} -n \
-c ";SELECT replicate('foobar', 120)" \ -c ";SELECT replicate('foobar', 120)" \
${test_dir}/logfile_empty.0 ${test_dir}/logfile_empty.0

@ -0,0 +1,71 @@
/**
* 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 "lnav.hh"
#include "base/injector.hh"
#include "service_tags.hh"
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;
template<>
void injector::force_linking(services::curl_streamer_t anno)
{
}

@ -48,15 +48,6 @@ int gettimeofday(struct timeval * tp, void * tzp)
return 0; return 0;
} }
Result<string, string> 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 main(int argc, char *argv[])
{ {
int retval = EXIT_SUCCESS; int retval = EXIT_SUCCESS;

Loading…
Cancel
Save