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

tstack / lnav / 19115445439-2645

05 Nov 2025 08:31PM UTC coverage: 68.952% (+0.05%) from 68.905%
19115445439-2645

push

github

tstack
[tests] change /root use

50626 of 73422 relevant lines covered (68.95%)

433904.85 hits per line

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

54.7
/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& lnav_flags = injector::get<unsigned long&, lnav_flags_tag>();
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 (lnav_flags & LNF_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)
1✔
163
        {
164
            INSTANCE.bp_complete.store(complete);
165
            INSTANCE.bp_total.store(total);
166
        }
1✔
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()
1,613✔
189
{
190
    return {
191
        ";.save",
192
        backup_progress_t::INSTANCE.bp_status.load(),
1,613✔
193
        backup_progress_t::INSTANCE.bp_instance.load(),
1,613✔
194
        "Backing up DB",
195
        backup_progress_t::INSTANCE.bp_complete.load(),
1,613✔
196
        backup_progress_t::INSTANCE.bp_total.load(),
1,613✔
197
        *backup_progress_t::INSTANCE.bp_messages.readAccess(),
3,226✔
198
    };
1,613✔
199
}
12,904✔
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("file:user_db?mode=memory&cache=shared", db.out())
1✔
223
        != SQLITE_OK)
1✔
224
    {
225
        auto um = lnav::console::user_message::error(
×
226
                      "unable to open lnav's user DB")
227
                      .with_reason(sqlite3_errmsg(db.in()));
×
228
        bguard.push_msg(um);
×
229
        return;
×
230
    }
231

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

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

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

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

285
        loop_count += 1;
2✔
286
    }
287

288
    sqlite3_backup_finish(backup);
1✔
289
    log_info("backup complete");
1✔
290
}
4✔
291

292
static Result<std::string, lnav::console::user_message>
293
sql_cmd_save(exec_context& ec,
2✔
294
             std::string cmdline,
295
             std::vector<std::string>& args)
296
{
297
    static auto& lnav_flags = injector::get<unsigned long&, lnav_flags_tag>();
2✔
298
    std::string retval;
2✔
299

300
    if (args.empty()) {
2✔
301
        args.emplace_back("filename");
×
302
        return Ok(retval);
×
303
    }
304

305
    if (args.size() < 2) {
2✔
306
        return ec.make_error("expecting a file name to write to");
×
307
    }
308

309
    if (lnav_flags & LNF_SECURE_MODE) {
2✔
310
        return ec.make_error("{} -- unavailable in secure mode", args[0]);
×
311
    }
312

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

316
    return Ok(retval);
2✔
317
}
2✔
318

319
static Result<std::string, lnav::console::user_message>
320
sql_cmd_read(exec_context& ec,
2✔
321
             std::string cmdline,
322
             std::vector<std::string>& args)
323
{
324
    static const intern_string_t SRC = intern_string::lookup("cmdline");
6✔
325
    static auto& lnav_db = injector::get<auto_sqlite3&>();
2✔
326
    static auto& lnav_flags = injector::get<unsigned long&, lnav_flags_tag>();
2✔
327

328
    std::string retval;
2✔
329

330
    if (args.empty()) {
2✔
331
        args.emplace_back("filename");
×
332
        return Ok(retval);
×
333
    }
334

335
    if (lnav_flags & LNF_SECURE_MODE) {
2✔
336
        return ec.make_error("{} -- unavailable in secure mode", args[0]);
×
337
    }
338

339
    shlex lexer(cmdline);
2✔
340

341
    auto split_args_res = lexer.split(ec.create_resolver());
2✔
342
    if (split_args_res.isErr()) {
2✔
343
        auto split_err = split_args_res.unwrapErr();
×
344
        auto um
345
            = lnav::console::user_message::error("unable to parse file name")
×
346
                  .with_reason(split_err.se_error.te_msg)
×
347
                  .with_snippet(lnav::console::snippet::from(
×
348
                      SRC, lexer.to_attr_line(split_err.se_error)))
×
349
                  .move();
×
350

351
        return Err(um);
×
352
    }
353

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

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

360
        if (read_res.isErr()) {
2✔
361
            return ec.make_error("unable to read script file: {} -- {}",
362
                                 split_args[lpc],
1✔
363
                                 read_res.unwrapErr());
2✔
364
        }
365

366
        auto script = read_res.unwrap();
1✔
367
        auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
1✔
368
        const char* start = script.c_str();
1✔
369

370
        do {
371
            const char* tail;
372
            auto rc = sqlite3_prepare_v2(
2✔
373
                lnav_db.in(), start, -1, stmt.out(), &tail);
374

375
            if (rc != SQLITE_OK) {
2✔
376
                const char* errmsg = sqlite3_errmsg(lnav_db.in());
×
377

378
                return ec.make_error("{}", errmsg);
×
379
            }
380

381
            if (stmt.in() != nullptr) {
2✔
382
                std::string alt_msg;
2✔
383
                auto exec_res = execute_sql(
384
                    ec, std::string(start, tail - start), alt_msg);
2✔
385
                if (exec_res.isErr()) {
2✔
386
                    return exec_res;
×
387
                }
388
            }
2✔
389

390
            start = tail;
2✔
391
        } while (start[0]);
2✔
392
    }
