• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tstack / lnav / 22752382176-2819

06 Mar 2026 06:42AM UTC coverage: 68.915% (-0.01%) from 68.925%
22752382176-2819

push

github

tstack
[timeline] some tweaks for recent change

0 of 1 new or added line in 1 file covered. (0.0%)

22 existing lines in 2 files now uncovered.

52047 of 75524 relevant lines covered (68.91%)

443776.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

82.14
/src/command_executor.cc
1
/**
2
 * Copyright (c) 2015, Timothy Stack
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 * * Redistributions of source code must retain the above copyright notice, this
10
 * list of conditions and the following disclaimer.
11
 * * Redistributions in binary form must reproduce the above copyright notice,
12
 * this list of conditions and the following disclaimer in the documentation
13
 * and/or other materials provided with the distribution.
14
 * * Neither the name of Timothy Stack nor the names of its contributors
15
 * may be used to endorse or promote products derived from this software
16
 * without specific prior written permission.
17
 *
18
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
19
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
22
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
 */
29

30
#include <vector>
31

32
#include "command_executor.hh"
33

34
#include "base/ansi_scrubber.hh"
35
#include "base/ansi_vars.hh"
36
#include "base/fs_util.hh"
37
#include "base/humanize.hh"
38
#include "base/injector.hh"
39
#include "base/itertools.hh"
40
#include "base/paths.hh"
41
#include "base/string_util.hh"
42
#include "bound_tags.hh"
43
#include "breadcrumb_curses.hh"
44
#include "config.h"
45
#include "curl_looper.hh"
46
#include "db_sub_source.hh"
47
#include "help_text_formatter.hh"
48
#include "lnav.hh"
49
#include "lnav.indexing.hh"
50
#include "lnav.prompt.hh"
51
#include "lnav_config.hh"
52
#include "lnav_util.hh"
53
#include "log_format_loader.hh"
54
#include "prql-modules.h"
55
#include "readline_highlighters.hh"
56
#include "service_tags.hh"
57
#include "shlex.hh"
58
#include "sql_help.hh"
59
#include "sql_util.hh"
60
#include "vtab_module.hh"
61

62
#ifdef HAVE_RUST_DEPS
63
#    include "lnav_rs_ext.cxx.hh"
64
#endif
65

66
using namespace std::literals::chrono_literals;
67
using namespace lnav::roles::literals;
68

69
exec_context INIT_EXEC_CONTEXT;
70

71
static sig_atomic_t sql_counter = 0;
72

73
int
74
sql_progress(const log_cursor& lc)
11,750✔
75
{
76
    if (lnav_data.ld_window == nullptr) {
11,750✔
77
        return 0;
11,739✔
78
    }
79

80
    if (!lnav_data.ld_looping) {
11✔
81
        return 1;
×
82
    }
83

84
    if (ui_periodic_timer::singleton().time_to_update(sql_counter)) {
11✔
85
        auto* breadcrumb_view = injector::get<breadcrumb_curses*>();
5✔
86
        breadcrumb_view->set_enabled(false);
5✔
87
        lnav_data.ld_status[LNS_TOP].set_enabled(false);
5✔
88
        lnav_data.ld_view_stack.top() |
5✔
89
            [](auto* tc) { tc->set_enabled(false); };
3✔
90
        ssize_t total = lnav_data.ld_log_source.text_line_count();
5✔
91
        off_t off = lc.lc_curr_line;
5✔
92

93
        if (off >= 0 && off <= total) {
5✔
94
            lnav_data.ld_bottom_source.update_loading(off, total);
5✔
95
            lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
5✔
96
        }
97
        lnav::prompt::get().p_editor.clear_alt_value();
5✔
98
        auto refresh_res
99
            = lnav_data.ld_status_refresher(lnav::func::op_type::blocking);
5✔
100
        if (refresh_res == lnav::progress_result_t::interrupt) {
5✔
101
            return 1;
2✔
102
        }
103
    }
104

105
    return 0;
9✔
106
}
107

108
void
109
sql_progress_finished()
1,639✔
110
{
111
    if (sql_counter == 0) {
1,639✔
112
        return;
1,634✔
113
    }
114

115
    sql_counter = 0;
5✔
116

117
    if (lnav_data.ld_window == nullptr) {
5✔
118
        return;
×
119
    }
120

121
    auto* breadcrumb_view = injector::get<breadcrumb_curses*>();
5✔
122
    breadcrumb_view->set_enabled(true);
5✔
123
    lnav_data.ld_status[LNS_TOP].set_enabled(true);
5✔
124
    lnav_data.ld_view_stack.top() | [](auto* tc) { tc->set_enabled(true); };
8✔
125
    lnav_data.ld_bottom_source.update_loading(0, 0);
5✔
126
    lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
5✔
127
    lnav_data.ld_status_refresher(lnav::func::op_type::blocking);
5✔
128
    lnav_data.ld_views[LNV_DB].redo_search();
5✔
129
}
130

131
static Result<std::string, lnav::console::user_message> execute_from_file(
132
    exec_context& ec,
133
    const std::string& src,
134
    int line_number,
135
    const std::string& cmdline);
136

137
Result<std::string, lnav::console::user_message>
138
execute_command(exec_context& ec, const std::string& cmdline)
754✔
139
{
140
    static auto op = lnav_operation{__FUNCTION__};
754✔
141

142
    auto op_guard = lnav_opid_guard::internal(op);
754✔
143
    std::vector<std::string> args;
754✔
144

145
    log_info("Executing command: %s", cmdline.c_str());
754✔
146

147
    split_ws(cmdline, args);
754✔
148

149
    if (!args.empty()) {
754✔
150
        readline_context::command_map_t::iterator iter;
754✔
151

152
        if ((iter = lnav_commands.find(args[0])) == lnav_commands.end()) {
754✔
153
            return ec.make_error("unknown command - {}", args[0]);
×
154
        }
155

156
        ec.ec_current_help = &iter->second->c_help;
754✔
157
        try {
158
            auto retval = iter->second->c_func(ec, cmdline, args);
754✔
159
            if (retval.isErr()) {
754✔
160
                auto um = retval.unwrapErr();
46✔
161

162
                ec.add_error_context(um);
46✔
163
                ec.ec_current_help = nullptr;
46✔
164
                return Err(um);
46✔
165
            }
46✔
166
            ec.ec_current_help = nullptr;
708✔
167

168
            return retval;
708✔
169
        } catch (const std::exception& e) {
754✔
170
            auto um = lnav::console::user_message::error(
×
171
                          attr_line_t("unexpected error while executing: ")
×
172
                              .append(lnav::roles::quoted_code(cmdline)))
×
173
                          .with_reason(e.what());
×
174
            return Err(um);
×
175
        }
×
176
    }
177

178
    return ec.make_error("no command to execute");
×
179
}
754✔
180

181
static Result<std::map<std::string, scoped_value_t>,
182
              lnav::console::user_message>
