[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.
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

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

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

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

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

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

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

@ -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;
}
private:
};
}

@ -43,6 +43,9 @@
namespace injector {
template<typename Annotation>
void force_linking(Annotation anno);
template<class ...>
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>
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<T> create() {
static int _[] = {0, (force_linking(Annotations{}), 0)...};
(void)_;
return ss_factory();
}
protected:

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

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

@ -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<string, string> 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<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;
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;
}

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

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

@ -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 &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_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;

@ -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<isc::service>()
.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)
{
// 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 +

@ -979,11 +979,15 @@ Result<config_file_type, std::string>
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, "//");
}

@ -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<ghc::filesystem::path> &paths)
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) {
out.assign((std::istreambuf_iterator<char>(sql_file)),
std::istreambuf_iterator<char>());
return true;
}
if (!file_stream) {
return Err(std::string(strerror(errno)));
}
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>

@ -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<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 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);
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<std::string, std::string> read_file(const ghc::filesystem::path &path);
template<typename T>
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());
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()));
}
}
}

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

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

@ -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) {};

@ -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();

@ -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 "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<const help_text *> 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<string> 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;

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

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

@ -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(
.
${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)

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

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

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

@ -7,7 +7,7 @@
#include <iostream>
#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;

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

@ -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"
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 \
-c ";SELECT replicate('foobar', 120)" \
${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;
}
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 retval = EXIT_SUCCESS;

Loading…
Cancel
Save