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

tstack / lnav / 17589970077-2502

09 Sep 2025 05:00PM UTC coverage: 65.196% (-5.0%) from 70.225%
17589970077-2502

push

github

tstack
[format] add fields for source file/line

Knowing the source file/line context in a log
message can help find log messages when using
log2src.

56 of 70 new or added lines in 2 files covered. (80.0%)

13954 existing lines in 210 files now uncovered.

45516 of 69814 relevant lines covered (65.2%)

404154.37 hits per line

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

78.31
/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 "config.h"
44
#include "curl_looper.hh"
45
#include "db_sub_source.hh"
46
#include "help_text_formatter.hh"
47
#include "lnav.hh"
48
#include "lnav.indexing.hh"
49
#include "lnav.prompt.hh"
50
#include "lnav_config.hh"
51
#include "lnav_util.hh"
52
#include "log_format_loader.hh"
53
#include "prql-modules.h"
54
#include "readline_highlighters.hh"
55
#include "service_tags.hh"
56
#include "shlex.hh"
57
#include "sql_help.hh"
58
#include "sql_util.hh"
59
#include "vtab_module.hh"
60

61
#ifdef HAVE_RUST_DEPS
62
#    include "prqlc.cxx.hh"
63
#endif
64

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

68
exec_context INIT_EXEC_CONTEXT;
69

70
static sig_atomic_t sql_counter = 0;
71

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

UNCOV
79
    if (!lnav_data.ld_looping) {
×
UNCOV
80
        return 1;
×
81
    }
82

83
    if (ui_periodic_timer::singleton().time_to_update(sql_counter)) {
×
UNCOV
84
        ssize_t total = lnav_data.ld_log_source.text_line_count();
×
UNCOV
85
        off_t off = lc.lc_curr_line;
×
86

UNCOV
87
        if (off >= 0 && off <= total) {
×
UNCOV
88
            lnav_data.ld_bottom_source.update_loading(off, total);
×
UNCOV
89
            lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
×
90
        }
UNCOV
91
        lnav_data.ld_status_refresher(lnav::func::op_type::blocking);
×
92
    }
93

UNCOV
94
    return 0;
×
95
}
96

97
void
98
sql_progress_finished()
1,626✔
99
{
100
    if (sql_counter == 0) {
1,626✔
101
        return;
1,626✔
102
    }
103

UNCOV
104
    sql_counter = 0;
×
105

UNCOV
106
    if (lnav_data.ld_window == nullptr) {
×
UNCOV
107
        return;
×
108
    }
109

UNCOV
110
    lnav_data.ld_bottom_source.update_loading(0, 0);
×
UNCOV
111
    lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
×
UNCOV
112
    lnav_data.ld_status_refresher(lnav::func::op_type::blocking);
×
UNCOV
113
    lnav_data.ld_views[LNV_DB].redo_search();
×
114
}
115

116
static Result<std::string, lnav::console::user_message> execute_from_file(
117
    exec_context& ec,
118
    const std::string& src,
119
    int line_number,
120
    const std::string& cmdline);
121

122
Result<std::string, lnav::console::user_message>
123
execute_command(exec_context& ec, const std::string& cmdline)
672✔
124
{
125
    std::vector<std::string> args;
672✔
126

127
    log_info("Executing: %s", cmdline.c_str());
672✔
128

129
    split_ws(cmdline, args);
672✔
130

131
    if (!args.empty()) {
672✔
132
        readline_context::command_map_t::iterator iter;
672✔
133

134
        if ((iter = lnav_commands.find(args[0])) == lnav_commands.end()) {
672✔
UNCOV
135
            return ec.make_error("unknown command - {}", args[0]);
×
136
        }
137

138
        ec.ec_current_help = &iter->second->c_help;
672✔
139
        try {
140
            auto retval = iter->second->c_func(ec, cmdline, args);
672✔
141
            if (retval.isErr()) {
672✔
142
                auto um = retval.unwrapErr();
41✔
143

144
                ec.add_error_context(um);
41✔
145
                ec.ec_current_help = nullptr;
41✔
146
                return Err(um);
41✔
147
            }
41✔
148
            ec.ec_current_help = nullptr;
631✔
149

150
            return retval;
631✔
151
        } catch (const std::exception& e) {
672✔
UNCOV
152
            auto um = lnav::console::user_message::error(
×
UNCOV
153
                          attr_line_t("unexpected error while executing: ")
×
UNCOV
154
                              .append(lnav::roles::quoted_code(cmdline)))
×
UNCOV
155
                          .with_reason(e.what());
×
UNCOV
156
            return Err(um);
×
UNCOV
157
        }
×
158
    }
159

UNCOV
160
    return ec.make_error("no command to execute");
×
161
}
672✔
162

163
static Result<std::map<std::string, scoped_value_t>,
164
              lnav::console::user_message>
165
bind_sql_parameters(exec_context& ec, sqlite3_stmt* stmt)
1,619✔
166
{
167
    std::map<std::string, scoped_value_t> retval;
1,619✔
168
    auto param_count = sqlite3_bind_parameter_count(stmt);
1,619✔
169
    for (int lpc = 0; lpc < param_count; lpc++) {
1,779✔
170
        std::map<std::string, std::string>::iterator ov_iter;
161✔
171
        const auto* name = sqlite3_bind_parameter_name(stmt, lpc + 1);
161✔
172
        if (name == nullptr) {
161✔
173
            auto um
174
                = lnav::console::user_message::error("invalid SQL statement")
2✔
175
                      .with_reason(
2✔
176
                          "using a question-mark (?) for bound variables "
177
                          "is not supported, only named bound parameters "
178
                          "are supported")
179
                      .with_help(
2✔
180
                          "named parameters start with a dollar-sign "
181
                          "($) or colon (:) followed by the variable name")
182
                      .move();
1✔
183
            ec.add_error_context(um);
1✔
184

185
            return Err(um);
1✔
186
        }
1✔
187

188
        if (name[0] == '$') {
160✔
189
            const auto& lvars = ec.ec_local_vars.top();
131✔
190
            const auto& gvars = ec.ec_global_vars;
131✔
191
            std::map<std::string, scoped_value_t>::const_iterator local_var,
131✔
192
                global_var;
131✔
193
            const char* env_value;
194

195
            if (lnav_data.ld_window) {
131✔
196
                char buf[32];
197
                unsigned int lines, cols;
198

UNCOV
199
                ncplane_dim_yx(lnav_data.ld_window, &lines, &cols);
×
UNCOV
200
                if (strcmp(name, "$LINES") == 0) {
×
UNCOV
201
                    snprintf(buf, sizeof(buf), "%d", lines);
×
UNCOV
202
                    sqlite3_bind_text(stmt, lpc + 1, buf, -1, SQLITE_TRANSIENT);
×
UNCOV
203
                } else if (strcmp(name, "$COLS") == 0) {
×
UNCOV
204
                    snprintf(buf, sizeof(buf), "%d", cols);
×
UNCOV
205
                    sqlite3_bind_text(stmt, lpc + 1, buf, -1, SQLITE_TRANSIENT);
×
206
                }
207
            }
208

209
            if ((local_var = lvars.find(&name[1])) != lvars.end()) {
393✔
210
                mapbox::util::apply_visitor(
120✔
211
                    sqlitepp::bind_visitor(stmt, lpc + 1), local_var->second);
120✔
212
                retval[name] = local_var->second;
360✔
213
            } else if ((global_var = gvars.find(&name[1])) != gvars.end()) {
33✔
214
                mapbox::util::apply_visitor(
×
215
                    sqlitepp::bind_visitor(stmt, lpc + 1), global_var->second);
×
216
                retval[name] = global_var->second;
×
217
            } else if ((env_value = getenv(&name[1])) != nullptr) {
11✔
218
                sqlite3_bind_text(stmt, lpc + 1, env_value, -1, SQLITE_STATIC);
3✔
219
                retval[name] = string_fragment::from_c_str(env_value);
9✔
220
            }
221
        } else if (name[0] == ':' && ec.ec_line_values != nullptr) {
29✔
222
            for (auto& lv : ec.ec_line_values->lvv_values) {
159✔
223
                if (lv.lv_meta.lvm_name != &name[1]) {
130✔
224
                    continue;
101✔
225
                }
226
                switch (lv.lv_meta.lvm_kind) {
29✔
UNCOV
227
                    case value_kind_t::VALUE_BOOLEAN:
×
UNCOV
228
                        sqlite3_bind_int64(stmt, lpc + 1, lv.lv_value.i);
×
UNCOV
229
                        retval[name] = fmt::to_string(lv.lv_value.i);
×
UNCOV
230
                        break;
×
UNCOV
231
                    case value_kind_t::VALUE_FLOAT:
×
UNCOV
232
                        sqlite3_bind_double(stmt, lpc + 1, lv.lv_value.d);
×
UNCOV
233
                        retval[name] = fmt::to_string(lv.lv_value.d);
×
UNCOV
234
                        break;
×
UNCOV
235
                    case value_kind_t::VALUE_INTEGER:
×
UNCOV
236
                        sqlite3_bind_int64(stmt, lpc + 1, lv.lv_value.i);
×
UNCOV
237
                        retval[name] = fmt::to_string(lv.lv_value.i);
×
238
                        break;
×
239
                    case value_kind_t::VALUE_NULL:
×
240
                        sqlite3_bind_null(stmt, lpc + 1);
×
UNCOV
241
                        retval[name] = string_fragment::from_c_str(
×
UNCOV
242
                            db_label_source::NULL_STR);
×
UNCOV
243
                        break;
×
244
                    default:
29✔
245
                        sqlite3_bind_text(stmt,
29✔
246
                                          lpc + 1,
247
                                          lv.text_value(),
248
                                          lv.text_length(),
29✔
249
                                          SQLITE_TRANSIENT);
250
                        retval[name] = lv.to_string();
87✔
251
                        break;
29✔
252
                }
253
            }
254
        } else {
29✔
UNCOV
255
            sqlite3_bind_null(stmt, lpc + 1);
×
UNCOV
256
            log_warning("Could not bind variable: %s", name);
×
UNCOV
257
            retval[name]
×
UNCOV
258
                = string_fragment::from_c_str(db_label_source::NULL_STR);
×
259
        }
260
    }
261

262
    return Ok(retval);
1,618✔
263
}
1,619✔
264

265
static void
266
execute_search(const std::string& search_cmd)
7✔
267
{
268
    textview_curses* tc = get_textview_for_mode(lnav_data.ld_mode);
7✔
269
    auto search_term = string_fragment(search_cmd)
7✔
270
                           .find_right_boundary(0, string_fragment::tag1{'\n'})
7✔
271
                           .to_string();
7✔
272
    tc->execute_search(search_term);
7✔
273
}
7✔
274