183
bind_sql_parameters(exec_context& ec, sqlite3_stmt* stmt)
1,662✔
184
{
185
    std::map<std::string, scoped_value_t> retval;
1,662✔
186
    auto param_count = sqlite3_bind_parameter_count(stmt);
1,662✔
187
    for (int lpc = 0; lpc < param_count; lpc++) {
1,834✔
188
        std::map<std::string, std::string>::iterator ov_iter;
173✔
189
        const auto* name = sqlite3_bind_parameter_name(stmt, lpc + 1);
173✔
190
        if (name == nullptr) {
173✔
191
            auto um
192
                = lnav::console::user_message::error("invalid SQL statement")
2✔
193
                      .with_reason(
2✔
194
                          "using a question-mark (?) for bound variables "
195
                          "is not supported, only named bound parameters "
196
                          "are supported")
197
                      .with_help(
2✔
198
                          "named parameters start with a dollar-sign "
199
                          "($) or colon (:) followed by the variable name")
200
                      .move();
1✔
201
            ec.add_error_context(um);
1✔
202

203
            return Err(um);
1✔
204
        }
1✔
205

206
        if (name[0] == '$') {
172✔
207
            const auto& lvars = ec.ec_local_vars.top();
145✔
208
            const auto& gvars = ec.ec_global_vars;
145✔
209
            std::map<std::string, scoped_value_t>::const_iterator local_var,
145✔
210
                global_var;
145✔
211
            const char* env_value;
212

213
            if (lnav_data.ld_window) {
145✔
214
                char buf[32];
215
                unsigned int lines, cols;
216

217
                ncplane_dim_yx(lnav_data.ld_window, &lines, &cols);
12✔
218
                if (strcmp(name, "$LINES") == 0) {
12✔
219
                    snprintf(buf, sizeof(buf), "%d", lines);
×
220
                    sqlite3_bind_text(stmt, lpc + 1, buf, -1, SQLITE_TRANSIENT);
×
221
                } else if (strcmp(name, "$COLS") == 0) {
12✔
222
                    snprintf(buf, sizeof(buf), "%d", cols);
×
223
                    sqlite3_bind_text(stmt, lpc + 1, buf, -1, SQLITE_TRANSIENT);
×
224
                }
225
            }
226

227
            if ((local_var = lvars.find(&name[1])) != lvars.end()) {
435✔
228
                mapbox::util::apply_visitor(
126✔
229
                    sqlitepp::bind_visitor(stmt, lpc + 1), local_var->second);
126✔
230
                retval[name] = local_var->second;
378✔
231
            } else if ((global_var = gvars.find(&name[1])) != gvars.end()) {
57✔
232
                mapbox::util::apply_visitor(
6✔
233
                    sqlitepp::bind_visitor(stmt, lpc + 1), global_var->second);
6✔
234
                retval[name] = global_var->second;
18✔
235
            } else if ((env_value = getenv(&name[1])) != nullptr) {
13✔
236
                sqlite3_bind_text(stmt, lpc + 1, env_value, -1, SQLITE_STATIC);
3✔
237
                retval[name] = string_fragment::from_c_str(env_value);
9✔
238
            }
239
        } else if (name[0] == ':' && ec.ec_line_values != nullptr) {
27✔
240
            for (auto& lv : ec.ec_line_values->lvv_values) {
149✔
241
                if (lv.lv_meta.lvm_name != &name[1]) {
122✔
242
                    continue;
95✔
243
                }
244
                switch (lv.lv_meta.lvm_kind) {
27✔
245
                    case value_kind_t::VALUE_BOOLEAN:
×
246
                        sqlite3_bind_int64(stmt, lpc + 1, lv.lv_value.i);
×
247
                        retval[name] = fmt::to_string(lv.lv_value.i);
×
248
                        break;
×
249
                    case value_kind_t::VALUE_FLOAT:
×
250
                        sqlite3_bind_double(stmt, lpc + 1, lv.lv_value.d);
×
251
                        retval[name] = fmt::to_string(lv.lv_value.d);
×
252
                        break;
×
253
                    case value_kind_t::VALUE_INTEGER:
×
254
                        sqlite3_bind_int64(stmt, lpc + 1, lv.lv_value.i);
×
255
                        retval[name] = fmt::to_string(lv.lv_value.i);
×
256
                        break;
×
257
                    case value_kind_t::VALUE_NULL:
×
258
                        sqlite3_bind_null(stmt, lpc + 1);
×
259
                        retval[name] = string_fragment::from_c_str(
×
260
                            db_label_source::NULL_STR);
×
261
                        break;
×
262
                    default:
27✔
263
                        sqlite3_bind_text(stmt,
27✔
264
                                          lpc + 1,
265
                                          lv.text_value(),
266
                                          lv.text_length(),
27✔
267
                                          SQLITE_TRANSIENT);
268
                        retval[name] = lv.to_string();
81✔
269
                        break;
27✔
270
                }
271
            }
272
        } else {
27✔
273
            sqlite3_bind_null(stmt, lpc + 1);
×
274
            log_warning("Could not bind variable: %s", name);
×
275
            retval[name]
×
276
                = string_fragment::from_c_str(db_label_source::NULL_STR);
×
277
        }
278
    }
279

280
    return Ok(retval);
1,661✔
281
}
1,662✔
282

283
static void
284
execute_search(const std::string& search_cmd)
7✔
285
{
286
    textview_curses* tc = get_textview_for_mode(lnav_data.ld_mode);
7✔
287
    auto search_term = string_fragment(search_cmd)
7✔
288
                           .find_right_boundary(0, string_fragment::tag1{'\n'})
7✔
289
                           .to_string();
7✔
290
    tc->execute_search(search_term);
7✔
291
}
7✔
292

