mirror of https://github.com/tstack/lnav.git
[sqlite] implement .dump SQL command
This commit is contained in:
parent
6f4791ec35
commit
c0ed59e61e
1
NEWS
1
NEWS
|
@ -53,6 +53,7 @@ lnav v0.9.1:
|
|||
* The "generate_series()" SQLite extension is now included by default.
|
||||
One change from the standard implementation is that both the start and
|
||||
stop are required parameters.
|
||||
* Added the ";.read" SQL command for executing a plain SQL file.
|
||||
|
||||
Interface Changes:
|
||||
* When copying log lines, the file name and time offset will be included
|
||||
|
|
|
@ -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 ¶m : ht.ht_parameters) {
|
||||
out.append(" ");
|
||||
if (param.ht_nargs == help_nargs_t::HN_OPTIONAL) {
|
||||
out.append("[");
|
||||
}
|
||||
out.append(param.ht_name, &view_curses::VC_STYLE, A_UNDERLINE);
|
||||
if (param.ht_nargs == help_nargs_t::HN_OPTIONAL) {
|
||||
out.append("]");
|
||||
}
|
||||
if (param.ht_nargs == help_nargs_t::HN_ONE_OR_MORE) {
|
||||
out.append("1", &view_curses::VC_STYLE, A_UNDERLINE);
|
||||
out.append(" [");
|
||||
out.append("...", &view_curses::VC_STYLE, A_UNDERLINE);
|
||||
out.append(" ");
|
||||
out.append(param.ht_name, &view_curses::VC_STYLE,
|
||||
A_UNDERLINE);
|
||||
out.append("N", &view_curses::VC_STYLE, A_UNDERLINE);
|
||||
out.append("]");
|
||||
}
|
||||
}
|
||||
out.append(" - ")
|
||||
.append(attr_line_t::from_ansi_str(ht.ht_summary),
|
||||
&tws.with_indent(body_indent + 2))
|
||||
.append("\n");
|
||||
break;
|
||||
}
|
||||
case help_context_t::HC_SQL_INFIX:
|
||||
case help_context_t::HC_SQL_KEYWORD: {
|
||||
size_t line_start = body_indent;
|
||||
|
@ -501,6 +533,9 @@ void format_help_text_for_rst(const help_text &ht,
|
|||
case help_context_t::HC_COMMAND:
|
||||
prefix = ":";
|
||||
break;
|
||||
case help_context_t::HC_SQL_COMMAND:
|
||||
prefix = ";";
|
||||
break;
|
||||
case help_context_t::HC_SQL_FUNCTION:
|
||||
case help_context_t::HC_SQL_TABLE_VALUED_FUNCTION:
|
||||
is_sql = is_sql_func = true;
|
||||
|
|
27
src/lnav.cc
27
src/lnav.cc
|
@ -80,6 +80,7 @@
|
|||
#include "init-sql.h"
|
||||
#include "logfile.hh"
|
||||
#include "base/func_util.hh"
|
||||
#include "base/humanize.time.hh"
|
||||
#include "base/injector.bind.hh"
|
||||
#include "base/isc.hh"
|
||||
#include "base/string_util.hh"
|
||||
|
@ -252,6 +253,16 @@ static auto bound_curl =
|
|||
injector::bind_multiple<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, "//");
|
||||
}
|
||||
|
|
116
src/lnav_util.cc
116
src/lnav_util.cc
|
@ -45,101 +45,6 @@
|
|||
|
||||
using namespace std;
|
||||
|
||||
std::string time_ago(time_t last_time, bool convert_local)
|
||||
{
|
||||
time_t delta, current_time = time(nullptr);
|
||||
const char *fmt;
|
||||
char buffer[64];
|
||||
int amount;
|
||||
|
||||
if (convert_local) {
|
||||
current_time = convert_log_time_to_local(current_time);
|
||||
}
|
||||
|
||||
delta = current_time - last_time;
|
||||
if (delta < 0) {
|
||||
return "in the future";
|
||||
}
|
||||
else if (delta < 60) {
|
||||
return "just now";
|
||||
}
|
||||
else if (delta < (60 * 2)) {
|
||||
return "one minute ago";
|
||||
}
|
||||
else if (delta < (60 * 60)) {
|
||||
fmt = "%d minutes ago";
|
||||
amount = delta / 60;
|
||||
}
|
||||
else if (delta < (2 * 60 * 60)) {
|
||||
return "one hour ago";
|
||||
}
|
||||
else if (delta < (24 * 60 * 60)) {
|
||||
fmt = "%d hours ago";
|
||||
amount = delta / (60 * 60);
|
||||
}
|
||||
else if (delta < (2 * 24 * 60 * 60)) {
|
||||
return "one day ago";
|
||||
}
|
||||
else if (delta < (365 * 24 * 60 * 60)) {
|
||||
fmt = "%d days ago";
|
||||
amount = delta / (24 * 60 * 60);
|
||||
}
|
||||
else if (delta < (2 * 365 * 24 * 60 * 60)) {
|
||||
return "over a year ago";
|
||||
}
|
||||
else {
|
||||
fmt = "over %d years ago";
|
||||
amount = delta / (365 * 24 * 60 * 60);
|
||||
}
|
||||
|
||||
snprintf(buffer, sizeof(buffer), fmt, amount);
|
||||
|
||||
return std::string(buffer);
|
||||
}
|
||||
|
||||
std::string precise_time_ago(const struct timeval &tv, bool convert_local)
|
||||
{
|
||||
struct timeval now, diff;
|
||||
|
||||
gettimeofday(&now, nullptr);
|
||||
if (convert_local) {
|
||||
now.tv_sec = convert_log_time_to_local(now.tv_sec);
|
||||
}
|
||||
|
||||
timersub(&now, &tv, &diff);
|
||||
if (diff.tv_sec < 0) {
|
||||
return time_ago(tv.tv_sec);
|
||||
}
|
||||
else if (diff.tv_sec <= 1) {
|
||||
return "a second ago";
|
||||
}
|
||||
else if (diff.tv_sec < (10 * 60)) {
|
||||
char buf[64];
|
||||
|
||||
if (diff.tv_sec < 60) {
|
||||
snprintf(buf, sizeof(buf),
|
||||
"%2ld seconds ago",
|
||||
diff.tv_sec);
|
||||
}
|
||||
else {
|
||||
time_t seconds = diff.tv_sec % 60;
|
||||
time_t minutes = diff.tv_sec / 60;
|
||||
|
||||
snprintf(buf, sizeof(buf),
|
||||
"%2ld minute%s and %2ld second%s ago",
|
||||
minutes,
|
||||
minutes > 1 ? "s" : "",
|
||||
seconds,
|
||||
seconds == 1 ? "" : "s");
|
||||
}
|
||||
|
||||
return string(buf);
|
||||
}
|
||||
else {
|
||||
return time_ago(tv.tv_sec, convert_local);
|
||||
}
|
||||
}
|
||||
|
||||
bool change_to_parent_dir()
|
||||
{
|
||||
bool retval = false;
|
||||
|
@ -211,17 +116,22 @@ string build_path(const vector<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)));
|
||||
}
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
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…
Reference in New Issue