/** * Copyright (c) 2021, Timothy Stack * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of Timothy Stack nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "base/auto_mem.hh" #include "base/fs_util.hh" #include "base/injector.bind.hh" #include "base/itertools.hh" #include "base/lnav_log.hh" #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" static Result sql_cmd_dump(exec_context& ec, std::string cmdline, std::vector& args) { static auto& lnav_db = injector::get(); static auto& lnav_flags = injector::get(); std::string retval; if (args.empty()) { args.emplace_back("filename"); args.emplace_back("tables"); return Ok(retval); } if (args.size() < 2) { return ec.make_error("expecting a file name to write to"); } if (lnav_flags & LNF_SECURE_MODE) { return ec.make_error("{} -- unavailable in secure mode", args[0]); } auto_mem file(fclose); if ((file = fopen(args[1].c_str(), "w+")) == nullptr) { return ec.make_error( "unable to open '{}' for writing: {}", args[1], strerror(errno)); } for (size_t lpc = 2; lpc < args.size(); lpc++) { sqlite3_db_dump(lnav_db.in(), "main", args[lpc].c_str(), (int (*)(const char*, void*)) fputs, file.in()); } retval = "generated"; return Ok(retval); } static Result sql_cmd_read(exec_context& ec, std::string cmdline, std::vector& args) { static const intern_string_t SRC = intern_string::lookup("cmdline"); static auto& lnav_db = injector::get(); static auto& lnav_flags = injector::get(); std::string retval; if (args.empty()) { args.emplace_back("filename"); return Ok(retval); } if (lnav_flags & LNF_SECURE_MODE) { return ec.make_error("{} -- unavailable in secure mode", args[0]); } shlex lexer(cmdline); auto split_args_res = lexer.split(ec.create_resolver()); if (split_args_res.isErr()) { auto split_err = split_args_res.unwrapErr(); auto um = lnav::console::user_message::error("unable to parse file name") .with_reason(split_err.te_msg) .with_snippet(lnav::console::snippet::from( SRC, lexer.to_attr_line(split_err))); return Err(um); } auto split_args = split_args_res.unwrap() | lnav::itertools::map([](const auto& elem) { return elem.se_value; }); for (size_t lpc = 1; lpc < split_args.size(); lpc++) { auto read_res = lnav::filesystem::read_file(split_args[lpc]); if (read_res.isErr()) { return ec.make_error("unable to read script file: {} -- {}", split_args[lpc], read_res.unwrapErr()); } auto script = read_res.unwrap(); auto_mem stmt(sqlite3_finalize); const char* start = script.c_str(); do { const char* tail; auto rc = sqlite3_prepare_v2( lnav_db.in(), start, -1, stmt.out(), &tail); if (rc != SQLITE_OK) { const char* errmsg = sqlite3_errmsg(lnav_db.in()); return ec.make_error("{}", errmsg); } if (stmt.in() != nullptr) { std::string alt_msg; auto exec_res = execute_sql( ec, std::string(start, tail - start), alt_msg); if (exec_res.isErr()) { return exec_res; } } start = tail; } while (start[0]); } return Ok(retval); } static Result sql_cmd_schema(exec_context& ec, std::string cmdline, std::vector& args) { std::string retval; if (args.empty()) { return Ok(retval); } ensure_view(LNV_SCHEMA); return Ok(retval); } static Result sql_cmd_msgformats(exec_context& ec, std::string cmdline, std::vector& args) { static const std::string MSG_FORMAT_STMT = R"( SELECT count(*) AS total, min(log_line) AS log_line, min(log_time) AS log_time, humanize_duration(timediff(max(log_time), min(log_time))) AS duration, group_concat(DISTINCT log_format) AS log_formats, log_msg_format FROM all_logs WHERE log_msg_format != '' GROUP BY log_msg_format HAVING total > 1 ORDER BY total DESC, log_line ASC )"; std::string retval; if (args.empty()) { return Ok(retval); } std::string alt; return execute_sql(ec, MSG_FORMAT_STMT, alt); } static Result sql_cmd_generic(exec_context& ec, std::string cmdline, std::vector& args) { std::string retval; if (args.empty()) { args.emplace_back("*"); return Ok(retval); } 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", sql_cmd_dump, help_text(".dump", "Dump the contents of the database") .sql_command() .with_parameter({"path", "The path to the file to write"}) .with_tags({ "io", }), }, { ".msgformats", sql_cmd_msgformats, help_text(".msgformats", "Executes a query that will summarize the different message " "formats found in the logs") .sql_command(), }, { ".read", sql_cmd_read, help_text(".read", "Execute the SQLite statements in the given file") .sql_command() .with_parameter({"path", "The path to the file to write"}) .with_tags({ "io", }), }, { ".schema", sql_cmd_schema, help_text(".schema", "Switch to the SCHEMA view that contains a dump of the " "current database schema") .sql_command(), }, { "ATTACH", sql_cmd_generic, }, { "CREATE", sql_cmd_generic, }, { "DELETE", sql_cmd_generic, }, { "DETACH", sql_cmd_generic, }, { "DROP", sql_cmd_generic, }, { "INSERT", sql_cmd_generic, }, { "SELECT", sql_cmd_generic, }, { "UPDATE", sql_cmd_generic, }, { "WITH", sql_cmd_generic, }, { "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; static auto bound_sql_cmd_map = injector::bind::to_instance(+[]() { for (auto& cmd : sql_commands) { sql_cmd_map[cmd.c_name] = &cmd; } return &sql_cmd_map; }); namespace injector { template<> void force_linking(sql_cmd_map_tag anno) { } } // namespace injector