293
Result<std::string, lnav::console::user_message>
294
execute_sql(exec_context& ec, const std::string& sql, std::string& alt_msg)
1,677✔
295
{
296
    static auto op = lnav_operation{__FUNCTION__};
1,677✔
297

298
    auto op_guard = lnav_opid_guard::internal(op);
1,677✔
299
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
1,677✔
300
    timeval start_tv, end_tv;
301
    std::string stmt_str = trim(sql);
1,677✔
302
    std::string retval;
1,677✔
303
    int retcode = SQLITE_OK;
1,677✔
304

305
    if (lnav::sql::is_prql(stmt_str)) {
1,677✔
306
        log_info("compiling PRQL: %s", stmt_str.c_str());
42✔
307

308
#if HAVE_RUST_DEPS
309
        extern rust::Vec<lnav_rs_ext::SourceTreeElement> sqlite_extension_prql;
310
        auto opts = lnav_rs_ext::Options{true, "sql.sqlite", true};
42✔
311

312
        auto tree = sqlite_extension_prql;
42✔
313
        for (const auto& mod : lnav_prql_modules) {
126✔
314
            auto name = mod.get_name().to_string();
84✔
315
            log_debug("lnav_rs_ext adding mod %s", name.c_str());
84✔
316
            tree.emplace_back(lnav_rs_ext::SourceTreeElement{
168✔
317
                name.c_str(),
318
                mod.to_string_fragment_producer()->to_string(),
168✔
319
            });
320
        }
84✔
321
        tree.emplace_back(lnav_rs_ext::SourceTreeElement{"", stmt_str});
42✔
322
        log_debug("BEGIN compiling tree");
42✔
323
        auto cr = lnav_rs_ext::compile_tree(tree, opts);
42✔
324
        log_debug("END compiling tree");
42✔
325

326
        for (const auto& msg : cr.messages) {
42✔
327
            if (msg.kind != lnav_rs_ext::MessageKind::Error) {
1✔
328
                continue;
×
329
            }
330

331
            auto stmt_al = attr_line_t(stmt_str);
1✔
332
            readline_sql_highlighter(stmt_al, lnav::sql::dialect::prql, 0);
1✔
333
            auto um
334
                = lnav::console::user_message::error(
×
335
                      attr_line_t("unable to compile PRQL: ").append(stmt_al))
2✔
336
                      .with_reason(
1✔
337
                          attr_line_t::from_ansi_str((std::string) msg.reason));
2✔
338
            if (!msg.display.empty()) {
1✔
339
                um.with_note(
1✔
340
                    attr_line_t::from_ansi_str((std::string) msg.display));
2✔
341
            }
342
            for (const auto& hint : msg.hints) {
1✔
343
                um.with_help(attr_line_t::from_ansi_str((std::string) hint));
×
344
                break;
×
345
            }
346
            return Err(um);
1✔
347
        }
1✔
348
        log_debug("done!");
41✔
349
        stmt_str = (std::string) cr.output;
41✔
350
#else
351
        auto um = lnav::console::user_message::error(
352
            attr_line_t("PRQL is not supported in this build"));
353
        return Err(um);
354
#endif
355
    }
44✔
356

357
    log_info("Executing SQL: %s", stmt_str.c_str());
1,676✔
358

359
    auto old_mode = lnav_data.ld_mode;
1,676✔
360
    lnav_data.ld_mode = ln_mode_t::BUSY;
1,676✔
361
    auto mode_fin = finally([old_mode]() { lnav_data.ld_mode = old_mode; });
1,676✔
362
    lnav_data.ld_bottom_source.grep_error("");
1,676✔
363
    lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
1,676✔
364

365
    if (startswith(stmt_str, ".")) {
1,676✔
366
        std::vector<std::string> args;
7✔
367
        split_ws(stmt_str, args);
7✔
368

369
        const auto* sql_cmd_map
370
            = injector::get<readline_context::command_map_t*,
371
                            sql_cmd_map_tag>();
7✔
372
        auto cmd_iter = sql_cmd_map->find(args[0]);
7✔
373

374
        if (cmd_iter != sql_cmd_map->end()) {
7✔
375
            ec.ec_current_help = &cmd_iter->second->c_help;
7✔
376
            auto retval = cmd_iter->second->c_func(ec, stmt_str, args);
7✔
377
            ec.ec_current_help = nullptr;
7✔
378

379
            return retval;
7✔
380
        }
7✔
381
    }
7✔
382

383
    ec.ec_accumulator->clear();
1,669✔
384

385
    require(!ec.ec_source.empty());
1,669✔
386
    const auto& source = ec.ec_source.back();
1,669✔
387
    sql_progress_guard progress_guard(
388
        sql_progress,
389
        sql_progress_finished,
390
        source.s_location,
391
        source.s_content,
1,669✔
392
        !ec.get_provenance<exec_context::format_rewrite>().has_value());
1,669✔
393
    gettimeofday(&start_tv, nullptr);
1,669✔
394

395
    const auto* curr_stmt = stmt_str.c_str();
1,669✔
396
    auto last_is_readonly = false;
1,669✔
397
    while (curr_stmt != nullptr) {
3,273✔
398
        const char* tail = nullptr;
3,273✔
399
        while (isspace(*curr_stmt)) {
3,273✔
400
            curr_stmt += 1;
×
401
        }
402
        retcode = sqlite3_prepare_v2(
3,273✔
403
            lnav_data.ld_db.in(), curr_stmt, -1, stmt.out(), &tail);
404
        if (retcode != SQLITE_OK) {
3,273✔
405
            const char* errmsg = sqlite3_errmsg(lnav_data.ld_db);
7✔
406

407
            alt_msg = "";
7✔
408

409
            auto um = lnav::console::user_message::error(
14✔
410
                          "failed to compile SQL statement")
411
                          .with_reason(errmsg)
14✔
412
                          .with_snippets(ec.ec_source)
14✔
413
                          .move();
7✔
414

415
            auto annotated_sql = annotate_sql_with_error(
416
                lnav_data.ld_db.in(), curr_stmt, tail);
7✔
417
            auto loc = um.um_snippets.back().s_location;
7✔
418
            if (curr_stmt == stmt_str.c_str()) {
7✔
419
                um.um_snippets.pop_back();
7✔
420
            } else {
421
                auto tail_iter = stmt_str.begin();
×
422

423
                std::advance(tail_iter, (curr_stmt - stmt_str.c_str()));
×
424
                loc.sl_line_number
×
425
                    += std::count(stmt_str.begin(), tail_iter, '\n');
×
426
            }
427

428
            um.with_snippet(lnav::console::snippet::from(loc, annotated_sql));
7✔
429

430
            return Err(um);
7✔
431
        }
7✔
432
        if (stmt == nullptr) {
3,266✔
433
            retcode = SQLITE_DONE;
1,604✔
434
            break;
1,604✔
435
        }
436
#ifdef HAVE_SQLITE3_STMT_READONLY
437
        last_is_readonly = sqlite3_stmt_readonly(stmt.in());
1,662✔
438
        if (ec.is_read_only() && !last_is_readonly) {
1,662✔
439
            return ec.make_error(
440
                "modifying statements are not allowed in this context: {}",
441
                sql);
×
442
        }
443
#endif
444
        bool done = false;
1,662✔
445

446
        auto bound_values = TRY(bind_sql_parameters(ec, stmt.in()));
1,662✔
447
        if (last_is_readonly) {
1,661✔
448
            ec.ec_sql_callback(ec, stmt.in());
1,529✔
449
        }
450
        while (!done) {
5,977✔
451
            retcode = sqlite3_step(stmt.in());
4,373✔
452

453
            if (!ec.ec_label_source_stack.empty()) {
4,373✔
454
                ec.ec_label_source_stack.back()->dls_step_rc = retcode;
4,373✔
455
            }
456

457
            switch (retcode) {
4,373✔
458
                case SQLITE_OK:
1,604✔
459
                case SQLITE_DONE: {
460
                    auto changes = sqlite3_changes(lnav_data.ld_db.in());
1,604✔
461

462
                    log_info("sqlite3_changes() -> %d", changes);
1,604✔
463
                    done = true;
1,604✔
464
                    break;
1,604✔
465
                }
466

467
                case SQLITE_ROW:
2,712✔
468
                    ec.ec_sql_callback(ec, stmt.in());
2,712✔
469
                    break;
2,712✔
470

471
                default: {
57✔
472
                    attr_line_t bound_note;
57✔
473

474
                    if (!bound_values.empty()) {
57✔
475
                        bound_note.append(
3✔
476
                            "the bound parameters are set as follows:\n");
477
                        for (const auto& bval : bound_values) {
11✔
478
                            auto val_as_str = fmt::to_string(bval.second);
8✔
479
                            auto sql_type = bval.second.match(
8✔
480
                                [](const std::string&) { return SQLITE_TEXT; },
5✔
481
                                [](const string_fragment&) {
×
482
                                    return SQLITE_TEXT;
×
483
                                },
484
                                [](bool) { return SQLITE_INTEGER; },
×
485
                                [](int64_t) { return SQLITE_INTEGER; },
1✔
486
                                [](null_value_t) { return SQLITE_NULL; },
1✔
487
                                [](double) { return SQLITE_FLOAT; });
1✔
488
                            auto scrubbed_val = scrub_ws(val_as_str.c_str());
8✔
489
                            truncate_to(scrubbed_val, 40);
8✔
490
                            bound_note.append("  ")
8✔
491
                                .append(lnav::roles::variable(bval.first))
16✔
492
                                .append(":")
8✔
493
                                .append(sqlite3_type_to_string(sql_type))
8✔
494
                                .append(" = ")
8✔
495
                                .append_quoted(scrubbed_val)
16✔
496
                                .append("\n");
8✔
497
                        }
8✔
498
                    }
499

500
                    log_error("sqlite3_step error code: %d", retcode);
57✔
501
                    auto um = sqlite3_error_to_user_message(lnav_data.ld_db)
114✔
502
                                  .with_note(bound_note)
57✔
503
                                  .move();
57✔
504

505
                    if (!ec.get_provenance<exec_context::keyboard_input>()) {
57✔
506
                        um.with_context_snippets(ec.ec_source)
110✔
507
                            .remove_internal_snippets();
55✔
508
                    }
509

510
                    return Err(um);
57✔
511
                }
57✔
512
            }
513
        }
514

515
        curr_stmt = tail;
1,604✔
516
    }
1,661✔
517

518
    if (last_is_readonly && !ec.ec_label_source_stack.empty()) {
1,604✔
519
        auto& dls = *ec.ec_label_source_stack.back();
1,502✔
520
        dls.dls_query_end = std::chrono::steady_clock::now();
1,502✔
521

522
        size_t memory_usage = 0, total_size = 0, cached_chunks = 0;
1,502✔
523
        for (auto cc = dls.dls_cell_container.cc_first.get(); cc != nullptr;
3,005✔
524
             cc = cc->cc_next.get())
1,503✔
525
        {
526
            total_size += cc->cc_capacity;
1,503✔
527
            if (cc->cc_data) {
1,503✔
528
                cached_chunks += 1;
1,502✔
529
                memory_usage += cc->cc_capacity;
1,502✔
530
            } else {
531
                memory_usage += cc->cc_compressed_size;
1✔
532
            }
533
        }
534
        log_debug(
1,502✔
535
            "cell memory footprint: rows=%zu total=%zu; actual=%zu; "
536
            "cached-chunks=%zu",
537
            dls.dls_row_cursors.size(),
538
            total_size,
539
            memory_usage,
540
            cached_chunks);
541
    }
542
    gettimeofday(&end_tv, nullptr);
1,604✔
543
    lnav_data.ld_mode = old_mode;
1,604✔
544
    if (retcode == SQLITE_DONE) {
1,604✔
545
        if (lnav_data.ld_log_source.is_line_meta_changed()) {
1,604✔
546
            lnav_data.ld_log_source.text_filters_changed();
13✔
547
            lnav_data.ld_views[LNV_LOG].reload_data();
13✔
548
        }
549
        lnav_data.ld_filter_view.reload_data();
1,604✔
550
        lnav_data.ld_files_view.reload_data();
1,604✔
551

552
        lnav_data.ld_active_files.fc_files
553
            | lnav::itertools::for_each(&logfile::dump_stats);
1,604✔
554
        if (ec.ec_sql_callback != sql_callback) {
1,604✔
555
            retval = ec.ec_accumulator->get_string();
46✔
556
        } else {
557
            auto& dls = *(ec.ec_label_source_stack.back());
1,558✔
558
            if (!dls.dls_row_cursors.empty()) {
1,558✔
559
                lnav_data.ld_views[LNV_DB].reload_data();
1,442✔
560
                lnav_data.ld_views[LNV_DB].set_selection(0_vl);
1,442✔
561
                lnav_data.ld_views[LNV_DB].set_left(0);
1,442✔
562
                if (lnav_data.ld_flags.is_set<lnav_flags::headless>()) {
1,442✔
563
                    retval = "";
956✔
564
                    alt_msg = "";
956✔
565
                } else if (dls.dls_row_cursors.size() == 1) {
486✔
566
                    retval = dls.get_row_as_string(0_vl);
438✔
567
                } else {
568
                    int row_count = dls.dls_row_cursors.size();
48✔
569
                    char row_count_buf[128];
570
                    timeval diff_tv;
571

572
                    timersub(&end_tv, &start_tv, &diff_tv);
48✔
573
                    snprintf(row_count_buf,
48✔
574
                             sizeof(row_count_buf),
575
                             ANSI_BOLD("%'d") " row%s matched in " ANSI_BOLD(
576
                                 "%ld.%03ld") " seconds",
577
                             row_count,
578
                             row_count == 1 ? "" : "s",
579
                             diff_tv.tv_sec,
580
                             std::max((long) diff_tv.tv_usec / 1000, 1L));
48✔
581
                    retval = row_count_buf;
48✔
582
                    if (dls.has_log_time_column()) {
48✔
583
                        alt_msg
584
                            = HELP_MSG_1(Q,
2✔
585
                                         "to switch back to the previous view "
586
                                         "at the matching 'log_time' value");
587
                    } else {
588
                        alt_msg = "";
46✔
589
                    }
590
                }
591
            }
592
#ifdef HAVE_SQLITE3_STMT_READONLY
593
            else if (last_is_readonly)
116✔
594
            {
595
                retval = "info: No rows matched";
26✔
596
                alt_msg = "";
26✔
597
            }
598
#endif
599
        }
600
    }
601

602
    return Ok(retval);
1,604✔
603
}
1,677✔
604

