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

tstack / lnav / 25348852825-3024

04 May 2026 11:18PM UTC coverage: 69.963% (+0.7%) from 69.226%
25348852825-3024

push

github

tstack
[ui] horizontal scroll should work on columns

Related to #1685

7 of 141 new or added lines in 5 files covered. (4.96%)

7760 existing lines in 84 files now uncovered.

57014 of 81492 relevant lines covered (69.96%)

622491.44 hits per line

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

82.95
/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 <set>
31
#include <vector>
32

33
#include "command_executor.hh"
34

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

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

70
exec_context INIT_EXEC_CONTEXT;
71

72
static sig_atomic_t sql_counter = 0;
73

74
int
75
sql_progress(const log_cursor& lc)
12,169✔
76
{
77
    if (lnav_data.ld_window == nullptr) {
12,169✔
78
        return 0;
12,158✔
79
    }
80

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

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

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

106
    return 0;
9✔
107
}
108

109
void
110
sql_progress_finished()
1,956✔
111
{
112
    if (sql_counter == 0) {
1,956✔
113
        return;
1,951✔
114
    }
115

116
    sql_counter = 0;
5✔
117

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

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

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

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

143
    auto op_guard = lnav_opid_guard::internal(op);
1,047✔
144
    std::vector<std::string> args;
1,047✔
145

146
    log_info("Executing command: %s", cmdline.c_str());
1,047✔
147

148
    split_ws(cmdline, args);
1,047✔
149

150
    if (!args.empty()) {
1,047✔
151
        readline_context::command_map_t::iterator iter;
1,047✔
152

153
        if ((iter = lnav_commands.find(args[0])) == lnav_commands.end()) {
1,047✔
154
            auto cmd_names
155
                = lnav_commands | lnav::itertools::map([](const auto& pair) {
1✔
156
                      return pair.first.to_string();
119✔
157
                  });
1✔
158
            auto similar = cmd_names | lnav::itertools::similar_to(args[0], 10)
2✔
159
                | lnav::itertools::sorted();
2✔
160
            auto um = lnav::console::user_message::error(
161
                attr_line_t("unknown command: ").append_quoted(args[0]));
1✔
162
            if (!similar.empty()) {
1✔
163
                auto note = attr_line_t("did you mean one of the following:\n");
1✔
164
                for (const auto& name : similar) {
11✔
165
                    auto cmd_iter = lnav_commands.find(name);
10✔
166
                    note.append("  ").append(lnav::roles::symbol(name));
10✔
167
                    if (cmd_iter != lnav_commands.end()
10✔
168
                        && cmd_iter->second->c_help.ht_summary[0])
10✔
169
                    {
170
                        note.append(" - ").append(
10✔
171
                            cmd_iter->second->c_help.ht_summary);
10✔
172
                    }
173
                    note.append("\n");
10✔
174
                }
175
                um.with_note(note);
1✔
176
            }
1✔
177
            return Err(um);
1✔
178
        }
1✔
179

180
        ec.ec_current_help = &iter->second->c_help;
1,046✔
181
        try {
182
            auto retval = iter->second->c_func(ec, cmdline, args);
1,046✔
183
            if (retval.isErr()) {
1,046✔
184
                auto um = retval.unwrapErr();
62✔
185

186
                ec.add_error_context(um);
62✔
187
                ec.ec_current_help = nullptr;
62✔
188
                return Err(um);
62✔
189
            }
62✔
190
            ec.ec_current_help = nullptr;
984✔
191

192
            return retval;
984✔
193
        } catch (const std::exception& e) {
1,046✔
194
            auto um = lnav::console::user_message::error(
×
195
                          attr_line_t("unexpected error while executing: ")
×
196
                              .append(lnav::roles::quoted_code(cmdline)))
×
197
                          .with_reason(e.what());
×
198
            return Err(um);
×
199
        }
×
200
    }
201

202
    return ec.make_error("no command to execute");
×
203
}
1,047✔
204

205
static Result<std::map<std::string, scoped_value_t>,
206
              lnav::console::user_message>
207
bind_sql_parameters(exec_context& ec, sqlite3_stmt* stmt)
1,979✔
208
{
209
    constexpr auto ZOOM_LEVEL_VAR = "$zoom_level"_frag;
1,979✔
210

211
    std::map<std::string, scoped_value_t> retval;
1,979✔
212
    auto param_count = sqlite3_bind_parameter_count(stmt);
1,979✔
213
    for (int lpc = 0; lpc < param_count; lpc++) {
2,156✔
214
        const auto* name = sqlite3_bind_parameter_name(stmt, lpc + 1);
178✔
215
        if (name == nullptr) {
178✔
216
            auto um
217
                = lnav::console::user_message::error("invalid SQL statement")
2✔
218
                      .with_reason(
2✔
219
                          "using a question-mark (?) for bound variables "
220
                          "is not supported, only named bound parameters "
221
                          "are supported")
222
                      .with_help(
2✔
223
                          "named parameters start with a dollar-sign "
224
                          "($) or colon (:) followed by the variable name")
225
                      .move();
1✔
226
            ec.add_error_context(um);
1✔
227

228
            return Err(um);
1✔
229
        }
1✔
230

231
        if (name[0] == '$') {
177✔
232
            const auto& lvars = ec.ec_local_vars.top();
150✔
233
            const auto& gvars = ec.ec_global_vars;
150✔
234
            std::map<std::string, scoped_value_t>::const_iterator local_var,
150✔
235
                global_var;
150✔
236
            const char* env_value;
237

238
            if (lnav_data.ld_window) {
150✔
239
                char buf[32];
240
                unsigned int lines, cols;
241

242
                ncplane_dim_yx(lnav_data.ld_window, &lines, &cols);
12✔
243
                if (strcmp(name, "$LINES") == 0) {
12✔
244
                    snprintf(buf, sizeof(buf), "%d", lines);
×
UNCOV
245
                    sqlite3_bind_text(stmt, lpc + 1, buf, -1, SQLITE_TRANSIENT);
×
246
                } else if (strcmp(name, "$COLS") == 0) {
12✔
247
                    snprintf(buf, sizeof(buf), "%d", cols);
×
UNCOV
248
                    sqlite3_bind_text(stmt, lpc + 1, buf, -1, SQLITE_TRANSIENT);
×
249
                }
250
            }
251

252
            if ((local_var = lvars.find(&name[1])) != lvars.end()) {
450✔
253
                mapbox::util::apply_visitor(
126✔
254
                    sqlitepp::bind_visitor(stmt, lpc + 1), local_var->second);
126✔
255
                retval[name] = local_var->second;
378✔
256
            } else if ((global_var = gvars.find(&name[1])) != gvars.end()) {
72✔
257
                mapbox::util::apply_visitor(
6✔
258
                    sqlitepp::bind_visitor(stmt, lpc + 1), global_var->second);
6✔
259
                retval[name] = global_var->second;
18✔
260
            } else if ((env_value = getenv(&name[1])) != nullptr) {
18✔
261
                sqlite3_bind_text(stmt, lpc + 1, env_value, -1, SQLITE_STATIC);
3✔
262
                retval[name] = string_fragment::from_c_str(env_value);
9✔
263
            } else if (ZOOM_LEVEL_VAR == name) {
30✔
264
                if (ec.ec_label_source_stack.empty()) {
4✔
UNCOV
265
                    sqlite3_bind_null(stmt, lpc + 1);
×
UNCOV
266
                    log_warning(
×
267
                        "Cannot bind variable: %s because there is no "
268
                        "active label source",
269
                        name);
270
                    retval[name] = string_fragment::from_c_str(
×
271
                        db_label_source::NULL_STR);
×
272
                } else {
273
                    auto zoom_level
274
                        = ec.ec_label_source_stack.back()->get_zoom_level();
4✔
275
                    auto zoom_level_str = humanize::time::duration::from_tv(
4✔
276
                                              to_timeval(zoom_level))
4✔
277
                                              .with_compact(false)
4✔
278
                                              .to_string();
4✔
279

280
                    bind_to_sqlite(stmt, lpc + 1, zoom_level_str);
4✔
281
                    retval[name] = zoom_level_str;
4✔
282
                }
4✔
283
            }
284
        } else if (name[0] == ':' && ec.ec_line_values != nullptr) {
27✔
285
            for (auto& lv : ec.ec_line_values->lvv_values) {
149✔
286
                if (lv.lv_meta.lvm_name != &name[1]) {
122✔
287
                    continue;
95✔
288
                }
289
                switch (lv.lv_meta.lvm_kind) {
27✔
UNCOV
290
                    case value_kind_t::VALUE_BOOLEAN:
×
UNCOV
291
                        sqlite3_bind_int64(stmt, lpc + 1, lv.lv_value.i);
×
UNCOV
292
                        retval[name] = fmt::to_string(lv.lv_value.i);
×
UNCOV
293
                        break;
×
UNCOV
294
                    case value_kind_t::VALUE_FLOAT:
×
UNCOV
295
                        sqlite3_bind_double(stmt, lpc + 1, lv.lv_value.d);
×
UNCOV
296
                        retval[name] = fmt::to_string(lv.lv_value.d);
×
297
                        break;
×
298
                    case value_kind_t::VALUE_INTEGER:
×
299
                        sqlite3_bind_int64(stmt, lpc + 1, lv.lv_value.i);
×
300
                        retval[name] = fmt::to_string(lv.lv_value.i);
×
UNCOV
301
                        break;
×
UNCOV
302
                    case value_kind_t::VALUE_NULL:
×
UNCOV
303
                        sqlite3_bind_null(stmt, lpc + 1);
×
UNCOV
304
                        retval[name] = string_fragment::from_c_str(
×
UNCOV
305
                            db_label_source::NULL_STR);
×
UNCOV
306
                        break;
×
307
                    default:
27✔
308
                        sqlite3_bind_text(stmt,
27✔
309
                                          lpc + 1,
310
                                          lv.text_value(),
311
                                          lv.text_length(),
27✔
312
                                          SQLITE_TRANSIENT);
313
                        retval[name] = lv.to_string();
81✔
314
                        break;
27✔
315
                }
316
            }
317
        } else {
27✔
UNCOV
318
            sqlite3_bind_null(stmt, lpc + 1);
×
UNCOV
319
            log_warning("Could not bind variable: %s", name);
×
UNCOV
320
            retval[name]
×
UNCOV
321
                = string_fragment::from_c_str(db_label_source::NULL_STR);
×
322
        }
323
    }
324

325
    return Ok(retval);
1,978✔
326
}
1,979✔
327