275
Result<std::string, lnav::console::user_message>
276
execute_sql(exec_context& ec, const std::string& sql, std::string& alt_msg)
1,632✔
277
{
278
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
1,632✔
279
    timeval start_tv, end_tv;
280
    std::string stmt_str = trim(sql);
1,632✔
281
    std::string retval;
1,632✔
282
    int retcode = SQLITE_OK;
1,632✔
283

284
    if (lnav::sql::is_prql(stmt_str)) {
1,632✔
285
        log_info("compiling PRQL: %s", stmt_str.c_str());
42✔
286

287
#if HAVE_RUST_DEPS
288
        auto opts = prqlc::Options{true, "sql.sqlite", true};
42✔
289

290
        auto tree = sqlite_extension_prql;
42✔
291
        for (const auto& mod : lnav_prql_modules) {
126✔
292
            log_debug("prqlc adding mod %s", mod.get_name());
84✔
293
            tree.emplace_back(prqlc::SourceTreeElement{
168✔
294
                mod.get_name(),
295
                mod.to_string_fragment_producer()->to_string(),
168✔
296
            });
297
        }
298
        tree.emplace_back(prqlc::SourceTreeElement{"", stmt_str});
42✔
299
        log_debug("BEGIN compiling tree");
42✔
300
        auto cr = prqlc::compile_tree(tree, opts);
42✔
301
        log_debug("END compiling tree");
42✔
302

303
        for (const auto& msg : cr.messages) {
42✔
304
            if (msg.kind != prqlc::MessageKind::Error) {
1✔
UNCOV
305
                continue;
×
306
            }
307

308
            auto stmt_al = attr_line_t(stmt_str);
1✔
309
            readline_sql_highlighter(stmt_al, lnav::sql::dialect::prql, 0);
1✔
310
            auto um
UNCOV
311
                = lnav::console::user_message::error(
×
312
                      attr_line_t("unable to compile PRQL: ").append(stmt_al))
2✔
313
                      .with_reason(
1✔
314
                          attr_line_t::from_ansi_str((std::string) msg.reason));
2✔
315
            if (!msg.display.empty()) {
1✔
316
                um.with_note(
1✔
317
                    attr_line_t::from_ansi_str((std::string) msg.display));
2✔
318
            }
319
            for (const auto& hint : msg.hints) {
1✔
UNCOV
320
                um.with_help(attr_line_t::from_ansi_str((std::string) hint));
×
UNCOV
321
                break;
×
322
            }
323
            return Err(um);
1✔
324
        }
1✔
325
        log_debug("done!");
41✔
326
        stmt_str = (std::string) cr.output;
41✔
327
#else
328
        auto um = lnav::console::user_message::error(
329
            attr_line_t("PRQL is not supported in this build"));
330
        return Err(um);
331
#endif
332
    }
44✔
333

334
    log_info("Executing SQL: %s", stmt_str.c_str());
1,631✔
335

336
    auto old_mode = lnav_data.ld_mode;
1,631✔
337
    lnav_data.ld_mode = ln_mode_t::BUSY;
1,631✔
338
    auto mode_fin = finally([old_mode]() { lnav_data.ld_mode = old_mode; });
1,631✔
339
    lnav_data.ld_bottom_source.grep_error("");
1,631✔
340
    lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
1,631✔
341

342
    if (startswith(stmt_str, ".")) {
1,631✔
343
        std::vector<std::string> args;
5✔
344
        split_ws(stmt_str, args);
5✔
345

346
        const auto* sql_cmd_map
347
            = injector::get<readline_context::command_map_t*,
348
                            sql_cmd_map_tag>();
5✔
349
        auto cmd_iter = sql_cmd_map->find(args[0]);
5✔
350

351
        if (cmd_iter != sql_cmd_map->end()) {
5✔
352
            ec.ec_current_help = &cmd_iter->second->c_help;
5✔
353
            auto retval = cmd_iter->second->c_func(ec, stmt_str, args);
5✔
354
            ec.ec_current_help = nullptr;
5✔
355

356
            return retval;
5✔
357
        }
5✔
358
    }
5✔
359

360
    ec.ec_accumulator->clear();
1,626✔
361

362
    require(!ec.ec_source.empty());
1,626✔
363
    const auto& source = ec.ec_source.back();
1,626✔
364
    sql_progress_guard progress_guard(sql_progress,
365
                                      sql_progress_finished,
366
                                      source.s_location,
367
                                      source.s_content);
1,626✔
368
    gettimeofday(&start_tv, nullptr);
1,626✔
369

370
    const auto* curr_stmt = stmt_str.c_str();
1,626✔
371
    auto last_is_readonly = false;
1,626✔
372
    while (curr_stmt != nullptr) {
3,189✔
373
        const char* tail = nullptr;
3,189✔
374
        while (isspace(*curr_stmt)) {
3,189✔
375
            curr_stmt += 1;
×
376
        }
377
        retcode = sqlite3_prepare_v2(
3,189✔
378
            lnav_data.ld_db.in(), curr_stmt, -1, stmt.out(), &tail);
379
        if (retcode != SQLITE_OK) {
3,189✔
380
            const char* errmsg = sqlite3_errmsg(lnav_data.ld_db);
7✔
381

382
            alt_msg = "";
7✔
383

384
            auto um = lnav::console::user_message::error(
14✔
385
                          "failed to compile SQL statement")
386
                          .with_reason(errmsg)
14✔
387
                          .with_snippets(ec.ec_source)
14✔
388
                          .move();
7✔
389

390
            auto annotated_sql = annotate_sql_with_error(
391
                lnav_data.ld_db.in(), curr_stmt, tail);
7✔
392
            auto loc = um.um_snippets.back().s_location;
7✔
393
            if (curr_stmt == stmt_str.c_str()) {
7✔
394
                um.um_snippets.pop_back();
7✔
395
            } else {
UNCOV
396
                auto tail_iter = stmt_str.begin();
×
397

UNCOV
398
                std::advance(tail_iter, (curr_stmt - stmt_str.c_str()));
×
UNCOV
399
                loc.sl_line_number
×
UNCOV
400
                    += std::count(stmt_str.begin(), tail_iter, '\n');
×
401
            }
402

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

405
            return Err(um);
7✔
406
        }
7✔
407
        if (stmt == nullptr) {
3,182✔
408
            retcode = SQLITE_DONE;
1,563✔
409
            break;
1,563✔
410
        }
411
#ifdef HAVE_SQLITE3_STMT_READONLY
412
        last_is_readonly = sqlite3_stmt_readonly(stmt.in());
1,619✔
413
        if (ec.is_read_only() && !last_is_readonly) {
1,619✔
414
            return ec.make_error(
415
                "modifying statements are not allowed in this context: {}",
UNCOV
416
                sql);
×
417
        }
418
#endif
419
        bool done = false;
1,619✔
420

421
        auto bound_values = TRY(bind_sql_parameters(ec, stmt.in()));
1,619✔
422
        if (last_is_readonly) {
1,618✔
423
            ec.ec_sql_callback(ec, stmt.in());
1,501✔
424
        }
425
        while (!done) {
5,792✔
426
            retcode = sqlite3_step(stmt.in());
4,229✔
427

428
            switch (retcode) {
4,229✔
429
                case SQLITE_OK:
1,563✔
430
                case SQLITE_DONE: {
431
                    auto changes = sqlite3_changes(lnav_data.ld_db.in());
1,563✔
432

433
                    log_info("sqlite3_changes() -> %d", changes);
1,563✔
434
                    done = true;
1,563✔
435
                    break;
1,563✔
436
                }
437

438
                case SQLITE_ROW:
2,611✔
439
                    ec.ec_sql_callback(ec, stmt.in());
2,611✔
440
                    break;
2,611✔
441

442
                default: {
55✔
443
                    attr_line_t bound_note;
55✔
444

445
                    if (!bound_values.empty()) {
55✔
446
                        bound_note.append(
3✔
447
                            "the bound parameters are set as follows:\n");
448
                        for (const auto& bval : bound_values) {
11✔
449
                            auto val_as_str = fmt::to_string(bval.second);
8✔
450
                            auto sql_type = bval.second.match(
8✔
451
                                [](const std::string&) { return SQLITE_TEXT; },
5✔
UNCOV
452
                                [](const string_fragment&) {
×
UNCOV
453
                                    return SQLITE_TEXT;
×
454
                                },
UNCOV
455
                                [](bool) { return SQLITE_INTEGER; },
×
456
                                [](int64_t) { return SQLITE_INTEGER; },
1✔
457
                                [](null_value_t) { return SQLITE_NULL; },
1✔
458
                                [](double) { return SQLITE_FLOAT; });
1✔
459
                            auto scrubbed_val = scrub_ws(val_as_str.c_str());
8✔
460
                            truncate_to(scrubbed_val, 40);
8✔
461
                            bound_note.append("  ")
8✔
462
                                .append(lnav::roles::variable(bval.first))
16✔
463
                                .append(":")
8✔
464
                                .append(sqlite3_type_to_string(sql_type))
8✔
465
                                .append(" = ")
8✔
466
                                .append_quoted(scrubbed_val)
16✔
467
                                .append("\n");
8✔
468
                        }
8✔
469
                    }
470

471
                    log_error("sqlite3_step error code: %d", retcode);
55✔
472
                    auto um = sqlite3_error_to_user_message(lnav_data.ld_db)
55✔
473
                                  .with_context_snippets(ec.ec_source)
110✔
474
                                  .remove_internal_snippets()
55✔
475
                                  .with_note(bound_note)
55✔
476
                                  .move();
55✔
477

478
                    return Err(um);
55✔
479
                }
55✔
480
            }
481
        }
482

483
        curr_stmt = tail;
1,563✔
484
    }
1,618✔
485

486
    if (last_is_readonly && !ec.ec_label_source_stack.empty()) {
1,563✔
487
        auto& dls = *ec.ec_label_source_stack.back();
1,476✔
488
        dls.dls_query_end = std::chrono::steady_clock::now();
1,476✔
489

490
        size_t memory_usage = 0, total_size = 0, cached_chunks = 0;
1,476✔
491
        for (auto cc = dls.dls_cell_container.cc_first.get(); cc != nullptr;
2,953✔
492
             cc = cc->cc_next.get())
1,477✔
493
        {
494
            total_size += cc->cc_capacity;
1,477✔
495
            if (cc->cc_data) {
1,477✔
496
                cached_chunks += 1;
1,476✔
497
                memory_usage += cc->cc_capacity;
1,476✔
498
            } else {
499
                memory_usage += cc->cc_compressed_size;
1✔
500
            }
501
        }
502
        log_debug(
1,476✔
503
            "cell memory footprint: total=%zu; actual=%zu; cached-chunks=%zu",
504
            total_size,
505
            memory_usage,
506
            cached_chunks);
507
    }
508
    gettimeofday(&end_tv, nullptr);
1,563✔
509
    lnav_data.ld_mode = old_mode;
1,563✔
510
    if (retcode == SQLITE_DONE) {
1,563✔
511
        if (lnav_data.ld_log_source.is_line_meta_changed()) {
1,563✔
512
            lnav_data.ld_log_source.text_filters_changed();
9✔
513
            lnav_data.ld_views[LNV_LOG].reload_data();
9✔
514
        }
515
        lnav_data.ld_filter_view.reload_data();
1,563✔
516
        lnav_data.ld_files_view.reload_data();
1,563✔
517

518
        lnav_data.ld_active_files.fc_files
519
            | lnav::itertools::for_each(&logfile::dump_stats);
1,563✔
520
        if (ec.ec_sql_callback != sql_callback) {
1,563✔
521
            retval = ec.ec_accumulator->get_string();
39✔
522
        } else {
523
            auto& dls = *(ec.ec_label_source_stack.back());
1,524✔
524
            if (!dls.dls_row_cursors.empty()) {
1,524✔
525
                lnav_data.ld_views[LNV_DB].reload_data();
1,422✔
526
                lnav_data.ld_views[LNV_DB].set_selection(0_vl);
1,422✔
527
                lnav_data.ld_views[LNV_DB].set_left(0);
1,422✔
528
                if (lnav_data.ld_flags & LNF_HEADLESS) {
1,422✔
529
                    if (ec.ec_local_vars.size() == 1) {
938✔
530
                        ensure_view(&lnav_data.ld_views[LNV_DB]);
851✔
531
                    }
532

533
                    retval = "";
938✔
534
                    alt_msg = "";
938✔
535
                } else if (dls.dls_row_cursors.size() == 1) {
484✔
536
                    retval = dls.get_row_as_string(0_vl);
436✔
537
                } else {
538
                    int row_count = dls.dls_row_cursors.size();
48✔
539
                    char row_count_buf[128];
540
                    timeval diff_tv;
541

542
                    timersub(&end_tv, &start_tv, &diff_tv);
48✔
543
                    snprintf(row_count_buf,
48✔
544
                             sizeof(row_count_buf),
545
                             ANSI_BOLD("%'d") " row%s matched in " ANSI_BOLD(
546
                                 "%ld.%03ld") " seconds",
547
                             row_count,
548
                             row_count == 1 ? "" : "s",
549
                             diff_tv.tv_sec,
550
                             std::max((long) diff_tv.tv_usec / 1000, 1L));
48✔
551
                    retval = row_count_buf;
48✔
552
                    if (dls.has_log_time_column()) {
48✔
553
                        alt_msg
554
                            = HELP_MSG_1(Q,
2✔
555
                                         "to switch back to the previous view "
556
                                         "at the matching 'log_time' value");
557
                    } else {
558
                        alt_msg = "";
46✔
559
                    }
560
                }
561
            }
562
#ifdef HAVE_SQLITE3_STMT_READONLY
563
            else if (last_is_readonly)
102✔
564
            {
565
                retval = "info: No rows matched";
24✔
566
                alt_msg = "";
24✔
567

568
                if (lnav_data.ld_flags & LNF_HEADLESS) {
24✔
569
                    if (ec.ec_local_vars.size() == 1) {
24✔
570
                        lnav_data.ld_views[LNV_DB].reload_data();
14✔
571
                        ensure_view(&lnav_data.ld_views[LNV_DB]);
14✔
572
                    }
573
                }
574
            }
575
#endif
576
        }
577
    }
578

579
    return Ok(retval);
1,563✔
580
}
1,632✔
581