605
Result<void, lnav::console::user_message>
606
multiline_executor::handle_command(const std::string& cmdline)
234✔
607
{
608
    this->me_last_result = TRY(execute_from_file(this->me_exec_context,
234✔
609
                                                 this->p_source,
610
                                                 this->p_starting_line_number,
611
                                                 cmdline));
612

613
    return Ok();
228✔
614
}
615

616
static Result<std::string, lnav::console::user_message>
617
execute_file_contents(exec_context& ec, const std::filesystem::path& path)
38✔
618
{
619
    static const std::filesystem::path stdin_path("-");
38✔
620
    static const std::filesystem::path dev_stdin_path("/dev/stdin");
38✔
621

622
    FILE* file;
623

624
    if (path == stdin_path || path == dev_stdin_path) {
38✔
625
        if (isatty(STDIN_FILENO)) {
3✔
626
            return ec.make_error("stdin has already been consumed");
×
627
        }
628
        file = stdin;
3✔
629
    } else if ((file = fopen(path.c_str(), "re")) == nullptr) {
35✔
630
        return ec.make_error("unable to open file");
×
631
    }
632

633
    std::string out_name;
38✔
634

635
    auto_mem<char> line;
38✔
636
    size_t line_max_size;
637
    ssize_t line_size;
638
    multiline_executor me(ec, path.string());
38✔
639

640
    ec.ec_local_vars.top()["0"] = path.string();
114✔
641
    ec.ec_path_stack.emplace_back(path.parent_path());
38✔
642
    exec_context::output_guard og(ec);
76✔
643
    while ((line_size = getline(line.out(), &line_max_size, file)) != -1) {
872✔
644
        TRY(me.push_back(string_fragment::from_bytes(line.in(), line_size)));
840✔
645
    }
646

647
    TRY(me.final());
32✔
648
    auto retval = std::move(me.me_last_result);
32✔
649

650
    if (file == stdin) {
32✔
651
        if (isatty(STDOUT_FILENO)) {
3✔
652
            log_perror(dup2(STDOUT_FILENO, STDIN_FILENO));
×
653
        }
654
    } else {
655
        fclose(file);
29✔
656
    }
657
    ec.ec_path_stack.pop_back();
32✔
658

659
    return Ok(retval);
32✔
660
}
38✔
661