2✔
393

394
    return Ok(retval);
1✔
395
}
2✔
396

397
static Result<std::string, lnav::console::user_message>
398
sql_cmd_schema(exec_context& ec,
1✔
399
               std::string cmdline,
400
               std::vector<std::string>& args)
401
{
402
    std::string retval;
1✔
403

404
    if (args.empty()) {
1✔
405
        args.emplace_back("sql-table");
×
406
        return Ok(retval);
×
407
    }
408

409
    ensure_view(LNV_SCHEMA);
1✔
410
    if (args.size() == 2) {
1✔
411
        const auto id = text_anchors::to_anchor_string(args[1]);
×
412
        auto* tss = lnav_data.ld_views[LNV_SCHEMA].get_sub_source();
×
413
        if (auto* ta = dynamic_cast<text_anchors*>(tss); ta != nullptr) {
×
414
            ta->row_for_anchor(id) |
×
415
                [](auto row) { lnav_data.ld_views[LNV_SCHEMA].set_top(row); };
×
416
        }
417
    }
418

419
    return Ok(retval);
1✔
420
}
1✔
421

422
static Result<std::string, lnav::console::user_message>
423
sql_cmd_msgformats(exec_context& ec,
1✔
424
                   std::string cmdline,
425
                   std::vector<std::string>& args)
426
{
427
    static const std::string MSG_FORMAT_STMT = R"(
3✔
428
SELECT count(*) AS total,
429
       min(log_line) AS log_line,
430
       min(log_time) AS log_time,
431
       humanize_duration(timediff(max(log_time), min(log_time))) AS duration,
432
       group_concat(DISTINCT log_format) AS log_formats,
433
       log_msg_format
434
    FROM all_logs
435
    WHERE log_msg_format != ''
436
    GROUP BY log_msg_format
437
    HAVING total > 1
438
    ORDER BY total DESC, log_line ASC
439
)";
440

441
    std::string retval;
1✔
442

443
    if (args.empty()) {
1✔
444
        return Ok(retval);
×
445
    }
446

447
    std::string alt;
1✔
448

449
    return execute_sql(ec, MSG_FORMAT_STMT, alt);
1✔
450
}
1✔
451

452
static Result<std::string, lnav::console::user_message>
453
sql_cmd_generic(exec_context& ec,
×
454
                std::string cmdline,
455
                std::vector<std::string>& args)
456
{
457
    std::string retval;
×
458

459
    if (args.empty()) {
×
460
        args.emplace_back("*");
×
461
        return Ok(retval);
×
462
    }
463

464
    return Ok(retval);
×
465
}
466

467
static Result<std::string, lnav::console::user_message>
468
prql_cmd_from(exec_context& ec,
×
469
              std::string cmdline,
470
              std::vector<std::string>& args)
471
{
472
    std::string retval;
×
473

474
    if (args.empty()) {
×
475
        args.emplace_back("prql-table");
×
476
        return Ok(retval);
×
477
    }
478

479
    return Ok(retval);
×
480
}
481

482
static readline_context::prompt_result_t
483
prql_cmd_from_prompt(exec_context& ec, const std::string& cmdline)
×
484
{
485
    if (!endswith(cmdline, "from ")) {
×
486
        return {};
×
487
    }
488

489
    auto* tc = *lnav_data.ld_view_stack.top();
×
490
    auto* lss = dynamic_cast<logfile_sub_source*>(tc->get_sub_source());
×
491
    auto sel = tc->get_selection();
×
492

493
    if (!sel || lss == nullptr || lss->text_line_count() == 0) {
×
494
        return {};
×
495
    }
496

497
    auto line_pair = lss->find_line_with_file(lss->at(sel.value()));
×
498
    if (!line_pair) {
×
499
        return {};
×
500
    }
501

502
    auto format_name
503
        = line_pair->first->get_format_ptr()->get_name().to_string();
×
504
    return {
505
        "",
506
        lnav::prql::quote_ident(format_name) + " ",
×
507
    };
508
}
509