328
static void
329
execute_search(const std::string& search_cmd)
8✔
330
{
331
    textview_curses* tc = get_textview_for_mode(lnav_data.ld_mode);
8✔
332
    auto search_term = string_fragment(search_cmd)
8✔
333
                           .find_right_boundary(0, string_fragment::tag1{'\n'})
8✔
334
                           .to_string();
8✔
335
    tc->execute_search(search_term);
8✔
336
}
8✔
337

338
Result<std::string, lnav::console::user_message>
339
execute_sql(exec_context& ec, const std::string& sql, std::string& alt_msg)
1,996✔
340
{
341
    static auto op = lnav_operation{__FUNCTION__};
1,996✔
342

343
    auto op_guard = lnav_opid_guard::internal(op);
1,996✔
344
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
1,996✔
345
    timeval start_tv, end_tv;
346
    std::string stmt_str = trim(sql);
1,996✔
347
    std::string retval;
1,996✔
348
    int retcode = SQLITE_OK;
1,996✔
349

350
    if (lnav::sql::is_prql(stmt_str)) {
1,996✔
351
        log_info("compiling PRQL: %s", stmt_str.c_str());
50✔
352

353
        auto compile_res = lnav::prql::compile(stmt_str);
50✔
354
        if (compile_res.isErr()) {
50✔
355
            return Err(compile_res.unwrapErr());
3✔
356
        }
357
        stmt_str = compile_res.unwrap();
47✔
358
    }
50✔
359

360
    log_info("Executing SQL: %s", stmt_str.c_str());
1,993✔
361

362
    auto old_mode = lnav_data.ld_mode;
1,993✔
363
    lnav_data.ld_mode = ln_mode_t::BUSY;
1,993✔
364
    auto mode_fin = finally([old_mode]() { lnav_data.ld_mode = old_mode; });
1,993✔
365
    lnav_data.ld_bottom_source.grep_error("");
1,993✔
366
    lnav_data.ld_status[LNS_BOTTOM].set_needs_update();
1,993✔
367

368
    if (startswith(stmt_str, ".")) {
1,993✔
369
        std::vector<std::string> args;
7✔
370
        split_ws(stmt_str, args);
7✔
371

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

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

382
            return retval;
7✔
383
        }
7✔
384
    }
7✔
385

386
    ec.ec_accumulator->clear();
1,986✔
387

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

398
    const auto* curr_stmt = stmt_str.c_str();
1,986✔
399
    auto last_is_readonly = false;