662
Result<std::string, lnav::console::user_message>
663
execute_file(exec_context& ec, const std::string& path_and_args)
41✔
664
{
665
    static const intern_string_t SRC = intern_string::lookup("cmdline");
95✔
666
    static auto op = lnav_operation{__FUNCTION__};
41✔
667

668
    auto op_guard = lnav_opid_guard::internal(op);
41✔
669

670
    std::string retval, msg;
41✔
671
    shlex lexer(path_and_args);
41✔
672

673
    log_info("Executing file: %s", path_and_args.c_str());
41✔
674

675
    auto split_args_res = lexer.split(scoped_resolver{&ec.ec_local_vars.top()});
41✔
676
    if (split_args_res.isErr()) {
41✔
677
        auto split_err = split_args_res.unwrapErr();
×
678
        auto um = lnav::console::user_message::error(
×
679
                      "unable to parse script command-line")
680
                      .with_reason(split_err.se_error.te_msg)
×
681
                      .with_snippet(lnav::console::snippet::from(
×
682
                          SRC, lexer.to_attr_line(split_err.se_error)))
×
683
                      .move();
×
684

685
        return Err(um);
×
686
    }
687
    auto split_args = split_args_res.unwrap();
41✔
688
    if (split_args.empty()) {
41✔
689
        return ec.make_error("no script specified");
×
690
    }
691

692
    ec.ec_local_vars.emplace();
41✔
693

694
    auto script_name = split_args[0].se_value;
41✔
695
    auto& vars = ec.ec_local_vars.top();
41✔
696
    std::string star, open_error = "file not found";
82✔
697

698
    add_ansi_vars(vars);
41✔
699

700
    vars["#"] = fmt::to_string(split_args.size() - 1);
123✔
701
    for (size_t lpc = 0; lpc < split_args.size(); lpc++) {
124✔
702
        vars[fmt::to_string(lpc)] = split_args[lpc].se_value;
83✔
703
    }
704
    for (size_t lpc = 1; lpc < split_args.size(); lpc++) {
83✔
705
        if (lpc > 1) {
42✔
706
            star.append(" ");
21✔
707
        }
708
        star.append(split_args[lpc].se_value);
42✔
709
    }
710
    vars["__all__"] = star;
41✔
711

712
    std::vector<script_metadata> paths_to_exec;
41✔
713

714
    auto scripts = find_format_scripts(lnav_data.ld_config_paths);
41✔
715
    auto iter = scripts.as_scripts.find(script_name);
41✔
716
    if (iter != scripts.as_scripts.end()) {
41✔
717
        paths_to_exec = iter->second;
28✔
718
    }
719
    if (lnav::filesystem::is_url(script_name)) {
41✔
720
        auto_mem<CURLU> cu(curl_url_cleanup);
×
721
        cu = curl_url();
×
722
        auto set_rc = curl_url_set(cu, CURLUPART_URL, script_name.c_str(), 0);
×
723
        if (set_rc == CURLUE_OK) {
×
724
            auto_mem<char> scheme_part(curl_free);
×
725
            auto get_rc
726
                = curl_url_get(cu, CURLUPART_SCHEME, scheme_part.out(), 0);
×
727
            if (get_rc == CURLUE_OK
×
728
                && string_fragment::from_c_str(scheme_part.in()) == "file")
×
729
            {
730
                auto_mem<char> path_part;
×
731
                auto get_rc
732
                    = curl_url_get(cu, CURLUPART_PATH, path_part.out(), 0);
×
733
                if (get_rc == CURLUE_OK) {
×
734
                    auto rp_res = lnav::filesystem::realpath(path_part.in());
×
735
                    if (rp_res.isOk()) {
×
736
                        struct script_metadata meta;
×
737

738
                        meta.sm_path = rp_res.unwrap();
×
739
                        extract_metadata_from_file(meta);
×
740
                        paths_to_exec.push_back(meta);
×
741
                    }
742
                }
743
            }
744
        }
745
    }
746
    if (script_name == "-" || script_name == "/dev/stdin") {
41✔
747
        paths_to_exec.push_back({script_name, "", "", ""});
3✔
748
    } else if (access(script_name.c_str(), R_OK) == 0) {
38✔
749
        struct script_metadata meta;
7✔
750
        auto rp_res = lnav::filesystem::realpath(script_name);
7✔
751

752
        if (rp_res.isErr()) {
7✔
753
            log_error("unable to get realpath() of %s -- %s",
×
754
                      script_name.c_str(),
755
                      rp_res.unwrapErr().c_str());
756
            meta.sm_path = script_name;
×
757
        } else {
758
            meta.sm_path = rp_res.unwrap();
7✔
759
        }
760
        extract_metadata_from_file(meta);
7✔
761
        paths_to_exec.push_back(meta);
7✔
762
    } else if (errno != ENOENT) {
38✔
763
        open_error = lnav::from_errno().message();
×
764
    } else {
765
        auto script_path = std::filesystem::path(script_name);
31✔
766

767
        if (!script_path.is_absolute()) {
31✔
768
            script_path = ec.ec_path_stack.back() / script_path;
31✔
769
        }
770

771
        if (std::filesystem::is_regular_file(script_path)) {
31✔
772
            script_metadata meta;
×
773

774
            meta.sm_path = script_path;
×
775
            extract_metadata_from_file(meta);
×
776
            paths_to_exec.push_back(meta);
×
777
        } else if (errno != ENOENT) {
31✔
778
            open_error = lnav::from_errno().message();
×
779
        }
780
    }
31✔
781

782
    if (!paths_to_exec.empty()) {
41✔
783
        for (auto& path_iter : paths_to_exec) {
70✔
784
            if (!ec.ec_output_stack.empty()) {
38✔
785
                ec.ec_output_stack.back().od_format
38✔
786
                    = path_iter.sm_output_format;
38✔
787
                log_info("%s: setting out format for '%s' to %d",
38✔
788
                         script_name.c_str(),
789
                         ec.ec_output_stack.back().od_name.c_str(),
790
                         ec.ec_output_stack.back().od_format);
791
            }
792
            retval = TRY(execute_file_contents(ec, path_iter.sm_path));
38✔
793
        }
794
    }
795
    ec.ec_local_vars.pop();
35✔
796

797
    if (paths_to_exec.empty()) {
35✔
798
        return ec.make_error(
799
            "unknown script -- {} -- {}", script_name, open_error);
3✔
800
    }
801

802
    return Ok(retval);
32✔
803
}
56✔
804

805
Result<std::string, lnav::console::user_message>
806
execute_from_file(exec_context& ec,
234✔
807
                  const std::string& src,
808
                  int line_number,
809
                  const std::string& cmdline)
810
{
811
    std::string retval, alt_msg;
234✔
812
    auto _sg
813
        = ec.enter_source(intern_string::lookup(src), line_number, cmdline);
234✔
814

815
    lnav_data.ld_view_stack.top() | [&ec](auto* tc) {
234✔
816
        ec.ec_top_line = tc->get_selection().value_or(0_vl);
230✔
817
    };
230✔
818
    switch (cmdline[0]) {
234✔
819
        case ':':
103✔
820
            retval = TRY(execute_command(ec, cmdline.substr(1)));
103✔
821
            break;
103✔
822
        case '/':
1✔
823
            execute_search(cmdline.substr(1));
1✔
824
            break;
1✔
825
        case ';': {
128✔
826
            if (!ec.ec_msg_callback_stack.empty()) {
128✔
827
                auto src_slash = src.rfind('/');
124✔
828
                auto cmd_al
829
                    = attr_line_t(cmdline.substr(0, cmdline.find('\n')));
124✔
830
                readline_lnav_highlighter(cmd_al, std::nullopt);
124✔
831
                const auto um = lnav::console::user_message::info(
124✔
832
                                    attr_line_t("Executing command at ")
124✔
833
                                        .append(lnav::roles::file(
124✔
834
                                            src_slash != std::string::npos
835
                                                ? src.substr(src_slash + 1)
248✔
836
                                                : src))
837
                                        .append(":")
124✔
838
                                        .append(lnav::roles::number(
248✔
839
                                            fmt::to_string(line_number)))
248✔
840
                                        .append(" \u2014 ")
124✔
841
                                        .append(cmd_al))
124✔
842
                                    .move();
124✔
843
                ec.ec_msg_callback_stack.back()(um);
124✔
844
            }
124✔
845

846
            setup_logline_table(ec);
128✔
847
            retval = TRY(execute_sql(ec, cmdline.substr(1), alt_msg));
128✔
848
            break;
122✔
849
        }
850
        case '|':
2✔
851
            retval = TRY(execute_file(ec, cmdline.substr(1)));
2✔
852
            break;
2✔
853
        default:
×
854
            retval = TRY(execute_command(ec, cmdline));
×
855
            break;
×
856
    }
857

858
    log_info(
228✔
859
        "%s:%d:execute result -- %s", src.c_str(), line_number, retval.c_str());
860

861
    return Ok(retval);
228✔
862
}
234✔
863