510
static Result<std::string, lnav::console::user_message>
511
prql_cmd_aggregate(exec_context& ec,
×
512
                   std::string cmdline,
513
                   std::vector<std::string>& args)
514
{
515
    std::string retval;
×
516

517
    if (args.empty()) {
×
518
        args.emplace_back("prql-expr");
×
519
        return Ok(retval);
×
520
    }
521

522
    return Ok(retval);
×
523
}
524

525
static Result<std::string, lnav::console::user_message>
526
prql_cmd_append(exec_context& ec,
×
527
                std::string cmdline,
528
                std::vector<std::string>& args)
529
{
530
    std::string retval;
×
531

532
    if (args.empty()) {
×
533
        args.emplace_back("prql-table");
×
534
        return Ok(retval);
×
535
    }
536

537
    return Ok(retval);
×
538
}
539

540
static Result<std::string, lnav::console::user_message>
541
prql_cmd_derive(exec_context& ec,
×
542
                std::string cmdline,
543
                std::vector<std::string>& args)
544
{
545
    std::string retval;
×
546

547
    if (args.empty()) {
×
548
        args.emplace_back("prql-expr");
×
549
        return Ok(retval);
×
550
    }
551

552
    return Ok(retval);
×
553
}
554

555
static Result<std::string, lnav::console::user_message>
556
prql_cmd_filter(exec_context& ec,
×
557
                std::string cmdline,
558
                std::vector<std::string>& args)
559
{
560
    std::string retval;
×
561

562
    if (args.empty()) {
×
563
        args.emplace_back("prql-expr");
×
564
        return Ok(retval);
×
565
    }
566

567
    return Ok(retval);
×
568
}
569

570
static Result<std::string, lnav::console::user_message>
571
prql_cmd_group(exec_context& ec,
×
572
               std::string cmdline,
573
               std::vector<std::string>& args)
574
{
575
    std::string retval;
×
576

577
    if (args.empty()) {
×
578
        args.emplace_back("prql-expr");
×
579
        args.emplace_back("prql-source");
×
580
        return Ok(retval);
×
581
    }
582

583
    return Ok(retval);
×
584
}
585

586
static Result<std::string, lnav::console::user_message>
587
prql_cmd_join(exec_context& ec,
×
588
              std::string cmdline,
589
              std::vector<std::string>& args)
590
{
591
    std::string retval;
×
592

593
    if (args.empty()) {
×
594
        args.emplace_back("prql-table");
×
595
        args.emplace_back("prql-expr");
×
596
        return Ok(retval);
×
597
    }
598

599
    return Ok(retval);
×
600
}
601

602
static Result<std::string, lnav::console::user_message>
603
prql_cmd_select(exec_context& ec,
×
604
                std::string cmdline,
605
                std::vector<std::string>& args)
606
{
607
    std::string retval;
×
608

609
    if (args.empty()) {
×
610
        args.emplace_back("prql-expr");
×
611
        return Ok(retval);
×
612
    }
613

614
    return Ok(retval);
×
615
}
616

617
static Result<std::string, lnav::console::user_message>
618
prql_cmd_sort(exec_context& ec,
×
619
              std::string cmdline,
620
              std::vector<std::string>& args)
621
{
622
    std::string retval;
×
623

624
    if (args.empty()) {
×
625
        args.emplace_back("prql-expr");
×
626
        return Ok(retval);
×
627
    }
628

629
    return Ok(retval);
×
630
}
631

632
static Result<std::string, lnav::console::user_message>
633
prql_cmd_take(exec_context& ec,
×
634
              std::string cmdline,
635
              std::vector<std::string>& args)
636
{
637
    std::string retval;
×
638

639
    if (args.empty()) {
×
640
        return Ok(retval);
×
641
    }
642

643
    return Ok(retval);
×
644
}
645

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

1053
static readline_context::command_map_t sql_cmd_map;
1054

1055
static auto bound_sql_cmd_map
1056
    = injector::bind<readline_context::command_map_t,
1057
                     sql_cmd_map_tag>::to_instance(+[]() {
1,157✔
1058
          for (auto& cmd : sql_commands) {
35,867✔
1059
              sql_cmd_map[cmd.c_name] = &cmd;
34,710✔
1060
              if (cmd.c_help.ht_name) {
34,710✔
1061
                  cmd.c_help.index_tags();
34,710✔
1062
              }
1063
          }
1064

1065
          return &sql_cmd_map;
1,157✔
1066
      });
1067

1068
namespace injector {
1069
template<>
1070
void
1071
force_linking(sql_cmd_map_tag anno)
19✔
1072
{
1073
}
19✔
1074
}  // 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