diff --git a/CMakeLists.txt b/CMakeLists.txt index 5e07a383..abea357c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/NEWS.md b/NEWS.md index e0b3edf5..dc1fe854 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,12 @@ ## lnav v0.12.1 Features: +* Database queries can now be written in + [PRQL](https://prql-lang.org). When executing a query with `;`, + if the query starts with `from`, it will be treated as PRQL. + The pipeline structure of PRQL queries is more desirable for + interactive use since lnav can make better suggestions and + show previews of the stages of the pipeline. * Log partitions can automatically be created by defining a log message pattern in a log format. Under a format definition, add an entry into the "partitions" object in a format definition. @@ -12,6 +18,12 @@ Features: that will be matched against file names. Interface changes: +* When using PRQL in the database query prompt (`;`), + the preview pane will show the results for the pipeline + stage the cursor is within along with the results of + the previous stage (if there is one). The preview + works on a limited data set, so the preview results + may differ from the final results. * Changed the breadcrumb bar styling to space things out more and make the divisions between items clearer. * The `ESC` key can now be used to exit the files/filters diff --git a/conanfile.py b/conanfile.py index dc5d0738..b5ae53c0 100644 --- a/conanfile.py +++ b/conanfile.py @@ -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" diff --git a/configure.ac b/configure.ac index caa8a455..4387a5cb 100644 --- a/configure.ac +++ b/configure.ac @@ -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" ]) diff --git a/docs/source/sqlext.rst b/docs/source/sqlext.rst index 0a836fae..d1b7e5f1 100644 --- a/docs/source/sqlext.rst +++ b/docs/source/sqlext.rst @@ -71,6 +71,15 @@ The DB view has the following display features: the `jget`_ function. +PRQL Support (v0.12.1+) +----------------------- + +`PRQL `_ 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 ---------- diff --git a/release/Makefile b/release/Makefile index 30298100..b344cc9b 100644 --- a/release/Makefile +++ b/release/Makefile @@ -1,5 +1,5 @@ -VERSION=0.12.0 +VERSION=0.12.1 VERSION_TAG=v$(VERSION) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d3e92e3e..435c8807 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 ) diff --git a/src/Makefile.am b/src/Makefile.am index 447fd63d..894b69fb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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: diff --git a/src/base/attr_line.cc b/src/base/attr_line.cc index 38ba3fb7..c4065259 100644 --- a/src/base/attr_line.cc +++ b/src/base/attr_line.cc @@ -457,7 +457,7 @@ attr_line_t::apply_hide() } attr_line_t& -attr_line_t::rtrim() +attr_line_t::rtrim(nonstd::optional 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); + } } } } diff --git a/src/base/attr_line.hh b/src/base/attr_line.hh index 94c155bd..c5fde357 100644 --- a/src/base/attr_line.hh +++ b/src/base/attr_line.hh @@ -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 chars = nonstd::nullopt); attr_line_t& erase_utf8_chars(size_t start) { diff --git a/src/command_executor.cc b/src/command_executor.cc index f9990647..57d04029 100644 --- a/src/command_executor.cc +++ b/src/command_executor.cc @@ -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 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 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 args; split_ws(stmt_str, args); - auto* sql_cmd_map = injector::get(); + const auto* sql_cmd_map + = injector::get(); 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(); diff --git a/src/command_executor.hh b/src/command_executor.hh index 8ba313ab..ca54776e 100644 --- a/src/command_executor.hh +++ b/src/command_executor.hh @@ -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 ec_error_callback_stack; + std::vector ec_label_source_stack; }; Result execute_command( diff --git a/src/config.cmake.h.in b/src/config.cmake.h.in index 56803fb3..931ffcf5 100644 --- a/src/config.cmake.h.in +++ b/src/config.cmake.h.in @@ -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" diff --git a/src/dump_internals.cc b/src/dump_internals.cc index 83177696..c6ae76e5 100644 --- a/src/dump_internals.cc +++ b/src/dump_internals.cc @@ -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( fopen(sql_ref_path.c_str(), "w+"), fclose); - std::set unique_sql_help; + std::set 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; } diff --git a/src/extension-functions.cc b/src/extension-functions.cc index 461a3828..e3ab985e 100644 --- a/src/extension-functions.cc +++ b/src/extension-functions.cc @@ -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"}) diff --git a/src/fs-extension-functions.cc b/src/fs-extension-functions.cc index 746125a2..cbc23667 100644 --- a/src/fs-extension-functions.cc +++ b/src/fs-extension-functions.cc @@ -328,6 +328,7 @@ fs_extension_functions(struct FuncDef** basic_funcs, sqlite_func_adapter::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::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::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::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", diff --git a/src/help.md b/src/help.md index b5630ee7..189c6b31 100644 --- a/src/help.md +++ b/src/help.md @@ -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 diff --git a/src/help_text.cc b/src/help_text.cc index a4099799..46113150 100644 --- a/src/help_text.cc +++ b/src/help_text.cc @@ -96,6 +96,14 @@ help_text::with_opposites( return *this; } +help_text& +help_text::with_prql_path( + const std::initializer_list& prql) noexcept +{ + this->ht_prql_path = prql; + return *this; +} + void help_text::index_tags() { diff --git a/src/help_text.hh b/src/help_text.hh index 81dc8fb9..68c844a5 100644 --- a/src/help_text.hh +++ b/src/help_text.hh @@ -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 ht_tags; std::vector ht_opposites; help_function_type_t ht_function_type{help_function_type_t::HFT_REGULAR}; + std::vector 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& opps) noexcept; + help_text& with_prql_path( + const std::initializer_list& prql) noexcept; + template help_text& with_impl(F impl) { diff --git a/src/help_text_formatter.cc b/src/help_text_formatter.cc index 36ad82f0..0c20115d 100644 --- a/src/help_text_formatter.cc +++ b/src/help_text_formatter.cc @@ -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); } diff --git a/src/help_text_formatter.hh b/src/help_text_formatter.hh index 29636950..677d5795 100644 --- a/src/help_text_formatter.hh +++ b/src/help_text_formatter.hh @@ -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, diff --git a/src/hotkeys.cc b/src/hotkeys.cc index ab19964f..4a7af434 100644 --- a/src/hotkeys.cc +++ b/src/hotkeys.cc @@ -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; diff --git a/src/internals/sql-ref.rst b/src/internals/sql-ref.rst index 942a229f..9e1e7d71 100644 --- a/src/internals/sql-ref.rst +++ b/src/internals/sql-ref.rst @@ -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. diff --git a/src/json-extension-functions.cc b/src/json-extension-functions.cc index a8479025..034b713d 100644 --- a/src/json-extension-functions.cc +++ b/src/json-extension-functions.cc @@ -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( diff --git a/src/lnav.cc b/src/lnav.cc index 8dfd8c01..beca285a 100644 --- a/src/lnav.cc +++ b/src/lnav.cc @@ -291,29 +291,8 @@ struct lnav_data_t lnav_data; bool setup_logline_table(exec_context& ec) { - // Hidden columns don't show up in the table_info pragma. - static const char* hidden_table_columns[] = { - "log_time_msecs", - "log_path", - "log_text", - "log_body", - - nullptr, - }; - auto& log_view = lnav_data.ld_views[LNV_LOG]; bool retval = false; - bool update_possibilities - = (lnav_data.ld_rl_view != nullptr && ec.ec_local_vars.size() == 1); - - if (update_possibilities) { - lnav_data.ld_rl_view->clear_possibilities(ln_mode_t::SQL, "*"); - add_view_text_possibilities(lnav_data.ld_rl_view, - ln_mode_t::SQL, - "*", - &log_view, - text_quoting::sql); - } if (log_view.get_inner_height()) { static intern_string_t logline = intern_string::lookup("logline"); @@ -327,34 +306,6 @@ setup_logline_table(exec_context& ec) cl, logline)); - if (update_possibilities) { - log_data_helper ldh(lnav_data.ld_log_source); - - ldh.parse_line(cl); - - for (const auto& jextra : ldh.ldh_extra_json) { - lnav_data.ld_rl_view->add_possibility( - ln_mode_t::SQL, - "*", - lnav::sql::mprintf("%Q", jextra.first.c_str()).in()); - } - for (const auto& jpair : ldh.ldh_json_pairs) { - for (const auto& wt : jpair.second) { - lnav_data.ld_rl_view->add_possibility( - ln_mode_t::SQL, - "*", - lnav::sql::mprintf("%Q", wt.wt_ptr.c_str()).in()); - } - } - for (const auto& xml_pair : ldh.ldh_xml_pairs) { - lnav_data.ld_rl_view->add_possibility( - ln_mode_t::SQL, - "*", - lnav::sql::mprintf("%Q", xml_pair.first.second.c_str()) - .in()); - } - } - retval = true; } @@ -362,62 +313,6 @@ setup_logline_table(exec_context& ec) db_key_names = DEFAULT_DB_KEY_NAMES; - if (update_possibilities) { - add_env_possibilities(ln_mode_t::SQL); - - lnav_data.ld_rl_view->add_possibility(ln_mode_t::SQL, - "*", - std::begin(sql_keywords), - std::end(sql_keywords)); - lnav_data.ld_rl_view->add_possibility( - ln_mode_t::SQL, "*", sql_function_names); - lnav_data.ld_rl_view->add_possibility( - ln_mode_t::SQL, "*", hidden_table_columns); - - for (int lpc = 0; sqlite_registration_funcs[lpc]; lpc++) { - struct FuncDef* basic_funcs; - struct FuncDefAgg* agg_funcs; - - sqlite_registration_funcs[lpc](&basic_funcs, &agg_funcs); - for (int lpc2 = 0; basic_funcs && basic_funcs[lpc2].zName; lpc2++) { - const FuncDef& func_def = basic_funcs[lpc2]; - - lnav_data.ld_rl_view->add_possibility( - ln_mode_t::SQL, - "*", - std::string(func_def.zName) + (func_def.nArg ? "(" : "()")); - } - for (int lpc2 = 0; agg_funcs && agg_funcs[lpc2].zName; lpc2++) { - const FuncDefAgg& func_def = agg_funcs[lpc2]; - - lnav_data.ld_rl_view->add_possibility( - ln_mode_t::SQL, - "*", - std::string(func_def.zName) + (func_def.nArg ? "(" : "()")); - } - } - - for (const auto& pair : sqlite_function_help) { - switch (pair.second->ht_context) { - case help_context_t::HC_SQL_FUNCTION: - case help_context_t::HC_SQL_TABLE_VALUED_FUNCTION: { - std::string poss = pair.first - + (pair.second->ht_parameters.empty() ? "()" : ("(")); - - lnav_data.ld_rl_view->add_possibility( - ln_mode_t::SQL, "*", poss); - break; - } - default: - break; - } - } - } - - if (update_possibilities) { - walk_sqlite_metadata(lnav_data.ld_db.in(), lnav_sql_meta_callbacks); - } - for (const auto& iter : *lnav_data.ld_vtab_manager) { iter.second->get_foreign_keys(db_key_names); } @@ -1176,7 +1071,8 @@ looper() sql_context.set_highlighter(readline_sqlite_highlighter) .set_quote_chars("\"") .with_readline_var((char**) &rl_completer_word_break_characters, - " \t\n(),"); + " \t\n(),") + .with_splitter(prql_splitter); exec_context.set_highlighter(readline_shlex_highlighter); lnav_data.ld_log_source.lss_sorting_observer @@ -1351,7 +1247,8 @@ looper() setup_highlights(lnav_data.ld_views[LNV_TEXT].get_highlights()); setup_highlights(lnav_data.ld_views[LNV_SCHEMA].get_highlights()); setup_highlights(lnav_data.ld_views[LNV_PRETTY].get_highlights()); - setup_highlights(lnav_data.ld_preview_view.get_highlights()); + setup_highlights(lnav_data.ld_preview_view[0].get_highlights()); + setup_highlights(lnav_data.ld_preview_view[1].get_highlights()); for (const auto& format : log_format::get_root_formats()) { for (auto& hl : format->lf_highlighters) { @@ -1437,8 +1334,10 @@ looper() lnav_data.ld_match_view.set_window(lnav_data.ld_window); - lnav_data.ld_preview_view.set_window(lnav_data.ld_window); - lnav_data.ld_preview_view.set_show_scrollbar(false); + lnav_data.ld_preview_view[0].set_window(lnav_data.ld_window); + lnav_data.ld_preview_view[0].set_show_scrollbar(false); + lnav_data.ld_preview_view[1].set_window(lnav_data.ld_window); + lnav_data.ld_preview_view[1].set_show_scrollbar(false); lnav_data.ld_filter_view.set_selectable(true); lnav_data.ld_filter_view.set_window(lnav_data.ld_window); @@ -1500,8 +1399,10 @@ looper() &lnav_data.ld_filter_help_status_source); lnav_data.ld_status[LNS_DOC].set_data_source( &lnav_data.ld_doc_status_source); - lnav_data.ld_status[LNS_PREVIEW].set_data_source( - &lnav_data.ld_preview_status_source); + lnav_data.ld_status[LNS_PREVIEW0].set_data_source( + &lnav_data.ld_preview_status_source[0]); + lnav_data.ld_status[LNS_PREVIEW1].set_data_source( + &lnav_data.ld_preview_status_source[1]); lnav_data.ld_spectro_status_source = std::make_unique(); lnav_data.ld_status[LNS_SPECTRO].set_data_source( @@ -1604,7 +1505,8 @@ looper() gettimeofday(¤t_time, nullptr); top_source->update_time(current_time); - lnav_data.ld_preview_view.set_needs_update(); + lnav_data.ld_preview_view[0].set_needs_update(); + lnav_data.ld_preview_view[1].set_needs_update(); layout_views(); @@ -1708,7 +1610,8 @@ looper() lnav_data.ld_doc_view.do_update(); lnav_data.ld_example_view.do_update(); lnav_data.ld_match_view.do_update(); - lnav_data.ld_preview_view.do_update(); + lnav_data.ld_preview_view[0].do_update(); + lnav_data.ld_preview_view[1].do_update(); lnav_data.ld_spectro_details_view.do_update(); lnav_data.ld_gantt_details_view.do_update(); lnav_data.ld_user_message_view.do_update(); @@ -2219,7 +2122,7 @@ main(int argc, char* argv[]) { std::vector config_errors; std::vector 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) diff --git a/src/lnav.hh b/src/lnav.hh index 51e451ea..b260333d 100644 --- a/src/lnav.hh +++ b/src/lnav.hh @@ -73,7 +73,8 @@ typedef enum { LNS_FILTER, LNS_FILTER_HELP, LNS_DOC, - LNS_PREVIEW, + LNS_PREVIEW0, + LNS_PREVIEW1, LNS_SPECTRO, LNS_GANTT, @@ -182,7 +183,7 @@ struct lnav_data_t { filter_status_source ld_filter_status_source; filter_help_status_source ld_filter_help_status_source; doc_status_source ld_doc_status_source; - preview_status_source ld_preview_status_source; + preview_status_source ld_preview_status_source[2]; std::unique_ptr 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 @@ -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 ld_db_key_names; vis_line_t ld_last_pretty_print_top; diff --git a/src/lnav_commands.cc b/src/lnav_commands.cc index fd0ddb3c..10b63f90 100644 --- a/src/lnav_commands.cc +++ b/src/lnav_commands.cc @@ -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& 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& 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().send( [id, fn](auto& tlooper) { @@ -3236,7 +3254,7 @@ com_open(exec_context& ec, std::string cmdline, std::vector& 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 gl; @@ -3253,9 +3271,12 @@ com_open(exec_context& ec, std::string cmdline, std::vector& 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& 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& 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& 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& args) static void sql_prompt(std::vector& 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& 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 diff --git a/src/logfile.cc b/src/logfile.cc index 2f67f8fb..1aa54cc3 100644 --- a/src/logfile.cc +++ b/src/logfile.cc @@ -779,16 +779,28 @@ logfile::rebuild_index(nonstd::optional 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); diff --git a/src/logfile_sub_source.cc b/src/logfile_sub_source.cc index 7aa65ffd..c2bb3020 100644 --- a/src/logfile_sub_source.cc +++ b/src/logfile_sub_source.cc @@ -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()); 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); diff --git a/src/network-extension-functions.cc b/src/network-extension-functions.cc index 6164a40d..af5641a2 100644 --- a/src/network-extension-functions.cc +++ b/src/network-extension-functions.cc @@ -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({ diff --git a/src/prelude.prql b/src/prelude.prql new file mode 100644 index 00000000..4e422609 --- /dev/null +++ b/src/prelude.prql @@ -0,0 +1,2 @@ + +let json_each = func input -> s"SELECT * FROM json_each({input})" diff --git a/src/readline_callbacks.cc b/src/readline_callbacks.cc index 2d449622..b3f9ed3b 100644 --- a/src/readline_callbacks.cc +++ b/src/readline_callbacks.cc @@ -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(); + + const auto line = rc->get_line_buffer(); + std::vector 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; +} diff --git a/src/readline_callbacks.hh b/src/readline_callbacks.hh index d89f4b18..65429c2a 100644 --- a/src/readline_callbacks.hh +++ b/src/readline_callbacks.hh @@ -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; diff --git a/src/readline_context.hh b/src/readline_context.hh index 33322784..75662926 100644 --- a/src/readline_context.hh +++ b/src/readline_context.hh @@ -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 s_args; + }; + + struct split_result_t { + std::vector 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 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 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 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 rc_prefixes; @@ -176,11 +196,13 @@ private: HISTORY_STATE rc_history; std::map> rc_possibilities; std::map> rc_prototypes; + std::map rc_commands; bool rc_case_sensitive; int rc_append_character; const char* rc_quote_chars; readline_highlighter_t rc_highlighter; std::vector rc_vars; + splitter_func_t rc_splitter; }; #endif diff --git a/src/readline_curses.cc b/src/readline_curses.cc index c6d151cf..9d07e247 100644 --- a/src/readline_curses.cc +++ b/src/readline_curses.cc @@ -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& 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]); } diff --git a/src/readline_highlighters.cc b/src/readline_highlighters.cc index e18278f6..8e08fd55 100644 --- a/src/readline_highlighters.cc +++ b/src/readline_highlighters.cc @@ -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)); } } diff --git a/src/readline_possibilities.cc b/src/readline_possibilities.cc index 7a212200..9d74c0e0 100644 --- a/src/readline_possibilities.cc +++ b/src/readline_possibilities.cc @@ -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); +} diff --git a/src/readline_possibilities.hh b/src/readline_possibilities.hh index 57c3a25d..8f812b2c 100644 --- a/src/readline_possibilities.hh +++ b/src/readline_possibilities.hh @@ -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; diff --git a/src/sql_commands.cc b/src/sql_commands.cc index 7c0f3784..593fcf16 100644 --- a/src/sql_commands.cc +++ b/src/sql_commands.cc @@ -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 +prql_cmd_from(exec_context& ec, + std::string cmdline, + std::vector& 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(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 +prql_cmd_aggregate(exec_context& ec, + std::string cmdline, + std::vector& args) +{ + std::string retval; + + if (args.empty()) { + args.emplace_back("prql-expr"); + return Ok(retval); + } + + return Ok(retval); +} + +static Result +prql_cmd_append(exec_context& ec, + std::string cmdline, + std::vector& args) +{ + std::string retval; + + if (args.empty()) { + args.emplace_back("prql-table"); + return Ok(retval); + } + + return Ok(retval); +} + +static Result +prql_cmd_derive(exec_context& ec, + std::string cmdline, + std::vector& args) +{ + std::string retval; + + if (args.empty()) { + args.emplace_back("prql-expr"); + return Ok(retval); + } + + return Ok(retval); +} + +static Result +prql_cmd_filter(exec_context& ec, + std::string cmdline, + std::vector& args) +{ + std::string retval; + + if (args.empty()) { + args.emplace_back("prql-expr"); + return Ok(retval); + } + + return Ok(retval); +} + +static Result +prql_cmd_group(exec_context& ec, + std::string cmdline, + std::vector& 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 +prql_cmd_join(exec_context& ec, + std::string cmdline, + std::vector& 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 +prql_cmd_select(exec_context& ec, + std::string cmdline, + std::vector& args) +{ + std::string retval; + + if (args.empty()) { + args.emplace_back("prql-expr"); + return Ok(retval); + } + + return Ok(retval); +} + +static Result +prql_cmd_sort(exec_context& ec, + std::string cmdline, + std::vector& args) +{ + std::string retval; + + if (args.empty()) { + args.emplace_back("prql-expr"); + return Ok(retval); + } + + return Ok(retval); +} + +static Result +prql_cmd_take(exec_context& ec, + std::string cmdline, + std::vector& 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; diff --git a/src/sql_help.hh b/src/sql_help.hh index 08bcc519..4eaa77d8 100644 --- a/src/sql_help.hh +++ b/src/sql_help.hh @@ -33,6 +33,7 @@ #define sql_help_hh #include +#include #include "base/attr_line.hh" #include "help_text.hh" @@ -50,10 +51,45 @@ extern string_attr_type SQL_COMMENT_ATTR; void annotate_sql_statement(attr_line_t& al_inout); -extern std::multimap sqlite_function_help; +extern std::multimap sqlite_function_help; std::string sql_keyword_re(); std::vector find_sql_help_for_line(const attr_line_t& al, size_t x); +namespace lnav { +namespace sql { + +extern string_attr_type PRQL_STAGE_ATTR; +extern string_attr_type PRQL_TRANSFORM_ATTR; +extern string_attr_type PRQL_KEYWORD_ATTR; +extern string_attr_type PRQL_IDENTIFIER_ATTR; +extern string_attr_type PRQL_FQID_ATTR; +extern string_attr_type PRQL_PIPE_ATTR; +extern string_attr_type PRQL_DOT_ATTR; +extern string_attr_type PRQL_STRING_ATTR; +extern string_attr_type PRQL_NUMBER_ATTR; +extern string_attr_type PRQL_OPERATOR_ATTR; +extern string_attr_type PRQL_PAREN_ATTR; +extern string_attr_type PRQL_UNTERMINATED_PAREN_ATTR; +extern string_attr_type PRQL_GARBAGE_ATTR; +extern string_attr_type 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 prql_functions; + +} // namespace sql + +namespace prql { + +std::string quote_ident(std::string id); + +} + +} // namespace lnav + #endif diff --git a/src/sql_util.cc b/src/sql_util.cc index 1bf78125..8badb62f 100644 --- a/src/sql_util.cc +++ b/src/sql_util.cc @@ -285,7 +285,7 @@ const std::unordered_map sql_constraint_names = { #endif }; -std::multimap sqlite_function_help; +std::multimap 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(); + auto sa_opt = get_string_attr(al.get_attrs(), &SQL_COMMAND_ATTR); if (sa_opt) { - auto* sql_cmd_map = injector::get(); 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 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 PRQL_STAGE_ATTR("prql_stage"); +string_attr_type PRQL_TRANSFORM_ATTR("prql_transform"); +string_attr_type PRQL_KEYWORD_ATTR("prql_keyword"); +string_attr_type PRQL_IDENTIFIER_ATTR("prql_ident"); +string_attr_type PRQL_FQID_ATTR("prql_fqid"); +string_attr_type PRQL_DOT_ATTR("prql_dot"); +string_attr_type PRQL_PIPE_ATTR("prql_pipe"); +string_attr_type PRQL_STRING_ATTR("prql_string"); +string_attr_type PRQL_NUMBER_ATTR("prql_number"); +string_attr_type PRQL_OPERATOR_ATTR("prql_oper"); +string_attr_type PRQL_PAREN_ATTR("prql_paren"); +string_attr_type PRQL_UNTERMINATED_PAREN_ATTR("prql_unterminated_paren"); +string_attr_type PRQL_GARBAGE_ATTR("prql_garbage"); +string_attr_type 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* 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{}; + std::vector> groups; + std::vector fqids; + nonstd::optional 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 diff --git a/src/sqlite-extension-func.cc b/src/sqlite-extension-func.cc index 19991cdd..ac9d7fc4 100644 --- a/src/sqlite-extension-func.cc +++ b/src/sqlite-extension-func.cc @@ -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 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 ph_modules; + std::map 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() diff --git a/src/sqlite-extension-func.hh b/src/sqlite-extension-func.hh index 1e62a6a6..57045023 100644 --- a/src/sqlite-extension-func.hh +++ b/src/sqlite-extension-func.hh @@ -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( diff --git a/src/state-extension-functions.cc b/src/state-extension-functions.cc index 119ca861..e7aca121 100644 --- a/src/state-extension-functions.cc +++ b/src/state-extension-functions.cc @@ -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:: 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:: 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:: 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:: builder( help_text("lnav_version", "Return the current version of lnav") - .sql_function()), + .sql_function() + .with_prql_path({"lnav", "version"})), sqlite_func_adapter::builder( help_text("raise_error", diff --git a/src/string-extension-functions.cc b/src/string-extension-functions.cc index 0e8804da..604d6922 100644 --- a/src/string-extension-functions.cc +++ b/src/string-extension-functions.cc @@ -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({ diff --git a/src/tailer/tailer.looper.cc b/src/tailer/tailer.looper.cc index e815fbef..9986653e 100644 --- a/src/tailer/tailer.looper.cc +++ b/src/tailer/tailer.looper.cc @@ -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); diff --git a/src/text_format.cc b/src/text_format.cc index 0f2d14fe..dc63cf66 100644 --- a/src/text_format.cc +++ b/src/text_format.cc @@ -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" diff --git a/src/third-party/prqlc-c/Cargo.lock b/src/third-party/prqlc-c/Cargo.lock new file mode 100644 index 00000000..076084b3 --- /dev/null +++ b/src/third-party/prqlc-c/Cargo.lock @@ -0,0 +1,1527 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" +dependencies = [ + "backtrace", +] + +[[package]] +name = "ariadne" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd002a6223f12c7a95cdd4b1cb3a0149d22d37f7a9ecdb2cb691a071fe236c29" +dependencies = [ + "unicode-width", + "yansi", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + +[[package]] +name = "bumpalo" +version = "3.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" + +[[package]] +name = "cc" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf5903dcbc0a39312feb77df2ff4c76387d591b9fc7b04a238dcf8bb62639a" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.52.4", +] + +[[package]] +name = "chumsky" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9" +dependencies = [ + "hashbrown", + "stacker", +] + +[[package]] +name = "clap" +version = "4.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b230ab84b0ffdf890d5a10abdbc8b83ae1c4918275daea1ab8801f71536b2651" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", + "terminal_size", +] + +[[package]] +name = "clap_complete" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "885e4d7d5af40bfb99ae6f9433e292feac98d452dcb3ec3d25dfe7552b77da8c" +dependencies = [ + "clap", +] + +[[package]] +name = "clap_complete_command" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "183495371ea78d4c9ff638bfc6497d46fed2396e4f9c50aebc1278a4a9919a3d" +dependencies = [ + "clap", + "clap_complete", + "clap_complete_fig", + "clap_complete_nushell", +] + +[[package]] +name = "clap_complete_fig" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b3e65f91fabdd23cac3d57d39d5d938b4daabd070c335c006dccb866a61110" +dependencies = [ + "clap", + "clap_complete", +] + +[[package]] +name = "clap_complete_nushell" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d02bc8b1a18ee47c4d2eec3fb5ac034dc68ebea6125b1509e9ccdffcddce66e" +dependencies = [ + "clap", + "clap_complete", +] + +[[package]] +name = "clap_derive" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + +[[package]] +name = "clio" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7fc6734af48458f72f5a3fa7b840903606427d98a710256e808f76a965047d9" +dependencies = [ + "cfg-if", + "clap", + "is-terminal", + "libc", + "tempfile", + "walkdir", + "windows-sys 0.42.0", +] + +[[package]] +name = "color-eyre" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "colorchoice-clap" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe9ad5d064caf028aeb50818a5c7857c28f746ad04111652b85d9bca8b0d0be" +dependencies = [ + "clap", + "colorchoice", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "crossbeam-channel" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" + +[[package]] +name = "csv" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" +dependencies = [ + "memchr", +] + +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "enum-as-inner" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ffccbb6966c05b32ef8fbac435df276c4ae4d3dc55a8cd0eb9745e6c12f546a" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "filetime" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys 0.52.0", +] + +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "2.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + +[[package]] +name = "is-terminal" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "kqueue" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "minijinja" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673d1ece89f7e166f43270800d78f9b1a9d21fda92dbcfa3b98b21213cdccbbc" +dependencies = [ + "serde", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "notify" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" +dependencies = [ + "bitflags 2.4.2", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "walkdir", + "windows-sys 0.48.0", +] + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prqlc" +version = "0.11.5" +source = "git+https://github.com/PRQL/prql.git#1d2b3c644b024642301494bbecee84c45106cca1" +dependencies = [ + "anstream", + "anyhow", + "ariadne", + "atty", + "chrono", + "clap", + "clap_complete", + "clap_complete_command", + "clio", + "color-eyre", + "colorchoice-clap", + "csv", + "enum-as-inner", + "env_logger", + "itertools", + "log", + "minijinja", + "notify", + "once_cell", + "prqlc-ast", + "prqlc-parser", + "regex", + "semver", + "serde", + "serde_json", + "serde_yaml", + "sqlformat", + "sqlparser", + "strum", + "strum_macros", + "walkdir", +] + +[[package]] +name = "prqlc-ast" +version = "0.11.5" +source = "git+https://github.com/PRQL/prql.git#1d2b3c644b024642301494bbecee84c45106cca1" +dependencies = [ + "enum-as-inner", + "semver", + "serde", + "strum", +] + +[[package]] +name = "prqlc-c" +version = "0.11.3" +dependencies = [ + "libc", + "prqlc", + "serde_json", +] + +[[package]] +name = "prqlc-parser" +version = "0.11.5" +source = "git+https://github.com/PRQL/prql.git#1d2b3c644b024642301494bbecee84c45106cca1" +dependencies = [ + "chumsky", + "itertools", + "prqlc-ast", + "semver", + "serde", + "stacker", +] + +[[package]] +name = "psm" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +dependencies = [ + "cc", +] + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "rustc-demangle" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" + +[[package]] +name = "rustix" +version = "0.38.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +dependencies = [ + "bitflags 2.4.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "semver" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_yaml" +version = "0.9.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd075d994154d4a774f95b51fb96bdc2832b0ea48425c92546073816cda1f2f" +dependencies = [ + "indexmap", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "sqlformat" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce81b7bd7c4493975347ef60d8c7e8b742d4694f4c49f93e0a12ea263938176c" +dependencies = [ + "itertools", + "nom", + "unicode_categories", +] + +[[package]] +name = "sqlparser" +version = "0.43.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f95c4bae5aba7cd30bd506f7140026ade63cff5afd778af8854026f9606bf5d4" +dependencies = [ + "log", + "serde", +] + +[[package]] +name = "stacker" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce" +dependencies = [ + "cc", + "cfg-if", + "libc", + "psm", + "winapi", +] + +[[package]] +name = "strsim" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + +[[package]] +name = "strum" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "723b93e8addf9aa965ebe2d11da6d7540fa2283fcea14b3371ff055f7ba13f5f" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a3417fc93d76740d974a01654a09777cb500428cc874ca9f45edfe0c4d4cd18" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + +[[package]] +name = "syn" +version = "2.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "terminal_size" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +dependencies = [ + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "sharded-slab", + "thread_local", + "tracing-core", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "unicode_categories" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" + +[[package]] +name = "unsafe-libyaml" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.4", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.4", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + +[[package]] +name = "zerocopy" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/src/third-party/prqlc-c/Cargo.toml b/src/third-party/prqlc-c/Cargo.toml new file mode 100644 index 00000000..682df43c --- /dev/null +++ b/src/third-party/prqlc-c/Cargo.toml @@ -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 = "" diff --git a/src/third-party/prqlc-c/README.md b/src/third-party/prqlc-c/README.md new file mode 100644 index 00000000..d816274b --- /dev/null +++ b/src/third-party/prqlc-c/README.md @@ -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 + +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. diff --git a/src/third-party/prqlc-c/cbindgen.toml b/src/third-party/prqlc-c/cbindgen.toml new file mode 100644 index 00000000..1f523e7d --- /dev/null +++ b/src/third-party/prqlc-c/cbindgen.toml @@ -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"' diff --git a/src/third-party/prqlc-c/prqlc.h b/src/third-party/prqlc-c/prqlc.h new file mode 100644 index 00000000..aa9fa275 --- /dev/null +++ b/src/third-party/prqlc-c/prqlc.h @@ -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 +#include +#include +#include +#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); diff --git a/src/third-party/prqlc-c/prqlc.hpp b/src/third-party/prqlc-c/prqlc.hpp new file mode 100644 index 00000000..e07e90d0 --- /dev/null +++ b/src/third-party/prqlc-c/prqlc.hpp @@ -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 +#include +#include +#include +#include +#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 diff --git a/src/third-party/prqlc-c/src/lib.rs b/src/third-party/prqlc-c/src/lib.rs new file mode 100644 index 00000000..90c9b19e --- /dev/null +++ b/src/third-party/prqlc-c/src/lib.rs @@ -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) -> 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(o: Option) -> *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 { + 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, + }) +} diff --git a/src/time-extension-functions.cc b/src/time-extension-functions.cc index 1ca688b3..b6567fd1 100644 --- a/src/time-extension-functions.cc +++ b/src/time-extension-functions.cc @@ -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::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"}) diff --git a/src/view_helpers.cc b/src/view_helpers.cc index b99e4c19..e9b9722f 100644 --- a/src/view_helpers.cc +++ b/src/view_helpers.cc @@ -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 sql_funcs; - std::map sql_keywords; + std::map sql_funcs; + std::map 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 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(); + + 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; -} \ No newline at end of file +} + +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); + } +} diff --git a/src/view_helpers.hh b/src/view_helpers.hh index 1d64161c..2771edfe 100644 --- a/src/view_helpers.hh +++ b/src/view_helpers.hh @@ -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 next_cluster( nonstd::optional (bookmark_vector::*f)(vis_line_t) diff --git a/src/views_vtab.cc b/src/views_vtab.cc index e069c2ac..9f025aae 100644 --- a/src/views_vtab.cc +++ b/src/views_vtab.cc @@ -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; } diff --git a/src/yaml-extension-functions.cc b/src/yaml-extension-functions.cc index 27da6202..e19af499 100644 --- a/src/yaml-extension-functions.cc +++ b/src/yaml-extension-functions.cc @@ -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({ diff --git a/test/Makefile.am b/test/Makefile.am index 56d91d24..d850592d 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -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 \ diff --git a/test/expected/expected.am b/test/expected/expected.am index ed8b7f82..471b70bc 100644 --- a/test/expected/expected.am +++ b/test/expected/expected.am @@ -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 \ diff --git a/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out b/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out index 6c4dca87..76a4112c 100644 --- a/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out +++ b/test/expected/test_cmds.sh_b6a3bb78e9d60e5e1f5ce5b18e40d2f1662707ab.out @@ -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  diff --git a/test/expected/test_prql.sh_451e242cdfa2db9005d4fe752a7b05d1ab5cba29.err b/test/expected/test_prql.sh_451e242cdfa2db9005d4fe752a7b05d1ab5cba29.err new file mode 100644 index 00000000..d9fb3f25 --- /dev/null +++ b/test/expected/test_prql.sh_451e242cdfa2db9005d4fe752a7b05d1ab5cba29.err @@ -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 + ────╯ + diff --git a/test/expected/test_prql.sh_451e242cdfa2db9005d4fe752a7b05d1ab5cba29.out b/test/expected/test_prql.sh_451e242cdfa2db9005d4fe752a7b05d1ab5cba29.out new file mode 100644 index 00000000..e69de29b diff --git a/test/expected/test_prql.sh_45d57a042092ffdcd28ea35a892f02859e78f33d.err b/test/expected/test_prql.sh_45d57a042092ffdcd28ea35a892f02859e78f33d.err new file mode 100644 index 00000000..e69de29b diff --git a/test/expected/test_prql.sh_45d57a042092ffdcd28ea35a892f02859e78f33d.out b/test/expected/test_prql.sh_45d57a042092ffdcd28ea35a892f02859e78f33d.out new file mode 100644 index 00000000..73c7f8c3 --- /dev/null +++ b/test/expected/test_prql.sh_45d57a042092ffdcd28ea35a892f02859e78f33d.out @@ -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   2009-07-20 22:59:26.000  0 info   0         192.168.202.254 GET  -    /vmw/cgi/tramp gPXE/0.9.7  -  HTTP/1.0   134  200  diff --git a/test/expected/test_sessions.sh_6d87ff483d5785c58fb271a405ff1c35e4f83cd9.out b/test/expected/test_sessions.sh_6d87ff483d5785c58fb271a405ff1c35e4f83cd9.out index a00d2f9b..36d43be2 100644 --- a/test/expected/test_sessions.sh_6d87ff483d5785c58fb271a405ff1c35e4f83cd9.out +++ b/test/expected/test_sessions.sh_6d87ff483d5785c58fb271a405ff1c35e4f83cd9.out @@ -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: diff --git a/test/expected/test_sessions.sh_e988439404f2e97604641c8d087855f3efe052e4.out b/test/expected/test_sessions.sh_e988439404f2e97604641c8d087855f3efe052e4.out index 29ad4dd7..b861e0eb 100644 --- a/test/expected/test_sessions.sh_e988439404f2e97604641c8d087855f3efe052e4.out +++ b/test/expected/test_sessions.sh_e988439404f2e97604641c8d087855f3efe052e4.out @@ -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: diff --git a/test/test_prql.sh b/test/test_prql.sh new file mode 100644 index 00000000..2bfb58b8 --- /dev/null +++ b/test/test_prql.sh @@ -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