864
Result<std::string, lnav::console::user_message>
865
execute_any(exec_context& ec, const std::string& cmdline_with_mode)
35✔
866
{
867
    if (cmdline_with_mode.empty()) {
35✔
868
        auto um = lnav::console::user_message::error("empty command")
×
869
                      .with_help(
×
870
                          "a command should start with ':', ';', '/', '|' and "
871
                          "followed by the operation to perform")
872
                      .move();
×
873
        if (!ec.ec_source.empty()) {
×
874
            um.with_snippet(ec.ec_source.back());
×
875
        }
876
        return Err(um);
×
877
    }
878

879
    std::string retval, alt_msg, cmdline = cmdline_with_mode.substr(1);
35✔
880
    auto _cleanup = finally([&ec] {
35✔
881
        if (ec.is_read_write() &&
38✔
882
            // only rebuild in a script or non-interactive mode so we don't
883
            // block the UI.
884
            lnav_data.ld_flags.is_set<lnav_flags::headless>())
3✔
885
        {
886
            rescan_files();
×
887
            wait_for_pipers(std::nullopt);
×
888
            rebuild_indexes_repeatedly();
×
889
        }
890
    });
70✔
891

892
    switch (cmdline_with_mode[0]) {
35✔
893
        case ':':
×
894
            retval = TRY(execute_command(ec, cmdline));
×
895
            break;
×
896
        case '/':
×
897
            execute_search(cmdline);
×
898
            break;
×
899
        case ';':
29✔
900
            setup_logline_table(ec);
29✔
901
            retval = TRY(execute_sql(ec, cmdline, alt_msg));
29✔
902
            break;
29✔
903
        case '|': {
6✔
904
            retval = TRY(execute_file(ec, cmdline));
6✔
905
            break;
4✔
906
        }
907
        default:
×
908
            retval = TRY(execute_command(ec, cmdline));
×
909
            break;
×
910
    }
911

912
    return Ok(retval);
33✔
913
}
35✔
914

915
void
916
execute_init_commands(
625✔
917
    exec_context& ec,
918
    std::vector<std::pair<Result<std::string, lnav::console::user_message>,
919
                          std::string>>& msgs)
920
{
921
    if (lnav_data.ld_cmd_init_done) {
625✔
922
        return;
×
923
    }
924

925
    std::string out_name;
625✔
926
    std::optional<exec_context::output_t> ec_out;
625✔
927
    auto_fd fd_copy;
625✔
928
    int option_index = 1;
625✔
929

930
    {
931
        log_info("Executing initial commands");
625✔
932
        exec_context::output_guard og(ec, out_name, ec_out);
625✔
933

934
        for (auto& cmd : lnav_data.ld_commands) {
1,658✔
935
            static const auto COMMAND_OPTION_SRC
936
                = intern_string::lookup("command-option");
2,133✔
937

938
            std::string alt_msg;
1,033✔
939

940
            wait_for_children();
1,033✔
941

942
            lnav_data.ld_view_stack.top() | [&ec](auto* tc) {
1,033✔
943
                ec.ec_top_line = tc->get_selection().value_or(0_vl);
1,033✔
944
            };
1,033✔
945
            log_debug("init cmd: %s", cmd.c_str());
1,033✔
946
            {
947
                auto top_view = lnav_data.ld_view_stack.top();
1,033✔
948
                auto _sg
949
                    = ec.enter_source(COMMAND_OPTION_SRC, option_index++, cmd);
1,033✔
950
                switch (cmd.at(0)) {
1,033✔
951
                    case ':':
647✔
952
                        msgs.emplace_back(execute_command(ec, cmd.substr(1)),
647✔
953
                                          alt_msg);
954
                        break;
647✔
955
                    case '/':
6✔
956
                        execute_search(cmd.substr(1));
6✔
957
                        break;
6✔
958
                    case ';':
354✔
959
                        setup_logline_table(ec);
354✔
960
                        msgs.emplace_back(
354✔
961
                            execute_sql(ec, cmd.substr(1), alt_msg), alt_msg);
708✔
962
                        break;
354✔
963
                    case '|':
26✔
964
                        msgs.emplace_back(execute_file(ec, cmd.substr(1)),
26✔
965
                                          alt_msg);
966
                        break;
26✔
967
                }
968

969
                rescan_files();
1,033✔
970
                auto deadline = ui_clock::now();
1,033✔
971
                if (lnav_data.ld_flags.is_set<lnav_flags::headless>()) {
1,033✔
972
                    deadline += 5s;
1,032✔
973
                } else {
974
                    deadline += 500ms;
1✔
975
                }
976
                wait_for_pipers(deadline);
1,033✔
977
                if (lnav_data.ld_flags.is_set<lnav_flags::headless>()
1,033✔
978
                    || lnav_data.ld_input_dispatcher.id_count == 0)
1,033✔
979
                {
980
                    rebuild_indexes_repeatedly();
1,033✔
981
                } else {
982
                    rebuild_indexes(deadline);
×
983
                }
984
                if (top_view == lnav_data.ld_view_stack.top()) {
1,033✔
985
                    setup_initial_view_stack();
966✔
986
                }
987

988
                lnav::progress_tracker::instance().wait_for_completion();
1,033✔
989
            }
1,033✔
990
        }
1,033✔
991
    }
625✔
992
    lnav_data.ld_commands.clear();
625✔
993
    lnav_data.ld_cmd_init_done = true;
625✔
994
}
625✔
995