582
Result<void, lnav::console::user_message>
583
multiline_executor::handle_command(const std::string& cmdline)
209✔
584
{
585
    this->me_last_result = TRY(execute_from_file(this->me_exec_context,
209✔
586
                                                 this->p_source,
587
                                                 this->p_starting_line_number,
588
                                                 cmdline));
589

590
    return Ok();
205✔
591
}
592

593
static Result<std::string, lnav::console::user_message>
594
execute_file_contents(exec_context& ec, const std::filesystem::path& path)
35✔
595
{
596
    static const std::filesystem::path stdin_path("-");
35✔
597
    static const std::filesystem::path dev_stdin_path("/dev/stdin");
35✔
598

599
    FILE* file;
600

601
    if (path == stdin_path || path == dev_stdin_path) {
35✔
602
        if (isatty(STDIN_FILENO)) {
3✔
UNCOV
603
            return ec.make_error("stdin has already been consumed");
×
604
        }
605
        file = stdin;
3✔
606
    } else if ((file = fopen(path.c_str(), "re")) == nullptr) {
32✔
UNCOV
607
        return ec.make_error("unable to open file");
×
608
    }
609

610
    std::string out_name;
35✔
611

612
    auto_mem<char> line;
35✔
613
    size_t line_max_size;
614
    ssize_t line_size;
615
    multiline_executor me(ec, path.string());
35✔
616

617
    ec.ec_local_vars.top()["0"] = path.string();
105✔
618
    ec.ec_path_stack.emplace_back(path.parent_path());
35✔
619
    exec_context::output_guard og(ec);
70✔
620
    while ((line_size = getline(line.out(), &line_max_size, file)) != -1) {
788✔
621
        TRY(me.push_back(string_fragment::from_bytes(line.in(), line_size)));
757✔
622
    }
623

624
    TRY(me.final());
31✔
625
    auto retval = std::move(me.me_last_result);
31✔
626

627
    if (file == stdin) {
31✔
628
        if (isatty(STDOUT_FILENO)) {
3✔
UNCOV
629
            log_perror(dup2(STDOUT_FILENO, STDIN_FILENO));
×
630
        }
631
    } else {
632
        fclose(file);
28✔
633
    }
634
    ec.ec_path_stack.pop_back();
31✔
635

636
    return Ok(retval);
31✔
637
}
35✔
638