1,986✔
400
    while (curr_stmt != nullptr) {
3,901✔
401
        const char* tail = nullptr;
3,901✔
402
        while (isspace(*curr_stmt)) {
3,901✔
UNCOV
403
            curr_stmt += 1;
×
404
        }
405
        std::set<std::string> touched_tables;
3,901✔
406
        {
407
            sql_table_capture_guard capture_guard(touched_tables);
3,901✔
408
            retcode = sqlite3_prepare_v2(
3,901✔
409
                lnav_data.ld_db.in(), curr_stmt, -1, stmt.out(), &tail);
410
        }
3,901✔
411
        if (retcode != SQLITE_OK) {
3,901✔
412
            const char* errmsg = sqlite3_errmsg(lnav_data.ld_db);
7✔
413

414
            alt_msg = "";
7✔
415

416
            auto um = lnav::console::user_message::error(
14✔
417
                          "failed to compile SQL statement")
418
                          .with_reason(errmsg)
14✔
419
                          .with_snippets(ec.ec_source)
14✔
420
                          .move();
7✔
421

422
            auto annotated_sql = annotate_sql_with_error(
423
                lnav_data.ld_db.in(), curr_stmt, tail);
7✔
424
            auto loc = um.um_snippets.back().s_location;
7✔
425
            if (curr_stmt == stmt_str.c_str()) {
7✔
426
                um.um_snippets.pop_back();
7✔
427
            } else {
UNCOV
428
                auto tail_iter = stmt_str.begin();
×
429

UNCOV
430
                std::advance(tail_iter, (curr_stmt - stmt_str.c_str()));
×
UNCOV
431
                loc.sl_line_number
×
UNCOV
432
                    += std::count(stmt_str.begin(), tail_iter, '\n');
×
433
            }
434

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

437
            return Err(um);
7✔
438
        }
7✔
439
        if (stmt == nullptr) {
3,894✔
440
            retcode = SQLITE_DONE;
1,915✔
441
            break;
1,915✔
442
        }
443
#ifdef HAVE_SQLITE3_STMT_READONLY
444
        last_is_readonly = sqlite3_stmt_readonly(stmt.in());
1,979✔
445
        if (ec.is_read_only() && !last_is_readonly) {
1,979✔
446
            return ec.make_error(
447
                "modifying statements are not allowed in this context: {}",
448
                sql);
×
449
        }
450
#endif
451
        bool done = false;
1,979✔
452

453
        auto bound_values = TRY(bind_sql_parameters(ec, stmt.in()));
1,979✔
454
        if (last_is_readonly) {
1,978✔
455
            ec.ec_sql_callback(ec, stmt.in());
1,808✔
456
            if (!ec.ec_label_source_stack.empty()) {
1,808✔
457
                auto& dls = *ec.ec_label_source_stack.back();
1,808✔
458
                dls.dls_user_query = sql;
1,808✔
459
                if (!ec.ec_source.empty()) {
1,808✔
460
                    dls.dls_user_query_src_loc = ec.ec_source.back().s_location;
1,808✔
461
                } else {
UNCOV
462
                    dls.dls_user_query_src_loc = std::nullopt;
×
463
                }
464
                if (!ec.ec_local_vars.empty()) {
1,808✔
465
                    dls.dls_user_query_vars = ec.ec_local_vars.top();
1,808✔
466
                } else {
UNCOV
467
                    dls.dls_user_query_vars.clear();
×
468
                }
469
                dls.dls_query_start = std::chrono::system_clock::now();
1,808✔
470
                auto* vtab_mgr = injector::get<log_vtab_manager*>();
1,808✔
471
                dls.dls_query_touches_log_data
472
                    = vtab_mgr->has_log_backed_table(touched_tables);
1,808✔
473
            }
474
        }
475
        while (!done) {
7,105✔
476
            retcode = sqlite3_step(stmt.in());
5,190✔
477

478
            if (!ec.ec_label_source_stack.empty()) {
5,190✔
479
                ec.ec_label_source_stack.back()->dls_step_rc = retcode;
5,190✔
480
            }
481

482
            switch (retcode) {
5,190✔
483
                case SQLITE_OK:
1,915✔
484
                case SQLITE_DONE: {
485
                    auto changes = sqlite3_changes(lnav_data.ld_db.in());
1,915✔
486

487
                    log_info("sqlite3_changes() -> %d", changes);
1,915✔
488
                    done = true;
1,915✔
489
                    break;
1,915✔
490
                }
491

492
                case SQLITE_ROW:
3,212✔
493
                    ec.ec_sql_callback(ec, stmt.in());
3,212✔
494
                    break;
3,212✔
495

496
                default: {
63✔
497
                    attr_line_t bound_note;
63✔
498

499
                    if (!bound_values.empty()) {
63✔
500
                        bound_note.append(
3✔
501
                            "the bound parameters are set as follows:\n");
502
                        for (const auto& bval : bound_values) {
11✔
503
                            auto val_as_str = fmt::to_string(bval.second);
8✔
504
                            auto sql_type = bval.second.match(
8✔
505
                                [](const std::string&) { return SQLITE_TEXT; },
5✔
506
                                [](const string_fragment&) {
×
UNCOV
507
                                    return SQLITE_TEXT;
×
508
                                },
UNCOV
509
                                [](bool) { return SQLITE_INTEGER; },
×
510
                                [](int64_t) { return SQLITE_INTEGER; },
1✔
511
                                [](null_value_t) { return SQLITE_NULL; },
1✔
512
                                [](double) { return SQLITE_FLOAT; });
1✔
513
                            auto scrubbed_val = scrub_ws(val_as_str.c_str());
8✔
514
                            truncate_to(scrubbed_val, 40);
8✔
515
                            bound_note.append("  ")
8✔
516
                                .append(lnav::roles::variable(bval.first))
16✔
517
                                .append(":")
8✔
518
                                .append(sqlite3_type_to_string(sql_type))
8✔
519
                                .append(" = ")
8✔
520
                                .append_quoted(scrubbed_val)
16✔
521
                                .append("\n");
8✔
522
                        }
8✔
523
                    }
524

525
                    log_error("sqlite3_step error code: %d", retcode);
63✔
526
                    auto um = sqlite3_error_to_user_message(lnav_data.ld_db)
126✔
527
                                  .with_note(bound_note)
63✔
528
                                  .move();
63✔
529

530
                    if (ec.get_provenance<exec_context::keyboard_input>()
63✔
531
                        || ec.get_provenance<exec_context::mouse_input>())
63✔
532
                    {
533
                        auto um_copy = um;
2✔
534
                        um_copy.with_context_snippets(ec.ec_source);
2✔
535
                        auto err_al = um_copy.to_attr_line();
2✔
536
                        log_error("SQL error: %s", err_al.al_string.c_str());
2✔
537
                    } else {
2✔
538
                        um.with_context_snippets(ec.ec_source)
122✔
539
                            .remove_internal_snippets();
61✔
540
                    }
541

542
                    if (last_is_readonly && !ec.ec_label_source_stack.empty()) {
63✔
543
                        auto& dls = *ec.ec_label_source_stack.back();
29✔
544
                        dls.dls_query_end = std::chrono::system_clock::now();
29✔
545
                        dls.dls_log_gen_at_query
546
                            = lnav_data.ld_log_source.lss_index_generation;
29✔
547
                        dls.dls_log_line_count_at_query
548
                            = lnav_data.ld_log_source.text_line_count();
29✔
549
                        if (&dls == &lnav_data.ld_db_row_source
29✔
550
                            && lnav_data.ld_db_status_source
44✔
551
                                   .update_from_db_source())
15✔
552
                        {
553
                            lnav_data.ld_status[LNS_DB].set_needs_update();
15✔
554
                        }
555
                    }
556

557
                    return Err(um);
63✔
558
                }
63✔
559
            }
560
        }
561

562
        curr_stmt = tail;
1,915✔
563
    }
3,964✔
564

565
    if (last_is_readonly && !ec.ec_label_source_stack.empty()) {
1,915✔
566
        auto& dls = *ec.ec_label_source_stack.back();
1,779✔
567
        dls.dls_query_end = std::chrono::system_clock::now();
1,779✔
568
        dls.dls_log_gen_at_query = lnav_data.ld_log_source.lss_index_generation;
1,779✔
569
        dls.dls_log_line_count_at_query
570
            = lnav_data.ld_log_source.text_line_count();
1,779✔
571
        if (&dls == &lnav_data.ld_db_row_source
1,779✔
572
            && lnav_data.ld_db_status_source.update_from_db_source())
1,779✔
573
        {
574
            lnav_data.ld_status[LNS_DB].set_needs_update();
374✔
575
        }
576

577
        size_t memory_usage = 0, total_size = 0, cached_chunks = 0;
1,779✔
578
        for (auto cc = dls.dls_cell_container.cc_first.get(); cc != nullptr;
3,559✔
579
             cc = cc->cc_next.get())
1,780✔
580
        {
581
            total_size += cc->cc_capacity;
1,780✔
582
            if (cc->cc_data) {
1,780✔
583
                cached_chunks += 1;
1,779✔
584
                memory_usage += cc->cc_capacity;
1,779✔
585
            } else {
586
                memory_usage += cc->cc_compressed_size;
1✔
587
            }
588
        }
589
        log_debug(
1,779✔
590
            "cell memory footprint: rows=%zu total=%zu; actual=%zu; "
591
            "cached-chunks=%zu",
592
            dls.dls_row_cursors.size(),
593
            total_size,
594
            memory_usage,
595
            cached_chunks);
596
    }
597
    gettimeofday(&end_tv, nullptr);
1,915✔
598
    lnav_data.ld_mode = old_mode;