996
int
997
sql_callback(exec_context& ec, sqlite3_stmt* stmt)
4,148✔
998
{
999
    auto& dls = *(ec.ec_label_source_stack.back());
4,148✔
1000
    const int ncols = sqlite3_column_count(stmt);
4,148✔
1001

1002
    if (!sqlite3_stmt_busy(stmt)) {
4,148✔
1003
        dls.clear();
1,485✔
1004

1005
        for (int lpc = 0; lpc < ncols; lpc++) {
4,526✔
1006
            const int type = sqlite3_column_type(stmt, lpc);
3,041✔
1007
            std::string colname = sqlite3_column_name(stmt, lpc);
3,041✔
1008

1009
            dls.push_header(colname, type);
3,041✔
1010
        }
3,041✔
1011
        return 0;
1,485✔
1012
    }
1013

1014
    int retval = 0;
2,663✔
1015
    auto set_vars = dls.dls_row_cursors.empty();
2,663✔
1016

1017
    if (dls.dls_row_cursors.empty()) {
2,663✔
1018
        dls.dls_query_start = std::chrono::steady_clock::now();
1,434✔
1019
        for (int lpc = 0; lpc < ncols; lpc++) {
4,254✔
1020
            int type = sqlite3_column_type(stmt, lpc);
2,820✔
1021
            std::string colname = sqlite3_column_name(stmt, lpc);
2,820✔
1022

1023
            bool graphable = (type == SQLITE_INTEGER || type == SQLITE_FLOAT);
2,820✔
1024
            auto& hm = dls.dls_headers[lpc];
2,820✔
1025
            hm.hm_column_type = type;
2,820✔
1026
            if (lnav_data.ld_db_key_names.count(colname) > 0) {
2,820✔
1027
                hm.hm_graphable = false;
546✔
1028
            } else if (graphable) {
2,274✔
1029
                dls.set_col_as_graphable(lpc);
653✔
1030
            }
1031
        }
2,820✔
1032
    }
1033

1034
    dls.dls_row_cursors.emplace_back(dls.dls_cell_container.end_cursor());
2,663✔
1035
    dls.dls_push_column = 0;
2,663✔
1036
    for (int lpc = 0; lpc < ncols; lpc++) {
11,859✔
1037
        const auto value_type = sqlite3_column_type(stmt, lpc);
9,196✔
1038
        db_label_source::column_value_t value;
9,196✔
1039
        auto& hm = dls.dls_headers[lpc];
9,196✔
1040

1041
        switch (value_type) {
9,196✔
1042
            case SQLITE_INTEGER:
2,696✔
1043
                value = (int64_t) sqlite3_column_int64(stmt, lpc);
2,696✔
1044
                hm.hm_align = text_align_t::end;
2,696✔
1045
                break;
2,696✔
1046
            case SQLITE_FLOAT:
231✔
1047
                value = sqlite3_column_double(stmt, lpc);
231✔
1048
                hm.hm_align = text_align_t::end;
231✔
1049
                break;
231✔
1050
            case SQLITE_NULL:
2,176✔
1051
                value = null_value_t{};
2,176✔
1052
                break;
2,176✔
1053
            default: {
4,093✔
1054
                auto frag = string_fragment::from_bytes(
4,093✔
1055
                    sqlite3_column_text(stmt, lpc),
1056
                    sqlite3_column_bytes(stmt, lpc));
4,093✔
1057
                if (!frag.empty()) {
4,093✔
1058
                    if (isdigit(frag[0])) {
4,026✔
1059
                        hm.hm_align = text_align_t::end;
1,389✔
1060
                        if (!hm.hm_graphable.has_value()) {
1,389✔
1061
                            auto split_res = humanize::try_from<double>(frag);
245✔
1062
                            if (split_res.has_value()) {
245✔
1063
                                dls.set_col_as_graphable(lpc);
58✔
1064
                            } else {
1065
                                hm.hm_graphable = false;
187✔
1066
                            }
1067
                        }
1068
                    } else if (!hm.hm_graphable.has_value()) {
2,637✔
1069
                        hm.hm_graphable = false;
1,039✔
1070
                    }
1071
                }
1072
                value = frag;
4,093✔
1073
                break;
4,093✔
1074
            }
1075
        }
1076
        dls.push_column(value);
9,196✔
1077
        if ((hm.hm_column_type == SQLITE_TEXT
9,196✔
1078
             || hm.hm_column_type == SQLITE_NULL)
4,996✔
1079
            && hm.hm_sub_type == 0)
6,302✔
1080
        {
1081
            switch (value_type) {
6,188✔
1082
                case SQLITE_TEXT:
3,981✔
1083
                    auto* raw_value = sqlite3_column_value(stmt, lpc);
3,981✔
1084
                    hm.hm_column_type = SQLITE_TEXT;
3,981✔
1085
                    hm.hm_sub_type = sqlite3_value_subtype(raw_value);
3,981✔
1086
                    break;
3,981✔
1087
            }
1088
        }
1089
        if (set_vars && !ec.ec_local_vars.empty() && !ec.ec_dry_run) {
9,196✔
1090
            if (sql_ident_needs_quote(hm.hm_name.c_str())) {
2,820✔
1091
                continue;
1,152✔
1092
            }
1093
            auto& vars = ec.ec_local_vars.top();
1,668✔
1094

1095
            vars[hm.hm_name] = value.match(
3,336✔
1096
                [](const string_fragment& sf) {
×
1097
                    return scoped_value_t{sf.to_string()};
736✔
1098
                },
1099
                [](int64_t i) { return scoped_value_t{i}; },
480✔
1100
                [](double d) { return scoped_value_t{d}; },
15✔
1101
                [](null_value_t) { return scoped_value_t{null_value_t{}}; });
3,773✔
1102
        }
1103
    }
9,196✔
1104

1105
    return retval;
2,663✔
1106
}
1107

1108
int
1109
internal_sql_callback(exec_context& ec, sqlite3_stmt* stmt)
26✔
1110
{
1111
    if (!sqlite3_stmt_busy(stmt)) {
26✔
1112
        return 0;
15✔
1113
    }
1114

1115
    const int ncols = sqlite3_column_count(stmt);
11✔
1116

1117
    auto& vars = ec.ec_local_vars.top();
11✔
1118

1119
    for (int lpc = 0; lpc < ncols; lpc++) {
27✔
1120
        const char* column_name = sqlite3_column_name(stmt, lpc);
16✔
1121

1122
        if (sql_ident_needs_quote(column_name)) {
16✔
1123
            continue;
×
1124
        }
1125

1126
        auto value_type = sqlite3_column_type(stmt, lpc);
16✔
1127
        scoped_value_t value;
16✔
1128
        switch (value_type) {
16✔
1129
            case SQLITE_INTEGER:
2✔
1130
                value = (int64_t) sqlite3_column_int64(stmt, lpc);
2✔
1131
                break;
2✔
1132
            case SQLITE_FLOAT:
×
1133
                value = sqlite3_column_double(stmt, lpc);
×
1134
                break;
×
1135
            case SQLITE_NULL:
1✔
1136
                value = null_value_t{};
1✔
1137
                break;
1✔
1138
            default:
13✔
1139
                value
1140
                    = std::string((const char*) sqlite3_column_text(stmt, lpc),
26✔
1141
                                  sqlite3_column_bytes(stmt, lpc));
26✔
1142
                break;
13✔
1143
        }
1144
        vars[column_name] = value;
16✔
1145
    }
16✔
1146

1147
    return 0;
11✔
1148
}
1149

1150
std::future<std::string>
1151
pipe_callback(exec_context& ec, const std::string& cmdline, auto_fd& fd)
7✔
1152
{
1153
    static auto& prompt = lnav::prompt::get();
7✔
1154
    auto out = ec.get_output();
7✔
1155

1156
    if (out) {
7✔
1157
        FILE* file = *out;
7✔
1158

1159
        return std::async(std::launch::async, [fd = std::move(fd), file]() {
14✔
1160
            char buffer[1024];
1161
            ssize_t rc;
1162

1163
            if (file == stdout) {
7✔
1164
                lnav_data.ld_stdout_used = true;
7✔
1165
            }
1166

1167
            while ((rc = read(fd, buffer, sizeof(buffer))) > 0) {
14✔
1168
                fwrite(buffer, rc, 1, file);
7✔
1169
            }
1170

1171
            return std::string();
14✔
1172
        });
7✔
1173
    }
1174
    std::error_code errc;
×
1175
    std::filesystem::create_directories(lnav::paths::workdir(), errc);
×
1176
    auto open_temp_res = lnav::filesystem::open_temp_file(lnav::paths::workdir()
×
1177
                                                          / "exec.XXXXXX");
×
1178
    if (open_temp_res.isErr()) {
×
1179
        return lnav::futures::make_ready_future(
1180
            fmt::format(FMT_STRING("error: cannot open temp file -- {}"),
×
1181
                        open_temp_res.unwrapErr()));
×
1182
    }
1183

1184
    auto tmp_pair = open_temp_res.unwrap();
×
1185

1186
    auto reader = std::thread(
1187
        [in_fd = std::move(fd), out_fd = std::move(tmp_pair.second)]() {
×
1188
            char buffer[1024];
1189
            ssize_t rc;
1190

1191
            while ((rc = read(in_fd, buffer, sizeof(buffer))) > 0) {
×
1192
                write(out_fd, buffer, rc);
×
1193
            }
1194
        });
×
1195
    reader.detach();
×
1196

1197
    static int exec_count = 0;
1198
    auto desc
1199
        = fmt::format(FMT_STRING("exec-{}-output {}"), exec_count++, cmdline);
×
1200
    lnav_data.ld_active_files.fc_file_names[tmp_pair.first]
×
1201
        .with_filename(desc)
×
1202
        .with_include_in_session(false)
×
1203
        .with_detect_format(false)
×
1204
        .with_init_location(0_vl);
×
1205
    lnav_data.ld_files_to_front.emplace_back(desc);
×
1206
    prompt.p_editor.set_alt_value(HELP_MSG_1(X, "to close the file"));
×
1207

1208
    return lnav::futures::make_ready_future(std::string());
×
1209
}
1210

1211
void
1212
add_global_vars(exec_context& ec)
699✔
1213
{
1214
    for (const auto& iter : lnav_config.lc_global_vars) {
11,190✔
1215
        shlex subber(iter.second);
10,491✔
1216
        std::string str;
10,491✔
1217

1218
        if (!subber.eval(str, scoped_resolver{&ec.ec_global_vars})) {
10,491✔
1219
            log_error("Unable to evaluate global variable value: %s",
×
1220
                      iter.second.c_str());
1221
            continue;
×
1222
        }
1223

1224
        ec.ec_global_vars[iter.first] = str;
10,491✔
1225
    }
10,491✔
1226
}
699✔
1227