639
Result<std::string, lnav::console::user_message>
640
execute_file(exec_context& ec, const std::string& path_and_args)
36✔
641
{
642
    static const intern_string_t SRC = intern_string::lookup("cmdline");
84✔
643

644
    std::string retval, msg;
36✔
645
    shlex lexer(path_and_args);
36✔
646

647
    log_info("Executing file: %s", path_and_args.c_str());
36✔
648

649
    auto split_args_res = lexer.split(scoped_resolver{&ec.ec_local_vars.top()});
36✔
650
    if (split_args_res.isErr()) {
36✔
UNCOV
651
        auto split_err = split_args_res.unwrapErr();
×
UNCOV
652
        auto um = lnav::console::user_message::error(
×
653
                      "unable to parse script command-line")
UNCOV
654
                      .with_reason(split_err.se_error.te_msg)
×
UNCOV
655
                      .with_snippet(lnav::console::snippet::from(
×
UNCOV
656
                          SRC, lexer.to_attr_line(split_err.se_error)))
×
UNCOV
657
                      .move();
×
658

UNCOV
659
        return Err(um);
×
660
    }
661
    auto split_args = split_args_res.unwrap();
36✔
662
    if (split_args.empty()) {
36✔
UNCOV
663
        return ec.make_error("no script specified");
×
664
    }
665

666
    ec.ec_local_vars.emplace();
36✔
667

668
    auto script_name = split_args[0].se_value;
36✔
669
    auto& vars = ec.ec_local_vars.top();
36✔
670
    std::string star, open_error = "file not found";
72✔
671

672
    add_ansi_vars(vars);
36✔
673

674
    vars["#"] = fmt::to_string(split_args.size() - 1);
108✔
675
    for (size_t lpc = 0; lpc < split_args.size(); lpc++) {
111✔
676
        vars[fmt::to_string(lpc)] = split_args[lpc].se_value;
75✔
677
    }
678
    for (size_t lpc = 1; lpc < split_args.size(); lpc++) {
75✔
679
        if (lpc > 1) {
39✔
680
            star.append(" ");
21✔
681
        }
682
        star.append(split_args[lpc].se_value);
39✔
683
    }
684
    vars["__all__"] = star;
36✔
685

686
    std::vector<script_metadata> paths_to_exec;
36✔
687

688
    auto scripts = find_format_scripts(lnav_data.ld_config_paths);
36✔
689
    auto iter = scripts.as_scripts.find(script_name);
36✔
690
    if (iter != scripts.as_scripts.end()) {
36✔
691
        paths_to_exec = iter->second;
25✔
692
    }
693
    if (lnav::filesystem::is_url(script_name)) {
36✔
UNCOV
694
        auto_mem<CURLU> cu(curl_url_cleanup);
×
UNCOV
695
        cu = curl_url();
×
UNCOV
696
        auto set_rc = curl_url_set(cu, CURLUPART_URL, script_name.c_str(), 0);
×
UNCOV
697
        if (set_rc == CURLUE_OK) {
×
UNCOV
698
            auto_mem<char> scheme_part(curl_free);
×
699
            auto get_rc
UNCOV
700
                = curl_url_get(cu, CURLUPART_SCHEME, scheme_part.out(), 0);
×
UNCOV
701
            if (get_rc == CURLUE_OK
×
UNCOV
702
                && string_fragment::from_c_str(scheme_part.in()) == "file")
×
703
            {
UNCOV
704
                auto_mem<char> path_part;
×
705
                auto get_rc
UNCOV
706
                    = curl_url_get(cu, CURLUPART_PATH, path_part.out(), 0);
×
UNCOV
707
                if (get_rc == CURLUE_OK) {
×
UNCOV
708
                    auto rp_res = lnav::filesystem::realpath(path_part.in());
×
UNCOV
709
                    if (rp_res.isOk()) {
×
UNCOV
710
                        struct script_metadata meta;
×
711

712
                        meta.sm_path = rp_res.unwrap();
×
713
                        extract_metadata_from_file(meta);
×
714
                        paths_to_exec.push_back(meta);
×
715
                    }
716
                }
717
            }
718
        }
719
    }
720
    if (script_name == "-" || script_name == "/dev/stdin") {
36✔
721
        paths_to_exec.push_back({script_name, "", "", ""});
3✔
722
    } else if (access(script_name.c_str(), R_OK) == 0) {
33✔
723
        struct script_metadata meta;
7✔
724
        auto rp_res = lnav::filesystem::realpath(script_name);
7✔
725

726
        if (rp_res.isErr()) {
7✔
727
            log_error("unable to get realpath() of %s -- %s",
×
728
                      script_name.c_str(),
729
                      rp_res.unwrapErr().c_str());
730
            meta.sm_path = script_name;
×
731
        } else {
732
            meta.sm_path = rp_res.unwrap();
7✔
733
        }
734
        extract_metadata_from_file(meta);
7✔
735
        paths_to_exec.push_back(meta);
7✔
736
    } else if (errno != ENOENT) {
33✔
UNCOV
737
        open_error = strerror(errno);
×
738
    } else {
739
        auto script_path = std::filesystem::path(script_name);
26✔
740

741
        if (!script_path.is_absolute()) {
26✔
742
            script_path = ec.ec_path_stack.back() / script_path;
26✔
743
        }
744

745
        if (std::filesystem::is_regular_file(script_path)) {
26✔
UNCOV
746
            script_metadata meta;
×
747

UNCOV
748
            meta.sm_path = script_path;
×
UNCOV
749
            extract_metadata_from_file(meta);
×
UNCOV
750
            paths_to_exec.push_back(meta);
×
751
        } else if (errno != ENOENT) {
26✔
UNCOV
752
            open_error = strerror(errno);
×
753
        }
754
    }
26✔
755

756
    if (!paths_to_exec.empty()) {
36✔
757
        for (auto& path_iter : paths_to_exec) {
66✔
758
            if (!ec.ec_output_stack.empty()) {
35✔
759
                ec.ec_output_stack.back().od_format
35✔
760
                    = path_iter.sm_output_format;
35✔
761
                log_info("%s: setting out format for '%s' to %d",
35✔
762
                         script_name.c_str(),
763
                         ec.ec_output_stack.back().od_name.c_str(),
764
                         ec.ec_output_stack.back().od_format);
765
            }
766
            retval = TRY(execute_file_contents(ec, path_iter.sm_path));
35✔
767
        }
768
    }
769
    ec.ec_local_vars.pop();
32✔
770

771
    if (paths_to_exec.empty()) {
32✔
772
        return ec.make_error(
773
            "unknown script -- {} -- {}", script_name, open_error);
1✔
774
    }
775

776
    return Ok(retval);
31✔
777
}
51✔
778

779
Result<std::string, lnav::console::user_message>
780
execute_from_file(exec_context& ec,
209✔
781
                  const std::string& src,
782
                  int line_number,
783
                  const std::string& cmdline)
784
{
785
    std::string retval, alt_msg;
209✔
786
    auto _sg
787
        = ec.enter_source(intern_string::lookup(src), line_number, cmdline);
209✔
788

789
    lnav_data.ld_view_stack.top() | [&ec](auto* tc) {
209✔
790
        ec.ec_top_line = tc->get_selection().value_or(0_vl);
209✔
791
    };
209✔
792
    switch (cmdline[0]) {
209✔
793
        case ':':
92✔
794
            retval = TRY(execute_command(ec, cmdline.substr(1)));
92✔
795
            break;
92✔
796
        case '/':
1✔
797
            execute_search(cmdline.substr(1));
1✔
798
            break;
1✔
799
        case ';': {
114✔
800
            if (!ec.ec_msg_callback_stack.empty()) {
114✔
801
                auto src_slash = src.rfind('/');
110✔
802
                auto cmd_al
803
                    = attr_line_t(cmdline.substr(0, cmdline.find('\n')));
110✔
804
                readline_lnav_highlighter(cmd_al, std::nullopt);
110✔
805
                const auto um = lnav::console::user_message::info(
110✔
806
                                    attr_line_t("Executing command at ")
110✔
807
                                        .append(lnav::roles::file(
110✔
808
                                            src_slash != std::string::npos
809
                                                ? src.substr(src_slash + 1)
220✔
810
                                                : src))
811
                                        .append(":")
110✔
812
                                        .append(lnav::roles::number(
220✔
813
                                            fmt::to_string(line_number)))
220✔
814
                                        .append(" \u2014 ")
110✔
815
                                        .append(cmd_al))
110✔
816
                                    .move();
110✔
817
                ec.ec_msg_callback_stack.back()(um);
110✔
818
            }
110✔
819

820
            setup_logline_table(ec);
114✔
821
            retval = TRY(execute_sql(ec, cmdline.substr(1), alt_msg));
114✔
822
            break;
110✔
823
        }
824
        case '|':
2✔
825
            retval = TRY(execute_file(ec, cmdline.substr(1)));
2✔
826
            break;
2✔
827
        default:
×
828
            retval = TRY(execute_command(ec, cmdline));
×
UNCOV
829
            break;
×
830
    }
831

832
    log_info(
205✔
833
        "%s:%d:execute result -- %s", src.c_str(), line_number, retval.c_str());
834

835
    return Ok(retval);
205✔
836
}
209✔
837

838
Result<std::string, lnav::console::user_message>
839
execute_any(exec_context& ec, const std::string& cmdline_with_mode)
34✔
840
{
841
    if (cmdline_with_mode.empty()) {
34✔
UNCOV
842
        auto um = lnav::console::user_message::error("empty command")
×
UNCOV
843
                      .with_help(
×
844
                          "a command should start with ':', ';', '/', '|' and "
845
                          "followed by the operation to perform")
846
                      .move();
×
847
        if (!ec.ec_source.empty()) {
×
UNCOV
848
            um.with_snippet(ec.ec_source.back());
×
849
        }
UNCOV
850
        return Err(um);
×
851
    }
852

853
    std::string retval, alt_msg, cmdline = cmdline_with_mode.substr(1);
34✔
854
    auto _cleanup = finally([&ec] {
34✔
855
        if (ec.is_read_write() &&
34✔
856
            // only rebuild in a script or non-interactive mode so we don't
857
            // block the UI.
UNCOV
858
            lnav_data.ld_flags & LNF_HEADLESS)
×
859
        {
UNCOV
860
            rescan_files();
×
UNCOV
861
            wait_for_pipers(std::nullopt);
×
UNCOV
862
            rebuild_indexes_repeatedly();
×
863
        }
864
    });
68✔
865

866
    switch (cmdline_with_mode[0]) {
34✔
867
        case ':':
×
868
            retval = TRY(execute_command(ec, cmdline));
×
UNCOV
869
            break;
×
UNCOV
870
        case '/':
×
UNCOV
871
            execute_search(cmdline);
×
UNCOV
872
            break;
×
873
        case ';':
30✔
874
            setup_logline_table(ec);
30✔
875
            retval = TRY(execute_sql(ec, cmdline, alt_msg));
30✔
876
            break;
30✔
877
        case '|': {
4✔
878
            retval = TRY(execute_file(ec, cmdline));
4✔
879
            break;
4✔
880
        }
881
        default:
×
UNCOV
882
            retval = TRY(execute_command(ec, cmdline));
×
UNCOV
883
            break;
×
884
    }
885

886
    return Ok(retval);
34✔
887
}
34✔
888

889
void
890
execute_init_commands(
563✔
891
    exec_context& ec,
892
    std::vector<std::pair<Result<std::string, lnav::console::user_message>,
893
                          std::string>>& msgs)
894
{
895
    if (lnav_data.ld_cmd_init_done) {
563✔
896
        return;
×
897
    }
898

899
    std::string out_name;
563✔
900
    std::optional<exec_context::output_t> ec_out;
563✔
901
    auto_fd fd_copy;
563✔
902
    auto& dls = *(ec.ec_label_source_stack.back());
563✔
903
    int option_index = 1;
563✔
904

905
    {
906
        log_info("Executing initial commands");
563✔
907
        exec_context::output_guard og(ec, out_name, ec_out);
563✔
908

909
        for (auto& cmd : lnav_data.ld_commands) {
1,486✔
910
            static const auto COMMAND_OPTION_SRC
911
                = intern_string::lookup("command-option");
1,907✔
912

913
            std::string alt_msg;
923✔
914
            auto has_db_query = false;
923✔
915

916
            wait_for_children();
923✔
917

918
            lnav_data.ld_view_stack.top() | [&ec](auto* tc) {
923✔
919
                ec.ec_top_line = tc->get_selection().value_or(0_vl);
923✔
920
            };
923✔
921
            log_debug("init cmd: %s", cmd.c_str());
923✔
922
            {
923
                auto _sg
924
                    = ec.enter_source(COMMAND_OPTION_SRC, option_index++, cmd);
923✔
925
                switch (cmd.at(0)) {
923✔
926
                    case ':':
565✔
927
                        msgs.emplace_back(execute_command(ec, cmd.substr(1)),
565✔
928
                                          alt_msg);
929
                        break;
565✔
930
                    case '/':
6✔
931
                        execute_search(cmd.substr(1));
6✔
932
                        break;
6✔
933
                    case ';':
327✔
934
                        setup_logline_table(ec);
327✔
935
                        msgs.emplace_back(
327✔
936
                            execute_sql(ec, cmd.substr(1), alt_msg), alt_msg);
654✔
937
                        has_db_query = true;
327✔
938
                        break;
327✔
939
                    case '|':
25✔
940
                        msgs.emplace_back(execute_file(ec, cmd.substr(1)),
25✔
941
                                          alt_msg);
942
                        break;
25✔
943
                }
944

945
                rescan_files();
923✔
946
                auto deadline = ui_clock::now();
923✔
947
                if (lnav_data.ld_flags & LNF_HEADLESS) {
923✔
948
                    deadline += 5s;
923✔
949
                } else {
UNCOV
950
                    deadline += 500ms;
×
951
                }
952
                wait_for_pipers(deadline);
923✔
953
                rebuild_indexes_repeatedly();
923✔
954
            }
923✔
955
            if (has_db_query && !dls.dls_headers.empty()
327✔
956
                && lnav_data.ld_view_stack.size() == 1)
1,250✔
957
            {
958
                lnav_data.ld_views[LNV_DB].reload_data();
9✔
959
                ensure_view(LNV_DB);
9✔
960
            }
961
        }
923✔
962
    }
563✔
963
    lnav_data.ld_commands.clear();
563✔
964
    lnav_data.ld_cmd_init_done = true;
563✔
965
}
563✔
966

967
int
968
sql_callback(exec_context& ec, sqlite3_stmt* stmt)
4,027✔
969
{
970
    auto& dls = *(ec.ec_label_source_stack.back());
4,027✔
971
    const int ncols = sqlite3_column_count(stmt);
4,027✔
972

973
    if (!sqlite3_stmt_busy(stmt)) {
4,027✔
974
        dls.clear();
1,463✔
975

976
        for (int lpc = 0; lpc < ncols; lpc++) {
4,371✔
977
            const int type = sqlite3_column_type(stmt, lpc);
2,908✔
978
            std::string colname = sqlite3_column_name(stmt, lpc);
2,908✔
979

980
            dls.push_header(colname, type);
2,908✔
981
        }
2,908✔
982
        return 0;
1,463✔
983
    }
984

985
    int retval = 0;
2,564✔
986
    auto set_vars = dls.dls_row_cursors.empty();
2,564✔
987

988
    if (dls.dls_row_cursors.empty()) {
2,564✔
989
        dls.dls_query_start = std::chrono::steady_clock::now();
1,414✔
990
        for (int lpc = 0; lpc < ncols; lpc++) {
4,143✔
991
            int type = sqlite3_column_type(stmt, lpc);
2,729✔
992
            std::string colname = sqlite3_column_name(stmt, lpc);
2,729✔
993

994
            bool graphable = (type == SQLITE_INTEGER || type == SQLITE_FLOAT);
2,729✔
995
            auto& hm = dls.dls_headers[lpc];
2,729✔
996
            hm.hm_column_type = type;
2,729✔
997
            if (lnav_data.ld_db_key_names.count(colname) > 0) {
2,729✔
998
                hm.hm_graphable = false;
532✔
999
            } else if (graphable) {
2,197✔
1000
                dls.set_col_as_graphable(lpc);
631✔
1001
            }
1002
        }
2,729✔
1003
    }
1004

1005
    dls.dls_row_cursors.emplace_back(dls.dls_cell_container.end_cursor());
2,564✔
1006
    dls.dls_push_column = 0;
2,564✔
1007
    for (int lpc = 0; lpc < ncols; lpc++) {
11,304✔
1008
        const auto value_type = sqlite3_column_type(stmt, lpc);
8,740✔
1009
        db_label_source::column_value_t value;
8,740✔
1010
        auto& hm = dls.dls_headers[lpc];
8,740✔
1011

1012
        switch (value_type) {
8,740✔
1013
            case SQLITE_INTEGER:
2,539✔
1014
                value = (int64_t) sqlite3_column_int64(stmt, lpc);
2,539✔
1015
                hm.hm_align = text_align_t::end;
2,539✔
1016
                break;
2,539✔
1017
            case SQLITE_FLOAT:
231✔
1018
                value = sqlite3_column_double(stmt, lpc);
231✔
1019
                hm.hm_align = text_align_t::end;
231✔
1020
                break;
231✔
1021
            case SQLITE_NULL:
2,051✔
1022
                value = null_value_t{};
2,051✔
1023
                break;
2,051✔
1024
            default: {
3,919✔
1025
                auto frag = string_fragment::from_bytes(
3,919✔
1026
                    sqlite3_column_text(stmt, lpc),
1027
                    sqlite3_column_bytes(stmt, lpc));
3,919✔
1028
                if (!frag.empty()) {
3,919✔
1029
                    if (isdigit(frag[0])) {
3,860✔
1030
                        hm.hm_align = text_align_t::end;
1,322✔
1031
                        if (!hm.hm_graphable.has_value()) {
1,322✔
1032
                            auto split_res = humanize::try_from<double>(frag);
234✔
1033
                            if (split_res.has_value()) {
234✔
1034
                                dls.set_col_as_graphable(lpc);
58✔
1035
                            } else {
1036
                                hm.hm_graphable = false;
176✔
1037
                            }
1038
                        }
1039
                    } else if (!hm.hm_graphable.has_value()) {
2,538✔
1040
                        hm.hm_graphable = false;
1,014✔
1041
                    }
1042
                }
1043
                value = frag;
3,919✔
1044
                break;
3,919✔
1045
            }
1046
        }
1047
        dls.push_column(value);
8,740✔
1048
        if ((hm.hm_column_type == SQLITE_TEXT
8,740✔
1049
             || hm.hm_column_type == SQLITE_NULL)
4,740✔
1050
            && hm.hm_sub_type == 0)
6,005✔
1051
        {
1052
            switch (value_type) {
5,903✔
1053
                case SQLITE_TEXT:
3,819✔
1054
                    auto* raw_value = sqlite3_column_value(stmt, lpc);
3,819✔
1055
                    hm.hm_column_type = SQLITE_TEXT;
3,819✔
1056
                    hm.hm_sub_type = sqlite3_value_subtype(raw_value);
3,819✔
1057
                    break;
3,819✔
1058
            }
1059
        }
1060
        if (set_vars && !ec.ec_local_vars.empty() && !ec.ec_dry_run) {
8,740✔
1061
            if (sql_ident_needs_quote(hm.hm_name.c_str())) {
2,729✔
1062
                continue;
1,143✔
1063
            }
1064
            auto& vars = ec.ec_local_vars.top();
1,586✔
1065

1066
            vars[hm.hm_name] = value.match(
3,172✔
UNCOV
1067
                [](const string_fragment& sf) {
×
1068
                    return scoped_value_t{sf.to_string()};
700✔
1069
                },
1070
                [](int64_t i) { return scoped_value_t{i}; },
450✔
1071
                [](double d) { return scoped_value_t{d}; },
15✔
1072
                [](null_value_t) { return scoped_value_t{null_value_t{}}; });
3,593✔
1073
        }
1074
    }
8,740✔
1075

1076
    return retval;
2,564✔
1077
}
1078

1079
int
1080
internal_sql_callback(exec_context& ec, sqlite3_stmt* stmt)
14✔
1081
{
1082
    if (!sqlite3_stmt_busy(stmt)) {
14✔
1083
        return 0;
7✔
1084
    }
1085

1086
    const int ncols = sqlite3_column_count(stmt);
7✔
1087

1088
    auto& vars = ec.ec_local_vars.top();
7✔
1089

1090
    for (int lpc = 0; lpc < ncols; lpc++) {
15✔
1091
        const char* column_name = sqlite3_column_name(stmt, lpc);
8✔
1092

1093
        if (sql_ident_needs_quote(column_name)) {
8✔
UNCOV
1094
            continue;
×
1095
        }
1096

1097
        auto value_type = sqlite3_column_type(stmt, lpc);
8✔
1098
        scoped_value_t value;
8✔
1099
        switch (value_type) {
8✔
UNCOV
1100
            case SQLITE_INTEGER:
×
UNCOV
1101
                value = (int64_t) sqlite3_column_int64(stmt, lpc);
×
UNCOV
1102
                break;
×
UNCOV
1103
            case SQLITE_FLOAT:
×
UNCOV
1104
                value = sqlite3_column_double(stmt, lpc);
×
UNCOV
1105
                break;
×
1106
            case SQLITE_NULL:
×
UNCOV
1107
                value = null_value_t{};
×
UNCOV
1108
                break;
×
1109
            default:
8✔
1110
                value
1111
                    = std::string((const char*) sqlite3_column_text(stmt, lpc),
16✔
1112
                                  sqlite3_column_bytes(stmt, lpc));
16✔
1113
                break;
8✔
1114
        }
1115
        vars[column_name] = value;
8✔
1116
    }
8✔
1117

1118
    return 0;
7✔
1119
}
1120

1121
std::future<std::string>
1122
pipe_callback(exec_context& ec, const std::string& cmdline, auto_fd& fd)
7✔
1123
{
1124
    static auto& prompt = lnav::prompt::get();
7✔
1125
    auto out = ec.get_output();
7✔
1126

1127
    if (out) {
7✔
1128
        FILE* file = *out;
7✔
1129

1130
        return std::async(std::launch::async, [fd = std::move(fd), file]() {
14✔
1131
            char buffer[1024];
1132
            ssize_t rc;
1133

1134
            if (file == stdout) {
7✔
1135
                lnav_data.ld_stdout_used = true;
7✔
1136
            }
1137

1138
            while ((rc = read(fd, buffer, sizeof(buffer))) > 0) {
14✔
1139
                fwrite(buffer, rc, 1, file);
7✔
1140
            }
1141

1142
            return std::string();
14✔
1143
        });
7✔
1144
    }
UNCOV
1145
    std::error_code errc;
×
UNCOV
1146
    std::filesystem::create_directories(lnav::paths::workdir(), errc);
×
UNCOV
1147
    auto open_temp_res = lnav::filesystem::open_temp_file(lnav::paths::workdir()
×
UNCOV
1148
                                                          / "exec.XXXXXX");
×
UNCOV
1149
    if (open_temp_res.isErr()) {
×
1150
        return lnav::futures::make_ready_future(
UNCOV
1151
            fmt::format(FMT_STRING("error: cannot open temp file -- {}"),
×
UNCOV
1152
                        open_temp_res.unwrapErr()));
×
1153
    }
1154

UNCOV
1155
    auto tmp_pair = open_temp_res.unwrap();
×
1156

1157
    auto reader = std::thread(
1158
        [in_fd = std::move(fd), out_fd = std::move(tmp_pair.second)]() {
×
1159
            char buffer[1024];
1160
            ssize_t rc;
1161

1162
            while ((rc = read(in_fd, buffer, sizeof(buffer))) > 0) {
×
1163
                write(out_fd, buffer, rc);
×
1164
            }
1165
        });
×
UNCOV
1166
    reader.detach();
×
1167

1168
    static int exec_count = 0;
1169
    auto desc
UNCOV
1170
        = fmt::format(FMT_STRING("exec-{}-output {}"), exec_count++, cmdline);
×
1171
    lnav_data.ld_active_files.fc_file_names[tmp_pair.first]
×
UNCOV
1172
        .with_filename(desc)
×
UNCOV
1173
        .with_include_in_session(false)
×
UNCOV
1174
        .with_detect_format(false)
×
1175
        .with_init_location(0_vl);
×
1176
    lnav_data.ld_files_to_front.emplace_back(desc);
×
UNCOV
1177
    prompt.p_editor.set_alt_value(HELP_MSG_1(X, "to close the file"));
×
1178

1179
    return lnav::futures::make_ready_future(std::string());
×
1180
}
1181

1182
void
1183
add_global_vars(exec_context& ec)
632✔
1184
{
1185
    for (const auto& iter : lnav_config.lc_global_vars) {
9,486✔
1186
        shlex subber(iter.second);
8,854✔
1187
        std::string str;
8,854✔
1188

1189
        if (!subber.eval(str, scoped_resolver{&ec.ec_global_vars})) {
8,854✔
1190
            log_error("Unable to evaluate global variable value: %s",
×
1191
                      iter.second.c_str());
1192
            continue;
×
1193
        }
1194

1195
        ec.ec_global_vars[iter.first] = str;
8,854✔
1196
    }
8,854✔
1197
}
632✔
1198

1199
void
1200
exec_context::set_output(const std::string& name,
565✔
1201
                         FILE* file,
1202
                         int (*closer)(FILE*))
1203
{
1204
    log_info("redirecting command output to: %s", name.c_str());
565✔
1205
    this->ec_output_stack.back().od_output | [](auto out) {
565✔
UNCOV
1206
        if (out.second != nullptr) {
×
1207
            out.second(out.first);
×
1208
        }
1209
    };
1210
    this->ec_output_stack.back()
565✔
1211
        = output_desc{name, std::make_pair(file, closer)};
1,130✔
1212
}
565✔
1213

1214
void
1215
exec_context::clear_output()
69✔
1216
{
1217
    log_info("redirecting command output to screen");
69✔
1218
    this->ec_output_stack.back().od_output | [](auto out) {
69✔
1219
        if (out.second != nullptr) {
34✔
1220
            out.second(out.first);
34✔
1221
        }
1222
    };
34✔
1223
    this->ec_output_stack.back().od_name = "default";
69✔
1224
    this->ec_output_stack.back().od_output = std::nullopt;
69✔
1225
}
69✔
1226

1227
exec_context::exec_context(logline_value_vector* line_values,
3,391✔
1228
                           sql_callback_t sql_callback,
1229
                           pipe_callback_t pipe_callback)
3,391✔
1230
    : ec_line_values(line_values),
3,391✔
1231
      ec_accumulator(std::make_unique<attr_line_t>()),
3,391✔
1232
      ec_sql_callback(sql_callback), ec_pipe_callback(pipe_callback)
6,782✔
1233
{
1234
    this->ec_local_vars.push(std::map<std::string, scoped_value_t>());
3,391✔
1235
    this->ec_path_stack.emplace_back(".");
3,391✔
1236
    this->ec_output_stack.emplace_back("screen", std::nullopt);
3,391✔
1237
}
3,391✔
1238

1239
Result<std::string, lnav::console::user_message>
1240
exec_context::execute(source_location loc, const std::string& cmdline)
2✔
1241
{
1242
    static auto& prompt = lnav::prompt::get();
2✔
1243
    static const auto& dls = lnav_data.ld_db_row_source;
1244

1245
    lnav::textinput::history::op_guard hist_guard;
2✔
1246
    auto sg = this->enter_source(loc, cmdline);
2✔
1247

1248
    auto before_dls_gen = dls.dls_generation;
2✔
1249
    if (this->get_provenance<mouse_input>() && !prompt.p_editor.is_enabled()) {
2✔
UNCOV
1250
        auto& hist = prompt.get_history_for(cmdline[0]);
×
UNCOV
1251
        hist_guard = hist.start_operation(cmdline.substr(1));
×
1252
    }
1253

1254
    auto exec_res = execute_any(*this, cmdline);
2✔
1255
    if (exec_res.isErr()) {
2✔
UNCOV
1256
        hist_guard.og_status = log_level_t::LEVEL_ERROR;
×
UNCOV
1257
        if (!this->ec_msg_callback_stack.empty()) {
×
UNCOV
1258
            this->ec_msg_callback_stack.back()(exec_res.unwrapErr());
×
1259
        }
1260
    } else {
1261
        if (before_dls_gen != dls.dls_generation
4✔
1262
            && dls.dls_row_cursors.size() > 1)
2✔
1263
        {
UNCOV
1264
            ensure_view(LNV_DB);
×
1265
        }
1266
    }
1267

1268
    return exec_res;
4✔
1269
}
2✔
1270

1271
void
1272
exec_context::add_error_context(lnav::console::user_message& um)
67✔
1273
{
1274
    switch (um.um_level) {
67✔
1275
        case lnav::console::user_message::level::raw:
×
1276
        case lnav::console::user_message::level::info:
1277
        case lnav::console::user_message::level::ok:
1278
            return;
×
1279
        default:
67✔
1280
            break;
67✔
1281
    }
1282

1283
    if (um.um_snippets.empty()) {
67✔
1284
        um.with_snippets(this->ec_source);
29✔
1285
        um.remove_internal_snippets();
29✔
1286
    }
1287

1288
    if (this->ec_current_help != nullptr && um.um_help.empty()) {
67✔
1289
        attr_line_t help;
30✔
1290

1291
        format_help_text_for_term(*this->ec_current_help,
30✔
1292
                                  70,
1293
                                  help,
1294
                                  help_text_content::synopsis_and_summary);
1295
        um.with_help(help);
30✔
1296
    }
30✔
1297
}
1298

1299
exec_context::sql_callback_guard
1300
exec_context::push_callback(sql_callback_t cb)
5✔
1301
{
1302
    return sql_callback_guard(*this, cb);
5✔
1303
}
1304

1305
exec_context::source_guard
1306
exec_context::enter_source(source_location loc, const std::string& content)
2,344✔
1307
{
1308
    attr_line_t content_al{content};
2,344✔
1309
    content_al.with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE));
2,344✔
1310
    readline_lnav_highlighter(content_al, -1);
2,344✔
1311
    this->ec_source.emplace_back(lnav::console::snippet::from(loc, content_al));
2,344✔
1312
    return {this};
4,688✔
1313
}
2,344✔
1314

1315
exec_context::output_guard::output_guard(exec_context& context,
630✔
1316
                                         std::string name,
1317
                                         const std::optional<output_t>& file,
1318
                                         text_format_t tf)
630✔
1319
    : sg_context(context), sg_active(!name.empty())
630✔
1320
{
1321
    if (name.empty()) {
630✔
1322
        return;
563✔
1323
    }
1324
    if (file) {
67✔
1325
        log_info("redirecting command output to: %s", name.c_str());
32✔
1326
    } else if (!context.ec_output_stack.empty()) {
35✔
1327
        tf = context.ec_output_stack.back().od_format;
35✔
1328
    }
1329
    context.ec_output_stack.emplace_back(std::move(name), file, tf);
67✔
1330
}
1331

1332
exec_context::output_guard::~output_guard()
630✔
1333
{
1334
    if (this->sg_active) {
630✔
1335
        this->sg_context.clear_output();
67✔
1336
        this->sg_context.ec_output_stack.pop_back();
67✔
1337
    }
1338
}
630✔
1339
exec_context::sql_callback_guard::sql_callback_guard(exec_context& context,
5✔
1340
                                                     sql_callback_t cb)
5✔
1341
    : scg_context(context), scg_old_callback(context.ec_sql_callback)
5✔
1342
{
1343
    context.ec_sql_callback = cb;
5✔
1344
}
5✔
UNCOV
1345
exec_context::sql_callback_guard::sql_callback_guard(sql_callback_guard&& other)
×
UNCOV
1346
    : scg_context(other.scg_context),
×
UNCOV
1347
      scg_old_callback(std::exchange(other.scg_old_callback, nullptr))
×
1348
{
1349
}
1350
exec_context::sql_callback_guard::~sql_callback_guard()
5✔
1351
{
1352
    if (this->scg_old_callback != nullptr) {
5✔
1353
        this->scg_context.ec_sql_callback = this->scg_old_callback;
5✔
1354
    }
1355
}
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