mirror of
https://github.com/tstack/lnav.git
synced 2024-10-26 21:19:54 +03:00
[prql] initial work for integrating PRQL
This commit is contained in:
parent
1113320cd4
commit
bdc9c5a28d
@ -5,7 +5,7 @@ include(cmake/prelude.cmake)
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
project(
|
||||
lnav
|
||||
VERSION 0.12.0
|
||||
VERSION 0.12.1
|
||||
DESCRIPTION "An advanced log file viewer for the small-scale."
|
||||
HOMEPAGE_URL "https://lnav.org/"
|
||||
LANGUAGES CXX C
|
||||
|
12
NEWS.md
12
NEWS.md
@ -1,6 +1,12 @@
|
||||
## lnav v0.12.1
|
||||
|
||||
Features:
|
||||
* Database queries can now be written in
|
||||
[PRQL](https://prql-lang.org). When executing a query with `;`,
|
||||
if the query starts with `from`, it will be treated as PRQL.
|
||||
The pipeline structure of PRQL queries is more desirable for
|
||||
interactive use since lnav can make better suggestions and
|
||||
show previews of the stages of the pipeline.
|
||||
* Log partitions can automatically be created by defining a log
|
||||
message pattern in a log format. Under a format definition,
|
||||
add an entry into the "partitions" object in a format definition.
|
||||
@ -12,6 +18,12 @@ Features:
|
||||
that will be matched against file names.
|
||||
|
||||
Interface changes:
|
||||
* When using PRQL in the database query prompt (`;`),
|
||||
the preview pane will show the results for the pipeline
|
||||
stage the cursor is within along with the results of
|
||||
the previous stage (if there is one). The preview
|
||||
works on a limited data set, so the preview results
|
||||
may differ from the final results.
|
||||
* Changed the breadcrumb bar styling to space things out
|
||||
more and make the divisions between items clearer.
|
||||
* The `ESC` key can now be used to exit the files/filters
|
||||
|
@ -4,7 +4,7 @@ from conan.tools.cmake import CMake, CMakeToolchain, CMakeDeps
|
||||
|
||||
class LnavConan(ConanFile):
|
||||
name = "lnav"
|
||||
version = "0.12.0"
|
||||
version = "0.12.1"
|
||||
homepage = "https://lnav.org"
|
||||
url = "https://github.com/tstack/lnav.git"
|
||||
license = "BSD-2-Clause"
|
||||
|
@ -1,4 +1,4 @@
|
||||
AC_INIT([lnav],[0.12.0],[lnav@googlegroups.com],[lnav],[http://lnav.org])
|
||||
AC_INIT([lnav],[0.12.1],[lnav@googlegroups.com],[lnav],[http://lnav.org])
|
||||
AC_CONFIG_SRCDIR([src/lnav.cc])
|
||||
AC_CONFIG_MACRO_DIR([m4])
|
||||
AM_INIT_AUTOMAKE([foreign subdir-objects])
|
||||
@ -50,6 +50,7 @@ AM_PROG_AR
|
||||
AC_PROG_LN_S
|
||||
AC_PROG_MAKE_SET
|
||||
|
||||
AC_PATH_PROG(CARGO_CMD, [cargo])
|
||||
AC_PATH_PROG(BZIP2_CMD, [bzip2])
|
||||
AC_PATH_PROG(RE2C_CMD, [re2c])
|
||||
AM_CONDITIONAL(HAVE_RE2C, test x"$RE2C_CMD" != x"")
|
||||
@ -265,7 +266,7 @@ AC_SUBST(STATIC_LDFLAGS)
|
||||
|
||||
AS_CASE(["$host_os"],
|
||||
[darwin*],
|
||||
[],
|
||||
[LDFLAGS="$LDFLAGS -framework CoreFoundation"],
|
||||
[
|
||||
curses_lib=$(echo $CURSES_LIB | sed -e 's/-l//')
|
||||
AS_IF([test $? -eq 0],
|
||||
@ -305,6 +306,7 @@ AS_IF([test $? -eq 0],
|
||||
[VCS package string])],
|
||||
AC_DEFINE_UNQUOTED([VCS_PACKAGE_STRING], ["$PACKAGE_STRING"], [VCS package string]))
|
||||
|
||||
AM_CONDITIONAL(HAVE_CARGO, test x"$CARGO_CMD" != x"")
|
||||
AM_CONDITIONAL(USE_INCLUDED_YAJL, test $HAVE_LOCAL_YAJL -eq 0)
|
||||
AM_CONDITIONAL(HAVE_LIBCURL, test x"$LIBCURL" != x"")
|
||||
AM_CONDITIONAL([CROSS_COMPILING], [ test x"$cross_compiling" != x"no" ])
|
||||
|
@ -71,6 +71,15 @@ The DB view has the following display features:
|
||||
the `jget`_ function.
|
||||
|
||||
|
||||
PRQL Support (v0.12.1+)
|
||||
-----------------------
|
||||
|
||||
`PRQL <https://prql-lang.org>`_ is an alternative database query language
|
||||
that compiles to SQLite. You can enter PRQL in the database query prompt
|
||||
and lnav will switch accordingly. A major advantage of using PRQL is that
|
||||
lnav can show previews of the results of the pipeline stages and provide
|
||||
better tab completion options.
|
||||
|
||||
Log Tables
|
||||
----------
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
|
||||
VERSION=0.12.0
|
||||
VERSION=0.12.1
|
||||
|
||||
VERSION_TAG=v$(VERSION)
|
||||
|
||||
|
@ -130,7 +130,7 @@ function(bin2c)
|
||||
DEPENDS bin2c "${FILE_TO_LINK}")
|
||||
endfunction(bin2c)
|
||||
|
||||
foreach (FILE_TO_LINK animals.json ansi-palette.json css-color-names.json diseases.json emojis.json xml-entities.json xterm-palette.json help.txt help.md init.sql words.json)
|
||||
foreach (FILE_TO_LINK animals.json ansi-palette.json css-color-names.json diseases.json emojis.json xml-entities.json xterm-palette.json help.txt help.md init.sql prelude.prql words.json)
|
||||
string(REPLACE "." "-" DST_FILE "${FILE_TO_LINK}")
|
||||
add_custom_command(
|
||||
OUTPUT "${DST_FILE}.h" "${DST_FILE}.cc"
|
||||
@ -659,6 +659,8 @@ add_library(
|
||||
third-party/md4c/md4c.h
|
||||
|
||||
third-party/robin_hood/robin_hood.h
|
||||
|
||||
third-party/prqlc-c/prqlc.hpp
|
||||
)
|
||||
|
||||
set(lnav_SRCS lnav.cc)
|
||||
@ -667,6 +669,7 @@ target_include_directories(diag PUBLIC . fmtlib ${CMAKE_CURRENT_BINARY_DIR}
|
||||
third-party
|
||||
third-party/base64/include
|
||||
third-party/date/include
|
||||
third-party/prqlc-c
|
||||
third-party/rapidyaml
|
||||
)
|
||||
|
||||
|
@ -74,6 +74,9 @@ builtin-sh-scripts.cc: $(BIN2C_PATH) $(BUILTIN_SHSCRIPTS)
|
||||
%-sql.cc: $(srcdir)/%.sql $(BIN2C_PATH)
|
||||
$(BIN2C_V)$(BIN2C_PATH) $(*)-sql $<
|
||||
|
||||
%-prql.cc: $(srcdir)/%.prql $(BIN2C_PATH)
|
||||
$(BIN2C_V)$(BIN2C_PATH) $(*)-prql $<
|
||||
|
||||
%-lnav.cc: $(srcdir)/%.lnav $(BIN2C_PATH)
|
||||
$(BIN2C_V)$(BIN2C_PATH) $(*)-lnav $<
|
||||
|
||||
@ -104,6 +107,7 @@ LNAV_BUILT_FILES = \
|
||||
words-json.cc \
|
||||
help-md.cc \
|
||||
init-sql.cc \
|
||||
prelude-prql.cc \
|
||||
time_fmts.cc \
|
||||
xml-entities-json.cc \
|
||||
xterm-palette-json.cc
|
||||
@ -114,6 +118,22 @@ AM_LIBS = $(CODE_COVERAGE_LIBS)
|
||||
AM_CFLAGS = $(CODE_COVERAGE_CFLAGS)
|
||||
AM_CXXFLAGS = $(CODE_COVERAGE_CXXFLAGS) $(USER_CXXFLAGS)
|
||||
|
||||
if HAVE_CARGO
|
||||
RUST_DEPS_CPPFLAGS = -I$(srcdir)/third-party/prqlc-c -DHAVE_RUST_DEPS=1
|
||||
PRQLC_DIR = src/third-party/prqlc-c/target
|
||||
RUST_DEPS_LIBS = $(PRQLC_DIR)/release/libprqlc_c.a
|
||||
|
||||
$(RUST_DEPS_LIBS): $(srcdir)/third-party/prqlc-c/src/lib.rs
|
||||
mkdir -p $(PRQLC_DIR)
|
||||
env CARGO_TARGET_DIR=src/third-party/prqlc-c/target $(CARGO_CMD) build --manifest-path \
|
||||
$(srcdir)/third-party/prqlc-c/Cargo.toml --package prqlc-c --release
|
||||
|
||||
else
|
||||
RUST_DEPS =
|
||||
RUST_DEPS_CPPFLAGS =
|
||||
RUST_DEPS_LIBS =
|
||||
endif
|
||||
|
||||
AM_LDFLAGS = \
|
||||
$(STATIC_LDFLAGS) \
|
||||
$(LIBARCHIVE_LDFLAGS) \
|
||||
@ -136,7 +156,8 @@ AM_CPPFLAGS = \
|
||||
$(READLINE_CFLAGS) \
|
||||
$(SQLITE3_CFLAGS) \
|
||||
$(PCRE_CFLAGS) \
|
||||
$(LIBCURL_CPPFLAGS)
|
||||
$(LIBCURL_CPPFLAGS) \
|
||||
$(RUST_DEPS_CPPFLAGS)
|
||||
|
||||
LDADD = \
|
||||
libdiag.a \
|
||||
@ -154,6 +175,7 @@ LDADD = \
|
||||
yajl/libyajl.a \
|
||||
yajlpp/libyajlpp.a \
|
||||
third-party/base64/lib/libbase64.a \
|
||||
$(RUST_DEPS_LIBS) \
|
||||
$(READLINE_LIBS) \
|
||||
$(CURSES_LIB) \
|
||||
$(SQLITE3_LIBS) \
|
||||
@ -276,6 +298,7 @@ noinst_HEADERS = \
|
||||
piper.looper.cfg.hh \
|
||||
plain_text_source.hh \
|
||||
pollable.hh \
|
||||
prelude.prql \
|
||||
pretty_printer.hh \
|
||||
preview_status_source.hh \
|
||||
ptimec.hh \
|
||||
@ -547,11 +570,18 @@ DISTCLEANFILES = \
|
||||
words-json.h \
|
||||
help-md.h \
|
||||
init-sql.h \
|
||||
prelude-prql.h \
|
||||
time_fmts.h \
|
||||
xml-entities-json.h \
|
||||
xterm-palette-json.h \
|
||||
$(RE2C_FILES)
|
||||
|
||||
if HAVE_CARGO
|
||||
clean-local:
|
||||
env CARGO_TARGET_DIR=src/third-party/prqlc-c/target $(CARGO_CMD) clean --manifest-path \
|
||||
$(srcdir)/third-party/prqlc-c/Cargo.toml
|
||||
endif
|
||||
|
||||
distclean-local:
|
||||
$(RM_V)rm -rf *.dSYM
|
||||
|
||||
@ -560,13 +590,13 @@ uncrusty:
|
||||
$(HEADERS))
|
||||
|
||||
if !CROSS_COMPILING
|
||||
all-local: $(LNAV_BUILT_FILES) lnav
|
||||
all-local: $(LNAV_BUILT_FILES) lnav $(RUST_DEPS)
|
||||
if test -w $(srcdir)/internals; then \
|
||||
env DUMP_INTERNALS_DIR=$(srcdir)/internals DUMP_CRASH=1 ./lnav Makefile; \
|
||||
mv $(srcdir)/internals/*.schema.json $(top_srcdir)/docs/schemas; \
|
||||
fi
|
||||
else
|
||||
all-local: $(LNAV_BUILT_FILES)
|
||||
all-local: $(LNAV_BUILT_FILES) $(RUST_DEPS)
|
||||
endif
|
||||
|
||||
install-exec-hook:
|
||||
|
@ -457,7 +457,7 @@ attr_line_t::apply_hide()
|
||||
}
|
||||
|
||||
attr_line_t&
|
||||
attr_line_t::rtrim()
|
||||
attr_line_t::rtrim(nonstd::optional<const char*> chars)
|
||||
{
|
||||
auto index = this->al_string.length();
|
||||
|
||||
@ -468,7 +468,12 @@ attr_line_t::rtrim()
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (!isspace(this->al_string[index - 1])) {
|
||||
if (chars
|
||||
&& strchr(chars.value(), this->al_string[index - 1]) == nullptr)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (!chars && !isspace(this->al_string[index - 1])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -490,7 +495,9 @@ attr_line_t::erase(size_t pos, size_t len)
|
||||
|
||||
this->al_string.erase(pos, len);
|
||||
|
||||
shift_string_attrs(this->al_attrs, pos, -((int32_t) len));
|
||||
shift_string_attrs(this->al_attrs,
|
||||
line_range{(int) pos, (int) (pos + len)},
|
||||
-((int32_t) len));
|
||||
auto new_end = std::remove_if(
|
||||
this->al_attrs.begin(), this->al_attrs.end(), [](const auto& attr) {
|
||||
return attr.sa_range.empty();
|
||||
@ -541,12 +548,18 @@ line_range::shift_range(const line_range& cover, int32_t amount)
|
||||
if (this->lr_end != -1) {
|
||||
this->lr_end = std::max(0, this->lr_end + amount);
|
||||
}
|
||||
} else if (this->lr_end != -1) {
|
||||
if (cover.lr_start < this->lr_end) {
|
||||
if (amount < 0 && amount < (cover.lr_start - this->lr_end)) {
|
||||
this->lr_end = cover.lr_start;
|
||||
} else {
|
||||
this->lr_end = std::max(this->lr_start, this->lr_end + amount);
|
||||
} else {
|
||||
if (amount < 0 && cover.contains(*this)) {
|
||||
this->lr_start = cover.lr_start;
|
||||
}
|
||||
if (this->lr_end != -1) {
|
||||
if (cover.lr_start < this->lr_end) {
|
||||
if (amount < 0 && amount < (cover.lr_start - this->lr_end)) {
|
||||
this->lr_end = cover.lr_start;
|
||||
} else {
|
||||
this->lr_end
|
||||
= std::max(this->lr_start, this->lr_end + amount);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -472,7 +472,7 @@ public:
|
||||
|
||||
attr_line_t& erase(size_t pos, size_t len = std::string::npos);
|
||||
|
||||
attr_line_t& rtrim();
|
||||
attr_line_t& rtrim(nonstd::optional<const char*> chars = nonstd::nullopt);
|
||||
|
||||
attr_line_t& erase_utf8_chars(size_t start)
|
||||
{
|
||||
|
@ -47,12 +47,18 @@
|
||||
#include "lnav_config.hh"
|
||||
#include "lnav_util.hh"
|
||||
#include "log_format_loader.hh"
|
||||
#include "prelude-prql.h"
|
||||
#include "readline_highlighters.hh"
|
||||
#include "service_tags.hh"
|
||||
#include "shlex.hh"
|
||||
#include "sql_help.hh"
|
||||
#include "sql_util.hh"
|
||||
#include "vtab_module.hh"
|
||||
|
||||
#ifdef HAVE_RUST_DEPS
|
||||
# include "prqlc.hpp"
|
||||
#endif
|
||||
|
||||
using namespace lnav::roles::literals;
|
||||
|
||||
exec_context INIT_EXEC_CONTEXT;
|
||||
@ -250,14 +256,56 @@ execute_search(const std::string& search_cmd)
|
||||
Result<std::string, lnav::console::user_message>
|
||||
execute_sql(exec_context& ec, const std::string& sql, std::string& alt_msg)
|
||||
{
|
||||
db_label_source& dls = lnav_data.ld_db_row_source;
|
||||
db_label_source& dls = *(ec.ec_label_source_stack.back());
|
||||
auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
|
||||
struct timeval start_tv, end_tv;
|
||||
std::string stmt_str = trim(sql);
|
||||
std::string retval;
|
||||
int retcode = SQLITE_OK;
|
||||
|
||||
log_info("Executing SQL: %s", sql.c_str());
|
||||
if (lnav::sql::is_prql(stmt_str)) {
|
||||
log_info("compiling PRQL: %s", stmt_str.c_str());
|
||||
|
||||
#if HAVE_RUST_DEPS
|
||||
auto opts = prqlc::Options{true, "sql.sqlite", true};
|
||||
|
||||
auto full_prql = fmt::format(FMT_STRING("{}\n{}{}"),
|
||||
prelude_prql.to_string_fragment(),
|
||||
sqlite_extension_prql,
|
||||
stmt_str);
|
||||
auto cr = prqlc::compile(full_prql.c_str(), &opts);
|
||||
|
||||
for (size_t lpc = 0; lpc < cr.messages_len; lpc++) {
|
||||
const auto& msg = cr.messages[lpc];
|
||||
|
||||
if (msg.kind != prqlc::MessageKind::Error) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto um
|
||||
= lnav::console::user_message::error(
|
||||
attr_line_t("unable to compile PRQL: ").append(stmt_str))
|
||||
.with_reason(msg.reason);
|
||||
if (msg.display && *msg.display) {
|
||||
um.with_note(*msg.display);
|
||||
}
|
||||
if (msg.hint && *msg.hint) {
|
||||
um.with_help(*msg.hint);
|
||||
}
|
||||
return Err(um);
|
||||
}
|
||||
if (cr.output && cr.output[0]) {
|
||||
stmt_str = cr.output;
|
||||
}
|
||||
prqlc::result_destroy(cr);
|
||||
#else
|
||||
auto um = lnav::console::user_message::error(
|
||||
attr_line_t("PRQL is not supported in this build"));
|
||||
return Err(um);
|
||||
#endif
|
||||
}
|
||||
|
||||
log_info("Executing SQL: %s", stmt_str.c_str());
|
||||
|
||||
auto old_mode = lnav_data.ld_mode;
|
||||
lnav_data.ld_mode = ln_mode_t::BUSY;
|
||||
@ -268,8 +316,9 @@ execute_sql(exec_context& ec, const std::string& sql, std::string& alt_msg)
|
||||
std::vector<std::string> args;
|
||||
split_ws(stmt_str, args);
|
||||
|
||||
auto* sql_cmd_map = injector::get<readline_context::command_map_t*,
|
||||
sql_cmd_map_tag>();
|
||||
const auto* sql_cmd_map
|
||||
= injector::get<readline_context::command_map_t*,
|
||||
sql_cmd_map_tag>();
|
||||
auto cmd_iter = sql_cmd_map->find(args[0]);
|
||||
|
||||
if (cmd_iter != sql_cmd_map->end()) {
|
||||
@ -798,7 +847,7 @@ execute_init_commands(
|
||||
ec_out = std::make_pair(tmpout.release(), fclose);
|
||||
}
|
||||
|
||||
auto& dls = lnav_data.ld_db_row_source;
|
||||
auto& dls = *(ec.ec_label_source_stack.back());
|
||||
int option_index = 1;
|
||||
|
||||
{
|
||||
@ -883,7 +932,7 @@ execute_init_commands(
|
||||
int
|
||||
sql_callback(exec_context& ec, sqlite3_stmt* stmt)
|
||||
{
|
||||
auto& dls = lnav_data.ld_db_row_source;
|
||||
auto& dls = *(ec.ec_label_source_stack.back());
|
||||
|
||||
if (!sqlite3_stmt_busy(stmt)) {
|
||||
dls.clear();
|
||||
|
@ -38,6 +38,7 @@
|
||||
|
||||
#include "base/auto_fd.hh"
|
||||
#include "base/lnav.console.hh"
|
||||
#include "db_sub_source.hh"
|
||||
#include "fmt/format.h"
|
||||
#include "ghc/filesystem.hpp"
|
||||
#include "help_text.hh"
|
||||
@ -186,6 +187,33 @@ struct exec_context {
|
||||
int line_number,
|
||||
const std::string& content);
|
||||
|
||||
struct db_source_guard {
|
||||
db_source_guard(exec_context* context) : dsg_context(context) {}
|
||||
|
||||
db_source_guard(const source_guard&) = delete;
|
||||
|
||||
db_source_guard(source_guard&& other) : dsg_context(other.sg_context)
|
||||
{
|
||||
other.sg_context = nullptr;
|
||||
}
|
||||
|
||||
~db_source_guard()
|
||||
{
|
||||
if (this->dsg_context != nullptr) {
|
||||
this->dsg_context->ec_label_source_stack.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
exec_context* dsg_context;
|
||||
};
|
||||
|
||||
db_source_guard enter_db_source(db_label_source* dls)
|
||||
{
|
||||
this->ec_label_source_stack.push_back(dls);
|
||||
|
||||
return db_source_guard{this};
|
||||
}
|
||||
|
||||
struct error_cb_guard {
|
||||
error_cb_guard(exec_context* context) : sg_context(context) {}
|
||||
|
||||
@ -277,6 +305,7 @@ struct exec_context {
|
||||
sql_callback_t ec_sql_callback;
|
||||
pipe_callback_t ec_pipe_callback;
|
||||
std::vector<error_callback_t> ec_error_callback_stack;
|
||||
std::vector<db_label_source*> ec_label_source_stack;
|
||||
};
|
||||
|
||||
Result<std::string, lnav::console::user_message> execute_command(
|
||||
|
@ -26,6 +26,8 @@
|
||||
|
||||
#define HAVE_SQLITE3_DROP_MODULES
|
||||
|
||||
#define HAVE_RUST_DEPS 1
|
||||
|
||||
#define _XOPEN_SOURCE_EXTENDED 1
|
||||
|
||||
#define PACKAGE_BUGREPORT "lnav@googlegroups.com"
|
||||
|
@ -77,10 +77,10 @@ dump_internals(const char* internals_dir)
|
||||
auto sql_ref_path = ghc::filesystem::path(internals_dir) / "sql-ref.rst";
|
||||
auto sql_file = std::unique_ptr<FILE, decltype(&fclose)>(
|
||||
fopen(sql_ref_path.c_str(), "w+"), fclose);
|
||||
std::set<help_text*> unique_sql_help;
|
||||
std::set<const help_text*> unique_sql_help;
|
||||
|
||||
if (sql_file != nullptr) {
|
||||
for (auto& sql : sqlite_function_help) {
|
||||
for (const auto& sql : sqlite_function_help) {
|
||||
if (unique_sql_help.find(sql.second) != unique_sql_help.end()) {
|
||||
continue;
|
||||
}
|
||||
|
@ -106,7 +106,7 @@ Original code 2006 June 05 by relicoder.
|
||||
|
||||
*/
|
||||
|
||||
//#include "config.h"
|
||||
// #include "config.h"
|
||||
|
||||
// #define COMPILE_SQLITE_EXTENSIONS_AS_LOADABLE_MODULE 1
|
||||
#define HAVE_ACOSH 1
|
||||
@ -2550,6 +2550,7 @@ common_extension_functions(struct FuncDef** basic_funcs,
|
||||
reverseFunc,
|
||||
help_text("reverse")
|
||||
.sql_function()
|
||||
.with_prql_path({"text", "reverse"})
|
||||
.with_summary("Returns the reverse of the given string.")
|
||||
.with_parameter({"str", "The string to reverse."})
|
||||
.with_tags({"string"})
|
||||
|
@ -328,6 +328,7 @@ fs_extension_functions(struct FuncDef** basic_funcs,
|
||||
sqlite_func_adapter<decltype(&sql_basename), sql_basename>::builder(
|
||||
help_text("basename", "Extract the base portion of a pathname.")
|
||||
.sql_function()
|
||||
.with_prql_path({"fs", "basename"})
|
||||
.with_parameter({"path", "The path"})
|
||||
.with_tags({"filename"})
|
||||
.with_example({"To get the base of a plain file name",
|
||||
@ -341,12 +342,18 @@ fs_extension_functions(struct FuncDef** basic_funcs,
|
||||
.with_example({"To get the base of a Windows path",
|
||||
"SELECT basename('foo\\bar')"})
|
||||
.with_example({"To get the base of the root directory",
|
||||
"SELECT basename('/')"})),
|
||||
"SELECT basename('/')"})
|
||||
.with_example({
|
||||
"To get the base of a path",
|
||||
"from [{p='foo/bar'}] | select { fs.basename p }",
|
||||
help_example::language::prql,
|
||||
})),
|
||||
|
||||
sqlite_func_adapter<decltype(&sql_dirname), sql_dirname>::builder(
|
||||
help_text("dirname", "Extract the directory portion of a pathname.")
|
||||
.sql_function()
|
||||
.with_parameter({"path", "The path"})
|
||||
.with_prql_path({"fs", "dirname"})
|
||||
.with_tags({"filename"})
|
||||
.with_example({"To get the directory of a relative file path",
|
||||
"SELECT dirname('foo/bar')"})
|
||||
@ -363,6 +370,7 @@ fs_extension_functions(struct FuncDef** basic_funcs,
|
||||
sqlite_func_adapter<decltype(&sql_joinpath), sql_joinpath>::builder(
|
||||
help_text("joinpath", "Join components of a path together.")
|
||||
.sql_function()
|
||||
.with_prql_path({"fs", "join"})
|
||||
.with_parameter(
|
||||
help_text(
|
||||
"path",
|
||||
@ -391,6 +399,7 @@ fs_extension_functions(struct FuncDef** basic_funcs,
|
||||
sqlite_func_adapter<decltype(&sql_readlink), sql_readlink>::builder(
|
||||
help_text("readlink", "Read the target of a symbolic link.")
|
||||
.sql_function()
|
||||
.with_prql_path({"fs", "readlink"})
|
||||
.with_parameter({"path", "The path to the symbolic link."})
|
||||
.with_tags({"filename"})),
|
||||
|
||||
@ -401,6 +410,7 @@ fs_extension_functions(struct FuncDef** basic_funcs,
|
||||
"symbolic links and "
|
||||
"resolving '.' and '..' references.")
|
||||
.sql_function()
|
||||
.with_prql_path({"fs", "realpath"})
|
||||
.with_parameter({"path", "The path to resolve."})
|
||||
.with_tags({"filename"})),
|
||||
|
||||
@ -408,6 +418,7 @@ fs_extension_functions(struct FuncDef** basic_funcs,
|
||||
help_text("shell_exec",
|
||||
"Executes a shell command and returns its output.")
|
||||
.sql_function()
|
||||
.with_prql_path({"shell", "exec"})
|
||||
.with_parameter({"cmd", "The command to execute."})
|
||||
.with_parameter(help_text{
|
||||
"input",
|
||||
|
@ -315,12 +315,12 @@ mark lines of text and move the view by grabbing the scrollbar.
|
||||
NOTE: You need to manually enable this feature by setting the LNAV_EXP
|
||||
environment variable to "mouse". F2 toggles mouse support.
|
||||
|
||||
## SQL Queries (experimental)
|
||||
## SQL Queries
|
||||
|
||||
Lnav has support for performing SQL queries on log files using the
|
||||
Sqlite3 "virtual" table feature. For all supported log file types,
|
||||
SQLite3 "virtual" table feature. For all supported log file types,
|
||||
lnav will create tables that can be queried using the subset of SQL
|
||||
that is supported by Sqlite3. For example, to get the top ten URLs
|
||||
that is supported by SQLite3. For example, to get the top ten URLs
|
||||
being accessed in any loaded Apache log files, you can execute:
|
||||
|
||||
```lnav
|
||||
|
@ -96,6 +96,14 @@ help_text::with_opposites(
|
||||
return *this;
|
||||
}
|
||||
|
||||
help_text&
|
||||
help_text::with_prql_path(
|
||||
const std::initializer_list<const char*>& prql) noexcept
|
||||
{
|
||||
this->ht_prql_path = prql;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void
|
||||
help_text::index_tags()
|
||||
{
|
||||
|
@ -44,6 +44,7 @@ enum class help_context_t {
|
||||
HC_SQL_INFIX,
|
||||
HC_SQL_FUNCTION,
|
||||
HC_SQL_TABLE_VALUED_FUNCTION,
|
||||
HC_PRQL_TRANSFORM,
|
||||
};
|
||||
|
||||
enum class help_function_type_t {
|
||||
@ -68,8 +69,14 @@ enum class help_parameter_format_t {
|
||||
};
|
||||
|
||||
struct help_example {
|
||||
enum class language {
|
||||
undefined,
|
||||
prql,
|
||||
};
|
||||
|
||||
const char* he_description{nullptr};
|
||||
const char* he_cmd{nullptr};
|
||||
language he_language{language::undefined};
|
||||
};
|
||||
|
||||
struct help_text {
|
||||
@ -89,6 +96,7 @@ struct help_text {
|
||||
std::vector<const char*> ht_tags;
|
||||
std::vector<const char*> ht_opposites;
|
||||
help_function_type_t ht_function_type{help_function_type_t::HFT_REGULAR};
|
||||
std::vector<const char*> ht_prql_path;
|
||||
void* ht_impl{nullptr};
|
||||
|
||||
help_text() = default;
|
||||
@ -145,6 +153,12 @@ struct help_text {
|
||||
return *this;
|
||||
}
|
||||
|
||||
help_text& prql_transform() noexcept
|
||||
{
|
||||
this->ht_context = help_context_t::HC_PRQL_TRANSFORM;
|
||||
return *this;
|
||||
}
|
||||
|
||||
help_text& with_summary(const char* summary) noexcept
|
||||
{
|
||||
this->ht_summary = summary;
|
||||
@ -210,6 +224,9 @@ struct help_text {
|
||||
help_text& with_opposites(
|
||||
const std::initializer_list<const char*>& opps) noexcept;
|
||||
|
||||
help_text& with_prql_path(
|
||||
const std::initializer_list<const char*>& prql) noexcept;
|
||||
|
||||
template<typename F>
|
||||
help_text& with_impl(F impl)
|
||||
{
|
||||
|
@ -350,6 +350,45 @@ format_help_text_for_term(const help_text& ht,
|
||||
}
|
||||
break;
|
||||
}
|
||||
case help_context_t::HC_PRQL_TRANSFORM: {
|
||||
auto line_start = out.al_string.length();
|
||||
|
||||
out.append(";").append(lnav::roles::symbol(ht.ht_name));
|
||||
for (const auto& param : ht.ht_parameters) {
|
||||
out.append(" ");
|
||||
if (param.ht_nargs == help_nargs_t::HN_OPTIONAL) {
|
||||
out.append("[");
|
||||
}
|
||||
out.append(lnav::roles::variable(param.ht_name));
|
||||
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"_variable);
|
||||
out.append(" [");
|
||||
out.append("..."_variable);
|
||||
out.append(" ");
|
||||
out.append(lnav::roles::variable(param.ht_name));
|
||||
out.append("N"_variable);
|
||||
out.append("]");
|
||||
}
|
||||
}
|
||||
out.with_attr(string_attr{
|
||||
line_range{(int) line_start, (int) out.get_string().length()},
|
||||
VC_ROLE.value(role_t::VCR_H3),
|
||||
});
|
||||
if (htc != help_text_content::synopsis) {
|
||||
alb.append("\n")
|
||||
.append(lnav::roles::table_border(
|
||||
repeat("\u2550", tws.tws_width)))
|
||||
.append("\n")
|
||||
.indent(body_indent)
|
||||
.append(attr_line_t::from_ansi_str(ht.ht_summary),
|
||||
&tws.with_indent(body_indent + 2))
|
||||
.append("\n");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -460,7 +499,8 @@ void
|
||||
format_example_text_for_term(const help_text& ht,
|
||||
const help_example_to_attr_line_fun_t eval,
|
||||
size_t width,
|
||||
attr_line_t& out)
|
||||
attr_line_t& out,
|
||||
help_example::language lang)
|
||||
{
|
||||
if (ht.ht_example.empty()) {
|
||||
return;
|
||||
@ -472,6 +512,10 @@ format_example_text_for_term(const help_text& ht,
|
||||
out.append(ht.ht_example.size() == 1 ? "Example"_h4 : "Examples"_h4)
|
||||
.append("\n");
|
||||
for (const auto& ex : ht.ht_example) {
|
||||
if (ex.he_language != lang) {
|
||||
continue;
|
||||
}
|
||||
|
||||
attr_line_t ex_line(ex.he_cmd);
|
||||
const char* prompt = "";
|
||||
text_wrap_settings tws;
|
||||
@ -491,6 +535,7 @@ format_example_text_for_term(const help_text& ht,
|
||||
case help_context_t::HC_SQL_KEYWORD:
|
||||
case help_context_t::HC_SQL_FUNCTION:
|
||||
case help_context_t::HC_SQL_TABLE_VALUED_FUNCTION:
|
||||
case help_context_t::HC_PRQL_TRANSFORM:
|
||||
readline_sqlite_highlighter(ex_line, 0);
|
||||
prompt = ";";
|
||||
break;
|
||||
@ -581,6 +626,9 @@ format_help_text_for_rst(const help_text& ht,
|
||||
is_sql = true;
|
||||
prefix = "";
|
||||
break;
|
||||
case help_context_t::HC_PRQL_TRANSFORM:
|
||||
is_sql = true;
|
||||
break;
|
||||
default:
|
||||
prefix = "";
|
||||
break;
|
||||
@ -630,6 +678,13 @@ format_help_text_for_rst(const help_text& ht,
|
||||
|
||||
fmt::fprintf(rst_file, " %s\n", ht.ht_summary);
|
||||
fmt::fprintf(rst_file, "\n");
|
||||
|
||||
if (!ht.ht_prql_path.empty()) {
|
||||
fmt::print(rst_file,
|
||||
FMT_STRING(" **PRQL Name**: {}\n\n"),
|
||||
fmt::join(ht.ht_prql_path, "."));
|
||||
}
|
||||
|
||||
if (ht.ht_description != nullptr) {
|
||||
fmt::fprintf(rst_file, " %s\n", ht.ht_description);
|
||||
}
|
||||
|
@ -52,7 +52,9 @@ void format_help_text_for_term(const help_text& ht,
|
||||
void format_example_text_for_term(const help_text& ht,
|
||||
help_example_to_attr_line_fun_t eval,
|
||||
size_t width,
|
||||
attr_line_t& out);
|
||||
attr_line_t& out,
|
||||
help_example::language lang
|
||||
= help_example::language::undefined);
|
||||
|
||||
void format_help_text_for_rst(const help_text& ht,
|
||||
help_example_to_attr_line_fun_t eval,
|
||||
|
@ -155,6 +155,7 @@ handle_keyseq(const char* keyseq)
|
||||
exec_context ec(&values, key_sql_callback, pipe_callback);
|
||||
auto& var_stack = ec.ec_local_vars;
|
||||
|
||||
ec.ec_label_source_stack.push_back(&lnav_data.ld_db_row_source);
|
||||
ec.ec_global_vars = lnav_data.ld_exec_context.ec_global_vars;
|
||||
ec.ec_error_callback_stack
|
||||
= lnav_data.ld_exec_context.ec_error_callback_stack;
|
||||
|
@ -491,6 +491,8 @@ anonymize(*value*)
|
||||
|
||||
Replace identifying information with random values.
|
||||
|
||||
**PRQL Name**: text.anonymize
|
||||
|
||||
**Parameters**
|
||||
* **value\*** --- The text to anonymize
|
||||
|
||||
@ -694,6 +696,8 @@ basename(*path*)
|
||||
|
||||
Extract the base portion of a pathname.
|
||||
|
||||
**PRQL Name**: fs.basename
|
||||
|
||||
**Parameters**
|
||||
* **path\*** --- The path
|
||||
|
||||
@ -740,6 +744,13 @@ basename(*path*)
|
||||
;SELECT basename('/')
|
||||
/
|
||||
|
||||
To get the base of a path:
|
||||
|
||||
.. code-block:: custsqlite
|
||||
|
||||
;from [{p='foo/bar'}] | select { fs.basename p }
|
||||
bar
|
||||
|
||||
**See Also**
|
||||
:ref:`dirname`, :ref:`joinpath`, :ref:`readlink`, :ref:`realpath`
|
||||
|
||||
@ -1050,6 +1061,8 @@ dirname(*path*)
|
||||
|
||||
Extract the directory portion of a pathname.
|
||||
|
||||
**PRQL Name**: fs.dirname
|
||||
|
||||
**Parameters**
|
||||
* **path\*** --- The path
|
||||
|
||||
@ -1213,6 +1226,8 @@ extract(*str*)
|
||||
|
||||
Automatically Parse and extract data from a string
|
||||
|
||||
**PRQL Name**: text.extract
|
||||
|
||||
**Parameters**
|
||||
* **str\*** --- The string to parse
|
||||
|
||||
@ -1360,6 +1375,8 @@ gethostbyaddr(*hostname*)
|
||||
|
||||
Get the hostname for the given IP address
|
||||
|
||||
**PRQL Name**: net.gethostbyaddr
|
||||
|
||||
**Parameters**
|
||||
* **hostname\*** --- The IP address to lookup.
|
||||
|
||||
@ -1384,6 +1401,8 @@ gethostbyname(*hostname*)
|
||||
|
||||
Get the IP address for the given hostname
|
||||
|
||||
**PRQL Name**: net.gethostbyname
|
||||
|
||||
**Parameters**
|
||||
* **hostname\*** --- The DNS hostname to lookup.
|
||||
|
||||
@ -1548,6 +1567,8 @@ humanize_duration(*secs*)
|
||||
|
||||
Format the given seconds value as an abbreviated duration string
|
||||
|
||||
**PRQL Name**: humanize.duration
|
||||
|
||||
**Parameters**
|
||||
* **secs\*** --- The duration in seconds
|
||||
|
||||
@ -1579,6 +1600,8 @@ humanize_file_size(*value*)
|
||||
|
||||
Format the given file size as a human-friendly string
|
||||
|
||||
**PRQL Name**: humanize.file_size
|
||||
|
||||
**Parameters**
|
||||
* **value\*** --- The file size to format
|
||||
|
||||
@ -1651,6 +1674,8 @@ jget(*json*, *ptr*, *\[default\]*)
|
||||
|
||||
Get the value from a JSON object using a JSON-Pointer.
|
||||
|
||||
**PRQL Name**: json.get
|
||||
|
||||
**Parameters**
|
||||
* **json\*** --- The JSON object to query.
|
||||
* **ptr\*** --- The JSON-Pointer to lookup in the object.
|
||||
@ -1691,6 +1716,8 @@ joinpath(*path*)
|
||||
|
||||
Join components of a path together.
|
||||
|
||||
**PRQL Name**: fs.join
|
||||
|
||||
**Parameters**
|
||||
* **path** --- One or more path components to join together. If an argument starts with a forward or backward slash, it will be considered an absolute path and any preceding elements will be ignored.
|
||||
|
||||
@ -1815,6 +1842,8 @@ json_concat(*json*, *value*)
|
||||
|
||||
Returns an array with the given values concatenated onto the end. If the initial value is null, the result will be an array with the given elements. If the initial value is an array, the result will be an array with the given values at the end. If the initial value is not null or an array, the result will be an array with two elements: the initial value and the given value.
|
||||
|
||||
**PRQL Name**: json.concat
|
||||
|
||||
**Parameters**
|
||||
* **json\*** --- The initial JSON value.
|
||||
* **value** --- The value(s) to add to the end of the array.
|
||||
@ -1854,6 +1883,8 @@ json_contains(*json*, *value*)
|
||||
|
||||
Check if a JSON value contains the given element.
|
||||
|
||||
**PRQL Name**: json.contains
|
||||
|
||||
**Parameters**
|
||||
* **json\*** --- The JSON value to query.
|
||||
* **value\*** --- The value to look for in the first argument
|
||||
@ -1954,6 +1985,8 @@ json_group_array(*value*)
|
||||
|
||||
Collect the given values from a query into a JSON array
|
||||
|
||||
**PRQL Name**: json.group_array
|
||||
|
||||
**Parameters**
|
||||
* **value** --- The values to append to the array
|
||||
|
||||
@ -1985,6 +2018,8 @@ json_group_object(*name*, *value*)
|
||||
|
||||
Collect the given values from a query into a JSON object
|
||||
|
||||
**PRQL Name**: json.group_object
|
||||
|
||||
**Parameters**
|
||||
* **name\*** --- The property name for the value
|
||||
* **value** --- The value to add to the object
|
||||
@ -2544,6 +2579,8 @@ lnav_top_file()
|
||||
|
||||
Return the name of the file that the top line in the current view came from.
|
||||
|
||||
**PRQL Name**: lnav.view.top_file
|
||||
|
||||
|
||||
----
|
||||
|
||||
@ -2555,6 +2592,8 @@ lnav_version()
|
||||
|
||||
Return the current version of lnav
|
||||
|
||||
**PRQL Name**: lnav.version
|
||||
|
||||
|
||||
----
|
||||
|
||||
@ -2628,6 +2667,8 @@ log_msg_line()
|
||||
|
||||
Return the starting line number of the focused log message.
|
||||
|
||||
**PRQL Name**: lnav.view.msg_line
|
||||
|
||||
|
||||
----
|
||||
|
||||
@ -2639,6 +2680,8 @@ log_top_datetime()
|
||||
|
||||
Return the timestamp of the line at the top of the log view.
|
||||
|
||||
**PRQL Name**: lnav.view.top_datetime
|
||||
|
||||
|
||||
----
|
||||
|
||||
@ -2650,6 +2693,8 @@ log_top_line()
|
||||
|
||||
Return the number of the focused line of the log view.
|
||||
|
||||
**PRQL Name**: lnav.view.top_line
|
||||
|
||||
|
||||
----
|
||||
|
||||
@ -2661,6 +2706,8 @@ logfmt2json(*str*)
|
||||
|
||||
Convert a logfmt-encoded string into JSON
|
||||
|
||||
**PRQL Name**: logfmt.to_json
|
||||
|
||||
**Parameters**
|
||||
* **str\*** --- The logfmt message to parse
|
||||
|
||||
@ -3232,6 +3279,8 @@ readlink(*path*)
|
||||
|
||||
Read the target of a symbolic link.
|
||||
|
||||
**PRQL Name**: fs.readlink
|
||||
|
||||
**Parameters**
|
||||
* **path\*** --- The path to the symbolic link.
|
||||
|
||||
@ -3248,6 +3297,8 @@ realpath(*path*)
|
||||
|
||||
Returns the resolved version of the given path, expanding symbolic links and resolving '.' and '..' references.
|
||||
|
||||
**PRQL Name**: fs.realpath
|
||||
|
||||
**Parameters**
|
||||
* **path\*** --- The path to resolve.
|
||||
|
||||
@ -3338,6 +3389,8 @@ regexp_match(*re*, *str*)
|
||||
|
||||
Match a string against a regular expression and return the capture groups as JSON.
|
||||
|
||||
**PRQL Name**: text.regexp_match
|
||||
|
||||
**Parameters**
|
||||
* **re\*** --- The regular expression to use
|
||||
* **str\*** --- The string to test against the regular expression
|
||||
@ -3377,6 +3430,8 @@ regexp_replace(*str*, *re*, *repl*)
|
||||
|
||||
Replace the parts of a string that match a regular expression.
|
||||
|
||||
**PRQL Name**: text.regexp_replace
|
||||
|
||||
**Parameters**
|
||||
* **str\*** --- The string to perform replacements on
|
||||
* **re\*** --- The regular expression to match
|
||||
@ -3468,6 +3523,8 @@ reverse(*str*)
|
||||
|
||||
Returns the reverse of the given string.
|
||||
|
||||
**PRQL Name**: text.reverse
|
||||
|
||||
**Parameters**
|
||||
* **str\*** --- The string to reverse.
|
||||
|
||||
@ -3620,6 +3677,8 @@ shell_exec(*cmd*, *\[input\]*, *\[options\]*)
|
||||
|
||||
Executes a shell command and returns its output.
|
||||
|
||||
**PRQL Name**: shell.exec
|
||||
|
||||
**Parameters**
|
||||
* **cmd\*** --- The command to execute.
|
||||
* **input** --- A blob of data to write to the command's standard input.
|
||||
@ -3678,6 +3737,8 @@ sparkline(*value*, *\[upper\]*)
|
||||
|
||||
Function used to generate a sparkline bar chart. The non-aggregate version converts a single numeric value on a range to a bar chart character. The aggregate version returns a string with a bar character for every numeric input
|
||||
|
||||
**PRQL Name**: text.sparkline
|
||||
|
||||
**Parameters**
|
||||
* **value\*** --- The numeric value to convert
|
||||
* **upper** --- The upper bound of the numeric range. The non-aggregate version defaults to 100. The aggregate version uses the largest value in the inputs.
|
||||
@ -4044,6 +4105,8 @@ timediff(*time1*, *time2*)
|
||||
|
||||
Compute the difference between two timestamps in seconds
|
||||
|
||||
**PRQL Name**: time.diff
|
||||
|
||||
**Parameters**
|
||||
* **time1\*** --- The first timestamp
|
||||
* **time2\*** --- The timestamp to subtract from the first
|
||||
@ -4076,6 +4139,8 @@ timeslice(*time*, *slice*)
|
||||
|
||||
Return the start of the slice of time that the given timestamp falls in. If the time falls outside of the slice, NULL is returned.
|
||||
|
||||
**PRQL Name**: time.slice
|
||||
|
||||
**Parameters**
|
||||
* **time\*** --- The timestamp to get the time slice for.
|
||||
* **slice\*** --- The size of the time slices
|
||||
@ -4121,6 +4186,8 @@ timezone(*tz*, *ts*)
|
||||
|
||||
Convert a timestamp to the given timezone
|
||||
|
||||
**PRQL Name**: time.to_zone
|
||||
|
||||
**Parameters**
|
||||
* **tz\*** --- The target timezone
|
||||
* **ts\*** --- The source timestamp
|
||||
@ -4371,6 +4438,8 @@ yaml_to_json(*yaml*)
|
||||
|
||||
Convert a YAML document to a JSON-encoded string
|
||||
|
||||
**PRQL Name**: yaml.to_json
|
||||
|
||||
**Parameters**
|
||||
* **yaml\*** --- The YAML value to convert to JSON.
|
||||
|
||||
|
@ -773,6 +773,7 @@ json_extension_functions(struct FuncDef** basic_funcs,
|
||||
"array with "
|
||||
"two elements: the initial value and the given value.")
|
||||
.sql_function()
|
||||
.with_prql_path({"json", "concat"})
|
||||
.with_parameter({"json", "The initial JSON value."})
|
||||
.with_parameter(
|
||||
help_text("value",
|
||||
@ -796,6 +797,7 @@ json_extension_functions(struct FuncDef** basic_funcs,
|
||||
help_text("json_contains",
|
||||
"Check if a JSON value contains the given element.")
|
||||
.sql_function()
|
||||
.with_prql_path({"json", "contains"})
|
||||
.with_parameter({"json", "The JSON value to query."})
|
||||
.with_parameter(
|
||||
{"value", "The value to look for in the first argument"})
|
||||
@ -818,6 +820,7 @@ json_extension_functions(struct FuncDef** basic_funcs,
|
||||
help_text("jget",
|
||||
"Get the value from a JSON object using a JSON-Pointer.")
|
||||
.sql_function()
|
||||
.with_prql_path({"json", "get"})
|
||||
.with_parameter({"json", "The JSON object to query."})
|
||||
.with_parameter(
|
||||
{"ptr", "The JSON-Pointer to lookup in the object."})
|
||||
@ -859,6 +862,7 @@ json_extension_functions(struct FuncDef** basic_funcs,
|
||||
sql_json_group_object_final,
|
||||
help_text("json_group_object")
|
||||
.sql_function()
|
||||
.with_prql_path({"json", "group_object"})
|
||||
.with_summary(
|
||||
"Collect the given values from a query into a JSON object")
|
||||
.with_parameter(
|
||||
@ -883,6 +887,7 @@ json_extension_functions(struct FuncDef** basic_funcs,
|
||||
sql_json_group_array_final,
|
||||
help_text("json_group_array")
|
||||
.sql_function()
|
||||
.with_prql_path({"json", "group_array"})
|
||||
.with_summary(
|
||||
"Collect the given values from a query into a JSON array")
|
||||
.with_parameter(
|
||||
|
140
src/lnav.cc
140
src/lnav.cc
@ -291,29 +291,8 @@ struct lnav_data_t lnav_data;
|
||||
bool
|
||||
setup_logline_table(exec_context& ec)
|
||||
{
|
||||
// Hidden columns don't show up in the table_info pragma.
|
||||
static const char* hidden_table_columns[] = {
|
||||
"log_time_msecs",
|
||||
"log_path",
|
||||
"log_text",
|
||||
"log_body",
|
||||
|
||||
nullptr,
|
||||
};
|
||||
|
||||
auto& log_view = lnav_data.ld_views[LNV_LOG];
|
||||
bool retval = false;
|
||||
bool update_possibilities
|
||||
= (lnav_data.ld_rl_view != nullptr && ec.ec_local_vars.size() == 1);
|
||||
|
||||
if (update_possibilities) {
|
||||
lnav_data.ld_rl_view->clear_possibilities(ln_mode_t::SQL, "*");
|
||||
add_view_text_possibilities(lnav_data.ld_rl_view,
|
||||
ln_mode_t::SQL,
|
||||
"*",
|
||||
&log_view,
|
||||
text_quoting::sql);
|
||||
}
|
||||
|
||||
if (log_view.get_inner_height()) {
|
||||
static intern_string_t logline = intern_string::lookup("logline");
|
||||
@ -327,34 +306,6 @@ setup_logline_table(exec_context& ec)
|
||||
cl,
|
||||
logline));
|
||||
|
||||
if (update_possibilities) {
|
||||
log_data_helper ldh(lnav_data.ld_log_source);
|
||||
|
||||
ldh.parse_line(cl);
|
||||
|
||||
for (const auto& jextra : ldh.ldh_extra_json) {
|
||||
lnav_data.ld_rl_view->add_possibility(
|
||||
ln_mode_t::SQL,
|
||||
"*",
|
||||
lnav::sql::mprintf("%Q", jextra.first.c_str()).in());
|
||||
}
|
||||
for (const auto& jpair : ldh.ldh_json_pairs) {
|
||||
for (const auto& wt : jpair.second) {
|
||||
lnav_data.ld_rl_view->add_possibility(
|
||||
ln_mode_t::SQL,
|
||||
"*",
|
||||
lnav::sql::mprintf("%Q", wt.wt_ptr.c_str()).in());
|
||||
}
|
||||
}
|
||||
for (const auto& xml_pair : ldh.ldh_xml_pairs) {
|
||||
lnav_data.ld_rl_view->add_possibility(
|
||||
ln_mode_t::SQL,
|
||||
"*",
|
||||
lnav::sql::mprintf("%Q", xml_pair.first.second.c_str())
|
||||
.in());
|
||||
}
|
||||
}
|
||||
|
||||
retval = true;
|
||||
}
|
||||
|
||||
@ -362,62 +313,6 @@ setup_logline_table(exec_context& ec)
|
||||
|
||||
db_key_names = DEFAULT_DB_KEY_NAMES;
|
||||
|
||||
if (update_possibilities) {
|
||||
add_env_possibilities(ln_mode_t::SQL);
|
||||
|
||||
lnav_data.ld_rl_view->add_possibility(ln_mode_t::SQL,
|
||||
"*",
|
||||
std::begin(sql_keywords),
|
||||
std::end(sql_keywords));
|
||||
lnav_data.ld_rl_view->add_possibility(
|
||||
ln_mode_t::SQL, "*", sql_function_names);
|
||||
lnav_data.ld_rl_view->add_possibility(
|
||||
ln_mode_t::SQL, "*", hidden_table_columns);
|
||||
|
||||
for (int lpc = 0; sqlite_registration_funcs[lpc]; lpc++) {
|
||||
struct FuncDef* basic_funcs;
|
||||
struct FuncDefAgg* agg_funcs;
|
||||
|
||||
sqlite_registration_funcs[lpc](&basic_funcs, &agg_funcs);
|
||||
for (int lpc2 = 0; basic_funcs && basic_funcs[lpc2].zName; lpc2++) {
|
||||
const FuncDef& func_def = basic_funcs[lpc2];
|
||||
|
||||
lnav_data.ld_rl_view->add_possibility(
|
||||
ln_mode_t::SQL,
|
||||
"*",
|
||||
std::string(func_def.zName) + (func_def.nArg ? "(" : "()"));
|
||||
}
|
||||
for (int lpc2 = 0; agg_funcs && agg_funcs[lpc2].zName; lpc2++) {
|
||||
const FuncDefAgg& func_def = agg_funcs[lpc2];
|
||||
|
||||
lnav_data.ld_rl_view->add_possibility(
|
||||
ln_mode_t::SQL,
|
||||
"*",
|
||||
std::string(func_def.zName) + (func_def.nArg ? "(" : "()"));
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& pair : sqlite_function_help) {
|
||||
switch (pair.second->ht_context) {
|
||||
case help_context_t::HC_SQL_FUNCTION:
|
||||
case help_context_t::HC_SQL_TABLE_VALUED_FUNCTION: {
|
||||
std::string poss = pair.first
|
||||
+ (pair.second->ht_parameters.empty() ? "()" : ("("));
|
||||
|
||||
lnav_data.ld_rl_view->add_possibility(
|
||||
ln_mode_t::SQL, "*", poss);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (update_possibilities) {
|
||||
walk_sqlite_metadata(lnav_data.ld_db.in(), lnav_sql_meta_callbacks);
|
||||
}
|
||||
|
||||
for (const auto& iter : *lnav_data.ld_vtab_manager) {
|
||||
iter.second->get_foreign_keys(db_key_names);
|
||||
}
|
||||
@ -1176,7 +1071,8 @@ looper()
|
||||
sql_context.set_highlighter(readline_sqlite_highlighter)
|
||||
.set_quote_chars("\"")
|
||||
.with_readline_var((char**) &rl_completer_word_break_characters,
|
||||
" \t\n(),");
|
||||
" \t\n(),")
|
||||
.with_splitter(prql_splitter);
|
||||
exec_context.set_highlighter(readline_shlex_highlighter);
|
||||
|
||||
lnav_data.ld_log_source.lss_sorting_observer
|
||||
@ -1351,7 +1247,8 @@ looper()
|
||||
setup_highlights(lnav_data.ld_views[LNV_TEXT].get_highlights());
|
||||
setup_highlights(lnav_data.ld_views[LNV_SCHEMA].get_highlights());
|
||||
setup_highlights(lnav_data.ld_views[LNV_PRETTY].get_highlights());
|
||||
setup_highlights(lnav_data.ld_preview_view.get_highlights());
|
||||
setup_highlights(lnav_data.ld_preview_view[0].get_highlights());
|
||||
setup_highlights(lnav_data.ld_preview_view[1].get_highlights());
|
||||
|
||||
for (const auto& format : log_format::get_root_formats()) {
|
||||
for (auto& hl : format->lf_highlighters) {
|
||||
@ -1437,8 +1334,10 @@ looper()
|
||||
|
||||
lnav_data.ld_match_view.set_window(lnav_data.ld_window);
|
||||
|
||||
lnav_data.ld_preview_view.set_window(lnav_data.ld_window);
|
||||
lnav_data.ld_preview_view.set_show_scrollbar(false);
|
||||
lnav_data.ld_preview_view[0].set_window(lnav_data.ld_window);
|
||||
lnav_data.ld_preview_view[0].set_show_scrollbar(false);
|
||||
lnav_data.ld_preview_view[1].set_window(lnav_data.ld_window);
|
||||
lnav_data.ld_preview_view[1].set_show_scrollbar(false);
|
||||
|
||||
lnav_data.ld_filter_view.set_selectable(true);
|
||||
lnav_data.ld_filter_view.set_window(lnav_data.ld_window);
|
||||
@ -1500,8 +1399,10 @@ looper()
|
||||
&lnav_data.ld_filter_help_status_source);
|
||||
lnav_data.ld_status[LNS_DOC].set_data_source(
|
||||
&lnav_data.ld_doc_status_source);
|
||||
lnav_data.ld_status[LNS_PREVIEW].set_data_source(
|
||||
&lnav_data.ld_preview_status_source);
|
||||
lnav_data.ld_status[LNS_PREVIEW0].set_data_source(
|
||||
&lnav_data.ld_preview_status_source[0]);
|
||||
lnav_data.ld_status[LNS_PREVIEW1].set_data_source(
|
||||
&lnav_data.ld_preview_status_source[1]);
|
||||
lnav_data.ld_spectro_status_source
|
||||
= std::make_unique<spectro_status_source>();
|
||||
lnav_data.ld_status[LNS_SPECTRO].set_data_source(
|
||||
@ -1604,7 +1505,8 @@ looper()
|
||||
gettimeofday(¤t_time, nullptr);
|
||||
|
||||
top_source->update_time(current_time);
|
||||
lnav_data.ld_preview_view.set_needs_update();
|
||||
lnav_data.ld_preview_view[0].set_needs_update();
|
||||
lnav_data.ld_preview_view[1].set_needs_update();
|
||||
|
||||
layout_views();
|
||||
|
||||
@ -1708,7 +1610,8 @@ looper()
|
||||
lnav_data.ld_doc_view.do_update();
|
||||
lnav_data.ld_example_view.do_update();
|
||||
lnav_data.ld_match_view.do_update();
|
||||
lnav_data.ld_preview_view.do_update();
|
||||
lnav_data.ld_preview_view[0].do_update();
|
||||
lnav_data.ld_preview_view[1].do_update();
|
||||
lnav_data.ld_spectro_details_view.do_update();
|
||||
lnav_data.ld_gantt_details_view.do_update();
|
||||
lnav_data.ld_user_message_view.do_update();
|
||||
@ -2219,7 +2122,7 @@ main(int argc, char* argv[])
|
||||
{
|
||||
std::vector<lnav::console::user_message> config_errors;
|
||||
std::vector<lnav::console::user_message> loader_errors;
|
||||
exec_context& ec = lnav_data.ld_exec_context;
|
||||
auto& ec = lnav_data.ld_exec_context;
|
||||
int retval = EXIT_SUCCESS;
|
||||
|
||||
bool exec_stdin = false, load_stdin = false;
|
||||
@ -2230,6 +2133,8 @@ main(int argc, char* argv[])
|
||||
setenv("LANG", "en_US.UTF-8", 1);
|
||||
}
|
||||
|
||||
ec.ec_label_source_stack.push_back(&lnav_data.ld_db_row_source);
|
||||
|
||||
(void) signal(SIGPIPE, SIG_IGN);
|
||||
(void) signal(SIGCHLD, sigchld);
|
||||
setlocale(LC_ALL, "");
|
||||
@ -2890,6 +2795,10 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
|
||||
.set_sub_source(&lnav_data.ld_hist_source2);
|
||||
lnav_data.ld_views[LNV_DB].set_sub_source(&lnav_data.ld_db_row_source);
|
||||
lnav_data.ld_db_overlay.dos_labels = &lnav_data.ld_db_row_source;
|
||||
lnav_data.ld_db_preview_overlay_source[0].dos_labels
|
||||
= &lnav_data.ld_db_preview_source[0];
|
||||
lnav_data.ld_db_preview_overlay_source[1].dos_labels
|
||||
= &lnav_data.ld_db_preview_source[1];
|
||||
lnav_data.ld_views[LNV_DB]
|
||||
.set_reload_config_delegate(sel_reload_delegate)
|
||||
.set_overlay_source(&lnav_data.ld_db_overlay);
|
||||
@ -2923,7 +2832,8 @@ SELECT tbl_name FROM sqlite_master WHERE sql LIKE 'CREATE VIRTUAL TABLE%'
|
||||
lnav_data.ld_doc_view.set_sub_source(&lnav_data.ld_doc_source);
|
||||
lnav_data.ld_example_view.set_sub_source(&lnav_data.ld_example_source);
|
||||
lnav_data.ld_match_view.set_sub_source(&lnav_data.ld_match_source);
|
||||
lnav_data.ld_preview_view.set_sub_source(&lnav_data.ld_preview_source);
|
||||
lnav_data.ld_preview_view[0].set_sub_source(
|
||||
&lnav_data.ld_preview_source[0]);
|
||||
lnav_data.ld_filter_view.set_sub_source(filter_source)
|
||||
.add_input_delegate(*filter_source)
|
||||
.add_child_view(&filter_source->fss_match_view)
|
||||
|
11
src/lnav.hh
11
src/lnav.hh
@ -73,7 +73,8 @@ typedef enum {
|
||||
LNS_FILTER,
|
||||
LNS_FILTER_HELP,
|
||||
LNS_DOC,
|
||||
LNS_PREVIEW,
|
||||
LNS_PREVIEW0,
|
||||
LNS_PREVIEW1,
|
||||
LNS_SPECTRO,
|
||||
LNS_GANTT,
|
||||
|
||||
@ -182,7 +183,7 @@ struct lnav_data_t {
|
||||
filter_status_source ld_filter_status_source;
|
||||
filter_help_status_source ld_filter_help_status_source;
|
||||
doc_status_source ld_doc_status_source;
|
||||
preview_status_source ld_preview_status_source;
|
||||
preview_status_source ld_preview_status_source[2];
|
||||
std::unique_ptr<spectro_status_source> ld_spectro_status_source;
|
||||
gantt_status_source ld_gantt_status_source;
|
||||
bool ld_preview_hidden;
|
||||
@ -202,8 +203,8 @@ struct lnav_data_t {
|
||||
textview_curses ld_example_view;
|
||||
plain_text_source ld_match_source;
|
||||
textview_curses ld_match_view;
|
||||
plain_text_source ld_preview_source;
|
||||
textview_curses ld_preview_view;
|
||||
plain_text_source ld_preview_source[2];
|
||||
textview_curses ld_preview_view[2];
|
||||
plain_text_source ld_user_message_source;
|
||||
textview_curses ld_user_message_view;
|
||||
std::chrono::time_point<std::chrono::steady_clock>
|
||||
@ -231,6 +232,8 @@ struct lnav_data_t {
|
||||
|
||||
db_label_source ld_db_row_source;
|
||||
db_overlay_source ld_db_overlay;
|
||||
db_label_source ld_db_preview_source[2];
|
||||
db_overlay_source ld_db_preview_overlay_source[2];
|
||||
std::vector<std::string> ld_db_key_names;
|
||||
|
||||
vis_line_t ld_last_pretty_print_top;
|
||||
|
@ -1038,7 +1038,7 @@ com_mark_expr(exec_context& ec,
|
||||
if (set_res.isErr()) {
|
||||
return Err(set_res.unwrapErr());
|
||||
}
|
||||
lnav_data.ld_preview_status_source.get_description().set_value(
|
||||
lnav_data.ld_preview_status_source[0].get_description().set_value(
|
||||
"Matches are highlighted in the text view");
|
||||
} else {
|
||||
auto set_res = lss.set_sql_marker(expr, stmt.release());
|
||||
@ -1926,10 +1926,13 @@ com_save_to(exec_context& ec,
|
||||
|
||||
attr_line_t al(std::string(buffer, rc));
|
||||
|
||||
lnav_data.ld_preview_source.replace_with(al)
|
||||
lnav_data.ld_preview_view[0].set_sub_source(
|
||||
&lnav_data.ld_preview_source[0]);
|
||||
lnav_data.ld_preview_source[0]
|
||||
.replace_with(al)
|
||||
.set_text_format(detect_text_format(al.get_string()))
|
||||
.truncate_to(10);
|
||||
lnav_data.ld_preview_status_source.get_description().set_value(
|
||||
lnav_data.ld_preview_status_source[0].get_description().set_value(
|
||||
"First lines of file: %s", split_args[0].c_str());
|
||||
} else {
|
||||
retval = fmt::format(FMT_STRING("info: Wrote {:L} rows to {}"),
|
||||
@ -2243,7 +2246,7 @@ com_highlight(exec_context& ec,
|
||||
if (ec.ec_dry_run) {
|
||||
hm[{highlight_source_t::PREVIEW, "preview"}] = hl;
|
||||
|
||||
lnav_data.ld_preview_status_source.get_description().set_value(
|
||||
lnav_data.ld_preview_status_source[0].get_description().set_value(
|
||||
"Matches are highlighted in the view");
|
||||
|
||||
retval = "";
|
||||
@ -2363,9 +2366,12 @@ com_filter(exec_context& ec,
|
||||
}
|
||||
if (ec.ec_dry_run) {
|
||||
if (args[0] == "filter-in" && !fs.empty()) {
|
||||
lnav_data.ld_preview_status_source.get_description().set_value(
|
||||
"Match preview for :filter-in only works if there are no "
|
||||
"other filters");
|
||||
lnav_data.ld_preview_status_source[0]
|
||||
.get_description()
|
||||
.set_value(
|
||||
"Match preview for :filter-in only works if there are "
|
||||
"no "
|
||||
"other filters");
|
||||
retval = "";
|
||||
} else {
|
||||
auto& hm = tc->get_highlights();
|
||||
@ -2378,9 +2384,11 @@ com_filter(exec_context& ec,
|
||||
hm[{highlight_source_t::PREVIEW, "preview"}] = hl;
|
||||
tc->reload_data();
|
||||
|
||||
lnav_data.ld_preview_status_source.get_description().set_value(
|
||||
"Matches are highlighted in %s in the text view",
|
||||
role == role_t::VCR_DIFF_DELETE ? "red" : "green");
|
||||
lnav_data.ld_preview_status_source[0]
|
||||
.get_description()
|
||||
.set_value(
|
||||
"Matches are highlighted in %s in the text view",
|
||||
role == role_t::VCR_DIFF_DELETE ? "red" : "green");
|
||||
|
||||
retval = "";
|
||||
}
|
||||
@ -2581,7 +2589,7 @@ com_filter_expr(exec_context& ec,
|
||||
if (set_res.isErr()) {
|
||||
return Err(set_res.unwrapErr());
|
||||
}
|
||||
lnav_data.ld_preview_status_source.get_description().set_value(
|
||||
lnav_data.ld_preview_status_source[0].get_description().set_value(
|
||||
"Matches are highlighted in the text view");
|
||||
} else {
|
||||
lnav_data.ld_log_source.set_preview_sql_filter(nullptr);
|
||||
@ -2696,9 +2704,12 @@ com_create_logline_table(exec_context& ec,
|
||||
if (ec.ec_dry_run) {
|
||||
attr_line_t al(ldt->get_table_statement());
|
||||
|
||||
lnav_data.ld_preview_status_source.get_description().set_value(
|
||||
"The following table will be created:");
|
||||
lnav_data.ld_preview_source.replace_with(al).set_text_format(
|
||||
lnav_data.ld_preview_status_source[0]
|
||||
.get_description()
|
||||
.set_value("The following table will be created:");
|
||||
lnav_data.ld_preview_view[0].set_sub_source(
|
||||
&lnav_data.ld_preview_source[0]);
|
||||
lnav_data.ld_preview_source[0].replace_with(al).set_text_format(
|
||||
text_format_t::TF_SQL);
|
||||
|
||||
return Ok(std::string());
|
||||
@ -2813,10 +2824,12 @@ com_create_search_table(exec_context& ec,
|
||||
|
||||
attr_line_t al(lst->get_table_statement());
|
||||
|
||||
lnav_data.ld_preview_status_source.get_description().set_value(
|
||||
lnav_data.ld_preview_status_source[0].get_description().set_value(
|
||||
"The following table will be created:");
|
||||
|
||||
lnav_data.ld_preview_source.replace_with(al).set_text_format(
|
||||
lnav_data.ld_preview_view[0].set_sub_source(
|
||||
&lnav_data.ld_preview_source[0]);
|
||||
lnav_data.ld_preview_source[0].replace_with(al).set_text_format(
|
||||
text_format_t::TF_SQL);
|
||||
|
||||
return Ok(std::string());
|
||||
@ -3216,7 +3229,9 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
|
||||
}
|
||||
|
||||
if (ec.ec_dry_run) {
|
||||
lnav_data.ld_preview_source.clear();
|
||||
lnav_data.ld_preview_view[0].set_sub_source(
|
||||
&lnav_data.ld_preview_source[0]);
|
||||
lnav_data.ld_preview_source[0].clear();
|
||||
if (!fc.fc_file_names.empty()) {
|
||||
auto iter = fc.fc_file_names.begin();
|
||||
std::string fn = iter->first;
|
||||
@ -3224,10 +3239,13 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
|
||||
|
||||
if (fn.find(':') != std::string::npos) {
|
||||
auto id = lnav_data.ld_preview_generation;
|
||||
lnav_data.ld_preview_status_source.get_description()
|
||||
lnav_data.ld_preview_status_source[0]
|
||||
.get_description()
|
||||
.set_cylon(true)
|
||||
.set_value("Loading %s...", fn.c_str());
|
||||
lnav_data.ld_preview_source.clear();
|
||||
lnav_data.ld_preview_view[0].set_sub_source(
|
||||
&lnav_data.ld_preview_source[0]);
|
||||
lnav_data.ld_preview_source[0].clear();
|
||||
|
||||
isc::to<tailer::looper&, services::remote_tailer_t>().send(
|
||||
[id, fn](auto& tlooper) {
|
||||
@ -3236,7 +3254,7 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
|
||||
tlooper.load_preview(id, *rp_opt);
|
||||
}
|
||||
});
|
||||
lnav_data.ld_preview_view.set_needs_update();
|
||||
lnav_data.ld_preview_view[0].set_needs_update();
|
||||
} else if (lnav::filesystem::is_glob(fn.c_str())) {
|
||||
static_root_mem<glob_t, globfree> gl;
|
||||
|
||||
@ -3253,9 +3271,12 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
|
||||
std::to_string(gl->gl_pathc - 10)))
|
||||
.append(" files not shown ...");
|
||||
}
|
||||
lnav_data.ld_preview_status_source.get_description()
|
||||
lnav_data.ld_preview_status_source[0]
|
||||
.get_description()
|
||||
.set_value("The following files will be loaded:");
|
||||
lnav_data.ld_preview_source.replace_with(al);
|
||||
lnav_data.ld_preview_view[0].set_sub_source(
|
||||
&lnav_data.ld_preview_source[0]);
|
||||
lnav_data.ld_preview_source[0].replace_with(al);
|
||||
} else {
|
||||
return ec.make_error("failed to evaluate glob -- {}", fn);
|
||||
}
|
||||
@ -3288,10 +3309,14 @@ com_open(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
|
||||
lines.append(sbr.get_data(), sbr.length());
|
||||
}
|
||||
|
||||
lnav_data.ld_preview_source.replace_with(al.with_string(lines))
|
||||
lnav_data.ld_preview_view[0].set_sub_source(
|
||||
&lnav_data.ld_preview_source[0]);
|
||||
lnav_data.ld_preview_source[0]
|
||||
.replace_with(al.with_string(lines))
|
||||
.set_text_format(detect_text_format(al.get_string()));
|
||||
lnav_data.ld_preview_status_source.get_description().set_value(
|
||||
"For file: %s", fn.c_str());
|
||||
lnav_data.ld_preview_status_source[0]
|
||||
.get_description()
|
||||
.set_value("For file: %s", fn.c_str());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -5027,9 +5052,11 @@ com_echo(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
|
||||
|
||||
auto ec_out = ec.get_output();
|
||||
if (ec.ec_dry_run) {
|
||||
lnav_data.ld_preview_status_source.get_description().set_value(
|
||||
lnav_data.ld_preview_status_source[0].get_description().set_value(
|
||||
"The text to output:");
|
||||
lnav_data.ld_preview_source.replace_with(attr_line_t(retval));
|
||||
lnav_data.ld_preview_view[0].set_sub_source(
|
||||
&lnav_data.ld_preview_source[0]);
|
||||
lnav_data.ld_preview_source[0].replace_with(attr_line_t(retval));
|
||||
retval = "";
|
||||
} else if (ec_out) {
|
||||
FILE* outfile = *ec_out;
|
||||
@ -5111,10 +5138,12 @@ com_eval(exec_context& ec, std::string cmdline, std::vector<std::string>& args)
|
||||
if (ec.ec_dry_run) {
|
||||
attr_line_t al(expanded_cmd);
|
||||
|
||||
lnav_data.ld_preview_status_source.get_description().set_value(
|
||||
lnav_data.ld_preview_status_source[0].get_description().set_value(
|
||||
"The command to be executed:");
|
||||
|
||||
lnav_data.ld_preview_source.replace_with(al);
|
||||
lnav_data.ld_preview_view[0].set_sub_source(
|
||||
&lnav_data.ld_preview_source[0]);
|
||||
lnav_data.ld_preview_source[0].replace_with(al);
|
||||
|
||||
return Ok(std::string());
|
||||
}
|
||||
@ -5187,10 +5216,14 @@ com_config(exec_context& ec,
|
||||
if (ec.ec_dry_run) {
|
||||
attr_line_t al(old_value);
|
||||
|
||||
lnav_data.ld_preview_source.replace_with(al)
|
||||
lnav_data.ld_preview_view[0].set_sub_source(
|
||||
&lnav_data.ld_preview_source[0]);
|
||||
lnav_data.ld_preview_source[0]
|
||||
.replace_with(al)
|
||||
.set_text_format(detect_text_format(old_value))
|
||||
.truncate_to(10);
|
||||
lnav_data.ld_preview_status_source.get_description()
|
||||
lnav_data.ld_preview_status_source[0]
|
||||
.get_description()
|
||||
.set_value("Value of option: %s", option.c_str());
|
||||
|
||||
char help_text[1024];
|
||||
@ -5677,8 +5710,8 @@ search_spectro_details_prompt(std::vector<std::string>& args)
|
||||
static void
|
||||
sql_prompt(std::vector<std::string>& args)
|
||||
{
|
||||
textview_curses* tc = *lnav_data.ld_view_stack.top();
|
||||
textview_curses& log_view = lnav_data.ld_views[LNV_LOG];
|
||||
auto* tc = *lnav_data.ld_view_stack.top();
|
||||
auto& log_view = lnav_data.ld_views[LNV_LOG];
|
||||
|
||||
lnav_data.ld_exec_context.ec_top_line = tc->get_selection();
|
||||
|
||||
@ -5701,6 +5734,8 @@ sql_prompt(std::vector<std::string>& args)
|
||||
tc->reload_data();
|
||||
lnav_data.ld_bottom_source.set_prompt(
|
||||
"Enter an SQL query: (Press " ANSI_BOLD("CTRL+]") " to abort)");
|
||||
|
||||
add_sqlite_possibilities();
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -779,16 +779,28 @@ logfile::rebuild_index(nonstd::optional<ui_clock::time_point> deadline)
|
||||
if (old_size == 0
|
||||
&& this->lf_text_format == text_format_t::TF_UNKNOWN)
|
||||
{
|
||||
file_range fr = this->lf_line_buffer.get_available();
|
||||
auto fr = this->lf_line_buffer.get_available();
|
||||
auto avail_data = this->lf_line_buffer.read_range(fr);
|
||||
|
||||
this->lf_text_format
|
||||
= avail_data
|
||||
.map([path = this->get_path()](
|
||||
const shared_buffer_ref& avail_sbr)
|
||||
.map([path = this->get_path(),
|
||||
this](const shared_buffer_ref& avail_sbr)
|
||||
-> text_format_t {
|
||||
return detect_text_format(
|
||||
avail_sbr.to_string_fragment(), path);
|
||||
auto sbr_str = to_string(avail_sbr);
|
||||
|
||||
if (this->lf_line_buffer.is_piper()) {
|
||||
auto lines
|
||||
= string_fragment::from_str(sbr_str)
|
||||
.split_lines();
|
||||
for (auto line_iter = lines.rbegin();
|
||||
line_iter != lines.rend();
|
||||
++line_iter)
|
||||
{
|
||||
sbr_str.erase(line_iter->sf_begin, 22);
|
||||
}
|
||||
}
|
||||
return detect_text_format(sbr_str, path);
|
||||
})
|
||||
.unwrapOr(text_format_t::TF_UNKNOWN);
|
||||
log_debug("setting text format to %d", this->lf_text_format);
|
||||
|
@ -300,10 +300,12 @@ logfile_sub_source::text_value_for_line(textview_curses& tc,
|
||||
exec_context ec(
|
||||
&this->lss_token_values, pretty_sql_callback, pretty_pipe_callback);
|
||||
std::string rewritten_line;
|
||||
db_label_source rewrite_label_source;
|
||||
|
||||
ec.with_perms(exec_context::perm_t::READ_ONLY);
|
||||
ec.ec_local_vars.push(std::map<std::string, scoped_value_t>());
|
||||
ec.ec_top_line = vis_line_t(row);
|
||||
ec.ec_label_source_stack.push_back(&rewrite_label_source);
|
||||
add_ansi_vars(ec.ec_global_vars);
|
||||
add_global_vars(ec);
|
||||
format->rewrite(ec, sbr, this->lss_token_attrs, rewritten_line);
|
||||
|
@ -141,6 +141,7 @@ network_extension_functions(struct FuncDef** basic_funcs,
|
||||
help_text("gethostbyname",
|
||||
"Get the IP address for the given hostname")
|
||||
.sql_function()
|
||||
.with_prql_path({"net", "gethostbyname"})
|
||||
.with_parameter({"hostname", "The DNS hostname to lookup."})
|
||||
.with_tags({"net"})
|
||||
.with_example({
|
||||
@ -153,6 +154,7 @@ network_extension_functions(struct FuncDef** basic_funcs,
|
||||
help_text("gethostbyaddr",
|
||||
"Get the hostname for the given IP address")
|
||||
.sql_function()
|
||||
.with_prql_path({"net", "gethostbyaddr"})
|
||||
.with_parameter({"hostname", "The IP address to lookup."})
|
||||
.with_tags({"net"})
|
||||
.with_example({
|
||||
|
2
src/prelude.prql
Normal file
2
src/prelude.prql
Normal file
@ -0,0 +1,2 @@
|
||||
|
||||
let json_each = func input -> s"SELECT * FROM json_each({input})"
|
@ -31,6 +31,7 @@
|
||||
#include "base/humanize.network.hh"
|
||||
#include "base/injector.hh"
|
||||
#include "base/paths.hh"
|
||||
#include "bound_tags.hh"
|
||||
#include "command_executor.hh"
|
||||
#include "config.h"
|
||||
#include "field_overlay_source.hh"
|
||||
@ -128,8 +129,41 @@ const char *SQL_EXAMPLE =
|
||||
" SELECT * FROM logline LIMIT 10"
|
||||
;
|
||||
|
||||
const char *PRQL_HELP =
|
||||
" " ANSI_KW("from") " Specify a data source "
|
||||
" " ANSI_KW("derive") " Derive one or more columns\n"
|
||||
" " ANSI_KW("select") " Select one or more columns "
|
||||
" " ANSI_KW("aggregate") " Summary many rows into one\n"
|
||||
" " ANSI_KW("group") " Partition rows into groups "
|
||||
" " ANSI_KW("filter") " Pick rows based on their values\n"
|
||||
;
|
||||
|
||||
const char *PRQL_EXAMPLE =
|
||||
ANSI_UNDERLINE("Examples") "\n"
|
||||
" from db.%s | group { log_level } (aggregate { total = count this })\n"
|
||||
" from db.%s | filter log_line == lnav.view.top_line\n"
|
||||
;
|
||||
|
||||
static const char* LNAV_CMD_PROMPT = "Enter an lnav command: " ABORT_MSG;
|
||||
|
||||
static attr_line_t
|
||||
format_sql_example(const char* sql_example_fmt)
|
||||
{
|
||||
auto& log_view = lnav_data.ld_views[LNV_LOG];
|
||||
auto* lss = (logfile_sub_source*) log_view.get_sub_source();
|
||||
attr_line_t retval;
|
||||
|
||||
if (log_view.get_inner_height() > 0) {
|
||||
auto cl = lss->at(log_view.get_top());
|
||||
auto lf = lss->find(cl);
|
||||
const auto* format_name = lf->get_format()->get_name().get();
|
||||
|
||||
retval.with_ansi_string(sql_example_fmt, format_name, format_name);
|
||||
readline_sqlite_highlighter(retval, 0);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
void
|
||||
rl_set_help()
|
||||
{
|
||||
@ -140,20 +174,7 @@ rl_set_help()
|
||||
break;
|
||||
}
|
||||
case ln_mode_t::SQL: {
|
||||
auto& log_view = lnav_data.ld_views[LNV_LOG];
|
||||
auto* lss = (logfile_sub_source*) log_view.get_sub_source();
|
||||
attr_line_t example_al;
|
||||
|
||||
if (log_view.get_inner_height() > 0) {
|
||||
auto cl = lss->at(log_view.get_top());
|
||||
auto lf = lss->find(cl);
|
||||
const auto* format_name = lf->get_format()->get_name().get();
|
||||
|
||||
example_al.with_ansi_string(
|
||||
SQL_EXAMPLE, format_name, format_name);
|
||||
readline_sqlite_highlighter(example_al, 0);
|
||||
}
|
||||
|
||||
auto example_al = format_sql_example(SQL_EXAMPLE);
|
||||
lnav_data.ld_doc_source.replace_with(SQL_HELP);
|
||||
lnav_data.ld_example_source.replace_with(example_al);
|
||||
break;
|
||||
@ -171,8 +192,8 @@ rl_set_help()
|
||||
static bool
|
||||
rl_sql_help(readline_curses* rc)
|
||||
{
|
||||
attr_line_t al(rc->get_line_buffer());
|
||||
const string_attrs_t& sa = al.get_attrs();
|
||||
auto al = attr_line_t(rc->get_line_buffer());
|
||||
const auto& sa = al.get_attrs();
|
||||
size_t x = rc->get_x();
|
||||
bool has_doc = false;
|
||||
|
||||
@ -183,11 +204,15 @@ rl_sql_help(readline_curses* rc)
|
||||
annotate_sql_statement(al);
|
||||
|
||||
auto avail_help = find_sql_help_for_line(al, x);
|
||||
auto lang = help_example::language::undefined;
|
||||
if (lnav::sql::is_prql(al.get_string())) {
|
||||
lang = help_example::language::prql;
|
||||
}
|
||||
|
||||
if (!avail_help.empty()) {
|
||||
size_t help_count = avail_help.size();
|
||||
textview_curses& dtc = lnav_data.ld_doc_view;
|
||||
textview_curses& etc = lnav_data.ld_example_view;
|
||||
auto& dtc = lnav_data.ld_doc_view;
|
||||
auto& etc = lnav_data.ld_example_view;
|
||||
unsigned long doc_width, ex_width;
|
||||
vis_line_t doc_height, ex_height;
|
||||
attr_line_t doc_al, ex_al;
|
||||
@ -204,7 +229,7 @@ rl_sql_help(readline_curses* rc)
|
||||
: help_text_content::full);
|
||||
if (help_count == 1) {
|
||||
format_example_text_for_term(
|
||||
*ht, eval_example, std::min(70UL, ex_width), ex_al);
|
||||
*ht, eval_example, std::min(70UL, ex_width), ex_al, lang);
|
||||
} else {
|
||||
doc_al.append("\n");
|
||||
}
|
||||
@ -223,6 +248,10 @@ rl_sql_help(readline_curses* rc)
|
||||
|
||||
auto ident_iter = find_string_attr_containing(
|
||||
sa, &SQL_IDENTIFIER_ATTR, al.nearest_text(x));
|
||||
if (ident_iter == sa.end()) {
|
||||
ident_iter = find_string_attr_containing(
|
||||
sa, &lnav::sql::PRQL_IDENTIFIER_ATTR, al.nearest_text(x));
|
||||
}
|
||||
if (ident_iter != sa.end()) {
|
||||
auto ident = al.get_substring(ident_iter->sa_range);
|
||||
auto intern_ident = intern_string::lookup(ident);
|
||||
@ -243,10 +272,14 @@ rl_sql_help(readline_curses* rc)
|
||||
}
|
||||
|
||||
if (!ddl.empty()) {
|
||||
lnav_data.ld_preview_source.replace_with(ddl)
|
||||
lnav_data.ld_preview_view[0].set_sub_source(
|
||||
&lnav_data.ld_preview_source[0]);
|
||||
lnav_data.ld_preview_view[0].set_overlay_source(nullptr);
|
||||
lnav_data.ld_preview_source[0]
|
||||
.replace_with(ddl)
|
||||
.set_text_format(text_format_t::TF_SQL)
|
||||
.truncate_to(30);
|
||||
lnav_data.ld_preview_status_source.get_description().set_value(
|
||||
lnav_data.ld_preview_status_source[0].get_description().set_value(
|
||||
"Definition for table -- %s", ident.c_str());
|
||||
}
|
||||
}
|
||||
@ -267,18 +300,38 @@ rl_change(readline_curses* rc)
|
||||
"show-fields",
|
||||
};
|
||||
|
||||
textview_curses* tc = get_textview_for_mode(lnav_data.ld_mode);
|
||||
auto* tc = get_textview_for_mode(lnav_data.ld_mode);
|
||||
|
||||
tc->get_highlights().erase({highlight_source_t::PREVIEW, "preview"});
|
||||
tc->get_highlights().erase({highlight_source_t::PREVIEW, "bodypreview"});
|
||||
lnav_data.ld_log_source.set_preview_sql_filter(nullptr);
|
||||
lnav_data.ld_user_message_source.clear();
|
||||
lnav_data.ld_preview_source.clear();
|
||||
lnav_data.ld_preview_status_source.get_description()
|
||||
.set_cylon(false)
|
||||
.clear();
|
||||
clear_preview();
|
||||
|
||||
switch (lnav_data.ld_mode) {
|
||||
case ln_mode_t::SQL: {
|
||||
static const auto* sql_cmd_map
|
||||
= injector::get<readline_context::command_map_t*,
|
||||
sql_cmd_map_tag>();
|
||||
|
||||
const auto line = rc->get_line_buffer();
|
||||
std::vector<std::string> args;
|
||||
|
||||
split_ws(line, args);
|
||||
if (!args.empty()) {
|
||||
auto cmd_iter = sql_cmd_map->find(args[0]);
|
||||
if (cmd_iter != sql_cmd_map->end()) {
|
||||
const auto* sql_cmd = cmd_iter->second;
|
||||
if (sql_cmd->c_prompt != nullptr) {
|
||||
const auto prompt_res = sql_cmd->c_prompt(
|
||||
lnav_data.ld_exec_context, line);
|
||||
|
||||
rc->set_suggestion(prompt_res.pr_suggestion);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ln_mode_t::COMMAND: {
|
||||
static std::string last_command;
|
||||
static int generation = 0;
|
||||
@ -331,10 +384,6 @@ rl_change(readline_curses* rc)
|
||||
{
|
||||
lnav_data.ld_doc_source.replace_with(CMD_HELP);
|
||||
lnav_data.ld_example_source.replace_with(CMD_EXAMPLE);
|
||||
lnav_data.ld_preview_source.clear();
|
||||
lnav_data.ld_preview_status_source.get_description()
|
||||
.set_cylon(false)
|
||||
.clear();
|
||||
lnav_data.ld_bottom_source.set_prompt(LNAV_CMD_PROMPT);
|
||||
lnav_data.ld_bottom_source.grep_error("");
|
||||
} else if (args[0] == "config" && args.size() > 1) {
|
||||
@ -436,7 +485,7 @@ rl_change(readline_curses* rc)
|
||||
static void
|
||||
rl_search_internal(readline_curses* rc, ln_mode_t mode, bool complete = false)
|
||||
{
|
||||
textview_curses* tc = get_textview_for_mode(mode);
|
||||
auto* tc = get_textview_for_mode(mode);
|
||||
std::string term_val;
|
||||
std::string name;
|
||||
|
||||
@ -463,10 +512,7 @@ rl_search_internal(readline_curses* rc, ln_mode_t mode, bool complete = false)
|
||||
lnav_data.ld_exec_context.ec_dry_run = true;
|
||||
|
||||
lnav_data.ld_preview_generation += 1;
|
||||
lnav_data.ld_preview_status_source.get_description()
|
||||
.set_cylon(false)
|
||||
.clear();
|
||||
lnav_data.ld_preview_source.clear();
|
||||
clear_preview();
|
||||
auto result = execute_command(lnav_data.ld_exec_context,
|
||||
rc->get_value().get_string());
|
||||
|
||||
@ -486,18 +532,199 @@ rl_search_internal(readline_curses* rc, ln_mode_t mode, bool complete = false)
|
||||
result.unwrapErr().um_message.get_string());
|
||||
}
|
||||
|
||||
lnav_data.ld_preview_view.reload_data();
|
||||
lnav_data.ld_preview_view[0].reload_data();
|
||||
|
||||
lnav_data.ld_exec_context.ec_dry_run = false;
|
||||
return;
|
||||
}
|
||||
|
||||
case ln_mode_t::SQL: {
|
||||
term_val = trim(rc->get_value().get_string() + ";");
|
||||
term_val = trim(rc->get_value().get_string());
|
||||
|
||||
if (!term_val.empty() && term_val[0] == '.') {
|
||||
lnav_data.ld_bottom_source.grep_error("");
|
||||
} else if (!sqlite3_complete(term_val.c_str())) {
|
||||
} else if (lnav::sql::is_prql(term_val)) {
|
||||
std::string alt_msg;
|
||||
|
||||
lnav_data.ld_doc_source.replace_with(PRQL_HELP);
|
||||
lnav_data.ld_example_source.replace_with(
|
||||
format_sql_example(PRQL_EXAMPLE));
|
||||
lnav_data.ld_db_preview_source[0].clear();
|
||||
lnav_data.ld_db_preview_source[1].clear();
|
||||
rc->clear_possibilities(ln_mode_t::SQL, "prql-expr");
|
||||
|
||||
auto orig_prql_stmt = attr_line_t(term_val);
|
||||
orig_prql_stmt.rtrim("| \r\n\t");
|
||||
annotate_sql_statement(orig_prql_stmt);
|
||||
auto cursor_x = rc->get_x();
|
||||
if (cursor_x > orig_prql_stmt.get_string().length()) {
|
||||
cursor_x = orig_prql_stmt.length() - 1;
|
||||
}
|
||||
auto curr_stage_iter
|
||||
= find_string_attr_containing(orig_prql_stmt.get_attrs(),
|
||||
&lnav::sql::PRQL_STAGE_ATTR,
|
||||
cursor_x);
|
||||
auto curr_stage_prql = orig_prql_stmt.subline(
|
||||
0, curr_stage_iter->sa_range.lr_end);
|
||||
for (auto riter = curr_stage_prql.get_attrs().rbegin();
|
||||
riter != curr_stage_prql.get_attrs().rend();
|
||||
++riter)
|
||||
{
|
||||
if (riter->sa_type != &lnav::sql::PRQL_PIPE_ATTR) {
|
||||
continue;
|
||||
}
|
||||
curr_stage_prql.insert(riter->sa_range.lr_start,
|
||||
"| take 1000 ");
|
||||
}
|
||||
curr_stage_prql.rtrim();
|
||||
curr_stage_prql.append(" | take 10");
|
||||
log_debug("preview prql: %s",
|
||||
curr_stage_prql.get_string().c_str());
|
||||
|
||||
size_t curr_stage_index = 0;
|
||||
if (curr_stage_iter->sa_range.lr_start > 0) {
|
||||
auto prev_stage_iter = find_string_attr_containing(
|
||||
orig_prql_stmt.get_attrs(),
|
||||
&lnav::sql::PRQL_STAGE_ATTR,
|
||||
curr_stage_iter->sa_range.lr_start - 1);
|
||||
auto prev_stage_prql = orig_prql_stmt.subline(
|
||||
0, prev_stage_iter->sa_range.lr_end);
|
||||
for (auto riter = prev_stage_prql.get_attrs().rbegin();
|
||||
riter != prev_stage_prql.get_attrs().rend();
|
||||
++riter)
|
||||
{
|
||||
if (riter->sa_type != &lnav::sql::PRQL_PIPE_ATTR) {
|
||||
continue;
|
||||
}
|
||||
prev_stage_prql.insert(riter->sa_range.lr_start,
|
||||
"| take 1000 ");
|
||||
}
|
||||
prev_stage_prql.append(" | take 10");
|
||||
|
||||
curr_stage_index = 1;
|
||||
auto db_guard = lnav_data.ld_exec_context.enter_db_source(
|
||||
&lnav_data.ld_db_preview_source[0]);
|
||||
auto exec_res = execute_sql(lnav_data.ld_exec_context,
|
||||
prev_stage_prql.get_string(),
|
||||
alt_msg);
|
||||
lnav_data.ld_preview_status_source[0]
|
||||
.get_description()
|
||||
.set_value("Result for query: %s",
|
||||
prev_stage_prql.get_string().c_str());
|
||||
if (exec_res.isOk()) {
|
||||
for (const auto& hdr :
|
||||
lnav_data.ld_db_preview_source[0].dls_headers)
|
||||
{
|
||||
rc->add_possibility(
|
||||
ln_mode_t::SQL,
|
||||
"prql-expr",
|
||||
lnav::prql::quote_ident(hdr.hm_name));
|
||||
}
|
||||
|
||||
lnav_data.ld_preview_view[0].set_sub_source(
|
||||
&lnav_data.ld_db_preview_source[0]);
|
||||
lnav_data.ld_preview_view[0].set_overlay_source(
|
||||
&lnav_data.ld_db_preview_overlay_source[0]);
|
||||
} else {
|
||||
lnav_data.ld_preview_source[0].replace_with(
|
||||
exec_res.unwrapErr().to_attr_line());
|
||||
lnav_data.ld_preview_view[0].set_sub_source(
|
||||
&lnav_data.ld_preview_source[0]);
|
||||
lnav_data.ld_preview_view[0].set_overlay_source(
|
||||
nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
auto db_guard = lnav_data.ld_exec_context.enter_db_source(
|
||||
&lnav_data.ld_db_preview_source[curr_stage_index]);
|
||||
auto exec_res = execute_sql(lnav_data.ld_exec_context,
|
||||
curr_stage_prql.get_string(),
|
||||
alt_msg);
|
||||
if (exec_res.isErr()) {
|
||||
auto err = exec_res.unwrapErr();
|
||||
|
||||
lnav_data.ld_bottom_source.grep_error(
|
||||
err.um_reason.get_string());
|
||||
|
||||
auto near = term_val.length();
|
||||
while (near > 0) {
|
||||
auto paren_iter = rfind_string_attr_if(
|
||||
curr_stage_prql.get_attrs(),
|
||||
near,
|
||||
[](const string_attr& sa) {
|
||||
return sa.sa_type
|
||||
== &lnav::sql::PRQL_UNTERMINATED_PAREN_ATTR;
|
||||
});
|
||||
|
||||
if (paren_iter == curr_stage_prql.get_attrs().end()) {
|
||||
break;
|
||||
}
|
||||
switch (term_val[paren_iter->sa_range.lr_start]) {
|
||||
case '(':
|
||||
term_val.append(")");
|
||||
break;
|
||||
case '{':
|
||||
term_val.append("}");
|
||||
break;
|
||||
}
|
||||
near = paren_iter->sa_range.lr_start - 1;
|
||||
}
|
||||
|
||||
auto exec_termed_res = execute_sql(
|
||||
lnav_data.ld_exec_context, term_val, alt_msg);
|
||||
if (exec_termed_res.isErr()) {
|
||||
}
|
||||
} else {
|
||||
lnav_data.ld_bottom_source.grep_error("");
|
||||
}
|
||||
|
||||
rc->add_possibility(
|
||||
ln_mode_t::SQL, "prql-expr", lnav::sql::prql_keywords);
|
||||
for (const auto& pair : lnav::sql::prql_functions) {
|
||||
rc->add_possibility(
|
||||
ln_mode_t::SQL, "prql-expr", pair.first);
|
||||
}
|
||||
|
||||
rl_sql_help(rc);
|
||||
|
||||
lnav_data.ld_preview_status_source[curr_stage_index]
|
||||
.get_description()
|
||||
.set_value("Result for query: %s",
|
||||
curr_stage_prql.get_string().c_str());
|
||||
if (!lnav_data.ld_db_preview_source[curr_stage_index]
|
||||
.dls_headers.empty())
|
||||
{
|
||||
if (curr_stage_index == 0) {
|
||||
for (const auto& hdr :
|
||||
lnav_data.ld_db_preview_source[curr_stage_index]
|
||||
.dls_headers)
|
||||
{
|
||||
rc->add_possibility(
|
||||
ln_mode_t::SQL,
|
||||
"prql-expr",
|
||||
lnav::prql::quote_ident(hdr.hm_name));
|
||||
}
|
||||
}
|
||||
|
||||
lnav_data.ld_preview_view[curr_stage_index].set_sub_source(
|
||||
&lnav_data.ld_db_preview_source[curr_stage_index]);
|
||||
lnav_data.ld_preview_view[curr_stage_index]
|
||||
.set_overlay_source(
|
||||
&lnav_data.ld_db_preview_overlay_source
|
||||
[curr_stage_index]);
|
||||
} else if (exec_res.isErr()) {
|
||||
lnav_data.ld_preview_source[curr_stage_index].replace_with(
|
||||
exec_res.unwrapErr().to_attr_line());
|
||||
lnav_data.ld_preview_view[curr_stage_index].set_sub_source(
|
||||
&lnav_data.ld_preview_source[curr_stage_index]);
|
||||
lnav_data.ld_preview_view[curr_stage_index]
|
||||
.set_overlay_source(nullptr);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
term_val += ";";
|
||||
if (!sqlite3_complete(term_val.c_str())) {
|
||||
lnav_data.ld_bottom_source.grep_error(
|
||||
"SQL error: incomplete statement");
|
||||
} else {
|
||||
@ -522,7 +749,6 @@ rl_search_internal(readline_curses* rc, ln_mode_t mode, bool complete = false)
|
||||
|
||||
if (!rl_sql_help(rc)) {
|
||||
rl_set_help();
|
||||
lnav_data.ld_preview_source.clear();
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -561,10 +787,7 @@ lnav_rl_abort(readline_curses* rc)
|
||||
lnav_data.ld_bottom_source.set_prompt("");
|
||||
lnav_data.ld_example_source.clear();
|
||||
lnav_data.ld_doc_source.clear();
|
||||
lnav_data.ld_preview_status_source.get_description()
|
||||
.set_cylon(false)
|
||||
.clear();
|
||||
lnav_data.ld_preview_source.clear();
|
||||
clear_preview();
|
||||
tc->get_highlights().erase({highlight_source_t::PREVIEW, "preview"});
|
||||
tc->get_highlights().erase({highlight_source_t::PREVIEW, "bodypreview"});
|
||||
lnav_data.ld_log_source.set_preview_sql_filter(nullptr);
|
||||
@ -599,10 +822,7 @@ rl_callback_int(readline_curses* rc, bool is_alt)
|
||||
lnav_data.ld_bottom_source.set_prompt("");
|
||||
lnav_data.ld_doc_source.clear();
|
||||
lnav_data.ld_example_source.clear();
|
||||
lnav_data.ld_preview_status_source.get_description()
|
||||
.set_cylon(false)
|
||||
.clear();
|
||||
lnav_data.ld_preview_source.clear();
|
||||
clear_preview();
|
||||
tc->get_highlights().erase({highlight_source_t::PREVIEW, "preview"});
|
||||
tc->get_highlights().erase({highlight_source_t::PREVIEW, "bodypreview"});
|
||||
lnav_data.ld_log_source.set_preview_sql_filter(nullptr);
|
||||
@ -943,3 +1163,28 @@ rl_blur(readline_curses* rc)
|
||||
}
|
||||
lnav_data.ld_preview_generation += 1;
|
||||
}
|
||||
|
||||
readline_context::split_result_t
|
||||
prql_splitter(readline_context& rc, const std::string& cmdline)
|
||||
{
|
||||
auto stmt = attr_line_t(cmdline);
|
||||
readline_context::split_result_t retval;
|
||||
readline_context::stage st;
|
||||
|
||||
lnav::sql::annotate_prql_statement(stmt);
|
||||
for (const auto& attr : stmt.get_attrs()) {
|
||||
if (attr.sa_type == &lnav::sql::PRQL_STAGE_ATTR) {
|
||||
} else if (attr.sa_type == &lnav::sql::PRQL_PIPE_ATTR) {
|
||||
retval.sr_stages.emplace_back(st);
|
||||
st.s_args.clear();
|
||||
} else {
|
||||
st.s_args.emplace_back(attr.sa_range);
|
||||
}
|
||||
}
|
||||
if (!cmdline.empty() && isspace(cmdline.back())) {
|
||||
st.s_args.emplace_back(cmdline.length(), cmdline.length());
|
||||
}
|
||||
retval.sr_stages.emplace_back(st);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
@ -30,6 +30,8 @@
|
||||
#ifndef LNAV_READLINE_CALLBACKS_HH
|
||||
#define LNAV_READLINE_CALLBACKS_HH
|
||||
|
||||
#include "readline_curses.hh"
|
||||
|
||||
void rl_set_help();
|
||||
void rl_change(readline_curses* rc);
|
||||
void rl_search(readline_curses* rc);
|
||||
@ -42,6 +44,9 @@ void rl_completion_request(readline_curses* rc);
|
||||
void rl_focus(readline_curses* rc);
|
||||
void rl_blur(readline_curses* rc);
|
||||
|
||||
readline_context::split_result_t prql_splitter(readline_context& rc,
|
||||
const std::string& cmdline);
|
||||
|
||||
extern const char* RE_HELP;
|
||||
extern const char* RE_EXAMPLE;
|
||||
extern const char* SQL_HELP;
|
||||
|
@ -44,7 +44,7 @@
|
||||
class attr_line_t;
|
||||
struct exec_context;
|
||||
|
||||
typedef void (*readline_highlighter_t)(attr_line_t& line, int x);
|
||||
using readline_highlighter_t = void (*)(attr_line_t& line, int x);
|
||||
|
||||
/**
|
||||
* Container for information related to different readline contexts. Since
|
||||
@ -61,28 +61,42 @@ public:
|
||||
std::string pr_suggestion;
|
||||
};
|
||||
|
||||
struct stage {
|
||||
std::vector<line_range> s_args;
|
||||
};
|
||||
|
||||
struct split_result_t {
|
||||
std::vector<stage> sr_stages;
|
||||
};
|
||||
|
||||
using prompt_func_t
|
||||
= prompt_result_t (*)(exec_context& ec, const std::string& cmdline);
|
||||
typedef struct _command_t {
|
||||
using splitter_func_t
|
||||
= split_result_t (*)(readline_context& rc, const std::string& cmdline);
|
||||
using command_t = struct _command_t {
|
||||
const char* c_name;
|
||||
command_func_t c_func;
|
||||
|
||||
struct help_text c_help;
|
||||
prompt_func_t c_prompt{nullptr};
|
||||
std::string c_provides;
|
||||
std::set<std::string> c_dependencies;
|
||||
|
||||
_command_t(const char* name,
|
||||
command_func_t func,
|
||||
help_text help = {},
|
||||
prompt_func_t prompt = nullptr) noexcept
|
||||
prompt_func_t prompt = nullptr,
|
||||
std::string provides = {},
|
||||
std::set<std::string> deps = {}) noexcept
|
||||
: c_name(name), c_func(func), c_help(std::move(help)),
|
||||
c_prompt(prompt)
|
||||
c_prompt(prompt), c_provides(provides), c_dependencies(deps)
|
||||
{
|
||||
}
|
||||
|
||||
_command_t(command_func_t func) noexcept : c_name("anon"), c_func(func)
|
||||
{
|
||||
}
|
||||
} command_t;
|
||||
};
|
||||
typedef std::map<std::string, command_t*> command_map_t;
|
||||
|
||||
readline_context(std::string name,
|
||||
@ -146,6 +160,12 @@ public:
|
||||
return this->rc_highlighter;
|
||||
}
|
||||
|
||||
readline_context& with_splitter(splitter_func_t sf)
|
||||
{
|
||||
this->rc_splitter = sf;
|
||||
return *this;
|
||||
}
|
||||
|
||||
static int command_complete(int, int);
|
||||
|
||||
std::map<std::string, std::string> rc_prefixes;
|
||||
@ -176,11 +196,13 @@ private:
|
||||
HISTORY_STATE rc_history;
|
||||
std::map<std::string, std::set<std::string>> rc_possibilities;
|
||||
std::map<std::string, std::vector<std::string>> rc_prototypes;
|
||||
std::map<std::string, command_t*> rc_commands;
|
||||
bool rc_case_sensitive;
|
||||
int rc_append_character;
|
||||
const char* rc_quote_chars;
|
||||
readline_highlighter_t rc_highlighter;
|
||||
std::vector<readline_var> rc_vars;
|
||||
splitter_func_t rc_splitter;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -370,12 +370,49 @@ readline_context::attempted_completion(const char* text, int start, int end)
|
||||
{
|
||||
char** retval = nullptr;
|
||||
|
||||
log_info("completion start %d:%d -- %s", start, end, text);
|
||||
|
||||
auto at_start = start == 0;
|
||||
auto cmd_start = 0;
|
||||
auto cmd_key = std::string("__command");
|
||||
if (loaded_context->rc_splitter != nullptr) {
|
||||
auto split_res
|
||||
= loaded_context->rc_splitter(*loaded_context, rl_line_buffer);
|
||||
|
||||
readline_context::command_t* last_cmd = nullptr;
|
||||
for (const auto& stage : split_res.sr_stages) {
|
||||
if (stage.s_args.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (stage.s_args.front().lr_start == start) {
|
||||
at_start = true;
|
||||
break;
|
||||
}
|
||||
if (start <= stage.s_args.back().lr_end) {
|
||||
cmd_start = stage.s_args.front().lr_start;
|
||||
}
|
||||
auto cmd_lr = stage.s_args.front();
|
||||
auto cmd_name = std::string(&rl_line_buffer[cmd_lr.lr_start],
|
||||
cmd_lr.length());
|
||||
auto cmd_iter = loaded_context->rc_commands.find(cmd_name);
|
||||
if (cmd_iter == loaded_context->rc_commands.end()) {
|
||||
continue;
|
||||
}
|
||||
last_cmd = cmd_iter->second;
|
||||
}
|
||||
if (last_cmd != nullptr && !last_cmd->c_provides.empty()) {
|
||||
cmd_key
|
||||
= fmt::format(FMT_STRING("__command_{}"), last_cmd->c_provides);
|
||||
}
|
||||
}
|
||||
|
||||
completion_start = start;
|
||||
if (start == 0
|
||||
&& loaded_context->rc_possibilities.find("__command")
|
||||
if (at_start
|
||||
&& loaded_context->rc_possibilities.find(cmd_key)
|
||||
!= loaded_context->rc_possibilities.end())
|
||||
{
|
||||
arg_possibilities = &loaded_context->rc_possibilities["__command"];
|
||||
arg_possibilities = &loaded_context->rc_possibilities[cmd_key];
|
||||
arg_needs_shlex = false;
|
||||
rl_completion_append_character = loaded_context->rc_append_character;
|
||||
} else {
|
||||
@ -407,12 +444,12 @@ readline_context::attempted_completion(const char* text, int start, int end)
|
||||
}
|
||||
|
||||
if (arg_possibilities == nullptr) {
|
||||
space = strchr(rl_line_buffer, ' ');
|
||||
space = strchr(&rl_line_buffer[cmd_start], ' ');
|
||||
if (space == nullptr) {
|
||||
space = rl_line_buffer + strlen(rl_line_buffer);
|
||||
}
|
||||
cmd = std::string(rl_line_buffer, space - rl_line_buffer);
|
||||
|
||||
cmd = std::string(&rl_line_buffer[cmd_start],
|
||||
space - &rl_line_buffer[cmd_start]);
|
||||
auto iter = loaded_context->rc_prototypes.find(cmd);
|
||||
|
||||
if (iter == loaded_context->rc_prototypes.end()) {
|
||||
@ -425,8 +462,7 @@ readline_context::attempted_completion(const char* text, int start, int end)
|
||||
= loaded_context->rc_append_character;
|
||||
}
|
||||
} else {
|
||||
std::vector<std::string>& proto
|
||||
= loaded_context->rc_prototypes[cmd];
|
||||
auto& proto = loaded_context->rc_prototypes[cmd];
|
||||
|
||||
if (proto.empty()) {
|
||||
arg_possibilities = nullptr;
|
||||
@ -608,8 +644,24 @@ readline_context::readline_context(std::string name,
|
||||
|
||||
for (iter = commands->begin(); iter != commands->end(); ++iter) {
|
||||
std::string cmd = iter->first;
|
||||
auto cmd_complete = cmd;
|
||||
const auto& ht = iter->second->c_help;
|
||||
|
||||
this->rc_possibilities["__command"].insert(cmd);
|
||||
if (!ht.ht_parameters.empty()
|
||||
&& ht.ht_parameters.front().ht_group_start != nullptr)
|
||||
{
|
||||
cmd_complete.append(" ");
|
||||
cmd_complete.append(ht.ht_parameters.front().ht_group_start);
|
||||
}
|
||||
if (iter->second->c_dependencies.empty()) {
|
||||
this->rc_possibilities["__command"].insert(cmd_complete);
|
||||
} else {
|
||||
for (const auto& dep : iter->second->c_dependencies) {
|
||||
auto cmd_key = fmt::format(FMT_STRING("__command_{}"), dep);
|
||||
this->rc_possibilities[cmd_key].insert(cmd_complete);
|
||||
}
|
||||
}
|
||||
this->rc_commands[cmd] = iter->second;
|
||||
iter->second->c_func(
|
||||
INIT_EXEC_CONTEXT, cmd, this->rc_prototypes[cmd]);
|
||||
}
|
||||
|
@ -260,10 +260,14 @@ readline_sqlite_highlighter_int(attr_line_t& al, int x, line_range sub)
|
||||
sub.lr_start + attr.sa_range.lr_end,
|
||||
};
|
||||
if (attr.sa_type == &SQL_COMMAND_ATTR
|
||||
|| attr.sa_type == &SQL_KEYWORD_ATTR)
|
||||
|| attr.sa_type == &SQL_KEYWORD_ATTR
|
||||
|| attr.sa_type == &lnav::sql::PRQL_KEYWORD_ATTR
|
||||
|| attr.sa_type == &lnav::sql::PRQL_TRANSFORM_ATTR)
|
||||
{
|
||||
alb.overlay_attr(lr, VC_ROLE.value(role_t::VCR_KEYWORD));
|
||||
} else if (attr.sa_type == &SQL_IDENTIFIER_ATTR) {
|
||||
} else if (attr.sa_type == &SQL_IDENTIFIER_ATTR
|
||||
|| attr.sa_type == &lnav::sql::PRQL_IDENTIFIER_ATTR)
|
||||
{
|
||||
if (!attr.sa_range.contains(x) && attr.sa_range.lr_end != x) {
|
||||
alb.overlay_attr(lr, VC_ROLE.value(role_t::VCR_IDENTIFIER));
|
||||
}
|
||||
@ -271,9 +275,13 @@ readline_sqlite_highlighter_int(attr_line_t& al, int x, line_range sub)
|
||||
alb.overlay_attr(
|
||||
line_range{lr.lr_start, (int) line.find('(', lr.lr_start)},
|
||||
VC_ROLE.value(role_t::VCR_SYMBOL));
|
||||
} else if (attr.sa_type == &SQL_NUMBER_ATTR) {
|
||||
} else if (attr.sa_type == &SQL_NUMBER_ATTR
|
||||
|| attr.sa_type == &lnav::sql::PRQL_NUMBER_ATTR)
|
||||
{
|
||||
alb.overlay_attr(lr, VC_ROLE.value(role_t::VCR_NUMBER));
|
||||
} else if (attr.sa_type == &SQL_STRING_ATTR) {
|
||||
} else if (attr.sa_type == &SQL_STRING_ATTR
|
||||
|| attr.sa_type == &lnav::sql::PRQL_STRING_ATTR)
|
||||
{
|
||||
if (lr.length() > 1 && al.al_string[lr.lr_end - 1] == '\'') {
|
||||
alb.overlay_attr(lr, VC_ROLE.value(role_t::VCR_STRING));
|
||||
} else {
|
||||
@ -282,9 +290,13 @@ readline_sqlite_highlighter_int(attr_line_t& al, int x, line_range sub)
|
||||
alb.overlay_attr_for_char(lr.lr_start,
|
||||
VC_ROLE.value(role_t::VCR_ERROR));
|
||||
}
|
||||
} else if (attr.sa_type == &SQL_OPERATOR_ATTR) {
|
||||
} else if (attr.sa_type == &SQL_OPERATOR_ATTR
|
||||
|| attr.sa_type == &lnav::sql::PRQL_OPERATOR_ATTR)
|
||||
{
|
||||
alb.overlay_attr(lr, VC_ROLE.value(role_t::VCR_SYMBOL));
|
||||
} else if (attr.sa_type == &SQL_COMMENT_ATTR) {
|
||||
} else if (attr.sa_type == &SQL_COMMENT_ATTR
|
||||
|| attr.sa_type == &lnav::sql::PRQL_COMMENT_ATTR)
|
||||
{
|
||||
alb.overlay_attr(lr, VC_ROLE.value(role_t::VCR_COMMENT));
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,7 @@
|
||||
#include "date/tz.h"
|
||||
#include "lnav.hh"
|
||||
#include "lnav_config.hh"
|
||||
#include "log_data_helper.hh"
|
||||
#include "service_tags.hh"
|
||||
#include "session_data.hh"
|
||||
#include "sql_help.hh"
|
||||
@ -78,7 +79,12 @@ handle_table_list(void* ptr, int ncols, char** colvalues, char** colnames)
|
||||
|
||||
if (sqlite_function_help.count(table_name) == 0) {
|
||||
lnav_data.ld_rl_view->add_possibility(
|
||||
ln_mode_t::SQL, "*", colvalues[0]);
|
||||
ln_mode_t::SQL, "*", table_name);
|
||||
lnav_data.ld_rl_view->add_possibility(
|
||||
ln_mode_t::SQL,
|
||||
"prql-table",
|
||||
fmt::format(FMT_STRING("db.{}"),
|
||||
lnav::prql::quote_ident(std::move(table_name))));
|
||||
}
|
||||
|
||||
lnav_data.ld_table_ddl[colvalues[0]] = colvalues[1];
|
||||
@ -536,3 +542,112 @@ add_tz_possibilities(ln_mode_t context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
add_sqlite_possibilities()
|
||||
{
|
||||
// Hidden columns don't show up in the table_info pragma.
|
||||
static const char* hidden_table_columns[] = {
|
||||
"log_time_msecs",
|
||||
"log_path",
|
||||
"log_text",
|
||||
"log_body",
|
||||
|
||||
nullptr,
|
||||
};
|
||||
|
||||
auto& log_view = lnav_data.ld_views[LNV_LOG];
|
||||
|
||||
add_env_possibilities(ln_mode_t::SQL);
|
||||
|
||||
lnav_data.ld_rl_view->add_possibility(
|
||||
ln_mode_t::SQL, "prql-expr", lnav::sql::prql_keywords);
|
||||
for (const auto& pair : lnav::sql::prql_functions) {
|
||||
lnav_data.ld_rl_view->add_possibility(
|
||||
ln_mode_t::SQL, "prql-expr", pair.first);
|
||||
}
|
||||
|
||||
if (log_view.get_inner_height() > 0) {
|
||||
log_data_helper ldh(lnav_data.ld_log_source);
|
||||
auto vl = log_view.get_selection();
|
||||
auto cl = lnav_data.ld_log_source.at_base(vl);
|
||||
|
||||
ldh.parse_line(cl);
|
||||
|
||||
for (const auto& jextra : ldh.ldh_extra_json) {
|
||||
lnav_data.ld_rl_view->add_possibility(
|
||||
ln_mode_t::SQL,
|
||||
"*",
|
||||
lnav::sql::mprintf("%Q", jextra.first.c_str()).in());
|
||||
}
|
||||
for (const auto& jpair : ldh.ldh_json_pairs) {
|
||||
for (const auto& wt : jpair.second) {
|
||||
lnav_data.ld_rl_view->add_possibility(
|
||||
ln_mode_t::SQL,
|
||||
"*",
|
||||
lnav::sql::mprintf("%Q", wt.wt_ptr.c_str()).in());
|
||||
}
|
||||
}
|
||||
for (const auto& xml_pair : ldh.ldh_xml_pairs) {
|
||||
lnav_data.ld_rl_view->add_possibility(
|
||||
ln_mode_t::SQL,
|
||||
"*",
|
||||
lnav::sql::mprintf("%Q", xml_pair.first.second.c_str()).in());
|
||||
}
|
||||
}
|
||||
|
||||
lnav_data.ld_rl_view->clear_possibilities(ln_mode_t::SQL, "*");
|
||||
add_view_text_possibilities(lnav_data.ld_rl_view,
|
||||
ln_mode_t::SQL,
|
||||
"*",
|
||||
&log_view,
|
||||
text_quoting::sql);
|
||||
|
||||
lnav_data.ld_rl_view->add_possibility(
|
||||
ln_mode_t::SQL, "*", std::begin(sql_keywords), std::end(sql_keywords));
|
||||
lnav_data.ld_rl_view->add_possibility(
|
||||
ln_mode_t::SQL, "*", sql_function_names);
|
||||
lnav_data.ld_rl_view->add_possibility(
|
||||
ln_mode_t::SQL, "*", hidden_table_columns);
|
||||
|
||||
for (int lpc = 0; sqlite_registration_funcs[lpc]; lpc++) {
|
||||
struct FuncDef* basic_funcs;
|
||||
struct FuncDefAgg* agg_funcs;
|
||||
|
||||
sqlite_registration_funcs[lpc](&basic_funcs, &agg_funcs);
|
||||
for (int lpc2 = 0; basic_funcs && basic_funcs[lpc2].zName; lpc2++) {
|
||||
const FuncDef& func_def = basic_funcs[lpc2];
|
||||
|
||||
lnav_data.ld_rl_view->add_possibility(
|
||||
ln_mode_t::SQL,
|
||||
"*",
|
||||
std::string(func_def.zName) + (func_def.nArg ? "(" : "()"));
|
||||
}
|
||||
for (int lpc2 = 0; agg_funcs && agg_funcs[lpc2].zName; lpc2++) {
|
||||
const FuncDefAgg& func_def = agg_funcs[lpc2];
|
||||
|
||||
lnav_data.ld_rl_view->add_possibility(
|
||||
ln_mode_t::SQL,
|
||||
"*",
|
||||
std::string(func_def.zName) + (func_def.nArg ? "(" : "()"));
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& pair : sqlite_function_help) {
|
||||
switch (pair.second->ht_context) {
|
||||
case help_context_t::HC_SQL_FUNCTION:
|
||||
case help_context_t::HC_SQL_TABLE_VALUED_FUNCTION: {
|
||||
std::string poss = pair.first
|
||||
+ (pair.second->ht_parameters.empty() ? "()" : ("("));
|
||||
|
||||
lnav_data.ld_rl_view->add_possibility(
|
||||
ln_mode_t::SQL, "*", poss);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
walk_sqlite_metadata(lnav_data.ld_db.in(), lnav_sql_meta_callbacks);
|
||||
}
|
||||
|
@ -39,6 +39,7 @@
|
||||
enum class text_quoting {
|
||||
none,
|
||||
sql,
|
||||
prql,
|
||||
regex,
|
||||
};
|
||||
|
||||
@ -80,6 +81,7 @@ void add_tag_possibilities();
|
||||
void add_file_possibilities();
|
||||
void add_recent_netlocs_possibilities();
|
||||
void add_tz_possibilities(ln_mode_t context);
|
||||
void add_sqlite_possibilities();
|
||||
|
||||
extern struct sqlite_metadata_callbacks lnav_sql_meta_callbacks;
|
||||
|
||||
|
@ -35,8 +35,10 @@
|
||||
#include "bound_tags.hh"
|
||||
#include "command_executor.hh"
|
||||
#include "config.h"
|
||||
#include "lnav.hh"
|
||||
#include "readline_context.hh"
|
||||
#include "shlex.hh"
|
||||
#include "sql_help.hh"
|
||||
#include "sqlite-extension-func.hh"
|
||||
#include "sqlitepp.hh"
|
||||
#include "view_helpers.hh"
|
||||
@ -222,6 +224,184 @@ sql_cmd_generic(exec_context& ec,
|
||||
return Ok(retval);
|
||||
}
|
||||
|
||||
static Result<std::string, lnav::console::user_message>
|
||||
prql_cmd_from(exec_context& ec,
|
||||
std::string cmdline,
|
||||
std::vector<std::string>& args)
|
||||
{
|
||||
std::string retval;
|
||||
|
||||
if (args.empty()) {
|
||||
args.emplace_back("prql-table");
|
||||
return Ok(retval);
|
||||
}
|
||||
|
||||
return Ok(retval);
|
||||
}
|
||||
|
||||
static readline_context::prompt_result_t
|
||||
prql_cmd_from_prompt(exec_context& ec, const std::string& cmdline)
|
||||
{
|
||||
if (!endswith(cmdline, "from ")) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto* tc = *lnav_data.ld_view_stack.top();
|
||||
auto* lss = dynamic_cast<logfile_sub_source*>(tc->get_sub_source());
|
||||
|
||||
if (lss == nullptr || lss->text_line_count() == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto line_pair = lss->find_line_with_file(lss->at(tc->get_selection()));
|
||||
if (!line_pair) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto format_name
|
||||
= line_pair->first->get_format_ptr()->get_name().to_string();
|
||||
return {
|
||||
"",
|
||||
fmt::format(FMT_STRING("db.{}"), lnav::prql::quote_ident(format_name)),
|
||||
};
|
||||
}
|
||||
|
||||
static Result<std::string, lnav::console::user_message>
|
||||
prql_cmd_aggregate(exec_context& ec,
|
||||
std::string cmdline,
|
||||
std::vector<std::string>& args)
|
||||
{
|
||||
std::string retval;
|
||||
|
||||
if (args.empty()) {
|
||||
args.emplace_back("prql-expr");
|
||||
return Ok(retval);
|
||||
}
|
||||
|
||||
return Ok(retval);
|
||||
}
|
||||
|
||||
static Result<std::string, lnav::console::user_message>
|
||||
prql_cmd_append(exec_context& ec,
|
||||
std::string cmdline,
|
||||
std::vector<std::string>& args)
|
||||
{
|
||||
std::string retval;
|
||||
|
||||
if (args.empty()) {
|
||||
args.emplace_back("prql-table");
|
||||
return Ok(retval);
|
||||
}
|
||||
|
||||
return Ok(retval);
|
||||
}
|
||||
|
||||
static Result<std::string, lnav::console::user_message>
|
||||
prql_cmd_derive(exec_context& ec,
|
||||
std::string cmdline,
|
||||
std::vector<std::string>& args)
|
||||
{
|
||||
std::string retval;
|
||||
|
||||
if (args.empty()) {
|
||||
args.emplace_back("prql-expr");
|
||||
return Ok(retval);
|
||||
}
|
||||
|
||||
return Ok(retval);
|
||||
}
|
||||
|
||||
static Result<std::string, lnav::console::user_message>
|
||||
prql_cmd_filter(exec_context& ec,
|
||||
std::string cmdline,
|
||||
std::vector<std::string>& args)
|
||||
{
|
||||
std::string retval;
|
||||
|
||||
if (args.empty()) {
|
||||
args.emplace_back("prql-expr");
|
||||
return Ok(retval);
|
||||
}
|
||||
|
||||
return Ok(retval);
|
||||
}
|
||||
|
||||
static Result<std::string, lnav::console::user_message>
|
||||
prql_cmd_group(exec_context& ec,
|
||||
std::string cmdline,
|
||||
std::vector<std::string>& args)
|
||||
{
|
||||
std::string retval;
|
||||
|
||||
if (args.empty()) {
|
||||
args.emplace_back("prql-column");
|
||||
args.emplace_back("prql-source");
|
||||
return Ok(retval);
|
||||
}
|
||||
|
||||
return Ok(retval);
|
||||
}
|
||||
|
||||
static Result<std::string, lnav::console::user_message>
|
||||
prql_cmd_join(exec_context& ec,
|
||||
std::string cmdline,
|
||||
std::vector<std::string>& args)
|
||||
{
|
||||
std::string retval;
|
||||
|
||||
if (args.empty()) {
|
||||
args.emplace_back("prql-table");
|
||||
args.emplace_back("prql-expr");
|
||||
return Ok(retval);
|
||||
}
|
||||
|
||||
return Ok(retval);
|
||||
}
|
||||
|
||||
static Result<std::string, lnav::console::user_message>
|
||||
prql_cmd_select(exec_context& ec,
|
||||
std::string cmdline,
|
||||
std::vector<std::string>& args)
|
||||
{
|
||||
std::string retval;
|
||||
|
||||
if (args.empty()) {
|
||||
args.emplace_back("prql-expr");
|
||||
return Ok(retval);
|
||||
}
|
||||
|
||||
return Ok(retval);
|
||||
}
|
||||
|
||||
static Result<std::string, lnav::console::user_message>
|
||||
prql_cmd_sort(exec_context& ec,
|
||||
std::string cmdline,
|
||||
std::vector<std::string>& args)
|
||||
{
|
||||
std::string retval;
|
||||
|
||||
if (args.empty()) {
|
||||
args.emplace_back("prql-expr");
|
||||
return Ok(retval);
|
||||
}
|
||||
|
||||
return Ok(retval);
|
||||
}
|
||||
|
||||
static Result<std::string, lnav::console::user_message>
|
||||
prql_cmd_take(exec_context& ec,
|
||||
std::string cmdline,
|
||||
std::vector<std::string>& args)
|
||||
{
|
||||
std::string retval;
|
||||
|
||||
if (args.empty()) {
|
||||
return Ok(retval);
|
||||
}
|
||||
|
||||
return Ok(retval);
|
||||
}
|
||||
|
||||
static readline_context::command_t sql_commands[] = {
|
||||
{
|
||||
".dump",
|
||||
@ -295,6 +475,147 @@ static readline_context::command_t sql_commands[] = {
|
||||
"WITH",
|
||||
sql_cmd_generic,
|
||||
},
|
||||
{
|
||||
"from",
|
||||
prql_cmd_from,
|
||||
help_text("from")
|
||||
.prql_transform()
|
||||
.with_summary("PRQL command to specify a data source")
|
||||
.with_parameter({"table", "The table to use as a source"})
|
||||
.with_example({
|
||||
"To pull data from the 'http_status_codes' database table",
|
||||
"from db.http_status_codes | take 3",
|
||||
help_example::language::prql,
|
||||
})
|
||||
.with_example({
|
||||
"To use an array literal as a source",
|
||||
"from [{ col1=1, col2='abc' }, { col1=2, col2='def' }]",
|
||||
help_example::language::prql,
|
||||
}),
|
||||
prql_cmd_from_prompt,
|
||||
"prql-source",
|
||||
},
|
||||
{
|
||||
"aggregate",
|
||||
prql_cmd_aggregate,
|
||||
help_text("aggregate")
|
||||
.prql_transform()
|
||||
.with_summary("PRQL transform to summarize many rows into one")
|
||||
.with_parameter(
|
||||
help_text{"expr", "The aggregate expression(s)"}.with_grouping(
|
||||
"{", "}"))
|
||||
.with_example({"To group values into a JSON array", ""}),
|
||||
nullptr,
|
||||
"prql-source",
|
||||
{"prql-source"},
|
||||
},
|
||||
{
|
||||
"append",
|
||||
prql_cmd_append,
|
||||
help_text("append")
|
||||
.prql_transform()
|
||||
.with_summary("PRQL transform to concatenate tables together")
|
||||
.with_parameter({"table", "The table to use as a source"}),
|
||||
nullptr,
|
||||
"prql-source",
|
||||
{"prql-source"},
|
||||
},
|
||||
{
|
||||
"derive",
|
||||
prql_cmd_derive,
|
||||
help_text("derive")
|
||||
.prql_transform()
|
||||
.with_summary("PRQL transform to derive one or more columns")
|
||||
.with_parameter(
|
||||
help_text{"column", "The new column"}.with_grouping("{", "}")),
|
||||
nullptr,
|
||||
"prql-source",
|
||||
{"prql-source"},
|
||||
},
|
||||
{
|
||||
"filter",
|
||||
prql_cmd_filter,
|
||||
help_text("filter")
|
||||
.prql_transform()
|
||||
.with_summary("PRQL transform to pick rows based on their values")
|
||||
.with_parameter(
|
||||
{"expr", "The expression to evaluate over each row"}),
|
||||
nullptr,
|
||||
"prql-source",
|
||||
{"prql-source"},
|
||||
},
|
||||
{
|
||||
"group",
|
||||
prql_cmd_group,
|
||||
help_text("group")
|
||||
.prql_transform()
|
||||
.with_summary("PRQL transform to partition rows into groups")
|
||||
.with_parameter(
|
||||
help_text{"key_columns", "The columns that define the group"}
|
||||
.with_grouping("{", "}"))
|
||||
.with_parameter(
|
||||
help_text{"pipeline", "The pipeline to execute over a group"}
|
||||
.with_grouping("(", ")")),
|
||||
nullptr,
|
||||
"prql-source",
|
||||
{"prql-source"},
|
||||
},
|
||||
{
|
||||
"join",
|
||||
prql_cmd_join,
|
||||
help_text("join")
|
||||
.prql_transform()
|
||||
.with_summary("PRQL transform to add columns from another table")
|
||||
.with_parameter(
|
||||
help_text{"side", "Specifies which rows to include"}
|
||||
.with_enum_values({"inner", "left", "right", "full"})
|
||||
.optional())
|
||||
.with_parameter(
|
||||
{"table", "The other table to join with the current rows"})
|
||||
.with_parameter(
|
||||
help_text{"condition", "The condition used to join rows"}
|
||||
.with_grouping("(", ")")),
|
||||
nullptr,
|
||||
"prql-source",
|
||||
{"prql-source"},
|
||||
},
|
||||
{
|
||||
"select",
|
||||
prql_cmd_select,
|
||||
help_text("select")
|
||||
.prql_transform()
|
||||
.with_summary("PRQL transform to select columns")
|
||||
.with_parameter(
|
||||
help_text{"expr", "The columns to include in the result set"}
|
||||
.with_grouping("{", "}")),
|
||||
nullptr,
|
||||
"prql-source",
|
||||
{"prql-source"},
|
||||
},
|
||||
{
|
||||
"sort",
|
||||
prql_cmd_sort,
|
||||
help_text("sort")
|
||||
.prql_transform()
|
||||
.with_summary("PRQL transform to sort rows")
|
||||
.with_parameter(help_text{
|
||||
"expr", "The values to use when ordering the result set"}
|
||||
.with_grouping("{", "}")),
|
||||
nullptr,
|
||||
"prql-source",
|
||||
{"prql-source"},
|
||||
},
|
||||
{
|
||||
"take",
|
||||
prql_cmd_take,
|
||||
help_text("take")
|
||||
.prql_transform()
|
||||
.with_summary("PRQL command to pick rows based on their position")
|
||||
.with_parameter({"n_or_range", "The number of rows or range"}),
|
||||
nullptr,
|
||||
"prql-source",
|
||||
{"prql-source"},
|
||||
},
|
||||
};
|
||||
|
||||
static readline_context::command_map_t sql_cmd_map;
|
||||
|
@ -33,6 +33,7 @@
|
||||
#define sql_help_hh
|
||||
|
||||
#include <map>
|
||||
#include <set>
|
||||
|
||||
#include "base/attr_line.hh"
|
||||
#include "help_text.hh"
|
||||
@ -50,10 +51,45 @@ extern string_attr_type<void> SQL_COMMENT_ATTR;
|
||||
|
||||
void annotate_sql_statement(attr_line_t& al_inout);
|
||||
|
||||
extern std::multimap<std::string, help_text*> sqlite_function_help;
|
||||
extern std::multimap<std::string, const help_text*> sqlite_function_help;
|
||||
|
||||
std::string sql_keyword_re();
|
||||
std::vector<const help_text*> find_sql_help_for_line(const attr_line_t& al,
|
||||
size_t x);
|
||||
|
||||
namespace lnav {
|
||||
namespace sql {
|
||||
|
||||
extern string_attr_type<void> PRQL_STAGE_ATTR;
|
||||
extern string_attr_type<void> PRQL_TRANSFORM_ATTR;
|
||||
extern string_attr_type<void> PRQL_KEYWORD_ATTR;
|
||||
extern string_attr_type<void> PRQL_IDENTIFIER_ATTR;
|
||||
extern string_attr_type<void> PRQL_FQID_ATTR;
|
||||
extern string_attr_type<void> PRQL_PIPE_ATTR;
|
||||
extern string_attr_type<void> PRQL_DOT_ATTR;
|
||||
extern string_attr_type<void> PRQL_STRING_ATTR;
|
||||
extern string_attr_type<void> PRQL_NUMBER_ATTR;
|
||||
extern string_attr_type<void> PRQL_OPERATOR_ATTR;
|
||||
extern string_attr_type<void> PRQL_PAREN_ATTR;
|
||||
extern string_attr_type<void> PRQL_UNTERMINATED_PAREN_ATTR;
|
||||
extern string_attr_type<void> PRQL_GARBAGE_ATTR;
|
||||
extern string_attr_type<void> PRQL_COMMENT_ATTR;
|
||||
|
||||
bool is_prql(const string_fragment& sf);
|
||||
|
||||
void annotate_prql_statement(attr_line_t& al);
|
||||
|
||||
extern const char* prql_keywords[];
|
||||
extern std::multimap<std::string, const help_text*> prql_functions;
|
||||
|
||||
} // namespace sql
|
||||
|
||||
namespace prql {
|
||||
|
||||
std::string quote_ident(std::string id);
|
||||
|
||||
}
|
||||
|
||||
} // namespace lnav
|
||||
|
||||
#endif
|
||||
|
334
src/sql_util.cc
334
src/sql_util.cc
@ -285,7 +285,7 @@ const std::unordered_map<unsigned char, const char*> sql_constraint_names = {
|
||||
#endif
|
||||
};
|
||||
|
||||
std::multimap<std::string, help_text*> sqlite_function_help;
|
||||
std::multimap<std::string, const help_text*> sqlite_function_help;
|
||||
|
||||
static int
|
||||
handle_db_list(void* ptr, int ncols, char** colvalues, char** colnames)
|
||||
@ -1059,6 +1059,11 @@ annotate_sql_statement(attr_line_t& al)
|
||||
auto& line = al.get_string();
|
||||
auto& sa = al.get_attrs();
|
||||
|
||||
if (lnav::sql::is_prql(line)) {
|
||||
lnav::sql::annotate_prql_statement(al);
|
||||
return;
|
||||
}
|
||||
|
||||
auto cmd_find_res
|
||||
= cmd_pattern.find_in(line, PCRE2_ANCHORED).ignore_error();
|
||||
if (cmd_find_res) {
|
||||
@ -1147,11 +1152,12 @@ 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);
|
||||
const auto* sql_cmd_map
|
||||
= injector::get<readline_context::command_map_t*,
|
||||
sql_cmd_map_tag>();
|
||||
|
||||
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);
|
||||
|
||||
@ -1159,6 +1165,31 @@ find_sql_help_for_line(const attr_line_t& al, size_t x)
|
||||
return {&cmd_iter->second->c_help};
|
||||
}
|
||||
}
|
||||
|
||||
auto prql_trans_iter = find_string_attr_containing(
|
||||
al.get_attrs(), &lnav::sql::PRQL_TRANSFORM_ATTR, x);
|
||||
if (prql_trans_iter != al.get_attrs().end()) {
|
||||
auto cmd_name = al.get_substring(prql_trans_iter->sa_range);
|
||||
auto cmd_iter = sql_cmd_map->find(cmd_name);
|
||||
|
||||
if (cmd_iter != sql_cmd_map->end()) {
|
||||
return {&cmd_iter->second->c_help};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto prql_fqid_iter = find_string_attr_containing(
|
||||
al.get_attrs(), &lnav::sql ::PRQL_FQID_ATTR, x);
|
||||
if (prql_fqid_iter != al.get_attrs().end()) {
|
||||
auto fqid = al.get_substring(prql_fqid_iter->sa_range);
|
||||
auto func_pair = lnav::sql::prql_functions.equal_range(fqid);
|
||||
|
||||
for (auto func_iter = func_pair.first; func_iter != func_pair.second;
|
||||
++func_iter)
|
||||
{
|
||||
retval.emplace_back(func_iter->second);
|
||||
return retval;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> kw;
|
||||
@ -1239,5 +1270,300 @@ mprintf(const char* fmt, ...)
|
||||
return retval;
|
||||
}
|
||||
|
||||
bool
|
||||
is_prql(const string_fragment& sf)
|
||||
{
|
||||
auto trimmed = sf.trim().skip(string_fragment::tag1{';'});
|
||||
|
||||
return (trimmed.startswith("let ") || trimmed.startswith("from"));
|
||||
}
|
||||
|
||||
const char* prql_transforms[] = {
|
||||
"aggregate",
|
||||
"append",
|
||||
"derive",
|
||||
"filter",
|
||||
"from",
|
||||
"group",
|
||||
"join",
|
||||
"loop",
|
||||
"select",
|
||||
"sort",
|
||||
"take",
|
||||
"window",
|
||||
};
|
||||
|
||||
const char* prql_keywords[] = {
|
||||
"average",
|
||||
"avg",
|
||||
"case",
|
||||
"count",
|
||||
"count_distinct",
|
||||
"false",
|
||||
"func",
|
||||
"into",
|
||||
"let",
|
||||
"max",
|
||||
"min",
|
||||
"module",
|
||||
"null",
|
||||
"prql",
|
||||
"stddev",
|
||||
"sum",
|
||||
"true",
|
||||
"type",
|
||||
};
|
||||
|
||||
std::string
|
||||
prql_keyword_re()
|
||||
{
|
||||
std::string retval = "(?:";
|
||||
bool first = true;
|
||||
|
||||
for (const char* kw : prql_keywords) {
|
||||
if (!first) {
|
||||
retval.append("|");
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
retval.append("\\b");
|
||||
retval.append(kw);
|
||||
retval.append("\\b");
|
||||
}
|
||||
retval += ")";
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
std::string
|
||||
prql_transform_re()
|
||||
{
|
||||
std::string retval = "(?:";
|
||||
bool first = true;
|
||||
|
||||
for (const char* kw : prql_transforms) {
|
||||
if (!first) {
|
||||
retval.append("|");
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
retval.append("\\b");
|
||||
retval.append(kw);
|
||||
retval.append("\\b");
|
||||
}
|
||||
retval += ")";
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
string_attr_type<void> PRQL_STAGE_ATTR("prql_stage");
|
||||
string_attr_type<void> PRQL_TRANSFORM_ATTR("prql_transform");
|
||||
string_attr_type<void> PRQL_KEYWORD_ATTR("prql_keyword");
|
||||
string_attr_type<void> PRQL_IDENTIFIER_ATTR("prql_ident");
|
||||
string_attr_type<void> PRQL_FQID_ATTR("prql_fqid");
|
||||
string_attr_type<void> PRQL_DOT_ATTR("prql_dot");
|
||||
string_attr_type<void> PRQL_PIPE_ATTR("prql_pipe");
|
||||
string_attr_type<void> PRQL_STRING_ATTR("prql_string");
|
||||
string_attr_type<void> PRQL_NUMBER_ATTR("prql_number");
|
||||
string_attr_type<void> PRQL_OPERATOR_ATTR("prql_oper");
|
||||
string_attr_type<void> PRQL_PAREN_ATTR("prql_paren");
|
||||
string_attr_type<void> PRQL_UNTERMINATED_PAREN_ATTR("prql_unterminated_paren");
|
||||
string_attr_type<void> PRQL_GARBAGE_ATTR("prql_garbage");
|
||||
string_attr_type<void> PRQL_COMMENT_ATTR("prql_comment");
|
||||
|
||||
void
|
||||
annotate_prql_statement(attr_line_t& al)
|
||||
{
|
||||
static const std::string keyword_re_str = R"(\A)" + prql_keyword_re();
|
||||
static const std::string transform_re_str = R"(\A)" + prql_transform_re();
|
||||
|
||||
static const struct {
|
||||
lnav::pcre2pp::code re;
|
||||
string_attr_type<void>* type;
|
||||
} PATTERNS[] = {
|
||||
{
|
||||
lnav::pcre2pp::code::from_const(R"(\A(?:\[|\]|\{|\}|\(|\)))"),
|
||||
&PRQL_PAREN_ATTR,
|
||||
},
|
||||
{
|
||||
lnav::pcre2pp::code::from_const(R"(\A\|)"),
|
||||
&PRQL_PIPE_ATTR,
|
||||
},
|
||||
{
|
||||
lnav::pcre2pp::code::from(transform_re_str).unwrap(),
|
||||
&PRQL_TRANSFORM_ATTR,
|
||||
},
|
||||
{
|
||||
lnav::pcre2pp::code::from(keyword_re_str).unwrap(),
|
||||
&PRQL_KEYWORD_ATTR,
|
||||
},
|
||||
{
|
||||
lnav::pcre2pp::code::from_const(R"(\A(?:f|r|s)?'([^']|\\.)*')"),
|
||||
&PRQL_STRING_ATTR,
|
||||
},
|
||||
{
|
||||
lnav::pcre2pp::code::from_const(R"(\A(?:f|r|s)?\"([^\"]|\\.)*\")"),
|
||||
&PRQL_STRING_ATTR,
|
||||
},
|
||||
{
|
||||
lnav::pcre2pp::code::from_const(R"(\A0x[0-9a-fA-F]+)"),
|
||||
&PRQL_NUMBER_ATTR,
|
||||
},
|
||||
{
|
||||
lnav::pcre2pp::code::from_const(
|
||||
R"(\A-?\d+(?:\.\d+)?(?:[eE][\-\+]?\d+)?)"),
|
||||
&PRQL_NUMBER_ATTR,
|
||||
},
|
||||
{
|
||||
lnav::pcre2pp::code::from_const(
|
||||
R"(\A(?:(?:(?:\$)?\b[a-z_]\w*)|`([^`]+)`))", PCRE2_CASELESS),
|
||||
&PRQL_IDENTIFIER_ATTR,
|
||||
},
|
||||
{
|
||||
lnav::pcre2pp::code::from_const(R"(\A#.*)"),
|
||||
&PRQL_COMMENT_ATTR,
|
||||
},
|
||||
{
|
||||
lnav::pcre2pp::code::from_const(
|
||||
R"(\A(\*|\->{1,2}|<|>|=>|={1,2}|!|\-|\+|~=|\.\.|,))"),
|
||||
&PRQL_OPERATOR_ATTR,
|
||||
},
|
||||
{
|
||||
lnav::pcre2pp::code::from_const(R"(\A\.)"),
|
||||
&PRQL_DOT_ATTR,
|
||||
},
|
||||
{
|
||||
lnav::pcre2pp::code::from_const(R"(\A.)"),
|
||||
&PRQL_GARBAGE_ATTR,
|
||||
},
|
||||
};
|
||||
|
||||
static const auto ws_pattern = lnav::pcre2pp::code::from_const(R"(\A\s+)");
|
||||
|
||||
const auto& line = al.get_string();
|
||||
auto& sa = al.get_attrs();
|
||||
auto remaining = string_fragment::from_str(line);
|
||||
while (!remaining.empty()) {
|
||||
auto ws_find_res = ws_pattern.find_in(remaining).ignore_error();
|
||||
if (ws_find_res) {
|
||||
remaining = ws_find_res->f_remaining;
|
||||
continue;
|
||||
}
|
||||
for (const auto& pat : PATTERNS) {
|
||||
auto pat_find_res = pat.re.find_in(remaining).ignore_error();
|
||||
if (pat_find_res) {
|
||||
sa.emplace_back(to_line_range(pat_find_res->f_all),
|
||||
pat.type->value());
|
||||
remaining = pat_find_res->f_remaining;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto stages = std::vector<int>{};
|
||||
std::vector<std::pair<char, int>> groups;
|
||||
std::vector<line_range> fqids;
|
||||
nonstd::optional<line_range> id_start;
|
||||
bool saw_id_dot = false;
|
||||
for (const auto& attr : sa) {
|
||||
if (groups.empty() && attr.sa_type == &PRQL_PIPE_ATTR) {
|
||||
stages.push_back(attr.sa_range.lr_start);
|
||||
}
|
||||
if (!id_start) {
|
||||
if (attr.sa_type == &PRQL_IDENTIFIER_ATTR) {
|
||||
id_start = attr.sa_range;
|
||||
saw_id_dot = false;
|
||||
}
|
||||
} else if (!saw_id_dot) {
|
||||
if (attr.sa_type == &PRQL_DOT_ATTR) {
|
||||
saw_id_dot = true;
|
||||
} else {
|
||||
fqids.emplace_back(id_start.value());
|
||||
id_start = nonstd::nullopt;
|
||||
saw_id_dot = false;
|
||||
}
|
||||
} else {
|
||||
if (attr.sa_type == &PRQL_IDENTIFIER_ATTR) {
|
||||
id_start = line_range{
|
||||
id_start.value().lr_start,
|
||||
attr.sa_range.lr_end,
|
||||
};
|
||||
} else {
|
||||
id_start = nonstd::nullopt;
|
||||
}
|
||||
saw_id_dot = false;
|
||||
}
|
||||
if (attr.sa_type != &PRQL_PAREN_ATTR) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto ch = line[attr.sa_range.lr_start];
|
||||
switch (ch) {
|
||||
case '(':
|
||||
case '{':
|
||||
case '[':
|
||||
groups.emplace_back(ch, attr.sa_range.lr_start);
|
||||
break;
|
||||
case ')':
|
||||
if (!groups.empty() && groups.back().first == '(') {
|
||||
groups.pop_back();
|
||||
}
|
||||
break;
|
||||
case '}':
|
||||
if (!groups.empty() && groups.back().first == '{') {
|
||||
groups.pop_back();
|
||||
}
|
||||
break;
|
||||
case ']':
|
||||
if (!groups.empty() && groups.back().first == '[') {
|
||||
groups.pop_back();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (id_start) {
|
||||
fqids.emplace_back(id_start.value());
|
||||
}
|
||||
int prev_stage_index = 0;
|
||||
for (auto stage_index : stages) {
|
||||
sa.emplace_back(line_range{prev_stage_index, stage_index},
|
||||
PRQL_STAGE_ATTR.value());
|
||||
prev_stage_index = stage_index;
|
||||
}
|
||||
sa.emplace_back(
|
||||
line_range{prev_stage_index, (int) al.get_string().length()},
|
||||
PRQL_STAGE_ATTR.value());
|
||||
for (const auto& group : groups) {
|
||||
sa.emplace_back(line_range{group.second, group.second + 1},
|
||||
PRQL_UNTERMINATED_PAREN_ATTR.value());
|
||||
}
|
||||
for (const auto& fqid_range : fqids) {
|
||||
sa.emplace_back(fqid_range, PRQL_FQID_ATTR.value());
|
||||
}
|
||||
|
||||
stable_sort(sa.begin(), sa.end());
|
||||
}
|
||||
|
||||
} // namespace sql
|
||||
|
||||
namespace prql {
|
||||
|
||||
std::string
|
||||
quote_ident(std::string id)
|
||||
{
|
||||
static const auto PLAIN_NAME
|
||||
= pcre2pp::code::from_const("^[a-zA-Z_][a-zA-Z_0-9]*$");
|
||||
|
||||
if (PLAIN_NAME.find_in(id).ignore_error()) {
|
||||
return id;
|
||||
}
|
||||
|
||||
auto buf = auto_buffer::alloc(id.length() + 8);
|
||||
quote_content(buf, id, '`');
|
||||
|
||||
return fmt::format(FMT_STRING("`{}`"), buf.in());
|
||||
}
|
||||
|
||||
} // namespace prql
|
||||
|
||||
} // namespace lnav
|
||||
|
@ -32,6 +32,7 @@
|
||||
#include "sqlite-extension-func.hh"
|
||||
|
||||
#include "base/auto_mem.hh"
|
||||
#include "base/itertools.hh"
|
||||
#include "base/lnav_log.hh"
|
||||
#include "base/string_util.hh"
|
||||
#include "config.h"
|
||||
@ -46,6 +47,15 @@ int sqlite3_series_init(sqlite3* db,
|
||||
const sqlite3_api_routines* pApi);
|
||||
}
|
||||
|
||||
std::string sqlite_extension_prql;
|
||||
|
||||
namespace lnav {
|
||||
namespace sql {
|
||||
std::multimap<std::string, const help_text*> prql_functions;
|
||||
|
||||
}
|
||||
} // namespace lnav
|
||||
|
||||
sqlite_registration_func_t sqlite_registration_funcs[] = {
|
||||
common_extension_functions,
|
||||
state_extension_functions,
|
||||
@ -59,10 +69,73 @@ sqlite_registration_func_t sqlite_registration_funcs[] = {
|
||||
nullptr,
|
||||
};
|
||||
|
||||
struct prql_hier {
|
||||
std::map<std::string, prql_hier> ph_modules;
|
||||
std::map<std::string, std::string> ph_declarations;
|
||||
|
||||
void to_string(std::string& accum) const
|
||||
{
|
||||
for (const auto& mod_pair : this->ph_modules) {
|
||||
accum.append("module ");
|
||||
accum.append(mod_pair.first);
|
||||
accum.append(" {\n");
|
||||
mod_pair.second.to_string(accum);
|
||||
accum.append("}\n");
|
||||
}
|
||||
for (const auto& decl_pair : this->ph_declarations) {
|
||||
accum.append(decl_pair.second);
|
||||
accum.append("\n");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static void
|
||||
register_help(prql_hier& phier, const help_text& ht)
|
||||
{
|
||||
auto prql_fqid
|
||||
= fmt::format(FMT_STRING("{}"), fmt::join(ht.ht_prql_path, "."));
|
||||
lnav::sql::prql_functions.emplace(prql_fqid, &ht);
|
||||
|
||||
auto* curr_hier = &phier;
|
||||
for (size_t name_index = 0; name_index < ht.ht_prql_path.size();
|
||||
name_index++)
|
||||
{
|
||||
const auto& prql_name = ht.ht_prql_path[name_index];
|
||||
if (name_index == ht.ht_prql_path.size() - 1) {
|
||||
auto param_names
|
||||
= ht.ht_parameters | lnav::itertools::map([](const auto& elem) {
|
||||
if (elem.ht_nargs == help_nargs_t::HN_OPTIONAL) {
|
||||
return fmt::format(FMT_STRING("{}:null"),
|
||||
elem.ht_name);
|
||||
}
|
||||
return fmt::format(FMT_STRING("p_{}"), elem.ht_name);
|
||||
});
|
||||
auto func_args
|
||||
= ht.ht_parameters | lnav::itertools::map([](const auto& elem) {
|
||||
if (elem.ht_nargs == help_nargs_t::HN_OPTIONAL) {
|
||||
return fmt::format(FMT_STRING("{{{}:0}}"),
|
||||
elem.ht_name);
|
||||
}
|
||||
return fmt::format(FMT_STRING("{{p_{}:0}}"),
|
||||
elem.ht_name);
|
||||
});
|
||||
curr_hier->ph_declarations[prql_name]
|
||||
= fmt::format(FMT_STRING("let {} = func {} -> s\"{}({})\""),
|
||||
prql_name,
|
||||
fmt::join(param_names, " "),
|
||||
ht.ht_name,
|
||||
fmt::join(func_args, ", "));
|
||||
} else {
|
||||
curr_hier = &curr_hier->ph_modules[prql_name];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
register_sqlite_funcs(sqlite3* db, sqlite_registration_func_t* reg_funcs)
|
||||
{
|
||||
static bool help_registration_done = false;
|
||||
prql_hier phier;
|
||||
int lpc;
|
||||
|
||||
require(db != nullptr);
|
||||
@ -98,10 +171,13 @@ register_sqlite_funcs(sqlite3* db, sqlite_registration_func_t* reg_funcs)
|
||||
if (!help_registration_done
|
||||
&& fd.fd_help.ht_context != help_context_t::HC_NONE)
|
||||
{
|
||||
help_text& ht = fd.fd_help;
|
||||
auto& ht = fd.fd_help;
|
||||
|
||||
sqlite_function_help.insert(std::make_pair(ht.ht_name, &ht));
|
||||
ht.index_tags();
|
||||
if (!ht.ht_prql_path.empty()) {
|
||||
register_help(phier, ht);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -121,14 +197,21 @@ register_sqlite_funcs(sqlite3* db, sqlite_registration_func_t* reg_funcs)
|
||||
if (!help_registration_done
|
||||
&& fda.fda_help.ht_context != help_context_t::HC_NONE)
|
||||
{
|
||||
help_text& ht = fda.fda_help;
|
||||
auto& ht = fda.fda_help;
|
||||
|
||||
sqlite_function_help.insert(std::make_pair(ht.ht_name, &ht));
|
||||
ht.index_tags();
|
||||
if (!ht.ht_prql_path.empty()) {
|
||||
register_help(phier, ht);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sqlite_extension_prql.empty()) {
|
||||
phier.to_string(sqlite_extension_prql);
|
||||
}
|
||||
|
||||
static help_text builtin_funcs[] = {
|
||||
help_text("abs", "Return the absolute value of the argument")
|
||||
.sql_function()
|
||||
|
@ -95,6 +95,8 @@ extern sqlite_registration_func_t sqlite_registration_funcs[];
|
||||
|
||||
int register_sqlite_funcs(sqlite3* db, sqlite_registration_func_t* reg_funcs);
|
||||
|
||||
extern std::string sqlite_extension_prql;
|
||||
|
||||
extern "C"
|
||||
{
|
||||
int sqlite3_db_dump(
|
||||
|
@ -161,31 +161,36 @@ state_extension_functions(struct FuncDef** basic_funcs,
|
||||
help_text(
|
||||
"log_top_line",
|
||||
"Return the number of the focused line of the log view.")
|
||||
.sql_function()),
|
||||
.sql_function()
|
||||
.with_prql_path({"lnav", "view", "top_line"})),
|
||||
|
||||
sqlite_func_adapter<decltype(&sql_log_msg_line), sql_log_msg_line>::
|
||||
builder(help_text("log_msg_line",
|
||||
"Return the starting line number of the focused "
|
||||
"log message.")
|
||||
.sql_function()),
|
||||
.sql_function()
|
||||
.with_prql_path({"lnav", "view", "msg_line"})),
|
||||
|
||||
sqlite_func_adapter<decltype(&sql_log_top_datetime),
|
||||
sql_log_top_datetime>::
|
||||
builder(help_text("log_top_datetime",
|
||||
"Return the timestamp of the line at the top of "
|
||||
"the log view.")
|
||||
.sql_function()),
|
||||
.sql_function()
|
||||
.with_prql_path({"lnav", "view", "top_datetime"})),
|
||||
|
||||
sqlite_func_adapter<decltype(&sql_lnav_top_file), sql_lnav_top_file>::
|
||||
builder(help_text("lnav_top_file",
|
||||
"Return the name of the file that the top line "
|
||||
"in the current view came from.")
|
||||
.sql_function()),
|
||||
.sql_function()
|
||||
.with_prql_path({"lnav", "view", "top_file"})),
|
||||
|
||||
sqlite_func_adapter<decltype(&sql_lnav_version), sql_lnav_version>::
|
||||
builder(
|
||||
help_text("lnav_version", "Return the current version of lnav")
|
||||
.sql_function()),
|
||||
.sql_function()
|
||||
.with_prql_path({"lnav", "version"})),
|
||||
|
||||
sqlite_func_adapter<decltype(&sql_error), sql_error>::builder(
|
||||
help_text("raise_error",
|
||||
|
@ -898,6 +898,7 @@ string_extension_functions(struct FuncDef** basic_funcs,
|
||||
"Match a string against a regular expression and return "
|
||||
"the capture groups as JSON.")
|
||||
.sql_function()
|
||||
.with_prql_path({"text", "regexp_match"})
|
||||
.with_parameter({"re", "The regular expression to use"})
|
||||
.with_parameter({
|
||||
"str",
|
||||
@ -925,6 +926,7 @@ string_extension_functions(struct FuncDef** basic_funcs,
|
||||
"Replace the parts of a string that match a regular "
|
||||
"expression.")
|
||||
.sql_function()
|
||||
.with_prql_path({"text", "regexp_replace"})
|
||||
.with_parameter(
|
||||
{"str", "The string to perform replacements on"})
|
||||
.with_parameter({"re", "The regular expression to match"})
|
||||
@ -953,6 +955,7 @@ string_extension_functions(struct FuncDef** basic_funcs,
|
||||
"humanize_file_size",
|
||||
"Format the given file size as a human-friendly string")
|
||||
.sql_function()
|
||||
.with_prql_path({"humanize", "file_size"})
|
||||
.with_parameter({"value", "The file size to format"})
|
||||
.with_tags({"string"})
|
||||
.with_example({
|
||||
@ -970,6 +973,7 @@ string_extension_functions(struct FuncDef** basic_funcs,
|
||||
"aggregate version returns a string with a bar "
|
||||
"character for every numeric input")
|
||||
.sql_function()
|
||||
.with_prql_path({"text", "sparkline"})
|
||||
.with_parameter({"value", "The numeric value to convert"})
|
||||
.with_parameter(help_text("upper",
|
||||
"The upper bound of the numeric "
|
||||
@ -995,6 +999,7 @@ string_extension_functions(struct FuncDef** basic_funcs,
|
||||
help_text("anonymize",
|
||||
"Replace identifying information with random values.")
|
||||
.sql_function()
|
||||
.with_prql_path({"text", "anonymize"})
|
||||
.with_parameter({"value", "The text to anonymize"})
|
||||
.with_tags({"string"})
|
||||
.with_example({
|
||||
@ -1006,6 +1011,7 @@ string_extension_functions(struct FuncDef** basic_funcs,
|
||||
help_text("extract",
|
||||
"Automatically Parse and extract data from a string")
|
||||
.sql_function()
|
||||
.with_prql_path({"text", "extract"})
|
||||
.with_parameter({"str", "The string to parse"})
|
||||
.with_tags({"string"})
|
||||
.with_example({
|
||||
@ -1021,6 +1027,7 @@ string_extension_functions(struct FuncDef** basic_funcs,
|
||||
help_text("logfmt2json",
|
||||
"Convert a logfmt-encoded string into JSON")
|
||||
.sql_function()
|
||||
.with_prql_path({"logfmt", "to_json"})
|
||||
.with_parameter({"str", "The logfmt message to parse"})
|
||||
.with_tags({"string"})
|
||||
.with_example({
|
||||
|
@ -225,10 +225,11 @@ tailer::looper::load_preview(int64_t id, const network::path& path)
|
||||
if (lnav_data.ld_preview_generation != id) {
|
||||
return;
|
||||
}
|
||||
lnav_data.ld_preview_status_source.get_description()
|
||||
lnav_data.ld_preview_status_source[0]
|
||||
.get_description()
|
||||
.set_cylon(false)
|
||||
.clear();
|
||||
lnav_data.ld_preview_source.clear();
|
||||
lnav_data.ld_preview_source[0].clear();
|
||||
lnav_data.ld_bottom_source.grep_error(msg);
|
||||
});
|
||||
return;
|
||||
@ -539,7 +540,8 @@ tailer::looper::host_tailer::load_preview(int64_t id, const std::string& path)
|
||||
if (lnav_data.ld_preview_generation != id) {
|
||||
return;
|
||||
}
|
||||
lnav_data.ld_preview_status_source.get_description()
|
||||
lnav_data.ld_preview_status_source[0]
|
||||
.get_description()
|
||||
.set_cylon(false)
|
||||
.set_value(msg);
|
||||
});
|
||||
@ -995,10 +997,11 @@ tailer::looper::host_tailer::loop_body()
|
||||
ppe.ppe_id);
|
||||
return;
|
||||
}
|
||||
lnav_data.ld_preview_status_source.get_description()
|
||||
lnav_data.ld_preview_status_source[0]
|
||||
.get_description()
|
||||
.set_cylon(false)
|
||||
.clear();
|
||||
lnav_data.ld_preview_source.clear();
|
||||
lnav_data.ld_preview_source[0].clear();
|
||||
lnav_data.ld_bottom_source.grep_error(ppe.ppe_msg);
|
||||
});
|
||||
|
||||
@ -1015,12 +1018,14 @@ tailer::looper::host_tailer::loop_body()
|
||||
}
|
||||
std::string str(ppd.ppd_bits.begin(),
|
||||
ppd.ppd_bits.end());
|
||||
lnav_data.ld_preview_status_source.get_description()
|
||||
lnav_data.ld_preview_status_source[0]
|
||||
.get_description()
|
||||
.set_cylon(false)
|
||||
.set_value("For file: %s:%s",
|
||||
netloc.c_str(),
|
||||
ppd.ppd_path.c_str());
|
||||
lnav_data.ld_preview_source.replace_with(str)
|
||||
lnav_data.ld_preview_source[0]
|
||||
.replace_with(str)
|
||||
.set_text_format(detect_text_format(str));
|
||||
});
|
||||
return std::move(this->ht_state);
|
||||
|
@ -33,6 +33,7 @@
|
||||
|
||||
#include "text_format.hh"
|
||||
|
||||
#include "base/lnav_log.hh"
|
||||
#include "config.h"
|
||||
#include "pcrepp/pcre2pp.hh"
|
||||
#include "yajl/api/yajl_parse.h"
|
||||
|
1527
src/third-party/prqlc-c/Cargo.lock
generated
vendored
Normal file
1527
src/third-party/prqlc-c/Cargo.lock
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
29
src/third-party/prqlc-c/Cargo.toml
vendored
Normal file
29
src/third-party/prqlc-c/Cargo.toml
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
[package]
|
||||
name = "prqlc-c"
|
||||
publish = false
|
||||
version = "0.11.3"
|
||||
|
||||
edition = "2021"
|
||||
rust-version = "1.70.0"
|
||||
|
||||
# This means we can build with `--features=default`, which can make builds more generic
|
||||
[features]
|
||||
default = []
|
||||
|
||||
[lib]
|
||||
# We produce both of these at the moment, but could consider refining this. ref
|
||||
# https://github.com/rust-lang/cargo/issues/8607 &
|
||||
# https://github.com/rust-lang/rust/issues/59302
|
||||
crate_type = ["staticlib", "cdylib"]
|
||||
doctest = false
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[dependencies]
|
||||
libc = "0.2.153"
|
||||
prqlc = { git = "https://github.com/PRQL/prql.git" }
|
||||
serde_json = "1.0.114"
|
||||
|
||||
[package.metadata.release]
|
||||
tag-name = "{{version}}"
|
||||
tag-prefix = ""
|
102
src/third-party/prqlc-c/README.md
vendored
Normal file
102
src/third-party/prqlc-c/README.md
vendored
Normal file
@ -0,0 +1,102 @@
|
||||
# PRQL C library
|
||||
|
||||
## Description
|
||||
|
||||
This module compiles PRQL as a library (both `.a` and `.so` are generated). This
|
||||
allows embedding in languages that support FFI — for example, Golang.
|
||||
|
||||
## Linking
|
||||
|
||||
See [examples/minimal-c/Makefile](examples/minimal-c/Makefile).
|
||||
|
||||
Copy the `.a` and `.so` files in a convenient place and add the following
|
||||
compile flags to Go (cgo):
|
||||
|
||||
`CGO_LDFLAGS="-L/path/to/libprqlc_c.a -lprqlc -pthread -ldl" go build`
|
||||
|
||||
## Examples
|
||||
|
||||
For a minimal example, see
|
||||
[examples/minimal-c/main.c](examples/minimal-c/main.c).
|
||||
|
||||
Below is an example from an actual application that is using PRQL in Go.
|
||||
|
||||
```go
|
||||
package prql
|
||||
|
||||
/*
|
||||
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
int to_sql(char *prql_query, char *sql_query);
|
||||
int to_json(char *prql_query, char *json_query);
|
||||
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// ToSQL converts a PRQL query to SQL
|
||||
func ToSQL(prql string) (string, error) {
|
||||
// buffer length should not be less than 1K because we may get an error
|
||||
// from the PRQL compiler with a very short query
|
||||
cStringBufferLength := 1024
|
||||
|
||||
// allocate a buffer 3 times the length of the PRQL query to store the
|
||||
// generated SQL query
|
||||
if len(prql)*3 > cStringBufferLength {
|
||||
cStringBufferLength = len(prql) * 3
|
||||
}
|
||||
|
||||
// preallocate the buffer
|
||||
cstr := C.CString(strings.Repeat(" ", cStringBufferLength))
|
||||
defer C.free(unsafe.Pointer(cstr))
|
||||
|
||||
// convert the PRQL query to SQL
|
||||
res := C.to_sql(C.CString(prql), cstr)
|
||||
if res == 0 {
|
||||
return C.GoString(cstr), nil
|
||||
}
|
||||
|
||||
return "", errors.New(C.GoString(cstr))
|
||||
}
|
||||
|
||||
// ToJSON converts a PRQL query to JSON
|
||||
func ToJSON(prql string) (string, error) {
|
||||
// buffer length should not be less than 1K because we may get an error
|
||||
cStringBufferLength := 1024
|
||||
if len(prql)*3 > cStringBufferLength {
|
||||
cStringBufferLength = len(prql) * 10
|
||||
}
|
||||
|
||||
// preallocate the buffer
|
||||
cstr := C.CString(strings.Repeat(" ", cStringBufferLength))
|
||||
defer C.free(unsafe.Pointer(cstr))
|
||||
|
||||
// convert the PRQL query to SQL
|
||||
res := C.to_json(C.CString(prql), cstr)
|
||||
if res == 0 {
|
||||
return C.GoString(cstr), nil
|
||||
}
|
||||
|
||||
return "", errors.New(C.GoString(cstr))
|
||||
}
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Headers
|
||||
|
||||
The C & C++ header files `prqlc.h` & `prqlc.hpp` were generated using
|
||||
[cbindgen](https://github.com/eqrion/cbindgen). To generate a new one run:
|
||||
|
||||
```sh
|
||||
task build-prqlc-c-header
|
||||
```
|
||||
|
||||
...or copy & paste the commands from the Taskfile.
|
14
src/third-party/prqlc-c/cbindgen.toml
vendored
Normal file
14
src/third-party/prqlc-c/cbindgen.toml
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
language = "C"
|
||||
|
||||
header = '''/*
|
||||
* PRQL is a modern language for transforming data — a simple, powerful, pipelined SQL replacement
|
||||
*
|
||||
* License: Apache-2.0
|
||||
* Website: https://prql-lang.org/
|
||||
*/'''
|
||||
|
||||
autogen_warning = "/* This file is autogenerated. Do not modify this file manually. */"
|
||||
|
||||
namespace = "prqlc"
|
||||
|
||||
after_includes = '#define FFI_SCOPE "PRQL"'
|
193
src/third-party/prqlc-c/prqlc.h
vendored
Normal file
193
src/third-party/prqlc-c/prqlc.h
vendored
Normal file
@ -0,0 +1,193 @@
|
||||
/*
|
||||
* PRQL is a modern language for transforming data — a simple, powerful,
|
||||
* pipelined SQL replacement
|
||||
*
|
||||
* License: Apache-2.0
|
||||
* Website: https://prql-lang.org/
|
||||
*/
|
||||
|
||||
/* This file is autogenerated. Do not modify this file manually. */
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#define FFI_SCOPE "PRQL"
|
||||
|
||||
/**
|
||||
* Compile message kind. Currently only Error is implemented.
|
||||
*/
|
||||
typedef enum MessageKind {
|
||||
Error,
|
||||
Warning,
|
||||
Lint,
|
||||
} MessageKind;
|
||||
|
||||
/**
|
||||
* Identifier of a location in source.
|
||||
* Contains offsets in terms of chars.
|
||||
*/
|
||||
typedef struct Span {
|
||||
size_t start;
|
||||
size_t end;
|
||||
} Span;
|
||||
|
||||
/**
|
||||
* Location within a source file.
|
||||
*/
|
||||
typedef struct SourceLocation {
|
||||
size_t start_line;
|
||||
size_t start_col;
|
||||
size_t end_line;
|
||||
size_t end_col;
|
||||
} SourceLocation;
|
||||
|
||||
/**
|
||||
* Compile result message.
|
||||
*
|
||||
* Calling code is responsible for freeing all memory allocated
|
||||
* for fields as well as strings.
|
||||
*/
|
||||
typedef struct Message {
|
||||
/**
|
||||
* Message kind. Currently only Error is implemented.
|
||||
*/
|
||||
enum MessageKind kind;
|
||||
/**
|
||||
* Machine-readable identifier of the error
|
||||
*/
|
||||
const char *const *code;
|
||||
/**
|
||||
* Plain text of the error
|
||||
*/
|
||||
const char *reason;
|
||||
/**
|
||||
* A list of suggestions of how to fix the error
|
||||
*/
|
||||
const char *const *hint;
|
||||
/**
|
||||
* Character offset of error origin within a source file
|
||||
*/
|
||||
const struct Span *span;
|
||||
/**
|
||||
* Annotated code, containing cause and hints.
|
||||
*/
|
||||
const char *const *display;
|
||||
/**
|
||||
* Line and column number of error origin within a source file
|
||||
*/
|
||||
const struct SourceLocation *location;
|
||||
} Message;
|
||||
|
||||
/**
|
||||
* Result of compilation.
|
||||
*/
|
||||
typedef struct CompileResult {
|
||||
const char *output;
|
||||
const struct Message *messages;
|
||||
size_t messages_len;
|
||||
} CompileResult;
|
||||
|
||||
/**
|
||||
* Compilation options
|
||||
*/
|
||||
typedef struct Options {
|
||||
/**
|
||||
* Pass generated SQL string trough a formatter that splits it
|
||||
* into multiple lines and prettifies indentation and spacing.
|
||||
*
|
||||
* Defaults to true.
|
||||
*/
|
||||
bool format;
|
||||
/**
|
||||
* Target and dialect to compile to.
|
||||
*
|
||||
* Defaults to `sql.any`, which uses `target` argument from the query header
|
||||
* to determine the SQL dialect.
|
||||
*/
|
||||
char *target;
|
||||
/**
|
||||
* Emits the compiler signature as a comment after generated SQL
|
||||
*
|
||||
* Defaults to true.
|
||||
*/
|
||||
bool signature_comment;
|
||||
} Options;
|
||||
|
||||
/**
|
||||
* Compile a PRQL string into a SQL string.
|
||||
*
|
||||
* This is a wrapper for: `prql_to_pl`, `pl_to_rq` and `rq_to_sql` without
|
||||
* converting to JSON between each of the functions.
|
||||
*
|
||||
* See `Options` struct for available compilation options.
|
||||
*
|
||||
* # Safety
|
||||
*
|
||||
* This function assumes zero-terminated input strings.
|
||||
* Calling code is responsible for freeing memory allocated for `CompileResult`
|
||||
* by calling `result_destroy`.
|
||||
*/
|
||||
struct CompileResult compile(const char *prql_query,
|
||||
const struct Options *options);
|
||||
|
||||
/**
|
||||
* Build PL AST from a PRQL string. PL in documented in the
|
||||
* [prqlc Rust crate](https://docs.rs/prqlc/latest/prqlc/ir/pl).
|
||||
*
|
||||
* Takes PRQL source buffer and writes PL serialized as JSON to `out` buffer.
|
||||
*
|
||||
* Returns 0 on success and a negative number -1 on failure.
|
||||
*
|
||||
* # Safety
|
||||
*
|
||||
* This function assumes zero-terminated input strings.
|
||||
* Calling code is responsible for freeing memory allocated for `CompileResult`
|
||||
* by calling `result_destroy`.
|
||||
*/
|
||||
struct CompileResult prql_to_pl(const char *prql_query);
|
||||
|
||||
/**
|
||||
* Finds variable references, validates functions calls, determines frames and
|
||||
* converts PL to RQ. PL and RQ are documented in the [prqlc Rust
|
||||
* crate](https://docs.rs/prqlc/latest/prqlc/ast).
|
||||
*
|
||||
* Takes PL serialized as JSON buffer and writes RQ serialized as JSON to `out`
|
||||
* buffer.
|
||||
*
|
||||
* Returns 0 on success and a negative number -1 on failure.
|
||||
*
|
||||
* # Safety
|
||||
*
|
||||
* This function assumes zero-terminated input strings.
|
||||
* Calling code is responsible for freeing memory allocated for `CompileResult`
|
||||
* by calling `result_destroy`.
|
||||
*/
|
||||
struct CompileResult pl_to_rq(const char *pl_json);
|
||||
|
||||
/**
|
||||
* Convert RQ AST into an SQL string. RQ is documented in the
|
||||
* [prqlc Rust crate](https://docs.rs/prqlc/latest/prqlc/ir/rq).
|
||||
*
|
||||
* Takes RQ serialized as JSON buffer and writes SQL source to `out` buffer.
|
||||
*
|
||||
* Returns 0 on success and a negative number -1 on failure.
|
||||
*
|
||||
* # Safety
|
||||
*
|
||||
* This function assumes zero-terminated input strings.
|
||||
* Calling code is responsible for freeing memory allocated for `CompileResult`
|
||||
* by calling `result_destroy`.
|
||||
*/
|
||||
struct CompileResult rq_to_sql(const char *rq_json,
|
||||
const struct Options *options);
|
||||
|
||||
/**
|
||||
* Destroy a `CompileResult` once you are done with it.
|
||||
*
|
||||
* # Safety
|
||||
*
|
||||
* This function expects to be called exactly once after the call of any the
|
||||
* functions that return `CompileResult`. No fields should be freed manually.
|
||||
*/
|
||||
void result_destroy(struct CompileResult res);
|
158
src/third-party/prqlc-c/prqlc.hpp
vendored
Normal file
158
src/third-party/prqlc-c/prqlc.hpp
vendored
Normal file
@ -0,0 +1,158 @@
|
||||
/*
|
||||
* PRQL is a modern language for transforming data — a simple, powerful,
|
||||
* pipelined SQL replacement
|
||||
*
|
||||
* License: Apache-2.0
|
||||
* Website: https://prql-lang.org/
|
||||
*/
|
||||
|
||||
/* This file is autogenerated. Do not modify this file manually. */
|
||||
|
||||
#include <cstdarg>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <new>
|
||||
#include <ostream>
|
||||
#define FFI_SCOPE "PRQL"
|
||||
|
||||
namespace prqlc {
|
||||
|
||||
/// Compile message kind. Currently only Error is implemented.
|
||||
enum class MessageKind {
|
||||
Error,
|
||||
Warning,
|
||||
Lint,
|
||||
};
|
||||
|
||||
/// Identifier of a location in source.
|
||||
/// Contains offsets in terms of chars.
|
||||
struct Span {
|
||||
size_t start;
|
||||
size_t end;
|
||||
};
|
||||
|
||||
/// Location within a source file.
|
||||
struct SourceLocation {
|
||||
size_t start_line;
|
||||
size_t start_col;
|
||||
size_t end_line;
|
||||
size_t end_col;
|
||||
};
|
||||
|
||||
/// Compile result message.
|
||||
///
|
||||
/// Calling code is responsible for freeing all memory allocated
|
||||
/// for fields as well as strings.
|
||||
struct Message {
|
||||
/// Message kind. Currently only Error is implemented.
|
||||
MessageKind kind;
|
||||
/// Machine-readable identifier of the error
|
||||
const char* const* code;
|
||||
/// Plain text of the error
|
||||
const char* reason;
|
||||
/// A list of suggestions of how to fix the error
|
||||
const char* const* hint;
|
||||
/// Character offset of error origin within a source file
|
||||
const Span* span;
|
||||
/// Annotated code, containing cause and hints.
|
||||
const char* const* display;
|
||||
/// Line and column number of error origin within a source file
|
||||
const SourceLocation* location;
|
||||
};
|
||||
|
||||
/// Result of compilation.
|
||||
struct CompileResult {
|
||||
const char* output;
|
||||
const Message* messages;
|
||||
size_t messages_len;
|
||||
};
|
||||
|
||||
/// Compilation options
|
||||
struct Options {
|
||||
/// Pass generated SQL string trough a formatter that splits it
|
||||
/// into multiple lines and prettifies indentation and spacing.
|
||||
///
|
||||
/// Defaults to true.
|
||||
bool format;
|
||||
/// Target and dialect to compile to.
|
||||
///
|
||||
/// Defaults to `sql.any`, which uses `target` argument from the query
|
||||
/// header to determine the SQL dialect.
|
||||
const char* target;
|
||||
/// Emits the compiler signature as a comment after generated SQL
|
||||
///
|
||||
/// Defaults to true.
|
||||
bool signature_comment;
|
||||
};
|
||||
|
||||
extern "C"
|
||||
{
|
||||
/// Compile a PRQL string into a SQL string.
|
||||
///
|
||||
/// This is a wrapper for: `prql_to_pl`, `pl_to_rq` and `rq_to_sql` without
|
||||
/// converting to JSON between each of the functions.
|
||||
///
|
||||
/// See `Options` struct for available compilation options.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function assumes zero-terminated input strings.
|
||||
/// Calling code is responsible for freeing memory allocated for `CompileResult`
|
||||
/// by calling `result_destroy`.
|
||||
CompileResult compile(const char* prql_query, const Options* options);
|
||||
|
||||
/// Build PL AST from a PRQL string. PL in documented in the
|
||||
/// [prqlc Rust crate](https://docs.rs/prqlc/latest/prqlc/ir/pl).
|
||||
///
|
||||
/// Takes PRQL source buffer and writes PL serialized as JSON to `out` buffer.
|
||||
///
|
||||
/// Returns 0 on success and a negative number -1 on failure.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function assumes zero-terminated input strings.
|
||||
/// Calling code is responsible for freeing memory allocated for `CompileResult`
|
||||
/// by calling `result_destroy`.
|
||||
CompileResult prql_to_pl(const char* prql_query);
|
||||
|
||||
/// Finds variable references, validates functions calls, determines frames and
|
||||
/// converts PL to RQ. PL and RQ are documented in the [prqlc Rust
|
||||
/// crate](https://docs.rs/prqlc/latest/prqlc/ast).
|
||||
///
|
||||
/// Takes PL serialized as JSON buffer and writes RQ serialized as JSON to `out`
|
||||
/// buffer.
|
||||
///
|
||||
/// Returns 0 on success and a negative number -1 on failure.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function assumes zero-terminated input strings.
|
||||
/// Calling code is responsible for freeing memory allocated for `CompileResult`
|
||||
/// by calling `result_destroy`.
|
||||
CompileResult pl_to_rq(const char* pl_json);
|
||||
|
||||
/// Convert RQ AST into an SQL string. RQ is documented in the
|
||||
/// [prqlc Rust crate](https://docs.rs/prqlc/latest/prqlc/ir/rq).
|
||||
///
|
||||
/// Takes RQ serialized as JSON buffer and writes SQL source to `out` buffer.
|
||||
///
|
||||
/// Returns 0 on success and a negative number -1 on failure.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function assumes zero-terminated input strings.
|
||||
/// Calling code is responsible for freeing memory allocated for `CompileResult`
|
||||
/// by calling `result_destroy`.
|
||||
CompileResult rq_to_sql(const char* rq_json, const Options* options);
|
||||
|
||||
/// Destroy a `CompileResult` once you are done with it.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function expects to be called exactly once after the call of any the
|
||||
/// functions that return `CompileResult`. No fields should be freed manually.
|
||||
void result_destroy(CompileResult res);
|
||||
|
||||
} // extern "C"
|
||||
|
||||
} // namespace prqlc
|
346
src/third-party/prqlc-c/src/lib.rs
vendored
Normal file
346
src/third-party/prqlc-c/src/lib.rs
vendored
Normal file
@ -0,0 +1,346 @@
|
||||
#![cfg(not(target_family = "wasm"))]
|
||||
|
||||
extern crate libc;
|
||||
|
||||
use libc::{c_char, size_t};
|
||||
use prqlc::{ErrorMessage, ErrorMessages};
|
||||
use prqlc::Target;
|
||||
use std::ffi::CStr;
|
||||
use std::ffi::CString;
|
||||
use std::panic;
|
||||
use std::str::FromStr;
|
||||
|
||||
/// Compile a PRQL string into a SQL string.
|
||||
///
|
||||
/// This is a wrapper for: `prql_to_pl`, `pl_to_rq` and `rq_to_sql` without converting to JSON
|
||||
/// between each of the functions.
|
||||
///
|
||||
/// See `Options` struct for available compilation options.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function assumes zero-terminated input strings.
|
||||
/// Calling code is responsible for freeing memory allocated for `CompileResult`
|
||||
/// by calling `result_destroy`.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn compile(
|
||||
prql_query: *const c_char,
|
||||
options: *const Options,
|
||||
) -> CompileResult {
|
||||
let prql_query: String = c_str_to_string(prql_query);
|
||||
|
||||
let options = options.as_ref().map(convert_options).transpose();
|
||||
|
||||
let result = options
|
||||
.and_then(|opts| {
|
||||
panic::catch_unwind(|| {
|
||||
Ok(prql_query.as_str())
|
||||
.and_then(prqlc::prql_to_pl)
|
||||
.and_then(prqlc::pl_to_rq)
|
||||
.and_then(|rq| prqlc::rq_to_sql(rq, &opts.unwrap_or_default()))
|
||||
}).map_err(|p| ErrorMessages {
|
||||
inner: vec![ErrorMessage {
|
||||
kind: prqlc::MessageKind::Error,
|
||||
code: None,
|
||||
reason: format!("internal error: {:#?}", p),
|
||||
hints: vec![],
|
||||
span: None,
|
||||
display: None,
|
||||
location: None,
|
||||
}]
|
||||
})?
|
||||
})
|
||||
.map_err(|e| e.composed(&prql_query.into()));
|
||||
|
||||
result_into_c_str(result)
|
||||
}
|
||||
|
||||
/// Build PL AST from a PRQL string. PL in documented in the
|
||||
/// [prqlc Rust crate](https://docs.rs/prqlc/latest/prqlc/ir/pl).
|
||||
///
|
||||
/// Takes PRQL source buffer and writes PL serialized as JSON to `out` buffer.
|
||||
///
|
||||
/// Returns 0 on success and a negative number -1 on failure.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function assumes zero-terminated input strings.
|
||||
/// Calling code is responsible for freeing memory allocated for `CompileResult`
|
||||
/// by calling `result_destroy`.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn prql_to_pl(prql_query: *const c_char) -> CompileResult {
|
||||
let prql_query: String = c_str_to_string(prql_query);
|
||||
|
||||
let result = Ok(prql_query.as_str())
|
||||
.and_then(prqlc::prql_to_pl)
|
||||
.and_then(|x| prqlc::json::from_pl(&x));
|
||||
result_into_c_str(result)
|
||||
}
|
||||
|
||||
/// Finds variable references, validates functions calls, determines frames and converts PL to RQ.
|
||||
/// PL and RQ are documented in the
|
||||
/// [prqlc Rust crate](https://docs.rs/prqlc/latest/prqlc/ast).
|
||||
///
|
||||
/// Takes PL serialized as JSON buffer and writes RQ serialized as JSON to `out` buffer.
|
||||
///
|
||||
/// Returns 0 on success and a negative number -1 on failure.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function assumes zero-terminated input strings.
|
||||
/// Calling code is responsible for freeing memory allocated for `CompileResult`
|
||||
/// by calling `result_destroy`.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn pl_to_rq(pl_json: *const c_char) -> CompileResult {
|
||||
let pl_json: String = c_str_to_string(pl_json);
|
||||
|
||||
let result = Ok(pl_json.as_str())
|
||||
.and_then(prqlc::json::to_pl)
|
||||
.and_then(prqlc::pl_to_rq)
|
||||
.and_then(|x| prqlc::json::from_rq(&x));
|
||||
result_into_c_str(result)
|
||||
}
|
||||
|
||||
/// Convert RQ AST into an SQL string. RQ is documented in the
|
||||
/// [prqlc Rust crate](https://docs.rs/prqlc/latest/prqlc/ir/rq).
|
||||
///
|
||||
/// Takes RQ serialized as JSON buffer and writes SQL source to `out` buffer.
|
||||
///
|
||||
/// Returns 0 on success and a negative number -1 on failure.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function assumes zero-terminated input strings.
|
||||
/// Calling code is responsible for freeing memory allocated for `CompileResult`
|
||||
/// by calling `result_destroy`.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn rq_to_sql(
|
||||
rq_json: *const c_char,
|
||||
options: *const Options,
|
||||
) -> CompileResult {
|
||||
let rq_json: String = c_str_to_string(rq_json);
|
||||
|
||||
let options = options.as_ref().map(convert_options).transpose();
|
||||
|
||||
let result = options.and_then(|options| {
|
||||
Ok(rq_json.as_str())
|
||||
.and_then(prqlc::json::to_rq)
|
||||
.and_then(|x| prqlc::rq_to_sql(x, &options.unwrap_or_default()))
|
||||
});
|
||||
result_into_c_str(result)
|
||||
}
|
||||
|
||||
/// Compilation options
|
||||
#[repr(C)]
|
||||
pub struct Options {
|
||||
/// Pass generated SQL string trough a formatter that splits it
|
||||
/// into multiple lines and prettifies indentation and spacing.
|
||||
///
|
||||
/// Defaults to true.
|
||||
pub format: bool,
|
||||
|
||||
/// Target and dialect to compile to.
|
||||
///
|
||||
/// Defaults to `sql.any`, which uses `target` argument from the query header to determine
|
||||
/// the SQL dialect.
|
||||
pub target: *mut c_char,
|
||||
|
||||
/// Emits the compiler signature as a comment after generated SQL
|
||||
///
|
||||
/// Defaults to true.
|
||||
pub signature_comment: bool,
|
||||
}
|
||||
|
||||
/// Result of compilation.
|
||||
#[repr(C)]
|
||||
pub struct CompileResult {
|
||||
pub output: *const libc::c_char,
|
||||
pub messages: *const Message,
|
||||
pub messages_len: size_t,
|
||||
}
|
||||
|
||||
/// Compile message kind. Currently only Error is implemented.
|
||||
#[repr(C)]
|
||||
pub enum MessageKind {
|
||||
Error,
|
||||
Warning,
|
||||
Lint,
|
||||
}
|
||||
|
||||
/// Compile result message.
|
||||
///
|
||||
/// Calling code is responsible for freeing all memory allocated
|
||||
/// for fields as well as strings.
|
||||
// Make sure to keep in sync with prqlc::ErrorMessage
|
||||
#[repr(C)]
|
||||
pub struct Message {
|
||||
/// Message kind. Currently only Error is implemented.
|
||||
pub kind: MessageKind,
|
||||
/// Machine-readable identifier of the error
|
||||
pub code: *const *const libc::c_char,
|
||||
/// Plain text of the error
|
||||
pub reason: *const libc::c_char,
|
||||
/// A list of suggestions of how to fix the error
|
||||
pub hint: *const *const libc::c_char,
|
||||
/// Character offset of error origin within a source file
|
||||
pub span: *const Span,
|
||||
|
||||
/// Annotated code, containing cause and hints.
|
||||
pub display: *const *const libc::c_char,
|
||||
/// Line and column number of error origin within a source file
|
||||
pub location: *const SourceLocation,
|
||||
}
|
||||
|
||||
/// Identifier of a location in source.
|
||||
/// Contains offsets in terms of chars.
|
||||
// Make sure to keep in sync with prqlc::Span
|
||||
#[repr(C)]
|
||||
pub struct Span {
|
||||
pub start: size_t,
|
||||
pub end: size_t,
|
||||
}
|
||||
|
||||
/// Location within a source file.
|
||||
// Make sure to keep in sync with prqlc::SourceLocation
|
||||
#[repr(C)]
|
||||
pub struct SourceLocation {
|
||||
pub start_line: size_t,
|
||||
pub start_col: size_t,
|
||||
|
||||
pub end_line: size_t,
|
||||
pub end_col: size_t,
|
||||
}
|
||||
|
||||
/// Destroy a `CompileResult` once you are done with it.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This function expects to be called exactly once after the call of any the functions
|
||||
/// that return `CompileResult`. No fields should be freed manually.
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn result_destroy(res: CompileResult) {
|
||||
// This is required because we are allocating memory for
|
||||
// strings, vectors and options.
|
||||
// For strings and vectors this is required, but options may be
|
||||
// able to live entirely within the struct, instead of the heap.
|
||||
|
||||
for i in 0..res.messages_len {
|
||||
let e = &*res.messages.add(i);
|
||||
|
||||
if !e.code.is_null() {
|
||||
drop(CString::from_raw(*e.code as *mut libc::c_char));
|
||||
drop(Box::from_raw(e.code as *mut *const libc::c_char));
|
||||
}
|
||||
drop(CString::from_raw(e.reason as *mut libc::c_char));
|
||||
if !e.hint.is_null() {
|
||||
drop(CString::from_raw(*e.hint as *mut libc::c_char));
|
||||
drop(Box::from_raw(e.hint as *mut *const libc::c_char));
|
||||
}
|
||||
if !e.span.is_null() {
|
||||
drop(Box::from_raw(e.span as *mut Span));
|
||||
}
|
||||
if !e.display.is_null() {
|
||||
drop(CString::from_raw(*e.display as *mut libc::c_char));
|
||||
drop(Box::from_raw(e.display as *mut *const libc::c_char));
|
||||
}
|
||||
if !e.location.is_null() {
|
||||
drop(Box::from_raw(e.location as *mut SourceLocation));
|
||||
}
|
||||
}
|
||||
drop(Vec::from_raw_parts(
|
||||
res.messages as *mut i8,
|
||||
res.messages_len,
|
||||
res.messages_len,
|
||||
));
|
||||
drop(CString::from_raw(res.output as *mut libc::c_char));
|
||||
}
|
||||
|
||||
unsafe fn result_into_c_str(result: Result<String, ErrorMessages>) -> CompileResult {
|
||||
match result {
|
||||
Ok(output) => CompileResult {
|
||||
output: convert_string(output),
|
||||
messages: ::std::ptr::null_mut(),
|
||||
messages_len: 0,
|
||||
},
|
||||
Err(err) => {
|
||||
let mut errors = Vec::with_capacity(err.inner.len());
|
||||
errors.extend(err.inner.into_iter().map(|e| Message {
|
||||
kind: MessageKind::Error,
|
||||
code: option_to_ptr(e.code.map(convert_string)),
|
||||
reason: convert_string(e.reason),
|
||||
hint: option_to_ptr(if e.hints.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(convert_string(e.hints.join("\n")))
|
||||
}),
|
||||
span: option_to_ptr(e.span.map(convert_span)),
|
||||
display: option_to_ptr(e.display.map(convert_string)),
|
||||
location: option_to_ptr(e.location.map(convert_source_location)),
|
||||
}));
|
||||
CompileResult {
|
||||
output: CString::default().into_raw(),
|
||||
messages_len: errors.len(),
|
||||
messages: errors.leak().as_ptr(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocates the value on the heap and returns a pointer to it.
|
||||
/// If the input is None, it returns null pointer.
|
||||
fn option_to_ptr<T>(o: Option<T>) -> *const T {
|
||||
match o {
|
||||
Some(x) => {
|
||||
let b = Box::new(x);
|
||||
Box::into_raw(b)
|
||||
}
|
||||
None => ::std::ptr::null(),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_string(x: String) -> *const libc::c_char {
|
||||
CString::new(x).unwrap_or_default().into_raw()
|
||||
}
|
||||
|
||||
fn convert_span(x: prqlc::Span) -> Span {
|
||||
Span {
|
||||
start: x.start,
|
||||
end: x.end,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_source_location(x: prqlc::SourceLocation) -> SourceLocation {
|
||||
SourceLocation {
|
||||
start_line: x.start.0,
|
||||
start_col: x.start.1,
|
||||
end_line: x.end.0,
|
||||
end_col: x.end.1,
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn c_str_to_string(c_str: *const c_char) -> String {
|
||||
// inefficient, but simple
|
||||
CStr::from_ptr(c_str).to_string_lossy().into_owned()
|
||||
}
|
||||
|
||||
fn convert_options(o: &Options) -> Result<prqlc::Options, prqlc::ErrorMessages> {
|
||||
let target = if !o.target.is_null() {
|
||||
Some(unsafe { c_str_to_string(o.target) })
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let target = target
|
||||
.as_deref()
|
||||
.filter(|x| !x.is_empty())
|
||||
.unwrap_or("sql.any");
|
||||
|
||||
let target = Target::from_str(target).map_err(prqlc::ErrorMessages::from)?;
|
||||
|
||||
Ok(prqlc::Options {
|
||||
format: o.format,
|
||||
target,
|
||||
signature_comment: o.signature_comment,
|
||||
// TODO: add support for this
|
||||
color: false,
|
||||
})
|
||||
}
|
@ -267,6 +267,7 @@ time_extension_functions(struct FuncDef** basic_funcs,
|
||||
"timestamp falls in. "
|
||||
"If the time falls outside of the slice, NULL is returned.")
|
||||
.sql_function()
|
||||
.with_prql_path({"time", "slice"})
|
||||
.with_parameter(
|
||||
{"time", "The timestamp to get the time slice for."})
|
||||
.with_parameter({"slice", "The size of the time slices"})
|
||||
@ -293,6 +294,7 @@ time_extension_functions(struct FuncDef** basic_funcs,
|
||||
"timediff",
|
||||
"Compute the difference between two timestamps in seconds")
|
||||
.sql_function()
|
||||
.with_prql_path({"time", "diff"})
|
||||
.with_parameter({"time1", "The first timestamp"})
|
||||
.with_parameter(
|
||||
{"time2", "The timestamp to subtract from the first"})
|
||||
@ -314,6 +316,7 @@ time_extension_functions(struct FuncDef** basic_funcs,
|
||||
"Format the given seconds value as an abbreviated "
|
||||
"duration string")
|
||||
.sql_function()
|
||||
.with_prql_path({"humanize", "duration"})
|
||||
.with_parameter({"secs", "The duration in seconds"})
|
||||
.with_tags({"datetime", "string"})
|
||||
.with_example({
|
||||
@ -328,6 +331,7 @@ time_extension_functions(struct FuncDef** basic_funcs,
|
||||
sqlite_func_adapter<decltype(&sql_timezone), sql_timezone>::builder(
|
||||
help_text("timezone", "Convert a timestamp to the given timezone")
|
||||
.sql_function()
|
||||
.with_prql_path({"time", "to_zone"})
|
||||
.with_parameter({"tz", "The target timezone"})
|
||||
.with_parameter({"ts", "The source timestamp"})
|
||||
.with_tags({"datetime", "string"})
|
||||
|
@ -30,6 +30,7 @@
|
||||
#include "view_helpers.hh"
|
||||
|
||||
#include "base/itertools.hh"
|
||||
#include "bound_tags.hh"
|
||||
#include "config.h"
|
||||
#include "document.sections.hh"
|
||||
#include "environ_vtab.hh"
|
||||
@ -527,8 +528,8 @@ build_all_help_text()
|
||||
auto parse_res = md4cpp::parse(sub_help_text, mdal);
|
||||
attr_line_t all_help_text = parse_res.unwrap();
|
||||
|
||||
std::map<std::string, help_text*> sql_funcs;
|
||||
std::map<std::string, help_text*> sql_keywords;
|
||||
std::map<std::string, const help_text*> sql_funcs;
|
||||
std::map<std::string, const help_text*> sql_keywords;
|
||||
|
||||
for (const auto& iter : sqlite_function_help) {
|
||||
switch (iter.second->ht_context) {
|
||||
@ -630,8 +631,10 @@ layout_views()
|
||||
|
||||
int doc_height;
|
||||
bool doc_side_by_side = width > (90 + 60);
|
||||
bool preview_open
|
||||
= !lnav_data.ld_preview_status_source.get_description().empty();
|
||||
bool preview_open0
|
||||
= !lnav_data.ld_preview_status_source[0].get_description().empty();
|
||||
bool preview_open1
|
||||
= !lnav_data.ld_preview_status_source[1].get_description().empty();
|
||||
bool filters_supported = false;
|
||||
auto is_spectro = false;
|
||||
auto is_gantt = false;
|
||||
@ -659,9 +662,22 @@ layout_views()
|
||||
+ lnav_data.ld_example_source.text_line_count();
|
||||
}
|
||||
|
||||
int preview_height = lnav_data.ld_preview_hidden
|
||||
int preview_height0 = lnav_data.ld_preview_hidden
|
||||
? 0
|
||||
: lnav_data.ld_preview_source.text_line_count();
|
||||
: lnav_data.ld_preview_view[0].get_inner_height();
|
||||
if (preview_height0
|
||||
&& lnav_data.ld_preview_view[0].get_overlay_source() != nullptr)
|
||||
{
|
||||
preview_height0 += 1; // XXX extra height for db overlay
|
||||
}
|
||||
int preview_height1 = lnav_data.ld_preview_hidden
|
||||
? 0
|
||||
: lnav_data.ld_preview_view[1].get_inner_height();
|
||||
if (preview_height1
|
||||
&& lnav_data.ld_preview_view[1].get_overlay_source() != nullptr)
|
||||
{
|
||||
preview_height1 += 1; // XXX extra height for db overlay
|
||||
}
|
||||
|
||||
int match_rows = lnav_data.ld_match_source.text_line_count();
|
||||
int match_height = std::min(match_rows, (height - 4) / 2);
|
||||
@ -706,13 +722,21 @@ layout_views()
|
||||
lnav_data.ld_status[LNS_BOTTOM].set_enabled(!filters_open
|
||||
&& !breadcrumb_open);
|
||||
|
||||
vis = preview_open && bottom.try_consume(preview_height + 1);
|
||||
lnav_data.ld_preview_view.set_height(vis_line_t(preview_height));
|
||||
lnav_data.ld_preview_view.set_y(bottom + 1);
|
||||
lnav_data.ld_preview_view.set_visible(vis);
|
||||
vis = preview_open1 && bottom.try_consume(preview_height1 + 1);
|
||||
lnav_data.ld_preview_view[1].set_height(vis_line_t(preview_height1));
|
||||
lnav_data.ld_preview_view[1].set_y(bottom + 1);
|
||||
lnav_data.ld_preview_view[1].set_visible(vis);
|
||||
|
||||
lnav_data.ld_status[LNS_PREVIEW].set_top(bottom);
|
||||
lnav_data.ld_status[LNS_PREVIEW].set_visible(vis);
|
||||
lnav_data.ld_status[LNS_PREVIEW1].set_top(bottom);
|
||||
lnav_data.ld_status[LNS_PREVIEW1].set_visible(vis);
|
||||
|
||||
vis = preview_open0 && bottom.try_consume(preview_height0 + 1);
|
||||
lnav_data.ld_preview_view[0].set_height(vis_line_t(preview_height0));
|
||||
lnav_data.ld_preview_view[0].set_y(bottom + 1);
|
||||
lnav_data.ld_preview_view[0].set_visible(vis);
|
||||
|
||||
lnav_data.ld_status[LNS_PREVIEW0].set_top(bottom);
|
||||
lnav_data.ld_status[LNS_PREVIEW0].set_visible(vis);
|
||||
|
||||
if (doc_side_by_side && doc_height > 0) {
|
||||
vis = bottom.try_consume(doc_height + 1);
|
||||
@ -816,7 +840,7 @@ update_hits(textview_curses* tc)
|
||||
|
||||
int preview_count = 0;
|
||||
|
||||
vis_bookmarks& bm = tc->get_bookmarks();
|
||||
auto& bm = tc->get_bookmarks();
|
||||
const auto& bv = bm[&textview_curses::BM_SEARCH];
|
||||
auto vl = tc->get_top();
|
||||
unsigned long width;
|
||||
@ -888,11 +912,15 @@ update_hits(textview_curses* tc)
|
||||
}
|
||||
|
||||
if (preview_count > 0) {
|
||||
lnav_data.ld_preview_status_source.get_description().set_value(
|
||||
"Matching lines for search");
|
||||
lnav_data.ld_preview_source.replace_with(all_matches)
|
||||
lnav_data.ld_preview_status_source[0]
|
||||
.get_description()
|
||||
.set_value("Matching lines for search");
|
||||
lnav_data.ld_preview_view[0].set_sub_source(
|
||||
&lnav_data.ld_preview_source[0]);
|
||||
lnav_data.ld_preview_source[0]
|
||||
.replace_with(all_matches)
|
||||
.set_text_format(text_format_t::TF_UNKNOWN);
|
||||
lnav_data.ld_preview_view.set_needs_update();
|
||||
lnav_data.ld_preview_view[0].set_needs_update();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -900,77 +928,95 @@ update_hits(textview_curses* tc)
|
||||
|
||||
static std::unordered_map<std::string, attr_line_t> EXAMPLE_RESULTS;
|
||||
|
||||
static void
|
||||
execute_example(const help_text& ht)
|
||||
{
|
||||
auto& dls = lnav_data.ld_db_row_source;
|
||||
auto& dos = lnav_data.ld_db_overlay;
|
||||
auto& db_tc = lnav_data.ld_views[LNV_DB];
|
||||
|
||||
for (const auto& ex : ht.ht_example) {
|
||||
std::string alt_msg;
|
||||
attr_line_t result;
|
||||
|
||||
if (!ex.he_cmd) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (EXAMPLE_RESULTS.count(ex.he_cmd)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (ht.ht_context) {
|
||||
case help_context_t::HC_SQL_KEYWORD:
|
||||
case help_context_t::HC_SQL_INFIX:
|
||||
case help_context_t::HC_SQL_FUNCTION:
|
||||
case help_context_t::HC_SQL_TABLE_VALUED_FUNCTION:
|
||||
case help_context_t::HC_PRQL_TRANSFORM: {
|
||||
exec_context ec;
|
||||
|
||||
ec.ec_label_source_stack.push_back(&dls);
|
||||
|
||||
auto exec_res = execute_sql(ec, ex.he_cmd, alt_msg);
|
||||
|
||||
if (exec_res.isErr()) {
|
||||
auto um = exec_res.unwrapErr();
|
||||
result.append(um.to_attr_line());
|
||||
} else if (dls.dls_rows.size() == 1
|
||||
&& dls.dls_rows[0].size() == 1)
|
||||
{
|
||||
result.append(dls.dls_rows[0][0]);
|
||||
} else {
|
||||
attr_line_t al;
|
||||
dos.list_static_overlay(db_tc, 0, 1, al);
|
||||
result.append(al);
|
||||
for (int lpc = 0; lpc < (int) dls.text_line_count(); lpc++)
|
||||
{
|
||||
al.clear();
|
||||
dls.text_value_for_line(
|
||||
db_tc, lpc, al.get_string(), false);
|
||||
dls.text_attrs_for_line(db_tc, lpc, al.get_attrs());
|
||||
std::replace(al.get_string().begin(),
|
||||
al.get_string().end(),
|
||||
'\n',
|
||||
' ');
|
||||
result.append("\n").append(al);
|
||||
}
|
||||
}
|
||||
|
||||
EXAMPLE_RESULTS[ex.he_cmd] = result;
|
||||
|
||||
log_trace("example: %s", ex.he_cmd);
|
||||
log_trace("example result: %s", result.get_string().c_str());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
log_warning("Not executing example: %s", ex.he_cmd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
execute_examples()
|
||||
{
|
||||
db_label_source& dls = lnav_data.ld_db_row_source;
|
||||
db_overlay_source& dos = lnav_data.ld_db_overlay;
|
||||
textview_curses& db_tc = lnav_data.ld_views[LNV_DB];
|
||||
static const auto* sql_cmd_map
|
||||
= injector::get<readline_context::command_map_t*, sql_cmd_map_tag>();
|
||||
|
||||
auto& dls = lnav_data.ld_db_row_source;
|
||||
|
||||
auto old_width = dls.dls_max_column_width;
|
||||
dls.dls_max_column_width = 15;
|
||||
for (auto& help_iter : sqlite_function_help) {
|
||||
auto& ht = *(help_iter.second);
|
||||
|
||||
for (auto& ex : ht.ht_example) {
|
||||
std::string alt_msg;
|
||||
attr_line_t result;
|
||||
|
||||
if (!ex.he_cmd) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (EXAMPLE_RESULTS.count(ex.he_cmd)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (ht.ht_context) {
|
||||
case help_context_t::HC_SQL_KEYWORD:
|
||||
case help_context_t::HC_SQL_INFIX:
|
||||
case help_context_t::HC_SQL_FUNCTION:
|
||||
case help_context_t::HC_SQL_TABLE_VALUED_FUNCTION: {
|
||||
exec_context ec;
|
||||
|
||||
auto exec_res = execute_sql(ec, ex.he_cmd, alt_msg);
|
||||
|
||||
if (exec_res.isErr()) {
|
||||
auto um = exec_res.unwrapErr();
|
||||
result.append(um.to_attr_line());
|
||||
} else if (dls.dls_rows.size() == 1
|
||||
&& dls.dls_rows[0].size() == 1)
|
||||
{
|
||||
result.append(dls.dls_rows[0][0]);
|
||||
} else {
|
||||
attr_line_t al;
|
||||
dos.list_static_overlay(db_tc, 0, 1, al);
|
||||
result.append(al);
|
||||
for (int lpc = 0; lpc < (int) dls.text_line_count();
|
||||
lpc++)
|
||||
{
|
||||
al.clear();
|
||||
dls.text_value_for_line(
|
||||
db_tc, lpc, al.get_string(), false);
|
||||
dls.text_attrs_for_line(db_tc, lpc, al.get_attrs());
|
||||
std::replace(al.get_string().begin(),
|
||||
al.get_string().end(),
|
||||
'\n',
|
||||
' ');
|
||||
result.append("\n").append(al);
|
||||
}
|
||||
}
|
||||
|
||||
EXAMPLE_RESULTS[ex.he_cmd] = result;
|
||||
|
||||
log_trace("example: %s", ex.he_cmd);
|
||||
log_trace("example result: %s",
|
||||
result.get_string().c_str());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
log_warning("Not executing example: %s", ex.he_cmd);
|
||||
break;
|
||||
}
|
||||
for (auto help_pair : sqlite_function_help) {
|
||||
execute_example(*help_pair.second);
|
||||
}
|
||||
for (auto cmd_pair : *sql_cmd_map) {
|
||||
if (cmd_pair.second->c_help.ht_context
|
||||
!= help_context_t::HC_PRQL_TRANSFORM)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
execute_example(cmd_pair.second->c_help);
|
||||
}
|
||||
dls.dls_max_column_width = old_width;
|
||||
|
||||
@ -999,8 +1045,12 @@ toggle_view(textview_curses* toggle_tc)
|
||||
require(toggle_tc >= &lnav_data.ld_views[0]);
|
||||
require(toggle_tc < &lnav_data.ld_views[LNV__MAX]);
|
||||
|
||||
lnav_data.ld_preview_source.clear();
|
||||
lnav_data.ld_preview_status_source.get_description().clear();
|
||||
lnav_data.ld_preview_view[0].set_sub_source(
|
||||
&lnav_data.ld_preview_source[0]);
|
||||
lnav_data.ld_preview_source[0].clear();
|
||||
lnav_data.ld_preview_status_source[0].get_description().clear();
|
||||
lnav_data.ld_preview_view[1].set_sub_source(nullptr);
|
||||
lnav_data.ld_preview_status_source[1].get_description().clear();
|
||||
|
||||
if (tc == toggle_tc) {
|
||||
if (lnav_data.ld_view_stack.size() == 1) {
|
||||
@ -1321,3 +1371,18 @@ lnav_crumb_source()
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
void
|
||||
clear_preview()
|
||||
{
|
||||
for (size_t lpc = 0; lpc < 2; lpc++) {
|
||||
lnav_data.ld_preview_source[lpc].clear();
|
||||
lnav_data.ld_preview_status_source[lpc]
|
||||
.get_description()
|
||||
.set_cylon(false)
|
||||
.clear();
|
||||
lnav_data.ld_db_preview_source[lpc].clear();
|
||||
lnav_data.ld_preview_view[lpc].set_sub_source(nullptr);
|
||||
lnav_data.ld_preview_view[lpc].set_overlay_source(nullptr);
|
||||
}
|
||||
}
|
||||
|
@ -86,6 +86,7 @@ bool toggle_view(textview_curses* toggle_tc);
|
||||
bool handle_winch();
|
||||
void layout_views();
|
||||
void update_hits(textview_curses* tc);
|
||||
void clear_preview();
|
||||
|
||||
nonstd::optional<vis_line_t> next_cluster(
|
||||
nonstd::optional<vis_line_t> (bookmark_vector<vis_line_t>::*f)(vis_line_t)
|
||||
|
@ -719,8 +719,7 @@ CREATE TABLE lnav_view_stack (
|
||||
|
||||
lnav_data.ld_last_view = *lnav_data.ld_view_stack.top();
|
||||
lnav_data.ld_view_stack.pop_back();
|
||||
lnav_data.ld_preview_source.clear();
|
||||
lnav_data.ld_preview_status_source.get_description().clear();
|
||||
clear_preview();
|
||||
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
@ -87,6 +87,7 @@ yaml_extension_functions(struct FuncDef** basic_funcs,
|
||||
help_text("yaml_to_json",
|
||||
"Convert a YAML document to a JSON-encoded string")
|
||||
.sql_function()
|
||||
.with_prql_path({"yaml", "to_json"})
|
||||
.with_parameter({"yaml", "The YAML value to convert to JSON."})
|
||||
.with_tags({"json", "yaml"})
|
||||
.with_example({
|
||||
|
@ -12,6 +12,16 @@ AM_LIBS = $(CODE_COVERAGE_LIBS)
|
||||
AM_CFLAGS = $(CODE_COVERAGE_CFLAGS)
|
||||
AM_CXXFLAGS = $(CODE_COVERAGE_CXXFLAGS)
|
||||
|
||||
if HAVE_CARGO
|
||||
RUST_DEPS_CPPFLAGS = -I$(srcdir)/third-party/prqlc-c -DHAVE_RUST_DEPS=1
|
||||
PRQLC_DIR = ../src/third-party/prqlc-c/target
|
||||
RUST_DEPS_LD_FLAGS = -L$(PRQLC_DIR)/release -lprqlc_c
|
||||
else
|
||||
RUST_DEPS =
|
||||
RUST_DEPS_CPPFLAGS =
|
||||
RUST_DEPS_LD_FLAGS =
|
||||
endif
|
||||
|
||||
AM_CPPFLAGS = \
|
||||
-Wall \
|
||||
-I$(top_srcdir)/src \
|
||||
@ -23,7 +33,8 @@ AM_CPPFLAGS = \
|
||||
$(LIBARCHIVE_CFLAGS) \
|
||||
$(READLINE_CFLAGS) \
|
||||
$(PCRE_CFLAGS) \
|
||||
$(SQLITE3_CFLAGS)
|
||||
$(SQLITE3_CFLAGS) \
|
||||
$(RUST_DEPS_CPPFLAGS)
|
||||
|
||||
# AM_CFLAGS = -fprofile-arcs -ftest-coverage
|
||||
# AM_CXXFLAGS = -fprofile-arcs -ftest-coverage
|
||||
@ -86,7 +97,8 @@ AM_LDFLAGS = \
|
||||
$(STATIC_LDFLAGS) \
|
||||
$(SQLITE3_LDFLAGS) \
|
||||
$(READLINE_LDFLAGS) \
|
||||
$(CURSES_LIB)
|
||||
$(CURSES_LIB) \
|
||||
$(RUST_DEPS_LD_FLAGS)
|
||||
|
||||
CONFIG_OBJS = \
|
||||
../src/ansi-palette-json.$(OBJEXT) \
|
||||
@ -202,6 +214,7 @@ dist_noinst_SCRIPTS = \
|
||||
test_logfile.sh \
|
||||
test_meta.sh \
|
||||
test_mvwattrline.sh \
|
||||
test_prql.sh \
|
||||
test_regex101.sh \
|
||||
test_remote.sh \
|
||||
test_scripts.sh \
|
||||
@ -500,6 +513,10 @@ TESTS = \
|
||||
test_view_colors.sh \
|
||||
test_vt52_curses.sh
|
||||
|
||||
if HAVE_CARGO
|
||||
TESTS += test_prql.sh
|
||||
endif
|
||||
|
||||
DISABLED_TESTS = \
|
||||
test_regex101.sh \
|
||||
test_remote.sh \
|
||||
|
@ -466,6 +466,10 @@ EXPECTED_FILES = \
|
||||
$(srcdir)/%reldir%/test_pretty_print.sh_cd361eeca7e91bfab942b75d6c3422c7a456a111.out \
|
||||
$(srcdir)/%reldir%/test_pretty_print.sh_f8feb52a321026d9562b271eb37a2c56dfaed329.err \
|
||||
$(srcdir)/%reldir%/test_pretty_print.sh_f8feb52a321026d9562b271eb37a2c56dfaed329.out \
|
||||
$(srcdir)/%reldir%/test_prql.sh_451e242cdfa2db9005d4fe752a7b05d1ab5cba29.err \
|
||||
$(srcdir)/%reldir%/test_prql.sh_451e242cdfa2db9005d4fe752a7b05d1ab5cba29.out \
|
||||
$(srcdir)/%reldir%/test_prql.sh_45d57a042092ffdcd28ea35a892f02859e78f33d.err \
|
||||
$(srcdir)/%reldir%/test_prql.sh_45d57a042092ffdcd28ea35a892f02859e78f33d.out \
|
||||
$(srcdir)/%reldir%/test_regex101.sh_0fa3663a45aca6a328cb728872af7ed7ee896f1c.err \
|
||||
$(srcdir)/%reldir%/test_regex101.sh_0fa3663a45aca6a328cb728872af7ed7ee896f1c.out \
|
||||
$(srcdir)/%reldir%/test_regex101.sh_182ae9244db314a953af2bee969726e381bc5a32.err \
|
||||
|
@ -469,12 +469,12 @@ scrollbar.
|
||||
NOTE: You need to manually enable this feature by setting the LNAV_EXP
|
||||
environment variable to "mouse". F2 toggles mouse support.
|
||||
|
||||
[1mSQL Queries (experimental)[0m
|
||||
[1mSQL Queries[0m
|
||||
|
||||
Lnav has support for performing SQL queries on log files using the
|
||||
Sqlite3 "virtual" table feature. For all supported log file types,
|
||||
SQLite3 "virtual" table feature. For all supported log file types,
|
||||
lnav will create tables that can be queried using the subset of SQL
|
||||
that is supported by Sqlite3. For example, to get the top ten URLs
|
||||
that is supported by SQLite3. For example, to get the top ten URLs
|
||||
being accessed in any loaded Apache log files, you can execute:
|
||||
|
||||
▌[37m[40m;[0m[1m[36m[40mSELECT[0m[37m[40m [0m[37m[40mcs_uri_stem[0m[37m[40m, [0m[1m[37m[40mcount[0m[37m[40m([0m[1m[37m[40m*[0m[37m[40m) [0m[1m[36m[40mAS[0m[37m[40m [0m[37m[40mtotal[0m[37m[40m [0m[1m[36m[40mFROM[0m[37m[40m [0m[37m[40maccess_log[0m[37m[40m [0m
|
||||
|
@ -0,0 +1,10 @@
|
||||
[1m[31m✘ error[0m: unable to compile PRQL: from db.access_log | take abc
|
||||
[1m[31mreason[0m: `take` expected int or range, but found this.access_log.abc
|
||||
[36m =[0m [36mnote[0m: Error:
|
||||
╭─[:57:27]
|
||||
│
|
||||
57 │ from db.access_log | take abc
|
||||
│ ─┬─
|
||||
│ ╰─── `take` expected int or range, but found this.access_log.abc
|
||||
────╯
|
||||
|
@ -0,0 +1,2 @@
|
||||
[1m[4mlog_line [0m[1m[4mlog_part [0m[1m[4m log_time [0m[1m[4m[7mlog_idle_msecs [0m[1m[4mlog_level [0m[1m[4mlog_mark [0m[1m[4mlog_comment [0m[1m[4mlog_tags [0m[1m[4mlog_annotations [0m[1m[4mlog_filters [0m[1m[4m c_ip [0m[1m[4mcs_method [0m[1m[4mcs_referer [0m[1m[4mcs_uri_query [0m[1m[4m cs_uri_stem [0m[1m[4mcs_user_agent [0m[1m[4mcs_username [0m[1m[4mcs_version [0m[1m[4m[7msc_bytes [0m[1m[4msc_status [0m[1m[4mcs_host [0m
|
||||
[7m 0[0m[7m [0m[7m <NULL>[0m[7m [0m[7m2009-07-20 22:59:26.000[0m[7m [0m[1m[7m 0[0m[7m [0m[7minfo [0m[7m [0m[7m 0[0m[7m [0m[7m <NULL>[0m[7m [0m[7m <NULL>[0m[7m [0m[7m <NULL>[0m[7m [0m[7m <NULL>[0m[7m [0m[7m192.168.202.254[0m[7m [0m[7mGET [0m[7m [0m[7m- [0m[7m [0m[7m <NULL>[0m[7m [0m[7m/vmw/cgi/tramp[0m[7m [0m[7mgPXE/0.9.7 [0m[7m [0m[7m- [0m[7m [0m[7mHTTP/1.0 [0m[7m [0m[1m[7m 134[0m[7m [0m[7m 200[0m[7m [0m[7m <NULL[0m>
|
@ -3,8 +3,8 @@
|
||||
# '|/path/to/this/file' in lnav to execute this file and
|
||||
# restore the state of the session.
|
||||
|
||||
;SELECT raise_error('This session export was made with a newer version of lnav, please upgrade to ' || '0.12.0' || ' or later')
|
||||
WHERE lnav_version() < '0.12.0' COLLATE naturalcase
|
||||
;SELECT raise_error('This session export was made with a newer version of lnav, please upgrade to ' || '0.12.1' || ' or later')
|
||||
WHERE lnav_version() < '0.12.1' COLLATE naturalcase
|
||||
|
||||
# The files loaded into the session were:
|
||||
|
||||
|
@ -3,8 +3,8 @@
|
||||
# '|/path/to/this/file' in lnav to execute this file and
|
||||
# restore the state of the session.
|
||||
|
||||
;SELECT raise_error('This session export was made with a newer version of lnav, please upgrade to ' || '0.12.0' || ' or later')
|
||||
WHERE lnav_version() < '0.12.0' COLLATE naturalcase
|
||||
;SELECT raise_error('This session export was made with a newer version of lnav, please upgrade to ' || '0.12.1' || ' or later')
|
||||
WHERE lnav_version() < '0.12.1' COLLATE naturalcase
|
||||
|
||||
# The files loaded into the session were:
|
||||
|
||||
|
13
test/test_prql.sh
Normal file
13
test/test_prql.sh
Normal file
@ -0,0 +1,13 @@
|
||||
#! /bin/bash
|
||||
|
||||
export TZ=UTC
|
||||
export YES_COLOR=1
|
||||
unset XDG_CONFIG_HOME
|
||||
|
||||
run_cap_test ${lnav_test} -n \
|
||||
-c ";from db.access_log | take 1" \
|
||||
${test_dir}/logfile_access_log.0
|
||||
|
||||
run_cap_test ${lnav_test} -n \
|
||||
-c ";from db.access_log | take abc" \
|
||||
${test_dir}/logfile_access_log.0
|
Loading…
Reference in New Issue
Block a user