[prql] initial work for integrating PRQL

pull/1084/merge
Tim Stack 1 month ago
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

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

@ -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(&current_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)

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

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

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

File diff suppressed because it is too large Load Diff

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

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

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

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

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

@ -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_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_PREVIEW].set_top(bottom);
lnav_data.ld_status[LNS_PREVIEW].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,78 +928,96 @@ update_hits(textview_curses* tc)
static std::unordered_map<std::string, attr_line_t> EXAMPLE_RESULTS;
void
execute_examples()
static void
execute_example(const help_text& ht)
{
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];
auto& dls = lnav_data.ld_db_row_source;
auto& dos = lnav_data.ld_db_overlay;
auto& db_tc = lnav_data.ld_views[LNV_DB];
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 (const auto& ex : ht.ht_example) {
std::string alt_msg;
attr_line_t result;
for (auto& ex : ht.ht_example) {
std::string alt_msg;
attr_line_t result;
if (!ex.he_cmd) {
continue;
}
if (!ex.he_cmd) {
continue;
}
if (EXAMPLE_RESULTS.count(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;
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;
ec.ec_label_source_stack.push_back(&dls);
auto exec_res = execute_sql(ec, ex.he_cmd, alt_msg);
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)
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++)
{
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);
}
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;
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;
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()
{
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_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;
dls.clear();
@ -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) {
@ -1320,4 +1370,19 @@ 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.
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:
;SELECT cs_uri_stem, count(*) AS total FROM access_log 

@ -0,0 +1,10 @@
✘ error: unable to compile PRQL: from db.access_log | take abc
reason: `take` expected int or range, but found this.access_log.abc
 = note: 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 @@
log_line log_part  log_time log_idle_msecs log_level log_mark log_comment log_tags log_annotations log_filters  c_ip cs_method cs_referer cs_uri_query  cs_uri_stem cs_user_agent cs_username cs_version sc_bytes sc_status cs_host 
 0  <NULL> 2009-07-20 22:59:26.000  0 info   0  <NULL>  <NULL>  <NULL>  <NULL> 192.168.202.254 GET  -   <NULL> /vmw/cgi/tramp gPXE/0.9.7  -  HTTP/1.0   134  200  <NULL>

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

@ -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…
Cancel
Save