1,915✔
599
    if (retcode == SQLITE_DONE) {
1,915✔
600
        if (lnav_data.ld_log_source.is_line_meta_changed()) {
1,915✔
601
            lnav_data.ld_log_source.text_filters_changed();
33✔
602
            lnav_data.ld_views[LNV_LOG].reload_data();
33✔
603
        }
604
        lnav_data.ld_filter_view.reload_data();
1,915✔
605
        lnav_data.ld_files_view.reload_data();
1,915✔
606

607
        lnav_data.ld_active_files.fc_files
608
            | lnav::itertools::for_each(&logfile::dump_stats);
1,915✔
609
        if (ec.ec_sql_callback != sql_callback) {
1,915✔
610
            retval = ec.ec_accumulator->get_string();
46✔
611
        } else {
612
            auto& dls = *(ec.ec_label_source_stack.back());
1,869✔
613
            if (!dls.dls_row_cursors.empty()) {
1,869✔
614
                lnav_data.ld_views[LNV_DB].reload_data();
1,719✔
615
                if (lnav_data.ld_flags.is_set<lnav_flags::headless>()) {
1,719✔
616
                    retval = "";
1,229✔
617
                    alt_msg = "";
1,229✔
618
                } else if (dls.dls_row_cursors.size() == 1) {
490✔
619
                    retval = dls.get_row_as_string(0_vl);
436✔
620
                } else {
621
                    int row_count = dls.dls_row_cursors.size();
54✔
622
                    char row_count_buf[128];
623
                    timeval diff_tv;
624

625
                    timersub(&end_tv, &start_tv, &diff_tv);
54✔
626
                    snprintf(row_count_buf,
54✔
627
                             sizeof(row_count_buf),
628
                             ANSI_BOLD("%'d") " row%s matched in " ANSI_BOLD(
629
                                 "%ld.%03ld") " seconds",
630
                             row_count,
631
                             row_count == 1 ? "" : "s",
632
                             diff_tv.tv_sec,
633
                             std::max((long) diff_tv.tv_usec / 1000, 1L));
54✔
634
                    retval = row_count_buf;
54✔
635
                    if (dls.has_log_time_column()) {
54✔
636
                        alt_msg
637
                            = HELP_MSG_1(Q,
2✔
638
                                         "to switch back to the previous view "
639
                                         "at the matching 'log_time' value");
640
                    } else {
641
                        alt_msg = "";
52✔
642
                    }
643
                }
644
            }
645
#ifdef HAVE_SQLITE3_STMT_READONLY
646
            else if (last_is_readonly)
150✔
647
            {
648
                retval = "info: No rows matched";
26✔
649
                alt_msg = "";
26✔
650
            }
651
#endif
652
        }
653
    }
654

655
    return Ok(retval);
1,915✔
656
}
1,996✔
657

658
Result<void, lnav::console::user_message>
659
multiline_executor::handle_command(const std::string& cmdline)
234✔
660
{
661
    this->me_last_result = TRY(execute_from_file(this->me_exec_context,
234✔
662
                                                 this->p_source,
663
                                                 this->p_starting_line_number,
664
                                                 cmdline));
665

666
    return Ok();
228✔
667
}
668

669
static Result<std::string, lnav::console::user_message>
670
execute_file_contents(exec_context& ec, const std::filesystem::path& path)
38✔
671
{
672
    static const std::filesystem::path stdin_path("-");
38✔
673
    static const std::filesystem::path dev_stdin_path("/dev/stdin");
38✔
674

675
    FILE* file;
676

677
    if (path == stdin_path || path == dev_stdin_path) {
38✔
678
        if (isatty(STDIN_FILENO)) {
3✔
UNCOV
679
            return ec.make_error("stdin has already been consumed");
×
680
        }
681
        file = stdin;
3✔
682
    } else if ((file = fopen(path.c_str(), "re")) == nullptr) {
35✔
683
        return ec.make_error("unable to open file");
×
684
    }
685

686
    std::string out_name;
38✔
687

688
    auto_mem<char> line;
38✔
689
    size_t line_max_size;
690
    ssize_t line_size;
691
    multiline_executor me(ec, path.string());
38✔
692

693
    ec.ec_local_vars.top()["0"] = path.string();
114✔
694
    ec.ec_path_stack.emplace_back(path.parent_path());
38✔
695
    exec_context::output_guard og(ec);
76✔
696
    while ((line_size = getline(line.out(), &line_max_size, file)) != -1) {
878✔
697
        TRY(me.push_back(string_fragment::from_bytes(line.in(), line_size)));
846✔
698
    }
699

700
    TRY(me.final());
32✔
701
    auto retval = std::move(me.me_last_result);
32✔
702

703
    if (file == stdin) {
32✔
704
        if (isatty(STDOUT_FILENO)) {
3✔
UNCOV
705
            log_perror(dup2(STDOUT_FILENO, STDIN_FILENO));
×
706
        }
707
    } else {
708
        fclose(file);
29✔
709
    }
710
    ec.ec_path_stack.pop_back();
32✔
711

712
    return Ok(retval);
32✔
713
}
38✔
714

715
Result<std::string, lnav::console::user_message>
716
execute_file(exec_context& ec, const std::string& path_and_args)
41✔
717
{
718
    static const intern_string_t SRC = intern_string::lookup("cmdline");
95✔
719
    static auto op = lnav_operation{__FUNCTION__};
41✔
720

721
    auto op_guard = lnav_opid_guard::internal(op);
41✔
722

723
    std::string retval, msg;
41✔
724
    shlex lexer(path_and_args);
41✔
725

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

728
    auto split_args_res = lexer.split(scoped_resolver{&ec.ec_local_vars.top()});
41✔
729
    if (split_args_res.isErr()) {
41✔
UNCOV
730
        auto split_err = split_args_res.unwrapErr();
×
UNCOV
731
        auto um = lnav::console::user_message::error(
×
732
                      "unable to parse script command-line")
UNCOV
733
                      .with_reason(split_err.se_error.te_msg)
×
UNCOV
734
                      .with_snippet(lnav::console::snippet::from(
×
UNCOV
735
                          SRC, lexer.to_attr_line(split_err.se_error)))
×
UNCOV
736
                      .move();
×
737

UNCOV
738
        return Err(um);
×
739
    }
740
    auto split_args = split_args_res.unwrap();
41✔
741
    if (split_args.empty()) {
41✔
UNCOV
742
        return ec.make_error("no script specified");
×
743
    }
744

745
    ec.ec_local_vars.emplace();
41✔
746

747
    auto script_name = split_args[0].se_value;
41✔
748
    auto& vars = ec.ec_local_vars.top();
41✔
749
    std::string star, open_error = "file not found";
82✔
750

751
    add_ansi_vars(vars);
41✔
752

753
    vars["#"] = fmt::to_string(split_args.size() - 1);
123✔
754
    for (size_t lpc = 0; lpc < split_args.size(); lpc++) {
124✔
755
        vars[fmt::to_string(lpc)] = split_args[lpc].se_value;
83✔
756
    }
757
    for (size_t lpc = 1; lpc < split_args.size(); lpc++) {
83✔
758
        if (lpc > 1) {
42✔
759
            star.append(" ");
21✔
760
        }
761
        star.append(split_args[lpc].se_value);
42✔
762
    }
763
    vars["__all__"] = star;
41✔
764

765
    std::vector<script_metadata> paths_to_exec;
41✔
766

767
    auto scripts = find_format_scripts(lnav_data.ld_config_paths);
41✔
768
    auto iter = scripts.as_scripts.find(script_name);
41✔
769
    if (iter != scripts.as_scripts.end()) {
41✔
770
        paths_to_exec = iter->second;
28✔
771
    }
772
    if (lnav::filesystem::is_url(script_name)) {
41✔
UNCOV
773
        auto_mem<CURLU> cu(curl_url_cleanup);
×
UNCOV
774
        cu = curl_url();
×
UNCOV
775
        auto set_rc = curl_url_set(cu, CURLUPART_URL, script_name.c_str(), 0);
×
UNCOV
776
        if (set_rc == CURLUE_OK) {
×
UNCOV
777
            auto_mem<char> scheme_part(curl_free);
×
778
            auto get_rc
UNCOV
779
                = curl_url_get(cu, CURLUPART_SCHEME, scheme_part.out(), 0);
×
UNCOV
780
            if (get_rc == CURLUE_OK
×
UNCOV
781
                && string_fragment::from_c_str(scheme_part.in()) == "file")
×
782
            {
UNCOV
783
                auto_mem<char> path_part;
×
784
                auto get_rc
UNCOV
785
                    = curl_url_get(cu, CURLUPART_PATH, path_part.out(), 0);
×
UNCOV
786
                if (get_rc == CURLUE_OK) {
×
787
                    auto rp_res = lnav::filesystem::realpath(path_part.in());
×
UNCOV
788
                    if (rp_res.isOk()) {
×
UNCOV
789
                        struct script_metadata meta;
×
790

UNCOV
791
                        meta.sm_path = rp_res.unwrap();
×
UNCOV
792
                        extract_metadata_from_file(meta);
×
UNCOV
793
                        paths_to_exec.push_back(meta);
×
794
                    }
795
                }
796
            }
797
        }
798
    }
799
    if (script_name == "-" || script_name == "/dev/stdin") {
41✔
800
        paths_to_exec.push_back({script_name, "", "", ""});
3✔
801
    } else if (access(script_name.c_str(), R_OK) == 0) {
38✔
802
        struct script_metadata meta;
7✔
803
        auto rp_res = lnav::filesystem::realpath(script_name);
7✔
804

805
        if (rp_res.isErr()) {
7✔
806
            log_error("unable to get realpath() of %s -- %s",
×
807
                      script_name.c_str(),
808
                      rp_res.unwrapErr().c_str());
809
            meta.sm_path = script_name;
×
810
        } else {
811
            meta.sm_path = rp_res.unwrap();
7✔
812
        }
813
        extract_metadata_from_file(meta);
7✔
814
        paths_to_exec.push_back(meta);
7✔
815
    } else if (errno != ENOENT) {
38✔
UNCOV
816
        open_error = lnav::from_errno().message();
×
817
    } else {
818
        auto script_path = std::filesystem::path(script_name);
31✔
819

820
        if (!script_path.is_absolute()) {
31✔
821
            script_path = ec.ec_path_stack.back() / script_path;
31✔
822
        }
823

824
        if (std::filesystem::is_regular_file(script_path)) {
31✔
UNCOV
825
            script_metadata meta;
×
826

UNCOV
827
            meta.sm_path = script_path;
×
UNCOV
828
            extract_metadata_from_file(meta);
×
UNCOV
829
            paths_to_exec.push_back(meta);
×
830
        } else if (errno != ENOENT) {
31✔
UNCOV
831
            open_error = lnav::from_errno().message();
×
832
        }
833
    }
31✔
834

835
    if (!paths_to_exec.empty()) {
41✔
836
        for (auto& path_iter : paths_to_exec) {
70✔
837
            if (!ec.ec_output_stack.empty()) {
38✔
838
                ec.ec_output_stack.back().od_format
38✔
839
                    = path_iter.sm_output_format;
38✔
840
                log_info("%s: setting out format for '%s' to %d",
38✔
841
                         script_name.c_str(),
842
                         ec.ec_output_stack.back().od_name.c_str(),
843
                         ec.ec_output_stack.back().od_format);
844
            }
845
            retval = TRY(execute_file_contents(ec, path_iter.sm_path));
38✔
846
        }
847
    }
848
    ec.ec_local_vars.pop();
35✔
849

850
    if (paths_to_exec.empty()) {
35✔
851
        return ec.make_error(
852
            "unknown script -- {} -- {}", script_name, open_error);
3✔
853
    }
854

855
    return Ok(retval);
32✔
856
}
56✔
857

858
Result<std::string, lnav::console::user_message>
859
execute_from_file(exec_context& ec,
234✔
860
                  const std::string& src,
861
                  int line_number,
862
                  const std::string& cmdline)
863
{
864
    std::string retval, alt_msg;
234✔
865
    auto _sg
866
        = ec.enter_source(intern_string::lookup(src), line_number, cmdline);
234✔
867

868
    lnav_data.ld_view_stack.top() | [&ec](auto* tc) {
234✔
869
        ec.ec_top_line = tc->get_selection().value_or(0_vl);
230✔
870
    };
230✔
871
    switch (cmdline[0]) {
234✔
872
        case ':':
103✔
873
            retval = TRY(execute_command(ec, cmdline.substr(1)));
103✔
874
            break;
103✔
875
        case '/':
1✔
876
            execute_search(cmdline.substr(1));
1✔
877
            break;
1✔
878
        case ';': {
128✔
879
            if (!ec.ec_msg_callback_stack.empty()) {
128✔
880
                auto src_slash = src.rfind('/');
124✔
881
                auto cmd_al
882
                    = attr_line_t(cmdline.substr(0, cmdline.find('\n')));
124✔
883
                readline_lnav_highlighter(cmd_al, std::nullopt);
124✔
884
                const auto um = lnav::console::user_message::info(
124✔
885
                                    attr_line_t("Executing command at ")
124✔
886
                                        .append(lnav::roles::file(
124✔
887
                                            src_slash != std::string::npos
888
                                                ? src.substr(src_slash + 1)
248✔
889
                                                : src))
890
                                        .append(":")
124✔
891
                                        .append(lnav::roles::number(
248✔
892
                                            fmt::to_string(line_number)))
248✔
893
                                        .append(" \u2014 ")
124✔
894
                                        .append(cmd_al))
124✔
895
                                    .move();
124✔
896
                ec.ec_msg_callback_stack.back()(um);
124✔
897
            }
124✔
898

899
            setup_logline_table(ec);
128✔
900
            retval = TRY(execute_sql(ec, cmdline.substr(1), alt_msg));
128✔
901
            break;
122✔
902
        }
903
        case '|':
2✔
904
            retval = TRY(execute_file(ec, cmdline.substr(1)));
2✔
905
            break;
2✔
UNCOV
906
        default:
×
907
            retval = TRY(execute_command(ec, cmdline));
×
UNCOV
908
            break;
×
909
    }
910

911
    log_info(
228✔
912
        "%s:%d:execute result -- %s", src.c_str(), line_number, retval.c_str());
913

914
    return Ok(retval);
228✔
915
}
234✔
916

917
Result<std::string, lnav::console::user_message>
918
execute_any(exec_context& ec, const std::string& cmdline_with_mode)
35✔
919
{
920
    if (cmdline_with_mode.empty()) {
35✔
UNCOV
921
        auto um = lnav::console::user_message::error("empty command")
×
UNCOV
922
                      .with_help(
×
923
                          "a command should start with ':', ';', '/', '|' and "
924
                          "followed by the operation to perform")
925
                      .move();
×
926
        if (!ec.ec_source.empty()) {
×
927
            um.with_snippet(ec.ec_source.back());
×
928
        }
929
        return Err(um);
×
930
    }
931

932
    std::string retval, alt_msg, cmdline = cmdline_with_mode.substr(1);
35✔
933
    auto _cleanup = finally([&ec] {
35✔
934
        if (ec.is_read_write() &&
38✔
935
            // only rebuild in a script or non-interactive mode so we don't
936
            // block the UI.
937
            lnav_data.ld_flags.is_set<lnav_flags::headless>())
3✔
938
        {
939
            rescan_files();
×
940
            wait_for_pipers(std::nullopt);
×
UNCOV
941
            rebuild_indexes_repeatedly();
×
942
        }
943
    });
70✔
944

945
    switch (cmdline_with_mode[0]) {
35✔
UNCOV
946
        case ':':
×
UNCOV
947
            retval = TRY(execute_command(ec, cmdline));
×
UNCOV
948
            break;
×
UNCOV
949
        case '/':
×
UNCOV
950
            execute_search(cmdline);
×
UNCOV
951
            break;
×
952
        case ';':
29✔
953
            setup_logline_table(ec);
29✔
954
            retval = TRY(execute_sql(ec, cmdline, alt_msg));
29✔
955
            break;
29✔
956
        case '|': {
6✔
957
            retval = TRY(execute_file(ec, cmdline));
6✔
958
            break;
4✔
959
        }
UNCOV
960
        default:
×
UNCOV
961
            retval = TRY(execute_command(ec, cmdline));
×
UNCOV
962
            break;
×
963
    }
964

965
    return Ok(retval);
33✔
966
}
35✔
967

968
void
969
execute_init_commands(
774✔
970
    exec_context& ec,
971
    std::vector<std::pair<Result<std::string, lnav::console::user_message>,
972
                          std::string>>& msgs)
973
{
974
    if (lnav_data.ld_cmd_init_done) {
774✔
UNCOV
975
        return;
×
976
    }
977

978
    std::string out_name;
774✔
979
    std::optional<exec_context::output_t> ec_out;
774✔
980
    auto_fd fd_copy;
774✔
981
    int option_index = 1;
774✔
982

983
    {
984
        log_info("Executing initial commands");
774✔
985
        exec_context::output_guard og(ec, out_name, ec_out);
774✔
986

987
        for (auto& cmd : lnav_data.ld_commands) {
2,187✔
988
            static const auto COMMAND_OPTION_SRC
989
                = intern_string::lookup("command-option");
2,793✔
990

991
            std::string alt_msg;
1,413✔
992

993
            wait_for_children();
1,413✔
994

995
            lnav_data.ld_view_stack.top() | [&ec](auto* tc) {
1,413✔
996
                ec.ec_top_line = tc->get_selection().value_or(0_vl);
1,413✔
997
            };
1,413✔
998
            log_debug("init cmd: %s", cmd.c_str());
1,413✔
999
            {
1000
                auto top_view = lnav_data.ld_view_stack.top();
1,413✔
1001
                auto _sg
1002
                    = ec.enter_source(COMMAND_OPTION_SRC, option_index++, cmd);
1,413✔
1003
                switch (cmd.at(0)) {
1,413✔
1004
                    case ':':
936✔
1005
                        msgs.emplace_back(execute_command(ec, cmd.substr(1)),
936✔
1006
                                          alt_msg);
1007
                        break;
936✔
1008
                    case '/':
7✔
1009
                        execute_search(cmd.substr(1));
7✔
1010
                        break;
7✔
1011
                    case ';':
444✔
1012
                        setup_logline_table(ec);
444✔
1013
                        msgs.emplace_back(
444✔
1014
                            execute_sql(ec, cmd.substr(1), alt_msg), alt_msg);
888✔
1015
                        break;
444✔
1016
                    case '|':
26✔
1017
                        msgs.emplace_back(execute_file(ec, cmd.substr(1)),
26✔
1018
                                          alt_msg);
1019
                        break;
26✔
1020
                }
1021

1022
                rescan_files();
1,413✔
1023
                auto deadline = ui_clock::now();
1,413✔
1024
                if (lnav_data.ld_flags.is_set<lnav_flags::headless>()) {
1,413✔
1025
                    deadline += 5s;
1,412✔
1026
                } else {
1027
                    deadline += 500ms;
1✔
1028
                }
1029
                wait_for_pipers(deadline);
1,413✔
1030
                if (lnav_data.ld_flags.is_set<lnav_flags::headless>()
1,413✔
1031
                    || lnav_data.ld_input_dispatcher.id_count == 0)
1,413✔
1032
                {
1033
                    rebuild_indexes_repeatedly();
1,413✔
1034
                } else {
UNCOV
1035
                    rebuild_indexes(deadline);
×
1036
                }
1037
                if (top_view == lnav_data.ld_view_stack.top()) {
1,413✔
1038
                    setup_initial_view_stack();
1,310✔
1039
                }
1040

1041
                lnav::progress_tracker::instance().wait_for_completion(
1,413✔
1042
                    &lnav_data.ld_status_refresher);
1043
            }
1,413✔
1044
        }
1,413✔
1045
    }
774✔
1046
    lnav_data.ld_commands.clear();
774✔
1047
    lnav_data.ld_cmd_init_done = true;
774✔
1048
}
774✔
1049

1050
int
1051
sql_callback(exec_context& ec, sqlite3_stmt* stmt)
4,927✔
1052
{
1053
    auto& dls = *(ec.ec_label_source_stack.back());
4,927✔
1054
    const int ncols = sqlite3_column_count(stmt);
4,927✔
1055

1056
    if (!sqlite3_stmt_busy(stmt)) {
4,927✔
1057
        dls.clear();
1,764✔
1058

1059
        for (int lpc = 0; lpc < ncols; lpc++) {
5,268✔
1060
            const int type = sqlite3_column_type(stmt, lpc);
3,504✔
1061
            std::string colname = sqlite3_column_name(stmt, lpc);
3,504✔
1062

1063
            dls.push_header(colname, type);
3,504✔
1064
        }
3,504✔
1065
        return 0;
1,764✔
1066
    }
1067

1068
    int retval = 0;
3,163✔
1069
    auto set_vars = dls.dls_row_cursors.empty();
3,163✔
1070

1071
    if (dls.dls_row_cursors.empty()) {
3,163✔
1072
        for (int lpc = 0; lpc < ncols; lpc++) {
4,992✔
1073
            int type = sqlite3_column_type(stmt, lpc);
3,281✔
1074
            std::string colname = sqlite3_column_name(stmt, lpc);
3,281✔
1075

1076
            bool graphable = (type == SQLITE_INTEGER || type == SQLITE_FLOAT);
3,281✔
1077
            auto& hm = dls.dls_headers[lpc];
3,281✔
1078
            hm.hm_column_type = type;
3,281✔
1079
            if (lnav_data.ld_db_key_names.count(colname) > 0) {
3,281✔
1080
                hm.hm_graphable = false;
612✔
1081
            } else if (graphable) {
2,669✔
1082
                dls.set_col_as_graphable(lpc);
775✔
1083
            }
1084
        }
3,281✔
1085
    }
1086

1087
    dls.dls_row_cursors.push_row(dls.dls_cell_container.end_cursor());
3,163✔
1088
    dls.dls_push_column = 0;
3,163✔
1089
    for (int lpc = 0; lpc < ncols; lpc++) {
13,581✔
1090
        const auto value_type = sqlite3_column_type(stmt, lpc);
10,418✔
1091
        db_label_source::column_value_t value;
10,418✔
1092
        auto& hm = dls.dls_headers[lpc];
10,418✔
1093

1094
        switch (value_type) {
10,418✔
1095
            case SQLITE_INTEGER:
3,061✔
1096
                value = (int64_t) sqlite3_column_int64(stmt, lpc);
3,061✔
1097
                hm.hm_align = text_align_t::end;
3,061✔
1098
                break;
3,061✔
1099
            case SQLITE_FLOAT:
307✔
1100
                value = sqlite3_column_double(stmt, lpc);
307✔
1101
                hm.hm_align = text_align_t::end;
307✔
1102
                break;
307✔
1103
            case SQLITE_NULL:
2,340✔
1104
                value = null_value_t{};
2,340✔
1105
                break;
2,340✔
1106
            default: {
4,710✔
1107
                auto frag = string_fragment::from_bytes(
4,710✔
1108
                    sqlite3_column_text(stmt, lpc),
1109
                    sqlite3_column_bytes(stmt, lpc));
4,710✔
1110
                if (!frag.empty()) {
4,710✔
1111
                    if (isdigit(frag[0])) {
4,646✔
1112
                        hm.hm_align = text_align_t::end;
1,582✔
1113
                        if (!hm.hm_graphable.has_value()) {
1,582✔
1114
                            auto split_res = humanize::try_from<double>(frag);
294✔
1115
                            if (split_res.has_value()) {
294✔
1116
                                dls.set_col_as_graphable(lpc);
75✔
1117
                            } else {
1118
                                hm.hm_graphable = false;
219✔
1119
                            }
1120
                        }
1121
                    } else if (!hm.hm_graphable.has_value()) {
3,064✔
1122
                        hm.hm_graphable = false;
1,223✔
1123
                    }
1124
                }
1125
                value = frag;
4,710✔
1126
                break;
4,710✔
1127
            }
1128
        }
1129
        dls.push_column(value);
10,418✔
1130
        if ((hm.hm_column_type == SQLITE_TEXT
10,418✔
1131
             || hm.hm_column_type == SQLITE_NULL)
5,594✔
1132
            && hm.hm_sub_type == 0)
7,087✔
1133
        {
1134
            switch (value_type) {
6,959✔
1135
                case SQLITE_TEXT:
4,578✔
1136
                    auto* raw_value = sqlite3_column_value(stmt, lpc);
4,578✔
1137
                    hm.hm_column_type = SQLITE_TEXT;
4,578✔
1138
                    hm.hm_sub_type = sqlite3_value_subtype(raw_value);
4,578✔
1139
                    break;
4,578✔
1140
            }
1141
        }
1142
        if (set_vars && !ec.ec_local_vars.empty() && !ec.ec_dry_run) {
10,418✔
1143
            if (sql_ident_needs_quote(hm.hm_name.c_str())) {
3,281✔
1144
                continue;
1,391✔
1145
            }
1146
            auto& vars = ec.ec_local_vars.top();
1,890✔
1147

1148
            vars[hm.hm_name] = value.match(
3,780✔
UNCOV
1149
                [](const string_fragment& sf) {
×
1150
                    return scoped_value_t{sf.to_string()};
843✔
1151
                },
1152
                [](int64_t i) { return scoped_value_t{i}; },
554✔
1153
                [](double d) { return scoped_value_t{d}; },
25✔
1154
                [](null_value_t) { return scoped_value_t{null_value_t{}}; });
4,248✔
1155
        }
1156
    }
10,418✔
1157

1158
    return retval;
3,163✔
1159
}
1160

1161
int
1162
internal_sql_callback(exec_context& ec, sqlite3_stmt* stmt)
26✔
1163
{
1164
    if (!sqlite3_stmt_busy(stmt)) {
26✔
1165
        return 0;
15✔
1166
    }
1167

1168
    const int ncols = sqlite3_column_count(stmt);
11✔
1169

1170
    auto& vars = ec.ec_local_vars.top();
11✔
1171

1172
    for (int lpc = 0; lpc < ncols; lpc++) {
27✔
1173
        const char* column_name = sqlite3_column_name(stmt, lpc);
16✔
1174

1175
        if (sql_ident_needs_quote(column_name)) {
16✔
UNCOV
1176
            continue;
×
1177
        }
1178

1179
        auto value_type = sqlite3_column_type(stmt, lpc);
16✔
1180
        scoped_value_t value;
16✔
1181
        switch (value_type) {
16✔
1182
            case SQLITE_INTEGER:
2✔
1183
                value = (int64_t) sqlite3_column_int64(stmt, lpc);
2✔
1184
                break;
2✔
UNCOV
1185
            case SQLITE_FLOAT:
×
UNCOV
1186
                value = sqlite3_column_double(stmt, lpc);
×
UNCOV
1187
                break;
×
1188
            case SQLITE_NULL:
1✔
1189
                value = null_value_t{};
1✔
1190
                break;
1✔
1191
            default:
13✔
1192
                value
1193
                    = std::string((const char*) sqlite3_column_text(stmt, lpc),
26✔
1194
                                  sqlite3_column_bytes(stmt, lpc));
26✔
1195
                break;
13✔
1196
        }
1197
        vars[column_name] = value;
16✔
1198
    }
16✔
1199

1200
    return 0;
11✔
1201
}
1202

1203
std::future<std::string>
1204
pipe_callback(exec_context& ec, const std::string& cmdline, auto_fd& fd)
7✔
1205
{
1206
    static auto& prompt = lnav::prompt::get();
7✔
1207
    auto out = ec.get_output();
7✔
1208

1209
    if (out) {
7✔
1210
        FILE* file = *out;
7✔
1211

1212
        return std::async(std::launch::async, [fd = std::move(fd), file]() {
14✔
1213
            char buffer[1024];
1214
            ssize_t rc;
1215

1216
            if (file == stdout) {
7✔
1217
                lnav_data.ld_stdout_used = true;
7✔
1218
            }
1219

1220
            while ((rc = read(fd, buffer, sizeof(buffer))) > 0) {
14✔
1221
                fwrite(buffer, rc, 1, file);
7✔
1222
            }
1223

1224
            return std::string();
14✔
1225
        });
7✔
1226
    }
1227
    std::error_code errc;
×
UNCOV
1228
    std::filesystem::create_directories(lnav::paths::workdir(), errc);
×
UNCOV
1229
    auto open_temp_res = lnav::filesystem::open_temp_file(lnav::paths::workdir()
×
UNCOV
1230
                                                          / "exec.XXXXXX");
×
1231
    if (open_temp_res.isErr()) {
×
1232
        return lnav::futures::make_ready_future(
1233
            fmt::format(FMT_STRING("error: cannot open temp file -- {}"),
×
1234
                        open_temp_res.unwrapErr()));
×
1235
    }
1236

1237
    auto tmp_pair = open_temp_res.unwrap();
×
1238

1239
    auto reader = std::thread(
1240
        [in_fd = std::move(fd), out_fd = std::move(tmp_pair.second)]() {
×
1241
            char buffer[1024];
1242
            ssize_t rc;
1243

UNCOV
1244
            while ((rc = read(in_fd, buffer, sizeof(buffer))) > 0) {
×
UNCOV
1245
                write(out_fd, buffer, rc);
×
1246
            }
UNCOV
1247
        });
×
UNCOV
1248
    reader.detach();
×
1249

1250
    static int exec_count = 0;
1251
    auto desc
UNCOV
1252
        = fmt::format(FMT_STRING("exec-{}-output {}"), exec_count++, cmdline);
×
1253
    lnav_data.ld_active_files.fc_file_names[tmp_pair.first]
×
UNCOV
1254
        .with_filename(desc)
×
UNCOV
1255
        .with_include_in_session(false)
×
UNCOV
1256
        .with_detect_format(false)
×
UNCOV
1257
        .with_init_location(0_vl);
×
UNCOV
1258
    lnav_data.ld_files_to_front.emplace_back(desc);
×
UNCOV
1259
    prompt.p_editor.set_alt_value(HELP_MSG_1(X, "to close the file"));
×
1260

UNCOV
1261
    return lnav::futures::make_ready_future(std::string());
×
1262
}
1263

1264
void
1265
add_global_vars(exec_context& ec)
849✔
1266
{
1267
    for (const auto& iter : lnav_config.lc_global_vars) {
13,606✔
1268
        shlex subber(iter.second);
12,757✔
1269
        std::string str;
12,757✔
1270

1271
        if (!subber.eval(str, scoped_resolver{&ec.ec_global_vars})) {
12,757✔
UNCOV
1272
            log_error("Unable to evaluate global variable value: %s",
×
1273
                      iter.second.c_str());
UNCOV
1274
            continue;
×
1275
        }
1276

1277
        ec.ec_global_vars[iter.first] = str;
12,757✔
1278
    }
12,757✔
1279
}
849✔
1280

1281
void
1282
exec_context::set_output(const std::string& name,
775✔
1283
                         FILE* file,
1284
                         int (*closer)(FILE*))
1285
{
1286
    log_info("redirecting command output to: %s", name.c_str());
775✔
1287
    this->ec_output_stack.back().od_output | [](auto out) {
775✔
UNCOV
1288
        if (out.second != nullptr) {
×
UNCOV
1289
            out.second(out.first);
×
1290
        }
1291
    };
1292
    this->ec_output_stack.back()
775✔
1293
        = output_desc{name, std::make_pair(file, closer)};
1,550✔
1294
}
775✔
1295

1296
void
1297
exec_context::clear_output()
71✔
1298
{
1299
    log_info("redirecting command output to screen");
71✔
1300
    this->ec_output_stack.back().od_output | [](auto out) {
71✔
1301
        if (out.second != nullptr) {
33✔
1302
            out.second(out.first);
33✔
1303
        }
1304
    };
33✔
1305
    this->ec_output_stack.back().od_name = "default";
71✔
1306
    this->ec_output_stack.back().od_output = std::nullopt;
71✔
1307
}
71✔
1308

1309
exec_context::exec_context(logline_value_vector* line_values,
4,120✔
1310
                           sql_callback_t sql_callback,
1311
                           pipe_callback_t pipe_callback)
4,120✔
1312
    : ec_line_values(line_values),
4,120✔
1313
      ec_accumulator(std::make_unique<attr_line_t>()),
4,120✔
1314
      ec_sql_callback(sql_callback), ec_pipe_callback(pipe_callback)
8,240✔
1315
{
1316
    this->ec_local_vars.push(std::map<std::string, scoped_value_t>());
4,120✔
1317
    this->ec_path_stack.emplace_back(".");
4,120✔
1318
    this->ec_output_stack.emplace_back("screen", std::nullopt);
4,120✔
1319
}
4,120✔
1320

1321
Result<std::string, lnav::console::user_message>
1322
exec_context::execute(source_location loc, const std::string& cmdline)
2✔
1323
{
1324
    static auto& prompt = lnav::prompt::get();
2✔
1325
    static const auto& dls = lnav_data.ld_db_row_source;
1326

1327
    lnav::textinput::history::op_guard hist_guard;
2✔
1328
    auto sg = this->enter_source(loc, cmdline);
2✔
1329

1330
    auto before_dls_gen = dls.dls_generation;
2✔
1331
    if (this->get_provenance<mouse_input>() && !prompt.p_editor.is_enabled()
2✔
1332
        && !cmdline.empty())
2✔
1333
    {
UNCOV
1334
        auto& hist = prompt.get_history_for(cmdline[0]);
×
UNCOV
1335
        auto cmdline_sf = string_fragment::from_str(cmdline);
×
UNCOV
1336
        hist_guard = hist.start_operation(cmdline_sf.substr(1));
×
1337
    }
1338

1339
    auto exec_res = execute_any(*this, cmdline);
2✔
1340
    if (exec_res.isErr()) {
2✔
1341
        hist_guard.og_status = log_level_t::LEVEL_ERROR;
×
UNCOV
1342
        if (!this->ec_msg_callback_stack.empty()) {
×
UNCOV
1343
            this->ec_msg_callback_stack.back()(exec_res.unwrapErr());
×
1344
        }
1345
    } else {
1346
        if (before_dls_gen != dls.dls_generation
4✔
1347
            && dls.dls_row_cursors.size() > 1)
2✔
1348
        {
UNCOV
1349
            ensure_view(LNV_DB);
×
1350
        }
1351
    }
1352

1353
    return exec_res;
4✔
1354
}
2✔
1355

1356
void
1357
exec_context::add_error_context(lnav::console::user_message& um) const
102✔
1358
{
1359
    switch (um.um_level) {
102✔
1360
        case lnav::console::user_message::level::raw:
1✔
1361
        case lnav::console::user_message::level::info:
1362
        case lnav::console::user_message::level::ok:
1363
            return;
1✔
1364
        default:
101✔
1365
            break;
101✔
1366
    }
1367

1368
    if (!this->get_provenance<keyboard_input>()
101✔
1369
        && !this->get_provenance<mouse_input>() && um.um_snippets.empty())
101✔
1370
    {
1371
        um.with_snippets(this->ec_source);
45✔
1372
        um.remove_internal_snippets();
45✔
1373
    }
1374

1375
    if (this->ec_current_help != nullptr && um.um_help.empty()) {
101✔
1376
        attr_line_t help;
47✔
1377

1378
        format_help_text_for_term(*this->ec_current_help,
47✔
1379
                                  70,
1380
                                  help,
1381
                                  help_text_content::synopsis_and_summary);
1382
        um.with_help(help);
47✔
1383
    }
47✔
1384
}
1385

1386
lnav::console::user_message
1387
exec_context::make_error_from_str(std::string&& str) const
38✔
1388
{
1389
    auto retval = lnav::console::user_message::error(std::move(str));
38✔
1390
    this->add_error_context(retval);
38✔
1391
    return retval;
38✔
UNCOV
1392
}
×
1393

1394
exec_context::sql_callback_guard
1395
exec_context::push_callback(sql_callback_t cb)
5✔
1396
{
1397
    return sql_callback_guard(*this, cb);
5✔
1398
}
1399

1400
exec_context::source_guard
1401
exec_context::enter_source(source_location loc, const std::string& content)
3,096✔
1402
{
1403
    attr_line_t content_al{content};
3,096✔
1404
    content_al.with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE));
3,096✔
1405
    readline_lnav_highlighter(content_al, -1);
3,096✔
1406
    this->ec_source.emplace_back(lnav::console::snippet::from(loc, content_al));
3,096✔
1407
    return {this};
6,192✔
1408
}
3,096✔
1409

1410
exec_context::output_guard::output_guard(exec_context& context,
843✔
1411
                                         std::string name,
1412
                                         const std::optional<output_t>& file,
1413
                                         text_format_t tf)
843✔
1414
    : sg_context(context), sg_active(!name.empty())
843✔
1415
{
1416
    if (name.empty()) {
843✔
1417
        return;
774✔
1418
    }
1419
    if (file) {
69✔
1420
        log_info("redirecting command output to: %s", name.c_str());
31✔
1421
    } else if (!context.ec_output_stack.empty()) {
38✔
1422
        tf = context.ec_output_stack.back().od_format;
38✔
1423
    }
1424
    context.ec_output_stack.emplace_back(std::move(name), file, tf);
69✔
1425
}
1426

1427
exec_context::output_guard::~output_guard()
843✔
1428
{
1429
    if (this->sg_active) {
843✔
1430
        this->sg_context.clear_output();
69✔
1431
        this->sg_context.ec_output_stack.pop_back();
69✔
1432
    }
1433
}
843✔
1434
exec_context::sql_callback_guard::sql_callback_guard(exec_context& context,
5✔
1435
                                                     sql_callback_t cb)
5✔
1436
    : scg_context(context), scg_old_callback(context.ec_sql_callback)
5✔
1437
{
1438
    context.ec_sql_callback = cb;
5✔
1439
}
5✔
UNCOV
1440
exec_context::sql_callback_guard::sql_callback_guard(sql_callback_guard&& other)
×
UNCOV
1441
    : scg_context(other.scg_context),
×
UNCOV
1442
      scg_old_callback(std::exchange(other.scg_old_callback, nullptr))
×
1443
{
1444
}
1445
exec_context::sql_callback_guard::~sql_callback_guard()
5✔
1446
{
1447
    if (this->scg_old_callback != nullptr) {
5✔
1448
        this->scg_context.ec_sql_callback = this->scg_old_callback;
5✔
1449
    }
1450
}
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