• 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

54.61
/src/sql_commands.cc
1
/**
2
 * Copyright (c) 2021, 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 <atomic>
31
#include <chrono>
32
#include <string>
33
#include <thread>
34
#include <vector>
35

36
#include <stdio.h>
37
#include <stdlib.h>
38

39
#include "base/auto_mem.hh"
40
#include "base/fs_util.hh"
41
#include "base/injector.bind.hh"
42
#include "base/injector.hh"
43
#include "base/itertools.hh"
44
#include "base/lnav.console.hh"
45
#include "base/lnav_log.hh"
46
#include "base/opt_util.hh"
47
#include "base/progress.hh"
48
#include "base/result.h"
49
#include "bound_tags.hh"
50
#include "command_executor.hh"
51
#include "config.h"
52
#include "fmt/chrono.h"
53
#include "lnav.hh"
54
#include "lnav_util.hh"
55
#include "readline_context.hh"
56
#include "safe/safe.h"
57
#include "service_tags.hh"
58
#include "shlex.hh"
59
#include "sql_help.hh"
60
#include "sqlite-extension-func.hh"
61
#include "sqlitepp.client.hh"
62
#include "sqlitepp.hh"
63
#include "view_helpers.hh"
64

65
using namespace std::chrono_literals;
66

67
static Result<std::string, lnav::console::user_message>
68
sql_cmd_dump(exec_context& ec,
1✔
69
             std::string cmdline,
70
             std::vector<std::string>& args)
71
{
72
    static auto& lnav_db = injector::get<auto_sqlite3&>();
1✔
73
    static auto& lnflags = injector::get<lnav_flags_storage&>();
1✔
74

75
    std::string retval;
1✔
76

77
    if (args.empty()) {
1✔
78
        args.emplace_back("filename");
×
79
        args.emplace_back("tables");
×
80
        return Ok(retval);
×
81
    }
82

83
    if (args.size() < 2) {
1✔
84
        return ec.make_error("expecting a file name to write to");
×
85
    }
86

87
    if (args.size() < 3) {
1✔
88
        return ec.make_error("expecting a table name to dump");
×
89
    }
90

91
    if (lnflags.is_set<lnav_flags::secure_mode>()) {
1✔
92
        return ec.make_error("{} -- unavailable in secure mode", args[0]);
×
93
    }
94

95
    auto_mem<FILE> file(fclose);
1✔
96

97
    if ((file = fopen(args[1].c_str(), "w+")) == nullptr) {
1✔
98
        return ec.make_error(
99
            "unable to open '{}' for writing: {}", args[1], strerror(errno));
×
100
    }
101

102
    auto prep_res
103
        = prepare_stmt(lnav_db, "SELECT name FROM pragma_database_list");
1✔
104
    if (prep_res.isErr()) {
1✔
105
        return ec.make_error("unable to prepare database_list: {}",
106
                             prep_res.unwrapErr());
×
107
    }
108
    auto stmt = prep_res.unwrap();
1✔
109
    auto all_db_names = std::vector<std::string>();
1✔
110
    stmt.for_each_row<std::string>([&all_db_names](auto row) {
1✔
111
        all_db_names.emplace_back(row);
2✔
112
        return false;
2✔
113
    });
114

115
    for (size_t lpc = 2; lpc < args.size(); lpc++) {
2✔
116
        auto arg = string_fragment::from_str(args[lpc]);
1✔
117
        auto [schema_name, table_name]
1✔
118
            = arg.split_when(string_fragment::tag1{'.'});
1✔
119
        auto db_names = table_name.empty()
1✔
120
            ? all_db_names
1✔
121
            : std::vector{schema_name.to_string()};
1✔
122

123
        for (const auto& db_name : db_names) {
3✔
124
            sqlite3_db_dump(lnav_db.in(),
2✔
125
                            db_name.c_str(),
126
                            args[lpc].c_str(),
2✔
127
                            (int (*)(const char*, void*)) fputs,
128
                            file.in());
2✔
129
        }
130
    }
1✔
131

132
    auto table_count = args.size() - 2;
1✔
133
    retval = fmt::format(
1✔
134
        FMT_STRING("info: wrote {} table(s) to {}"), table_count, args[1]);
4✔
135
    return Ok(retval);
1✔
136
}
1✔
137

138
struct backup_progress_t {
139
    std::atomic<lnav::progress_status_t> bp_status{
140
        lnav::progress_status_t::idle};
141
    std::atomic_uint32_t bp_instance{0};
142
    std::atomic_uint64_t bp_complete{0};
143
    std::atomic_uint64_t bp_total{0};
144
    safe::Safe<std::vector<lnav::console::user_message>> bp_messages;
145

146
    struct backup_guard {
147
        ~backup_guard()
2✔
148
        {
149
            if (bg_helper.gh_enabled) {
2✔
150
                INSTANCE.bp_status.store(lnav::progress_status_t::idle);
2✔
151

152
                lnav::progress_tracker::instance().notify_completion();
2✔
153
            }
154
        }
2✔
155

156
        void push_msg(lnav::console::user_message um)
1✔
157
        {
158
            log_info("backup message: %s", um.to_attr_line().al_string.c_str());
1✔
159
            INSTANCE.bp_messages.writeAccess()->emplace_back(std::move(um));
1✔
160
        }
1✔
161

162
        void update(uint64_t complete, uint64_t total)
2✔
163
        {
164
            INSTANCE.bp_complete.store(complete);
165
            INSTANCE.bp_total.store(total);
166
        }
2✔
167

168
        lnav::guard_helper bg_helper;
169
    };
170

171
    static backup_guard begin()
2✔
172
    {
173
        INSTANCE.bp_messages.writeAccess()->clear();
2✔
174
        INSTANCE.bp_complete.store(0);
175
        INSTANCE.bp_total.store(1);
176
        INSTANCE.bp_instance.fetch_add(1, std::memory_order_relaxed);
177
        INSTANCE.bp_status.store(lnav::progress_status_t::working);
2✔
178

179
        return {};
2✔
180
    }
181

182
    static backup_progress_t INSTANCE;
183
};
184

185
backup_progress_t backup_progress_t::INSTANCE;
186

187
static lnav::task_progress
188
backup_prog_rep()
2,204✔
189
{
190
    return {
191
        ";.save",
192
        backup_progress_t::INSTANCE.bp_status.load(),
2,204✔
193
        (size_t) backup_progress_t::INSTANCE.bp_instance.load(),
2,204✔
194
        "Backing up DB",
195
        (size_t) backup_progress_t::INSTANCE.bp_complete.load(),
2,204✔
196
        (size_t) backup_progress_t::INSTANCE.bp_total.load(),
2,204✔
197
        *backup_progress_t::INSTANCE.bp_messages.readAccess(),
4,408✔
198
    };
2,204✔
199
}
17,632✔
200

201
DIST_SLICE(prog_reps) lnav::progress_reporter_t backup_rep = backup_prog_rep;
202

203
static void
204
backup_user_db(const std::string& filename)
2✔
205
{
206
    static auto op = lnav_operation{__FUNCTION__};
2✔
207
    auto op_guard = lnav_opid_guard::internal(op);
2✔
208
    log_info("starting backup of user DB: %s", filename.c_str());
2✔
209
    auto bguard = backup_progress_t::begin();
2✔
210

211
    auto_sqlite3 out_db;
2✔
212
    if (sqlite3_open(filename.c_str(), out_db.out()) != SQLITE_OK) {
2✔
213
        auto um = lnav::console::user_message::error(
×
214
                      attr_line_t("unable to open output DB: ")
1✔
215
                          .append(lnav::roles::file(filename)))
2✔
216
                      .with_reason(sqlite3_errmsg(out_db.in()));
1✔
217
        bguard.push_msg(um);
1✔
218
        return;
1✔
219
    }
1✔
220

221
    auto_sqlite3 db;
1✔
222
    if (sqlite3_open_v2("file:user_db?mode=memory&cache=shared",
1✔
223
                        db.out(),
224
                        SQLITE_OPEN_URI | SQLITE_OPEN_READWRITE,
225
                        nullptr)
226
        != SQLITE_OK)
1✔
227
    {
228
        auto um = lnav::console::user_message::error(
×
229
                      "unable to open lnav's user DB")
230
                      .with_reason(sqlite3_errmsg(db.in()));
×
231
        bguard.push_msg(um);
×
232
        return;
×
233
    }
234

235
    {
236
        auto stmt = prepare_stmt(db, LNAV_ATTACH_DB).unwrap();
1✔
237
        auto exec_res = stmt.execute();
1✔
238
        if (exec_res.isErr()) {
1✔
239
            auto um
240
                = lnav::console::user_message::error("unable to attach lnav_db")
×
241
                      .with_reason(sqlite3_errmsg(db.in()));
×
242
            bguard.push_msg(um);
×
243
            return;
×
244
        }
245
    }
1✔
246

247
    auto* backup = sqlite3_backup_init(out_db.in(), "main", db.in(), "main");
1✔
248
    if (backup == nullptr) {
1✔
249
        auto um = lnav::console::user_message::error("unable to backup user DB")
×
250
                      .with_reason(sqlite3_errmsg(db.in()));
×
251
        bguard.push_msg(um);
×
252
        return;
×
253
    }
254

255
    auto done = false;
1✔
256
    auto loop_count = 0;
1✔
257
    while (!done) {
3✔
258
        auto rc = sqlite3_backup_step(backup, 1);
2✔
259
        switch (rc) {
2✔
260
            case SQLITE_DONE:
1✔
261
                done = true;
1✔
262
                break;
1✔
263
            case SQLITE_OK:
1✔
264
                if (loop_count % 100 == 0) {
1✔
265
                    std::this_thread::sleep_for(1ms);
1✔
266
                }
267
                break;
1✔
268
            case SQLITE_BUSY:
×
269
            case SQLITE_LOCKED:
270
                std::this_thread::sleep_for(10ms);
×
271
                break;
×
272
            default: {
×
273
                auto um = lnav::console::user_message::error(
×
274
                              "unable to backup user DB")
275
                              .with_reason(sqlite3_errmsg(db.in()));
×
276
                bguard.push_msg(um);
×
277
                sqlite3_backup_finish(backup);
×
278
                return;
×
279
            }
280
        }
281

282
        if (loop_count % 100 == 0) {
2✔
283
            auto total = sqlite3_backup_pagecount(backup);
1✔
284
            auto complete = total - sqlite3_backup_remaining(backup);
1✔
285
            bguard.update(complete, total);
1✔
286
        }
287

288
        loop_count += 1;
2✔
289
    }
290

291
    {
292
        auto total = sqlite3_backup_pagecount(backup);
1✔
293
        auto complete = total - sqlite3_backup_remaining(backup);
1✔
294
        bguard.update(complete, total);
1✔
295
    }
296
    sqlite3_backup_finish(backup);
1✔
297
    log_info("backup complete: complete=%llu; total=%llu",
3✔
298
             backup_progress_t::INSTANCE.bp_complete.load(),
299
             backup_progress_t::INSTANCE.bp_total.load());
300
}
4✔
301

302
static Result<std::string, lnav::console::user_message>
303
sql_cmd_save(exec_context& ec,
2✔
304
             std::string cmdline,
305
             std::vector<std::string>& args)
306
{
307
    static auto& lnflags = injector::get<lnav_flags_storage&>();
2✔
308
    std::string retval;
2✔
309

310
    if (args.empty()) {
2✔
311
        args.emplace_back("filename");
×
312
        return Ok(retval);
×
313
    }
314

315
    if (args.size() < 2) {
2✔
316
        return ec.make_error("expecting a file name to write to");
×
317
    }
318

319
    if (lnflags.is_set<lnav_flags::secure_mode>()) {
2✔
320
        return ec.make_error("{} -- unavailable in secure mode", args[0]);
×
321
    }
322

323
    isc::to<bg_looper&, services::background_t>().send(
2✔
324
        [filename = args[1]](auto& bg_loop) { backup_user_db(filename); });
6✔
325

326
    return Ok(retval);
2✔
327
}
2✔
328

329
static Result<std::string, lnav::console::user_message>
330
sql_cmd_read(exec_context& ec,
2✔
331
             std::string cmdline,
332
             std::vector<std::string>& args)
333
{
334
    static const intern_string_t SRC = intern_string::lookup("cmdline");
6✔
335
    static auto& lnav_db = injector::get<auto_sqlite3&>();
2✔
336
    static auto& lnflags = injector::get<lnav_flags_storage&>();
2✔
337

338
    std::string retval;
2✔
339

340
    if (args.empty()) {
2✔
341
        args.emplace_back("filename");
×
342
        return Ok(retval);
×
343
    }
344

345
    if (lnflags.is_set<lnav_flags::secure_mode>()) {
2✔
346
        return ec.make_error("{} -- unavailable in secure mode", args[0]);
×
347
    }
348

349
    shlex lexer(cmdline);
2✔
350

351
    auto split_args_res = lexer.split(ec.create_resolver());
2✔
352
    if (split_args_res.isErr()) {
2✔
353
        auto split_err = split_args_res.unwrapErr();
×
354
        auto um
355
            = lnav::console::user_message::error("unable to parse file name")
×
356
                  .with_reason(split_err.se_error.te_msg)
×
357
                  .with_snippet(lnav::console::snippet::from(
×
358
                      SRC, lexer.to_attr_line(split_err.se_error)))
×
359
                  .move();
×
360

361
        return Err(um);
×
362
    }
363

364
    auto split_args = split_args_res.unwrap()
4✔
365
        | lnav::itertools::map([](const auto& elem) { return elem.se_value; });
8✔
366

367
    for (size_t lpc = 1; lpc < split_args.size(); lpc++) {
3✔
368
        auto read_res = lnav::filesystem::read_file(split_args[lpc]);
2✔
369

370
        if (read_res.isErr()) {
2✔
371
            return ec.make_error("unable to read script file: {} -- {}",
372
                                 split_args[lpc],
1✔
373
                                 read_res.unwrapErr());
2✔
374
        }
375

376
        auto script = read_res.unwrap();
1✔
377
        auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
1✔
378
        const char* start = script.c_str();
1✔
379

380
        do {
381
            const char* tail;
382
            auto rc = sqlite3_prepare_v2(
2✔
383
                lnav_db.in(), start, -1, stmt.out(), &tail);
384

385
            if (rc != SQLITE_OK) {
2✔
386
                const char* errmsg = sqlite3_errmsg(lnav_db.in());
×
387

388
                return ec.make_error("{}", errmsg);
×
389
            }
390

391
            if (stmt.in() != nullptr) {
2✔
392
                std::string alt_msg;
2✔
393
                auto exec_res = execute_sql(
394
                    ec, std::string(start, tail - start), alt_msg);
2✔
395
                if (exec_res.isErr()) {
2✔
396
                    return exec_res;
×
397
                }
398
            }
2✔
399

400
            start = tail;
2✔
401
        } while (start[0]);
2✔
402
    }
2✔
403

404
    return Ok(retval);
1✔
405
}
2✔
406

407
static Result<std::string, lnav::console::user_message>
408
sql_cmd_schema(exec_context& ec,
1✔
409
               std::string cmdline,
410
               std::vector<std::string>& args)
411
{
412
    std::string retval;
1✔
413

414
    if (args.empty()) {
1✔
415
        args.emplace_back("sql-table");
×
416
        return Ok(retval);
×
417
    }
418

419
    ensure_view(LNV_SCHEMA);
1✔
420
    if (args.size() == 2) {
1✔
421
        const auto id = text_anchors::to_anchor_string(args[1]);
×
422
        auto* tss = lnav_data.ld_views[LNV_SCHEMA].get_sub_source();
×
423
        if (auto* ta = dynamic_cast<text_anchors*>(tss); ta != nullptr) {
×
424
            ta->row_for_anchor(id) |
×
425
                [](auto row) { lnav_data.ld_views[LNV_SCHEMA].set_top(row); };
×
426
        }
427
    }
428

429
    return Ok(retval);
1✔
430
}
1✔
431

432
static Result<std::string, lnav::console::user_message>
433
sql_cmd_msgformats(exec_context& ec,
1✔
434
                   std::string cmdline,
435
                   std::vector<std::string>& args)
436
{
437
    static const std::string MSG_FORMAT_STMT = R"(
3✔
438
SELECT count(*) AS total,
439
       min(log_line) AS log_line,
440
       min(log_time) AS log_time,
441
       humanize_duration(timediff(max(log_time), min(log_time))) AS duration,
442
       group_concat(DISTINCT log_format) AS log_formats,
443
       log_msg_format
444
    FROM all_logs
445
    WHERE log_msg_format != ''
446
    GROUP BY log_msg_format
447
    HAVING total > 1
448
    ORDER BY total DESC, log_line ASC
449
)";
450

451
    std::string retval;
1✔
452

453
    if (args.empty()) {
1✔
454
        return Ok(retval);
×
455
    }
456

457
    std::string alt;
1✔
458

459
    return execute_sql(ec, MSG_FORMAT_STMT, alt);
1✔
460
}
1✔
461

462
static Result<std::string, lnav::console::user_message>
463
sql_cmd_generic(exec_context& ec,
×
464
                std::string cmdline,
465
                std::vector<std::string>& args)
466
{
467
    std::string retval;
×
468

469
    if (args.empty()) {
×
470
        args.emplace_back("*");
×
471
        return Ok(retval);
×
472
    }
473

474
    return Ok(retval);
×
475
}
476

477
static Result<std::string, lnav::console::user_message>
478
prql_cmd_from(exec_context& ec,
×
479
              std::string cmdline,
480
              std::vector<std::string>& args)
481
{
482
    std::string retval;
×
483

484
    if (args.empty()) {
×
485
        args.emplace_back("prql-table");
×
486
        return Ok(retval);
×
487
    }
488

489
    return Ok(retval);
×
490
}
491

492
static readline_context::prompt_result_t
493
prql_cmd_from_prompt(exec_context& ec, const std::string& cmdline)
×
494
{
495
    if (!endswith(cmdline, "from ")) {
×
496
        return {};
×
497
    }
498

499
    auto* tc = *lnav_data.ld_view_stack.top();
×
500
    auto* lss = dynamic_cast<logfile_sub_source*>(tc->get_sub_source());
×
501
    auto sel = tc->get_selection();
×
502

503
    if (!sel || lss == nullptr || lss->text_line_count() == 0) {
×
504
        return {};
×
505
    }
506

507
    auto line_pair = lss->find_line_with_file(lss->at(sel.value()));
×
508
    if (!line_pair) {
×
509
        return {};
×
510
    }
511

512
    auto format_name
513
        = line_pair->first->get_format_ptr()->get_name().to_string();
×
514
    return {
515
        "",
516
        lnav::prql::quote_ident(format_name) + " ",
×
517
    };
518
}
519

520
static Result<std::string, lnav::console::user_message>
521
prql_cmd_aggregate(exec_context& ec,
×
522
                   std::string cmdline,
523
                   std::vector<std::string>& args)
524
{
525
    std::string retval;
×
526

527
    if (args.empty()) {
×
528
        args.emplace_back("prql-expr");
×
529
        return Ok(retval);
×
530
    }
531

532
    return Ok(retval);
×
533
}
534

535
static Result<std::string, lnav::console::user_message>
536
prql_cmd_append(exec_context& ec,
×
537
                std::string cmdline,
538
                std::vector<std::string>& args)
539
{
540
    std::string retval;
×
541

542
    if (args.empty()) {
×
543
        args.emplace_back("prql-table");
×
544
        return Ok(retval);
×
545
    }
546

547
    return Ok(retval);
×
548
}
549

550
static Result<std::string, lnav::console::user_message>
551
prql_cmd_derive(exec_context& ec,
×
552
                std::string cmdline,
553
                std::vector<std::string>& args)
554
{
555
    std::string retval;
×
556

557
    if (args.empty()) {
×
558
        args.emplace_back("prql-expr");
×
559
        return Ok(retval);
×
560
    }
561

562
    return Ok(retval);
×
563
}
564

565
static Result<std::string, lnav::console::user_message>
566
prql_cmd_filter(exec_context& ec,
×
567
                std::string cmdline,
568
                std::vector<std::string>& args)
569
{
570
    std::string retval;
×
571

572
    if (args.empty()) {
×
573
        args.emplace_back("prql-expr");
×
574
        return Ok(retval);
×
575
    }
576

577
    return Ok(retval);
×
578
}
579

580
static Result<std::string, lnav::console::user_message>
581
prql_cmd_group(exec_context& ec,
×
582
               std::string cmdline,
583
               std::vector<std::string>& args)
584
{
585
    std::string retval;
×
586

587
    if (args.empty()) {
×
588
        args.emplace_back("prql-expr");
×
589
        args.emplace_back("prql-source");
×
590
        return Ok(retval);
×
591
    }
592

593
    return Ok(retval);
×
594
}
595

596
static Result<std::string, lnav::console::user_message>
597
prql_cmd_join(exec_context& ec,
×
598
              std::string cmdline,
599
              std::vector<std::string>& args)
600
{
601
    std::string retval;
×
602

603
    if (args.empty()) {
×
604
        args.emplace_back("prql-table");
×
605
        args.emplace_back("prql-expr");
×
606
        return Ok(retval);
×
607
    }
608

609
    return Ok(retval);
×
610
}
611

612
static Result<std::string, lnav::console::user_message>
613
prql_cmd_let(exec_context& ec,
×
614
             std::string cmdline,
615
             std::vector<std::string>& args)
616
{
617
    std::string retval;
×
618

619
    return Ok(retval);
×
620
}
621

622
static Result<std::string, lnav::console::user_message>
UNCOV
623
prql_cmd_select(exec_context& ec,
×
624
                std::string cmdline,
625
                std::vector<std::string>& args)
626
{
UNCOV
627
    std::string retval;
×
628

UNCOV
629
    if (args.empty()) {
×
UNCOV
630
        args.emplace_back("prql-expr");
×
UNCOV
631
        return Ok(retval);
×
632
    }
633

634
    return Ok(retval);
×
635
}
636

637
static Result<std::string, lnav::console::user_message>
UNCOV
638
prql_cmd_sort(exec_context& ec,
×
639
              std::string cmdline,
640
              std::vector<std::string>& args)
641
{
UNCOV
642
    std::string retval;
×
643

UNCOV
644
    if (args.empty()) {
×
UNCOV
645
        args.emplace_back("prql-expr");
×
UNCOV
646
        return Ok(retval);
×
647
    }
648

649
    return Ok(retval);
×
650
}
651

652
static Result<std::string, lnav::console::user_message>
653
prql_cmd_take(exec_context& ec,
×
654
              std::string cmdline,
655
              std::vector<std::string>& args)
656
{
UNCOV
657
    std::string retval;
×
658

UNCOV
659
    if (args.empty()) {
×
UNCOV
660
        return Ok(retval);
×
661
    }
662

UNCOV
663
    return Ok(retval);
×
664
}
665

666
static readline_context::command_t sql_commands[] = {
667
    {
668
        ".dump",
669
        sql_cmd_dump,
670
        help_text(
671
            ".dump",
672
            "Dump one or more tables to a file as a series of SQL statements")
673
            .sql_command()
674
            .with_parameter(
675
                help_text{"path", "The path to the file to write"}.with_format(
676
                    help_parameter_format_t::HPF_LOCAL_FILENAME))
677
            .with_parameter(help_text{"table", "The name of the table to dump"}
678
                                .one_or_more())
679
            .with_tags({
680
                "io",
681
            }),
682
    },
683
    {
684
        ".save",
685
        sql_cmd_save,
686
        help_text(".save", "Save the current database to a SQLite file")
687
            .sql_command()
688
            .with_parameter(
689
                help_text{"path", "The path to the file to write"}.with_format(
690
                    help_parameter_format_t::HPF_LOCAL_FILENAME))
691
            .with_tags({
692
                "io",
693
            }),
694
    },
695
    {
696
        ".msgformats",
697
        sql_cmd_msgformats,
698
        help_text(".msgformats",
699
                  "Executes a query that will summarize the different message "
700
                  "formats found in the logs")
701
            .sql_command(),
702
    },
703
    {
704
        ".read",
705
        sql_cmd_read,
706
        help_text(".read", "Execute the SQLite statements in the given file")
707
            .sql_command()
708
            .with_parameter(
709
                help_text{"path", "The path to the file to write"}.with_format(
710
                    help_parameter_format_t::HPF_LOCAL_FILENAME))
711
            .with_tags({
712
                "io",
713
            }),
714
    },
715
    {
716
        ".schema",
717
        sql_cmd_schema,
718
        help_text(".schema",
719
                  "Switch to the SCHEMA view that contains a dump of the "
720
                  "current database schema")
721
            .sql_command()
722
            .with_parameter({"name", "The name of a table to jump to"}),
723
    },
724
    {
725
        "ATTACH",
726
        sql_cmd_generic,
727
    },
728
    {
729
        "CREATE",
730
        sql_cmd_generic,
731
    },
732
    {
733
        "DELETE",
734
        sql_cmd_generic,
735
    },
736
    {
737
        "DETACH",
738
        sql_cmd_generic,
739
    },
740
    {
741
        "DROP",
742
        sql_cmd_generic,
743
    },
744
    {
745
        "INSERT",
746
        sql_cmd_generic,
747
    },
748
    {
749
        "SELECT",
750
        sql_cmd_generic,
751
    },
752
    {
753
        "UPDATE",
754
        sql_cmd_generic,
755
    },
756
    {
757
        "WITH",
758
        sql_cmd_generic,
759
    },
760
    {
761
        "from",
762
        prql_cmd_from,
763
        help_text("from")
764
            .prql_transform()
765
            .with_tags({"prql"})
766
            .with_summary("PRQL command to specify a data source")
767
            .with_parameter({"table", "The table to use as a source"})
768
            .with_example({
769
                "To pull data from the 'http_status_codes' database table",
770
                "from http_status_codes | take 3",
771
                help_example::language::prql,
772
            })
773
            .with_example({
774
                "To use an array literal as a source",
775
                "from [{ col1=1, col2='abc' }, { col1=2, col2='def' }]",
776
                help_example::language::prql,
777
            }),
778
        prql_cmd_from_prompt,
779
        "prql-source",
780
    },
781
    {
782
        "let",
783
        prql_cmd_let,
784
        help_text("let")
785
            .prql_transform()
786
            .with_tags({"prql"})
787
            .with_summary("Define a variable")
788
            .with_parameter({"name", "The name for the variable"})
789
            .with_example({
790
                "To set 'x' to 5",
791
                "let x = 5",
792
                help_example::language::prql,
793
            }),
794
    },
795
    {
796
        "aggregate",
797
        prql_cmd_aggregate,
798
        help_text("aggregate")
799
            .prql_transform()
800
            .with_tags({"prql"})
801
            .with_summary("PRQL transform to summarize many rows into one")
802
            .with_parameter(
803
                help_text{"expr", "The aggregate expression(s)"}.with_grouping(
804
                    "{", "}"))
805
            .with_example({
806
                "To group values into a JSON array",
807
                "from [{a=1}, {a=2}] | aggregate { arr = json.group_array a }",
808
                help_example::language::prql,
809
            }),
810
        nullptr,
811
        "prql-source",
812
        {"prql-source"},
813
    },
814
    {
815
        "append",
816
        prql_cmd_append,
817
        help_text("append")
818
            .prql_transform()
819
            .with_tags({"prql"})
820
            .with_summary("PRQL transform to concatenate tables together")
821
            .with_parameter({"table", "The table to use as a source"}),
822
        nullptr,
823
        "prql-source",
824
        {"prql-source"},
825
    },
826
    {
827
        "derive",
828
        prql_cmd_derive,
829
        help_text("derive")
830
            .prql_transform()
831
            .with_tags({"prql"})
832
            .with_summary("PRQL transform to derive one or more columns")
833
            .with_parameter(
834
                help_text{"column", "The new column"}.with_grouping("{", "}"))
835
            .with_example({
836
                "To add a column that is a multiplication of another",
837
                "from [{a=1}, {a=2}] | derive b = a * 2",
838
                help_example::language::prql,
839
            }),
840
        nullptr,
841
        "prql-source",
842
        {"prql-source"},
843
    },
844
    {
845
        "filter",
846
        prql_cmd_filter,
847
        help_text("filter")
848
            .prql_transform()
849
            .with_tags({"prql"})
850
            .with_summary("PRQL transform to pick rows based on their values")
851
            .with_parameter(
852
                {"expr", "The expression to evaluate over each row"})
853
            .with_example({
854
                "To pick rows where 'a' is greater than one",
855
                "from [{a=1}, {a=2}] | filter a > 1",
856
                help_example::language::prql,
857
            }),
858
        nullptr,
859
        "prql-source",
860
        {"prql-source"},
861
    },
862
    {
863
        "group",
864
        prql_cmd_group,
865
        help_text("group")
866
            .prql_transform()
867
            .with_tags({"prql"})
868
            .with_summary("PRQL transform to partition rows into groups")
869
            .with_parameter(
870
                help_text{"key_columns", "The columns that define the group"}
871
                    .with_grouping("{", "}"))
872
            .with_parameter(
873
                help_text{"pipeline", "The pipeline to execute over a group"}
874
                    .with_grouping("(", ")"))
875
            .with_example({
876
                "To group by log_level and count the rows in each partition",
877
                "from lnav_example_log | group { log_level } (aggregate { "
878
                "count this })",
879
                help_example::language::prql,
880
            }),
881
        nullptr,
882
        "prql-source",
883
        {"prql-source"},
884
    },
885
    {
886
        "join",
887
        prql_cmd_join,
888
        help_text("join")
889
            .prql_transform()
890
            .with_tags({"prql"})
891
            .with_summary("PRQL transform to add columns from another table")
892
            .with_parameter(help_text{"side", "Specifies which rows to include"}
893
                                .with_enum_values({
894
                                    "inner"_frag,
895
                                    "left"_frag,
896
                                    "right"_frag,
897
                                    "full"_frag,
898
                                })
899
                                .with_default_value("inner")
900
                                .optional())
901
            .with_parameter(
902
                {"table", "The other table to join with the current rows"})
903
            .with_parameter(
904
                help_text{"condition", "The condition used to join rows"}
905
                    .with_grouping("(", ")")),
906
        nullptr,
907
        "prql-source",
908
        {"prql-source"},
909
    },
910
    {
911
        "select",
912
        prql_cmd_select,
913
        help_text("select")
914
            .prql_transform()
915
            .with_tags({"prql"})
916
            .with_summary("PRQL transform to pick and compute columns")
917
            .with_parameter(
918
                help_text{"expr", "The columns to include in the result set"}
919
                    .with_grouping("{", "}"))
920
            .with_example({
921
                "To pick the 'b' column from the rows",
922
                "from [{a=1, b='abc'}, {a=2, b='def'}] | select b",
923
                help_example::language::prql,
924
            })
925
            .with_example({
926
                "To compute a new column from an input",
927
                "from [{a=1}, {a=2}] | select b = a * 2",
928
                help_example::language::prql,
929
            }),
930
        nullptr,
931
        "prql-source",
932
        {"prql-source"},
933
    },
934
    {
935
        "stats.average_of",
936
        prql_cmd_sort,
937
        help_text("stats.average_of", "Compute the average of col")
938
            .prql_function()
939
            .with_tags({"prql"})
940
            .with_parameter(help_text{"col", "The column to average"})
941
            .with_example({
942
                "To get the average of a",
943
                "from [{a=1}, {a=1}, {a=2}] | stats.average_of a",
944
                help_example::language::prql,
945
            }),
946
        nullptr,
947
        "prql-source",
948
        {"prql-source"},
949
    },
950
    {
951
        "stats.count_by",
952
        prql_cmd_sort,
953
        help_text(
954
            "stats.count_by",
955
            "Partition rows and count the number of rows in each partition")
956
            .prql_function()
957
            .with_tags({"prql"})
958
            .with_parameter(help_text{"column", "The columns to group by"}
959
                                .one_or_more()
960
                                .with_grouping("{", "}"))
961
            .with_example({
962
                "To count rows for a particular value of column 'a'",
963
                "from [{a=1}, {a=1}, {a=2}] | stats.count_by a",
964
                help_example::language::prql,
965
            }),
966
        nullptr,
967
        "prql-source",
968
        {"prql-source"},
969
    },
970
    {
971
        "stats.hist",
972
        prql_cmd_sort,
973
        help_text("stats.hist", "Count the top values per bucket of time")
974
            .prql_function()
975
            .with_tags({"prql"})
976
            .with_parameter(help_text{"col", "The column to count"})
977
            .with_parameter(help_text{"slice", "The time slice"}
978
                                .optional()
979
                                .with_default_value("$zoom_level"))
980
            .with_parameter(
981
                help_text{"top", "The limit on the number of values to report"}
982
                    .optional()
983
                    .with_default_value("10"))
984
            .with_example({
985
                "To chart the values of ex_procname over time",
986
                "from lnav_example_log | stats.hist ex_procname",
987
                help_example::language::prql,
988
            })
989
            .with_example({
990
                "To chart the values of ex_procname for every second",
991
                "from lnav_example_log | stats.hist ex_procname slice:'1s'",
992
                help_example::language::prql,
993
            }),
994
        nullptr,
995
        "prql-source",
996
        {"prql-source"},
997
    },
998
    {
999
        "stats.sum_of",
1000
        prql_cmd_sort,
1001
        help_text("stats.sum_of", "Compute the sum of col")
1002
            .prql_function()
1003
            .with_tags({"prql"})
1004
            .with_parameter(help_text{"col", "The column to sum"})
1005
            .with_example({
1006
                "To get the sum of a",
1007
                "from [{a=1}, {a=1}, {a=2}] | stats.sum_of a",
1008
                help_example::language::prql,
1009
            }),
1010
        nullptr,
1011
        "prql-source",
1012
        {"prql-source"},
1013
    },
1014
    {
1015
        "stats.timeseries",
1016
        prql_cmd_sort,
1017
        help_text("stats.timeseries", "Aggregate values per bucket of time")
1018
            .prql_function()
1019
            .with_tags({"prql"})
1020
            .with_parameter(help_text{"agg", "The aggregation to perform"})
1021
            .with_grouping("{", "}")
1022
            .with_parameter(help_text{"slice", "The time slice"}
1023
                                .optional()
1024
                                .with_default_value("$zoom_level"))
1025
            .with_example({
1026
                "To chart the sum of sc_bytes over time",
1027
                "from lnav_example_log | stats.timeseries { total_duration = "
1028
                "humanize.duration (sum ex_duration) }",
1029
                help_example::language::prql,
1030
            }),
1031
        nullptr,
1032
        "prql-source",
1033
        {"prql-source"},
1034
    },
1035
    {
1036
        "stats.by",
1037
        prql_cmd_sort,
1038
        help_text("stats.by", "A shorthand for grouping and aggregating")
1039
            .prql_function()
1040
            .with_tags({"prql"})
1041
            .with_parameter(help_text{"col", "The column to aggregate"})
1042
            .with_parameter(help_text{"values", "The aggregations to perform"})
1043
            .with_example({
1044
                "To partition by a and get the sum of b",
1045
                "from [{a=1, b=1}, {a=1, b=1}, {a=2, b=1}] | stats.by a "
1046
                "{sum b}",
1047
                help_example::language::prql,
1048
            }),
1049
        nullptr,
1050
        "prql-source",
1051
        {"prql-source"},
1052
    },
1053
    {
1054
        "sort",
1055
        prql_cmd_sort,
1056
        help_text("sort")
1057
            .prql_transform()
1058
            .with_tags({"prql"})
1059
            .with_summary("PRQL transform to sort rows")
1060
            .with_parameter(help_text{
1061
                "expr", "The values to use when ordering the result set"}
1062
                                .with_grouping("{", "}"))
1063
            .with_example({
1064
                "To sort the rows in descending order",
1065
                "from [{a=1}, {a=2}] | sort {-a}",
1066
                help_example::language::prql,
1067
            }),
1068
        nullptr,
1069
        "prql-source",
1070
        {"prql-source"},
1071
    },
1072
    {
1073
        "take",
1074
        prql_cmd_take,
1075
        help_text("take")
1076
            .prql_transform()
1077
            .with_tags({"prql"})
1078
            .with_summary("PRQL command to pick rows based on their position")
1079
            .with_parameter({"n_or_range", "The number of rows or range"})
1080
            .with_example({
1081
                "To pick the first row",
1082
                "from [{a=1}, {a=2}, {a=3}] | take 1",
1083
                help_example::language::prql,
1084
            })
1085
            .with_example({
1086
                "To pick the second and third rows",
1087
                "from [{a=1}, {a=2}, {a=3}] | take 2..3",
1088
                help_example::language::prql,
1089
            }),
1090
        nullptr,
1091
        "prql-source",
1092
        {"prql-source"},
1093
    },
1094
    {
1095
        "utils.distinct",
1096
        prql_cmd_sort,
1097
        help_text("utils.distinct",
1098
                  "A shorthand for getting distinct values of col")
1099
            .prql_function()
1100
            .with_tags({"prql"})
1101
            .with_parameter(help_text{"col", "The column to sum"})
1102
            .with_example({
1103
                "To get the distinct values of a",
1104
                "from [{a=1}, {a=1}, {a=2}] | utils.distinct a",
1105
                help_example::language::prql,
1106
            }),
1107
        nullptr,
1108
        "prql-source",
1109
        {"prql-source"},
1110
    },
1111
};
1112

1113
static readline_context::command_map_t sql_cmd_map;
1114

1115
static auto bound_sql_cmd_map
1116
    = injector::bind<readline_context::command_map_t,
1117
                     sql_cmd_map_tag>::to_instance(+[]() {
1,337✔
1118
          for (auto& cmd : sql_commands) {
44,121✔
1119
              sql_cmd_map[cmd.c_name] = &cmd;
42,784✔
1120
              if (cmd.c_help.ht_name) {
42,784✔
1121
                  cmd.c_help.index_tags();
42,784✔
1122
              }
1123
          }
1124

1125
          return &sql_cmd_map;
1,337✔
1126
      });
1127

1128
namespace injector {
1129
template<>
1130
void
1131
force_linking(sql_cmd_map_tag anno)
19✔
1132
{
1133
}
19✔
1134
}  // namespace injector
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