1228
void
1229
exec_context::set_output(const std::string& name,
626✔
1230
                         FILE* file,
1231
                         int (*closer)(FILE*))
1232
{
1233
    log_info("redirecting command output to: %s", name.c_str());
626✔
1234
    this->ec_output_stack.back().od_output | [](auto out) {
626✔
1235
        if (out.second != nullptr) {
×
1236
            out.second(out.first);
×
1237
        }
1238
    };
1239
    this->ec_output_stack.back()
626✔
1240
        = output_desc{name, std::make_pair(file, closer)};
1,252✔
1241
}
626✔
1242

1243
void
1244
exec_context::clear_output()
71✔
1245
{
1246
    log_info("redirecting command output to screen");
71✔
1247
    this->ec_output_stack.back().od_output | [](auto out) {
71✔
1248
        if (out.second != nullptr) {
33✔
1249
            out.second(out.first);
33✔
1250
        }
1251
    };
33✔
1252
    this->ec_output_stack.back().od_name = "default";
71✔
1253
    this->ec_output_stack.back().od_output = std::nullopt;
71✔
1254
}
71✔
1255

1256
exec_context::exec_context(logline_value_vector* line_values,
3,579✔
1257
                           sql_callback_t sql_callback,
1258
                           pipe_callback_t pipe_callback)
3,579✔
1259
    : ec_line_values(line_values),
3,579✔
1260
      ec_accumulator(std::make_unique<attr_line_t>()),
3,579✔
1261
      ec_sql_callback(sql_callback), ec_pipe_callback(pipe_callback)
7,158✔
1262
{
1263
    this->ec_local_vars.push(std::map<std::string, scoped_value_t>());
3,579✔
1264
    this->ec_path_stack.emplace_back(".");
3,579✔
1265
    this->ec_output_stack.emplace_back("screen", std::nullopt);
3,579✔
1266
}
3,579✔
1267

1268
Result<std::string, lnav::console::user_message>
1269
exec_context::execute(source_location loc, const std::string& cmdline)
2✔
1270
{
1271
    static auto& prompt = lnav::prompt::get();
2✔
1272
    static const auto& dls = lnav_data.ld_db_row_source;
1273

1274
    lnav::textinput::history::op_guard hist_guard;
2✔
1275
    auto sg = this->enter_source(loc, cmdline);
2✔
1276

1277
    auto before_dls_gen = dls.dls_generation;
2✔
1278
    if (this->get_provenance<mouse_input>() && !prompt.p_editor.is_enabled()
2✔
1279
        && !cmdline.empty())
2✔
1280
    {
UNCOV
1281
        auto& hist = prompt.get_history_for(cmdline[0]);
×
UNCOV
1282
        hist_guard = hist.start_operation(cmdline.substr(1));
×
1283
    }
1284

1285
    auto exec_res = execute_any(*this, cmdline);
2✔
1286
    if (exec_res.isErr()) {
2✔
1287
        hist_guard.og_status = log_level_t::LEVEL_ERROR;
×
UNCOV
1288
        if (!this->ec_msg_callback_stack.empty()) {
×
UNCOV
1289
            this->ec_msg_callback_stack.back()(exec_res.unwrapErr());
×
1290
        }
1291
    } else {
1292
        if (before_dls_gen != dls.dls_generation
4✔
1293
            && dls.dls_row_cursors.size() > 1)
2✔
1294
        {
UNCOV
1295
            ensure_view(LNV_DB);
×
1296
        }
1297
    }
1298

1299
    return exec_res;
4✔
1300
}
2✔
1301

1302
void
1303
exec_context::add_error_context(lnav::console::user_message& um) const
76✔
1304
{
1305
    switch (um.um_level) {
76✔
UNCOV
1306
        case lnav::console::user_message::level::raw:
×
1307
        case lnav::console::user_message::level::info:
1308
        case lnav::console::user_message::level::ok:
UNCOV
1309
            return;
×
1310
        default:
76✔
1311
            break;
76✔
1312
    }
1313

1314
    if (!this->get_provenance<keyboard_input>() && um.um_snippets.empty()) {
76✔
1315
        um.with_snippets(this->ec_source);
35✔
1316
        um.remove_internal_snippets();
35✔
1317
    }
1318

1319
    if (this->ec_current_help != nullptr && um.um_help.empty()) {
76✔
1320
        attr_line_t help;
35✔
1321

1322
        format_help_text_for_term(*this->ec_current_help,
35✔
1323
                                  70,
1324
                                  help,
1325
                                  help_text_content::synopsis_and_summary);
1326
        um.with_help(help);
35✔
1327
    }
35✔
1328
}
1329

1330
lnav::console::user_message
1331
exec_context::make_error_from_str(std::string&& str) const
28✔
1332
{
1333
    auto retval = lnav::console::user_message::error(std::move(str));
28✔
1334
    this->add_error_context(retval);
28✔
1335
    return retval;
28✔
UNCOV
1336
}
×
1337

1338
exec_context::sql_callback_guard
1339
exec_context::push_callback(sql_callback_t cb)
5✔
1340
{
1341
    return sql_callback_guard(*this, cb);
5✔
1342
}
1343

1344
exec_context::source_guard
1345
exec_context::enter_source(source_location loc, const std::string& content)
2,487✔
1346
{
1347
    attr_line_t content_al{content};
2,487✔
1348
    content_al.with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE));
2,487✔
1349
    readline_lnav_highlighter(content_al, -1);
2,487✔
1350
    this->ec_source.emplace_back(lnav::console::snippet::from(loc, content_al));
2,487✔
1351
    return {this};
4,974✔
1352
}
2,487✔
1353

1354
exec_context::output_guard::output_guard(exec_context& context,
694✔
1355
                                         std::string name,
1356
                                         const std::optional<output_t>& file,
1357
                                         text_format_t tf)
694✔
1358
    : sg_context(context), sg_active(!name.empty())
694✔
1359
{
1360
    if (name.empty()) {
694✔
1361
        return;
625✔
1362
    }
1363
    if (file) {
69✔
1364
        log_info("redirecting command output to: %s", name.c_str());
31✔
1365
    } else if (!context.ec_output_stack.empty()) {
38✔
1366
        tf = context.ec_output_stack.back().od_format;
38✔
1367
    }
1368
    context.ec_output_stack.emplace_back(std::move(name), file, tf);
69✔
1369
}
1370

1371
exec_context::output_guard::~output_guard()
694✔
1372
{
1373
    if (this->sg_active) {
694✔
1374
        this->sg_context.clear_output();
69✔
1375
        this->sg_context.ec_output_stack.pop_back();
69✔
1376
    }
1377
}
694✔
1378
exec_context::sql_callback_guard::sql_callback_guard(exec_context& context,
5✔
1379
                                                     sql_callback_t cb)
5✔
1380
    : scg_context(context), scg_old_callback(context.ec_sql_callback)
5✔
1381
{
1382
    context.ec_sql_callback = cb;
5✔
1383
}
5✔
1384
exec_context::sql_callback_guard::sql_callback_guard(sql_callback_guard&& other)
×
UNCOV
1385
    : scg_context(other.scg_context),
×
UNCOV
1386
      scg_old_callback(std::exchange(other.scg_old_callback, nullptr))
×
1387
{
1388
}
1389
exec_context::sql_callback_guard::~sql_callback_guard()
5✔
1390
{
1391
    if (this->scg_old_callback != nullptr) {
5✔
1392
        this->scg_context.ec_sql_callback = this->scg_old_callback;
5✔
1393
    }
1394
}
5✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc