• 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

84.66
/src/log_vtab_impl.cc
1
/**
2
 * Copyright (c) 2007-2012, 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 <cstdint>
31
#include <memory>
32
#include <string>
33
#include <unordered_set>
34
#include <vector>
35

36
#include "log_vtab_impl.hh"
37

38
#include "base/ansi_scrubber.hh"
39
#include "base/intern_string.hh"
40
#include "base/itertools.hh"
41
#include "base/lnav_log.hh"
42
#include "base/string_util.hh"
43
#include "bookmarks.json.hh"
44
#include "config.h"
45
#include "lnav_util.hh"
46
#include "logfile_sub_source.hh"
47
#include "logline_window.hh"
48
#include "sql_util.hh"
49
#include "vtab_module.hh"
50
#include "vtab_module_json.hh"
51
#include "yajlpp/json_op.hh"
52
#include "yajlpp/yajlpp_def.hh"
53

54
// #define DEBUG_INDEXING 1
55

56
using namespace lnav::roles::literals;
57

58
static auto intern_lifetime = intern_string::get_table_lifetime();
59

60
static log_cursor log_cursor_latest;
61

62
thread_local _log_vtab_data log_vtab_data;
63

64
const std::unordered_set<string_fragment, frag_hasher>
65
    log_vtab_impl::RESERVED_COLUMNS = {
66
        "log_line"_frag,
67
        "log_time"_frag,
68
        "log_level"_frag,
69
        "log_part"_frag,
70
        "log_actual_time"_frag,
71
        "log_idle_msecs"_frag,
72
        "log_mark"_frag,
73
        "log_sticky_mark"_frag,
74
        "log_comment"_frag,
75
        "log_tags"_frag,
76
        "log_annotations"_frag,
77
        "log_filters"_frag,
78
        "log_opid"_frag,
79
        "log_user_opid"_frag,
80
        "log_opid_definition"_frag,
81
        "log_format"_frag,
82
        "log_format_regex"_frag,
83
        "log_time_msecs"_frag,
84
        "log_path"_frag,
85
        "log_unique_path"_frag,
86
        "log_text"_frag,
87
        "log_body"_frag,
88
        "log_raw_text"_frag,
89
        "log_line_hash"_frag,
90
        "log_line_link"_frag,
91
        "log_src_file"_frag,
92
        "log_src_line"_frag,
93
        "log_thread_id"_frag,
94
        "log_duration"_frag,
95
};
96

97
static const char* const LOG_COLUMNS = R"(  (
98
  log_line        INTEGER,                         -- The line number for the log message
99
  log_time        DATETIME,                        -- The adjusted timestamp for the log message
100
  log_level       TEXT     COLLATE loglevel,       -- The log message level
101
  -- BEGIN Format-specific fields:
102
)";
103

104
static const char* const LOG_FOOTER_COLUMNS = R"(
105
  -- END Format-specific fields
106
  log_part         TEXT     COLLATE naturalnocase,    -- The partition the message is in
107
  log_actual_time  DATETIME HIDDEN,                   -- The timestamp from the original log file for this message
108
  log_idle_msecs   INTEGER,                           -- The difference in time between this messages and the previous
109
  log_mark         BOOLEAN,                           -- True if the log message was marked
110
  log_sticky_mark  BOOLEAN HIDDEN,                    -- True if the log message is a sticky header
111
  log_comment      TEXT,                              -- The comment for this message
112
  log_tags         TEXT,                              -- A JSON list of tags for this message
113
  log_annotations  TEXT,                              -- A JSON object of annotations for this messages
114
  log_filters      TEXT,                              -- A JSON list of filter IDs that matched this message
115
  log_opid         TEXT HIDDEN,                       -- The message's OPID from the log message or user
116
  log_user_opid    TEXT HIDDEN,                       -- The message's OPID as set by the user
117
  log_opid_definition TEXT HIDDEN,                    -- The matching OPID description definition
118
  log_format       TEXT HIDDEN,                       -- The name of the log file format
119
  log_format_regex TEXT HIDDEN,                       -- The name of the regex used to parse this log message
120
  log_time_msecs   INTEGER HIDDEN,                    -- The adjusted timestamp for the log message as the number of milliseconds from the epoch
121
  log_path         TEXT HIDDEN COLLATE naturalnocase, -- The path to the log file this message is from
122
  log_unique_path  TEXT HIDDEN COLLATE naturalnocase, -- The unique portion of the path this message is from
123
  log_text         TEXT HIDDEN,                       -- The full text of the log message
124
  log_body         TEXT HIDDEN,                       -- The body of the log message
125
  log_raw_text     TEXT HIDDEN,                       -- The raw text from the log file
126
  log_line_hash    TEXT HIDDEN,                       -- A hash of the first line of the log message
127
  log_line_link    TEXT HIDDEN,                       -- The permalink for the log message
128
  log_src_file     TEXT HIDDEN,                       -- The source file the log message came from
129
  log_src_line     TEXT HIDDEN,                       -- The source line the log message came from
130
  log_thread_id    TEXT HIDDEN,                       -- The ID of the thread that generated this message
131
  log_duration     REAL HIDDEN                        -- The duration associated with this log message
132
)";
133

134
enum class log_footer_columns : uint32_t {
135
    partition,
136
    actual_time,
137
    idle_msecs,
138
    mark,
139
    sticky_mark,
140
    comment,
141
    tags,
142
    annotations,
143
    filters,
144
    opid,
145
    user_opid,
146
    opid_definition,
147
    format,
148
    format_regex,
149
    time_msecs,
150
    path,
151
    unique_path,
152
    text,
153
    body,
154
    raw_text,
155
    line_hash,
156
    line_link,
157
    src_file,
158
    src_line,
159
    thread_id,
160
    duration,
161
};
162

163
const std::string&
164
log_vtab_impl::get_table_statement()
73,253✔
165
{
166
    if (this->vi_table_statement.empty()) {
73,253✔
167
        std::vector<vtab_column> cols;
73,048✔
168
        std::ostringstream oss;
73,048✔
169
        size_t max_name_len = 15;
73,048✔
170

171
        oss << "CREATE TABLE lnav_db." << this->get_name().to_string()
146,096✔
172
            << LOG_COLUMNS;
146,096✔
173
        this->get_columns(cols);
73,048✔
174
        this->vi_column_count = cols.size();
73,048✔
175
        for (const auto& col : cols) {
753,996✔
176
            max_name_len = std::max(max_name_len, col.vc_name.length());
680,948✔
177
        }
178
        for (const auto& col : cols) {
753,996✔
179
            std::string comment;
680,948✔
180

181
            require(!col.vc_name.empty());
680,948✔
182

183
            if (!col.vc_comment.empty()) {
680,948✔
184
                comment.append(" -- ").append(col.vc_comment);
89,361✔
185
            }
186

187
            auto colname = sql_quote_ident(col.vc_name.c_str());
680,948✔
188
            auto coldecl = lnav::sql::mprintf(
189
                "  %-*s %-7s %s COLLATE %-15Q,%s\n",
190
                max_name_len,
191
                colname.in(),
192
                sqlite3_type_to_string(col.vc_type),
680,948✔
193
                col.vc_hidden ? "hidden" : "",
680,948✔
194
                col.vc_collator.empty() ? "BINARY" : col.vc_collator.c_str(),
699,198✔
195
                comment.c_str());
1,380,146✔
196
            oss << coldecl;
680,948✔
197
        }
680,948✔
198
        oss << LOG_FOOTER_COLUMNS;
73,048✔
199

200
        {
201
            std::vector<std::string> primary_keys;
73,048✔
202

203
            this->get_primary_keys(primary_keys);
73,048✔
204
            if (!primary_keys.empty()) {
73,048✔
205
                auto first = true;
11,152✔
206

207
                oss << ", PRIMARY KEY (";
11,152✔
208
                for (const auto& pkey : primary_keys) {
33,456✔
209
                    if (!first) {
22,304✔
210
                        oss << ", ";
11,152✔
211
                    }
212
                    oss << pkey;
22,304✔
213
                    first = false;
22,304✔
214
                }
215
                oss << ")\n";
11,152✔
216
            } else {
217
                oss << ", PRIMARY KEY (log_line)\n";
61,896✔
218
            }
219
        }
73,048✔
220

221
        oss << ");\n";
73,048✔
222

223
        log_trace("log_vtab_impl.get_table_statement() -> %s",
73,048✔
224
                  oss.str().c_str());
225

226
        this->vi_table_statement = oss.str();
73,048✔
227
    }
73,048✔
228

229
    return this->vi_table_statement;
73,253✔
230
}
231

232
std::pair<int, unsigned int>
233
log_vtab_impl::logline_value_to_sqlite_type(value_kind_t kind)
645,666✔
234
{
235
    int type = 0;
645,666✔
236
    unsigned int subtype = 0;
645,666✔
237

238
    switch (kind) {
645,666✔
239
        case value_kind_t::VALUE_JSON:
18,568✔
240
            type = SQLITE3_TEXT;
18,568✔
241
            subtype = JSON_SUBTYPE;
18,568✔
242
            break;
18,568✔
243
        case value_kind_t::VALUE_NULL:
491,619✔
244
        case value_kind_t::VALUE_TEXT:
245
        case value_kind_t::VALUE_STRUCT:
246
        case value_kind_t::VALUE_QUOTED:
247
        case value_kind_t::VALUE_W3C_QUOTED:
248
        case value_kind_t::VALUE_TIMESTAMP:
249
        case value_kind_t::VALUE_XML:
250
        case value_kind_t::VALUE_ANY:
251
            type = SQLITE3_TEXT;
491,619✔
252
            break;
491,619✔
253
        case value_kind_t::VALUE_FLOAT:
9,772✔
254
            type = SQLITE_FLOAT;
9,772✔
255
            break;
9,772✔
256
        case value_kind_t::VALUE_BOOLEAN:
125,707✔
257
        case value_kind_t::VALUE_INTEGER:
258
            type = SQLITE_INTEGER;
125,707✔
259
            break;
125,707✔
260
        case value_kind_t::VALUE_UNKNOWN:
×
261
        case value_kind_t::VALUE__MAX:
262
            ensure(0);
×
263
            break;
264
    }
265
    return std::make_pair(type, subtype);
1,291,332✔
266
}
267

268
void
269
log_vtab_impl::get_foreign_keys(
55,528✔
270
    std::unordered_set<std::string>& keys_inout) const
271
{
272
    keys_inout.emplace("id");
55,528✔
273
    keys_inout.emplace("parent");
55,528✔
274
    keys_inout.emplace("notused");
55,528✔
275

276
    keys_inout.emplace("log_line");
55,528✔
277
    keys_inout.emplace("min(log_line)");
55,528✔
278
    keys_inout.emplace("log_mark");
55,528✔
279
    keys_inout.emplace("log_time_msecs");
55,528✔
280
    keys_inout.emplace("log_top_line()");
55,528✔
281
    keys_inout.emplace("log_msg_line()");
55,528✔
282
    keys_inout.emplace("log_src_line");
55,528✔
283
    keys_inout.emplace("log_thread_id");
55,528✔
284
    keys_inout.emplace("log_duration");
55,528✔
285
}
55,528✔
286

287
void
288
log_vtab_impl::extract(logfile* lf,
29,506✔
289
                       uint64_t line_number,
290
                       string_attrs_t& sa,
291
                       logline_value_vector& values)
292
{
293
    const auto* format = lf->get_format_ptr();
29,506✔
294

295
    format->annotate(lf, line_number, sa, values);
29,506✔
296
}
29,506✔
297

298
bool
299
log_vtab_impl::is_valid(log_cursor& lc, logfile_sub_source& lss)
55,224✔
300
{
301
    if (lc.lc_curr_line < 0_vl) {
55,224✔
302
        return false;
×
303
    }
304

305
    content_line_t cl(lss.at(lc.lc_curr_line));
55,224✔
306
    auto* lf = lss.find_file_ptr(cl);
55,224✔
307
    auto lf_iter = lf->begin() + cl;
55,224✔
308

309
    if (!lf_iter->is_message()) {
55,224✔
310
        return false;
385✔
311
    }
312

313
    if (!lc.lc_format_name.empty()
54,839✔
314
        && lc.lc_format_name != lf->get_format_name())
54,839✔
315
    {
316
        return false;
3✔
317
    }
318

319
    if (!lc.lc_pattern_name.empty()
54,836✔
320
        && lc.lc_pattern_name
54,836✔
321
            != lf->get_format_ptr()->get_pattern_name(
54,836✔
322
                lf->get_format_file_state().lffs_pattern_locks, cl))
×
323
    {
324
        return false;
×
325
    }
326

327
    if (lc.lc_level_constraint
54,836✔
328
        && !lc.lc_level_constraint->matches(lf_iter->get_msg_level()))
54,836✔
329
    {
330
        return false;
206✔
331
    }
332

333
    if (!lc.lc_log_path.empty()) {
54,630✔
334
        if (lf == lc.lc_last_log_path_match) {
4✔
335
        } else if (lf == lc.lc_last_log_path_mismatch) {
2✔
336
            return false;
×
337
        } else {
338
            for (const auto& path_cons : lc.lc_log_path) {
4✔
339
                if (!path_cons.matches(lf->get_filename())) {
2✔
340
                    lc.lc_last_log_path_mismatch = lf;
×
341
                    return false;
×
342
                }
343
            }
344
            lc.lc_last_log_path_match = lf;
2✔
345
        }
346
    }
347

348
    if (!lc.lc_unique_path.empty()) {
54,630✔
349
        if (lf == lc.lc_last_unique_path_match) {
×
350
        } else if (lf == lc.lc_last_unique_path_mismatch) {
×
351
            return false;
×
352
        } else {
353
            for (const auto& path_cons : lc.lc_unique_path) {
×
354
                if (!path_cons.matches(lf->get_unique_path())) {
×
355
                    lc.lc_last_unique_path_mismatch = lf;
×
356
                    return false;
×
357
                }
358
            }
359
            lc.lc_last_unique_path_match = lf;
×
360
        }
361
    }
362

363
    if (lc.lc_opid_bloom_bits
54,630✔
364
        && !lf_iter->match_bloom_bits(lc.lc_opid_bloom_bits.value()))
54,630✔
365
    {
366
        return false;
×
367
    }
368

369
    if (lc.lc_tid_bloom_bits
54,630✔
370
        && !lf_iter->match_bloom_bits(lc.lc_tid_bloom_bits.value()))
54,630✔
371
    {
372
        return false;
×
373
    }
374

375
    return true;
54,630✔
376
}
377

378
struct log_vtab {
379
    sqlite3_vtab base;
380
    sqlite3* db;
381
    textview_curses* tc{nullptr};
382
    logfile_sub_source* lss{nullptr};
383
    std::shared_ptr<log_vtab_impl> vi;
384

385
    size_t footer_index(log_footer_columns col) const
791✔
386
    {
387
        return VT_COL_MAX + this->vi->vi_column_count
791✔
388
            + lnav::enums::to_underlying(col);
791✔
389
    }
390
};
391

392
struct vtab_cursor {
393
    void cache_msg(logfile* lf, logfile::const_iterator ll)
33,301✔
394
    {
395
        if (this->log_msg_line == this->log_cursor.lc_curr_line) {
33,301✔
396
            return;
8✔
397
        }
398
        auto& sbr = this->line_values.lvv_sbr;
33,293✔
399
        lf->read_full_message(ll,
33,293✔
400
                              sbr,
401
                              this->log_cursor.lc_direction < 0
33,293✔
402
                                  ? line_buffer::scan_direction::backward
403
                                  : line_buffer::scan_direction::forward);
404
        sbr.erase_ansi();
33,293✔
405
        this->log_msg_line = this->log_cursor.lc_curr_line;
33,293✔
406
    }
407

408
    void invalidate()
34,348✔
409
    {
410
        this->attrs.clear();
34,348✔
411
        this->line_values.clear();
34,348✔
412
        this->log_msg_line = -1_vl;
34,348✔
413
    }
34,348✔
414

415
    sqlite3_vtab_cursor base;
416
    struct log_cursor log_cursor;
417
    vis_line_t log_msg_line{-1_vl};
418
    string_attrs_t attrs;
419
    logline_value_vector line_values;
420
};
421

422
static int vt_destructor(sqlite3_vtab* p_svt);
423

424
static int
425
vt_create(sqlite3* db,
73,072✔
426
          void* pAux,
427
          int argc,
428
          const char* const* argv,
429
          sqlite3_vtab** pp_vt,
430
          char** pzErr)
431
{
432
    auto* vm = (log_vtab_manager*) pAux;
73,072✔
433
    int rc = SQLITE_OK;
73,072✔
434
    /* Allocate the sqlite3_vtab/vtab structure itself */
435
    auto p_vt = std::make_unique<log_vtab>();
73,072✔
436

437
    p_vt->db = db;
73,072✔
438

439
    /* Declare the vtable's structure */
440
    p_vt->vi = vm->lookup_impl(intern_string::lookup(argv[3]));
146,144✔
441
    if (p_vt->vi == nullptr) {
73,072✔
442
        return SQLITE_ERROR;
×
443
    }
444
    p_vt->lss = vm->get_source();
73,072✔
445
    p_vt->tc = p_vt->lss->get_view();
73,072✔
446
    rc = sqlite3_declare_vtab(db, p_vt->vi->get_table_statement().c_str());
73,072✔
447

448
    if (rc == SQLITE_OK) {
73,072✔
449
        /* Success. Set *pp_vt and return */
450
        auto loose_p_vt = p_vt.release();
73,071✔
451
        *pp_vt = &loose_p_vt->base;
73,071✔
452
        log_debug("creating log format table: %s = %p", argv[3], loose_p_vt);
73,071✔
453
    } else {
454
        log_error("sqlite3_declare_vtab(%s) failed: %s",
1✔
455
                  p_vt->vi->get_name().c_str(),
456
                  sqlite3_errmsg(db));
457
    }
458

459
    return rc;
73,072✔
460
}
73,072✔
461

462
static int
463
vt_destructor(sqlite3_vtab* p_svt)
73,071✔
464
{
465
    log_vtab* p_vt = (log_vtab*) p_svt;
73,071✔
466

467
    log_debug("deleting log format table: %p", p_vt);
73,071✔
468

469
    delete p_vt;
73,071✔
470

471
    return SQLITE_OK;
73,071✔
472
}
473

474
static int
475
vt_connect(sqlite3* db,
24✔
476
           void* p_aux,
477
           int argc,
478
           const char* const* argv,
479
           sqlite3_vtab** pp_vt,
480
           char** pzErr)
481
{
482
    return vt_create(db, p_aux, argc, argv, pp_vt, pzErr);
24✔
483
}
484

485
static int
486
vt_disconnect(sqlite3_vtab* pVtab)
24✔
487
{
488
    return vt_destructor(pVtab);
24✔
489
}
490

491
static int
492
vt_destroy(sqlite3_vtab* p_vt)
73,047✔
493
{
494
    return vt_destructor(p_vt);
73,047✔
495
}
496

497
static int vt_next(sqlite3_vtab_cursor* cur);
498

499
static int
500
vt_open(sqlite3_vtab* p_svt, sqlite3_vtab_cursor** pp_cursor)
234✔
501
{
502
    log_vtab* p_vt = (log_vtab*) p_svt;
234✔
503

504
    p_vt->base.zErrMsg = nullptr;
234✔
505

506
    vtab_cursor* p_cur = new vtab_cursor();
234✔
507

508
    *pp_cursor = (sqlite3_vtab_cursor*) p_cur;
234✔
509

510
    p_cur->base.pVtab = p_svt;
234✔
511
    p_cur->log_cursor.lc_opid_bloom_bits = std::nullopt;
234✔
512
    p_cur->log_cursor.lc_tid_bloom_bits = std::nullopt;
234✔
513
    p_cur->log_cursor.lc_curr_line = 0_vl;
234✔
514
    p_cur->log_cursor.lc_direction = 1_vl;
234✔
515
    p_cur->log_cursor.lc_end_line = vis_line_t(p_vt->lss->text_line_count());
234✔
516
    p_cur->log_cursor.lc_sub_index = 0;
234✔
517

518
    for (auto& ld : *p_vt->lss) {
492✔
519
        auto* lf = ld->get_file_ptr();
258✔
520

521
        if (lf == nullptr) {
258✔
522
            continue;
×
523
        }
524

525
        lf->enable_cache();
258✔
526
    }
527

528
    return SQLITE_OK;
234✔
529
}
530

531
static int
532
vt_close(sqlite3_vtab_cursor* cur)
234✔
533
{
534
    auto* p_cur = (vtab_cursor*) cur;
234✔
535

536
    /* Free cursor struct. */
537
    delete p_cur;
234✔
538

539
    return SQLITE_OK;
234✔
540
}
541

542
static int
543
vt_eof(sqlite3_vtab_cursor* cur)
34,348✔
544
{
545
    auto* vc = (vtab_cursor*) cur;
34,348✔
546

547
    return vc->log_cursor.is_eof();
34,348✔
548
}
549

550
static void
551
populate_indexed_columns(vtab_cursor* vc, log_vtab* vt)
33,909✔
552
{
553
    if (vc->log_cursor.is_eof() || vc->log_cursor.lc_indexed_columns.empty()) {
33,909✔
554
        return;
3,092✔
555
    }
556

557
    logfile* lf = nullptr;
30,817✔
558

559
    for (const auto& ic : vc->log_cursor.lc_indexed_columns) {
3,254,468✔
560
        auto& ci = vt->vi->vi_column_indexes[ic.cc_column];
3,223,651✔
561
        const auto vl = vc->log_cursor.lc_curr_line;
3,223,651✔
562

563
        if (ci.ci_indexed_range.contains(vl)) {
3,223,651✔
564
            // the index already contains this column, nothing to do
565
            continue;
3,222,558✔
566
        }
567

568
        if (lf == nullptr) {
2,127✔
569
            const auto cl = vt->lss->at(vl);
2,127✔
570
            uint64_t line_number;
571
            auto ld = vt->lss->find_data(cl, line_number);
2,127✔
572
            lf = (*ld)->get_file_ptr();
2,127✔
573
            auto ll = lf->begin() + line_number;
2,127✔
574

575
            vc->cache_msg(lf, ll);
2,127✔
576
            require(vc->line_values.lvv_sbr.get_data() != nullptr);
2,127✔
577
            vt->vi->extract(lf, line_number, vc->attrs, vc->line_values);
2,127✔
578
        }
579

580
        auto sub_col = logline_value_meta::table_column{
581
            (size_t) (ic.cc_column - VT_COL_MAX)};
2,127✔
582
        auto lv_iter = find_if(vc->line_values.lvv_values.begin(),
2,127✔
583
                               vc->line_values.lvv_values.end(),
584
                               logline_value_col_eq(sub_col));
585
        if (lv_iter == vc->line_values.lvv_values.end()
2,127✔
586
            || lv_iter->lv_meta.lvm_kind == value_kind_t::VALUE_NULL)
2,127✔
587
        {
588
            continue;
1,034✔
589
        }
590

591
        auto value = lv_iter->to_string_fragment(ci.ci_string_arena);
1,093✔
592

593
#ifdef DEBUG_INDEXING
594
        log_debug("updated index for column %d %.*s -> %d",
595
                  ic.cc_column,
596
                  value.length(),
597
                  value.data(),
598
                  (int) vc->log_cursor.lc_curr_line);
599
#endif
600

601
        auto& line_deq = ci.ci_value_to_lines[value];
1,093✔
602
        if (line_deq.empty()
1,093✔
603
            || (line_deq.front() != vl && line_deq.back() != vl))
1,093✔
604
        {
605
            if (vc->log_cursor.lc_direction < 0) {
1,093✔
606
                line_deq.push_front(vl);
774✔
607
            } else {
608
                line_deq.push_back(vl);
319✔
609
            }
610
        }
611
    }
612
}
613

614
static int
615
vt_next(sqlite3_vtab_cursor* cur)
34,179✔
616
{
617
    auto* vc = (vtab_cursor*) cur;
34,179✔
618
    auto* vt = (log_vtab*) cur->pVtab;
34,179✔
619
    auto done = false;
34,179✔
620

621
#ifdef DEBUG_INDEXING
622
    log_debug("vt_next([%d:%d:%d])",
623
              vc->log_cursor.lc_curr_line,
624
              vc->log_cursor.lc_end_line,
625
              vc->log_cursor.lc_direction);
626
#endif
627

628
    vc->invalidate();
34,179✔
629
    if (!vc->log_cursor.lc_indexed_lines.empty()
34,179✔
630
        && vc->log_cursor.lc_indexed_lines_range.contains(
34,179✔
631
            vc->log_cursor.lc_curr_line))
632
    {
633
        vc->log_cursor.lc_curr_line = vc->log_cursor.lc_indexed_lines.back();
19,534✔
634
        vc->log_cursor.lc_indexed_lines.pop_back();
19,534✔
635
    } else {
636
        vc->log_cursor.lc_curr_line += vc->log_cursor.lc_direction;
14,645✔
637
    }
638
    vc->log_cursor.lc_sub_index = 0;
34,179✔
639
    do {
640
        log_cursor_latest = vc->log_cursor;
54,608✔
641
        if (((log_cursor_latest.lc_curr_line % 1024) == 0)
54,608✔
642
            && (log_vtab_data.lvd_progress != nullptr
54,950✔
643
                && log_vtab_data.lvd_progress(log_cursor_latest)))
342✔
644
        {
645
            break;
×
646
        }
647

648
        while (vc->log_cursor.lc_curr_line != -1_vl && !vc->log_cursor.is_eof()
110,142✔
649
               && !vt->vi->is_valid(vc->log_cursor, *vt->lss))
110,142✔
650
        {
651
            vc->log_cursor.lc_curr_line += vc->log_cursor.lc_direction;
464✔
652
            vc->log_cursor.lc_sub_index = 0;
464✔
653
        }
654
        if (vc->log_cursor.is_eof()) {
54,608✔
655
            log_info("vt_next at EOF (%d:%d:%d), scanned rows %lu",
439✔
656
                     (int) vc->log_cursor.lc_curr_line,
657
                     (int) vc->log_cursor.lc_end_line,
658
                     (int) vc->log_cursor.lc_direction,
659
                     vc->log_cursor.lc_scanned_rows);
660
            done = true;
439✔
661
        } else {
662
            done = vt->vi->next(vc->log_cursor, *vt->lss);
54,169✔
663
            if (done) {
54,169✔
664
                if (vc->log_cursor.lc_curr_line % 10000 == 0) {
33,740✔
665
                    log_debug("scanned %d", (int) vc->log_cursor.lc_curr_line);
331✔
666
                }
667
#ifdef DEBUG_INDEXING
668
                log_debug("scanned %d", vc->log_cursor.lc_curr_line);
669
#endif
670
                vc->log_cursor.lc_scanned_rows += 1;
33,740✔
671
                populate_indexed_columns(vc, vt);
33,740✔
672
                vt->vi->expand_indexes_to(vc->log_cursor.lc_indexed_columns,
33,740✔
673
                                          vc->log_cursor.lc_curr_line);
674
            } else {
675
                if (!vc->log_cursor.lc_indexed_lines.empty()
20,429✔
676
                    && vc->log_cursor.lc_indexed_lines_range.contains(
20,429✔
677
                        vc->log_cursor.lc_curr_line))
678
                {
679
                    vc->log_cursor.lc_curr_line
680
                        = vc->log_cursor.lc_indexed_lines.back();
×
681
                    vc->log_cursor.lc_indexed_lines.pop_back();
×
682
                } else {
683
                    vc->log_cursor.lc_curr_line += vc->log_cursor.lc_direction;
20,429✔
684
                }
685
                vc->log_cursor.lc_sub_index = 0;
20,429✔
686
            }
687
        }
688
    } while (!done);
54,608✔
689

690
#ifdef DEBUG_INDEXING
691
    log_debug("vt_next() -> [%d:%d:%d]",
692
              vc->log_cursor.lc_curr_line,
693
              vc->log_cursor.lc_end_line,
694
              vc->log_cursor.lc_direction);
695
#endif
696

697
    return SQLITE_OK;
34,179✔
698
}
699

700
static int
701
vt_next_no_rowid(sqlite3_vtab_cursor* cur)
169✔
702
{
703
    auto* vc = (vtab_cursor*) cur;
169✔
704
    auto* vt = (log_vtab*) cur->pVtab;
169✔
705
    auto done = false;
169✔
706

707
    vc->invalidate();
169✔
708
    do {
709
        log_cursor_latest = vc->log_cursor;
760✔
710
        if (((log_cursor_latest.lc_curr_line % 1024) == 0)
760✔
711
            && (log_vtab_data.lvd_progress != nullptr
792✔
712
                && log_vtab_data.lvd_progress(log_cursor_latest)))
32✔
713
        {
714
            break;
×
715
        }
716

717
        auto vl_before = vc->log_cursor.lc_curr_line;
760✔
718
        done = vt->vi->next(vc->log_cursor, *vt->lss);
760✔
719
        if (vl_before != vc->log_cursor.lc_curr_line) {
760✔
720
            vt->vi->expand_indexes_to(vc->log_cursor.lc_indexed_columns,
×
721
                                      vl_before);
722
        }
723
        if (done) {
760✔
724
            populate_indexed_columns(vc, vt);
169✔
725
        } else if (vc->log_cursor.is_eof()) {
591✔
726
            done = true;
×
727
        } else {
728
            require(vc->log_cursor.lc_curr_line
591✔
729
                    < (ssize_t) vt->lss->text_line_count());
730

731
            if (!vc->log_cursor.lc_indexed_lines.empty()
591✔
732
                && vc->log_cursor.lc_indexed_lines_range.contains(
591✔
733
                    vc->log_cursor.lc_curr_line))
734
            {
735
                vt->vi->expand_indexes_to(vc->log_cursor.lc_indexed_columns,
3✔
736
                                          vc->log_cursor.lc_curr_line);
737
                vc->log_cursor.lc_curr_line
738
                    = vc->log_cursor.lc_indexed_lines.back();
3✔
739
                vc->log_cursor.lc_indexed_lines.pop_back();
3✔
740
#ifdef DEBUG_INDEXING
741
                log_debug("going to next line from index %d",
742
                          (int) vc->log_cursor.lc_curr_line);
743
#endif
744
            } else {
745
                vt->vi->expand_indexes_to(vc->log_cursor.lc_indexed_columns,
588✔
746
                                          vc->log_cursor.lc_curr_line);
747
                vc->log_cursor.lc_curr_line += vc->log_cursor.lc_direction;
588✔
748
            }
749
            vc->log_cursor.lc_sub_index = 0;
591✔
750
        }
751
    } while (!done);
760✔
752

753
#ifdef DEBUG_INDEXING
754
    log_debug("vt_next_no_rowid() -> %d:%d",
755
              vc->log_cursor.lc_curr_line,
756
              vc->log_cursor.lc_end_line);
757
#endif
758

759
    return SQLITE_OK;
169✔
760
}
761

762
static int
763
vt_column(sqlite3_vtab_cursor* cur, sqlite3_context* ctx, int col)
86,957✔
764
{
765
    auto* vc = (vtab_cursor*) cur;
86,957✔
766
    auto* vt = (log_vtab*) cur->pVtab;
86,957✔
767

768
#ifdef DEBUG_INDEXING
769
    log_debug("vt_column(%s, %d:%d)",
770
              vt->vi->get_name().get(),
771
              (int) vc->log_cursor.lc_curr_line,
772
              col);
773
#endif
774

775
    content_line_t cl(vt->lss->at(vc->log_cursor.lc_curr_line));
86,957✔
776
    uint64_t line_number;
777
    auto ld = vt->lss->find_data(cl, line_number);
86,957✔
778
    auto* lf = (*ld)->get_file_ptr();
86,957✔
779
    auto ll = lf->begin() + line_number;
86,957✔
780

781
    require(col >= 0);
86,957✔
782

783
    /* Just return the ordinal of the column requested. */
784
    switch (col) {
86,957✔
785
        case VT_COL_LINE_NUMBER: {
2,862✔
786
            sqlite3_result_int64(ctx, vc->log_cursor.lc_curr_line);
2,862✔
787
            break;
2,862✔
788
        }
789

790
        case VT_COL_LOG_TIME: {
466✔
791
            char buffer[64];
792

793
            sql_strftime(buffer, sizeof(buffer), ll->get_timeval());
466✔
794
            sqlite3_result_text(ctx, buffer, strlen(buffer), SQLITE_TRANSIENT);
466✔
795
            break;
466✔
796
        }
797

798
        case VT_COL_LEVEL: {
1,369✔
799
            const auto& level_name = ll->get_level_name();
1,369✔
800

801
            sqlite3_result_text(
1,369✔
802
                ctx, level_name.data(), level_name.length(), SQLITE_STATIC);
803
            break;
1,369✔
804
        }
805

806
        default:
82,260✔
807
            if (col > (VT_COL_MAX + vt->vi->vi_column_count - 1)) {
82,260✔
808
                auto footer_column = static_cast<log_footer_columns>(
809
                    col - (VT_COL_MAX + vt->vi->vi_column_count - 1) - 1);
7,047✔
810

811
                switch (footer_column) {
7,047✔
812
                    case log_footer_columns::partition: {
591✔
813
                        auto& vb = vt->tc->get_bookmarks();
591✔
814
                        const auto& bv = vb[&textview_curses::BM_PARTITION];
591✔
815

816
                        if (bv.empty()) {
591✔
817
                            sqlite3_result_null(ctx);
354✔
818
                        } else {
819
                            vis_line_t curr_line(vc->log_cursor.lc_curr_line);
237✔
820
                            auto iter
821
                                = bv.bv_tree.lower_bound(curr_line + 1_vl);
237✔
822

823
                            if (iter != bv.bv_tree.begin()) {
237✔
824
                                --iter;
185✔
825
                                auto line_meta_opt
826
                                    = vt->lss->find_bookmark_metadata(*iter);
185✔
827
                                if (line_meta_opt
185✔
828
                                    && !line_meta_opt.value()->bm_name.empty())
185✔
829
                                {
830
                                    sqlite3_result_text(
185✔
831
                                        ctx,
832
                                        line_meta_opt.value()->bm_name.c_str(),
185✔
833
                                        line_meta_opt.value()->bm_name.size(),
185✔
834
                                        SQLITE_TRANSIENT);
835
                                } else {
836
                                    sqlite3_result_null(ctx);
×
837
                                }
838
                            } else {
839
                                sqlite3_result_null(ctx);
52✔
840
                            }
841
                        }
842
                        break;
591✔
843
                    }
844
                    case log_footer_columns::actual_time: {
127✔
845
                        char buffer[64] = "";
127✔
846

847
                        if (ll->is_time_skewed()) {
127✔
848
                            if (vc->line_values.lvv_values.empty()) {
2✔
849
                                vc->cache_msg(lf, ll);
2✔
850
                                require(vc->line_values.lvv_sbr.get_data()
2✔
851
                                        != nullptr);
852
                                vt->vi->extract(lf,
2✔
853
                                                line_number,
854
                                                vc->attrs,
2✔
855
                                                vc->line_values);
2✔
856
                            }
857

858
                            struct line_range time_range;
2✔
859

860
                            time_range = find_string_attr_range(vc->attrs,
2✔
861
                                                                &L_TIMESTAMP);
862

863
                            const auto* time_src
864
                                = vc->line_values.lvv_sbr.get_data()
2✔
865
                                + time_range.lr_start;
2✔
866
                            struct timeval actual_tv;
867
                            struct exttm tm;
2✔
868

869
                            if (lf->get_format_ptr()->lf_date_time.scan(
4✔
870
                                    time_src,
871
                                    time_range.length(),
2✔
872
                                    lf->get_format_ptr()
873
                                        ->get_timestamp_formats(),
874
                                    &tm,
875
                                    actual_tv,
876
                                    false))
877
                            {
878
                                sql_strftime(buffer, sizeof(buffer), actual_tv);
2✔
879
                            }
880
                        } else {
881
                            sql_strftime(
125✔
882
                                buffer, sizeof(buffer), ll->get_timeval());
250✔
883
                        }
884
                        sqlite3_result_text(
127✔
885
                            ctx, buffer, strlen(buffer), SQLITE_TRANSIENT);
127✔
886
                        break;
127✔
887
                    }
888
                    case log_footer_columns::idle_msecs: {
350✔
889
                        if (vc->log_cursor.lc_curr_line == 0) {
350✔
890
                            sqlite3_result_int64(ctx, 0);
61✔
891
                        } else {
892
                            content_line_t prev_cl(vt->lss->at(
289✔
893
                                vis_line_t(vc->log_cursor.lc_curr_line - 1)));
289✔
894
                            auto prev_lf = vt->lss->find(prev_cl);
289✔
895
                            auto prev_ll = prev_lf->begin() + prev_cl;
289✔
896

897
                            auto prev_time
898
                                = prev_ll
899
                                      ->get_time<std::chrono::milliseconds>();
289✔
900
                            auto curr_line_time
901
                                = ll->get_time<std::chrono::milliseconds>();
289✔
902
                            // require(curr_line_time >= prev_time);
903
                            sqlite3_result_int64(
289✔
904
                                ctx,
905
                                curr_line_time.count() - prev_time.count());
289✔
906
                        }
289✔
907
                        break;
350✔
908
                    }
909
                    case log_footer_columns::mark: {
367✔
910
                        sqlite3_result_int(ctx, ll->is_marked());
367✔
911
                        break;
367✔
912
                    }
913
                    case log_footer_columns::sticky_mark: {
114✔
914
                        auto& sticky_bv = vt->tc->get_bookmarks()
114✔
915
                                              [&textview_curses::BM_STICKY];
114✔
916
                        sqlite3_result_int(
114✔
917
                            ctx,
918
                            sticky_bv.bv_tree.find(vc->log_cursor.lc_curr_line)
114✔
919
                                != sticky_bv.bv_tree.end());
228✔
920
                        break;
114✔
921
                    }
922
                    case log_footer_columns::comment: {
380✔
923
                        auto line_meta_opt = vt->lss->find_bookmark_metadata(
380✔
924
                            vc->log_cursor.lc_curr_line);
925
                        if (!line_meta_opt
380✔
926
                            || line_meta_opt.value()->bm_comment.empty())
380✔
927
                        {
928
                            sqlite3_result_null(ctx);
378✔
929
                        } else {
930
                            const auto& meta = *(line_meta_opt.value());
2✔
931
                            sqlite3_result_text(ctx,
2✔
932
                                                meta.bm_comment.c_str(),
933
                                                meta.bm_comment.length(),
2✔
934
                                                SQLITE_TRANSIENT);
935
                        }
936
                        break;
380✔
937
                    }
938
                    case log_footer_columns::tags: {
773✔
939
                        auto line_meta_opt = vt->lss->find_bookmark_metadata(
773✔
940
                            vc->log_cursor.lc_curr_line);
941
                        if (!line_meta_opt
773✔
942
                            || line_meta_opt.value()->bm_tags.empty())
773✔
943
                        {
944
                            sqlite3_result_null(ctx);
766✔
945
                        } else {
946
                            const auto& meta = *(line_meta_opt.value());
7✔
947

948
                            yajlpp_gen gen;
7✔
949

950
                            yajl_gen_config(gen, yajl_gen_beautify, false);
7✔
951

952
                            {
953
                                yajlpp_array arr(gen);
7✔
954

955
                                for (const auto& entry : meta.bm_tags) {
14✔
956
                                    arr.gen(entry.te_tag);
7✔
957
                                }
958
                            }
7✔
959

960
                            to_sqlite(ctx, json_string(gen));
7✔
961
                        }
7✔
962
                        break;
773✔
963
                    }
964
                    case log_footer_columns::annotations: {
383✔
965
                        if (sqlite3_vtab_nochange(ctx)) {
383✔
966
                            return SQLITE_OK;
108✔
967
                        }
968

969
                        auto line_meta_opt = vt->lss->find_bookmark_metadata(
275✔
970
                            vc->log_cursor.lc_curr_line);
971
                        if (!line_meta_opt
275✔
972
                            || line_meta_opt.value()
279✔
973
                                   ->bm_annotations.la_pairs.empty())
4✔
974
                        {
975
                            sqlite3_result_null(ctx);
271✔
976
                        } else {
977
                            const auto& meta = *(line_meta_opt.value());
4✔
978
                            to_sqlite(
4✔
979
                                ctx,
980
                                logmsg_annotations_handlers.to_json_string(
4✔
981
                                    meta.bm_annotations));
4✔
982
                        }
983
                        break;
275✔
984
                    }
985
                    case log_footer_columns::filters: {
350✔
986
                        const auto& filter_mask
987
                            = (*ld)->ld_filter_state.lfo_filter_state.tfs_mask;
350✔
988

989
                        if (!filter_mask[line_number]) {
350✔
990
                            sqlite3_result_null(ctx);
349✔
991
                        } else {
992
                            const auto& filters = vt->lss->get_filters();
1✔
993
                            yajlpp_gen gen;
1✔
994

995
                            yajl_gen_config(gen, yajl_gen_beautify, false);
1✔
996

997
                            {
998
                                yajlpp_array arr(gen);
1✔
999

1000
                                for (const auto& filter : filters) {
2✔
1001
                                    if (filter->lf_deleted) {
1✔
1002
                                        continue;
×
1003
                                    }
1004

1005
                                    uint32_t mask
1006
                                        = (1UL << filter->get_index());
1✔
1007

1008
                                    if (filter_mask[line_number] & mask) {
1✔
1009
                                        arr.gen(filter->get_index());
1✔
1010
                                    }
1011
                                }
1012
                            }
1✔
1013

1014
                            to_sqlite(ctx, gen.to_string_fragment());
1✔
1015
                            sqlite3_result_subtype(ctx, JSON_SUBTYPE);
1✔
1016
                        }
1✔
1017
                        break;
350✔
1018
                    }
1019
                    case log_footer_columns::opid: {
104✔
1020
                        if (vc->line_values.lvv_values.empty()) {
104✔
1021
                            vc->cache_msg(lf, ll);
68✔
1022
                            require(vc->line_values.lvv_sbr.get_data()
68✔
1023
                                    != nullptr);
1024
                            vt->vi->extract(
68✔
1025
                                lf, line_number, vc->attrs, vc->line_values);
68✔
1026
                        }
1027

1028
                        if (vc->line_values.lvv_opid_value) {
104✔
1029
                            to_sqlite(ctx,
50✔
1030
                                      vc->line_values.lvv_opid_value.value());
50✔
1031
                        } else {
1032
                            sqlite3_result_null(ctx);
54✔
1033
                        }
1034
                        break;
104✔
1035
                    }
1036
                    case log_footer_columns::user_opid: {
145✔
1037
                        if (vc->line_values.lvv_values.empty()) {
145✔
1038
                            vc->cache_msg(lf, ll);
32✔
1039
                            require(vc->line_values.lvv_sbr.get_data()
32✔
1040
                                    != nullptr);
1041
                            vt->vi->extract(
32✔
1042
                                lf, line_number, vc->attrs, vc->line_values);
32✔
1043
                        }
1044

1045
                        if (vc->line_values.lvv_opid_value
145✔
1046
                            && vc->line_values.lvv_opid_provenance
145✔
1047
                                == logline_value_vector::opid_provenance::user)
145✔
1048
                        {
1049
                            to_sqlite(ctx,
3✔
1050
                                      vc->line_values.lvv_opid_value.value());
3✔
1051
                        } else {
1052
                            sqlite3_result_null(ctx);
142✔
1053
                        }
1054
                        break;
145✔
1055
                    }
1056
                    case log_footer_columns::opid_definition: {
114✔
1057
                        if (vc->line_values.lvv_values.empty()) {
114✔
1058
                            vc->cache_msg(lf, ll);
×
1059
                            require(vc->line_values.lvv_sbr.get_data()
×
1060
                                    != nullptr);
1061
                            vt->vi->extract(
×
1062
                                lf, line_number, vc->attrs, vc->line_values);
×
1063
                        }
1064

1065
                        if (vc->line_values.lvv_opid_value) {
114✔
1066
                            auto opids = lf->get_opids().readAccess();
33✔
1067

1068
                            auto iter = opids->los_opid_ranges.find(
66✔
1069
                                vc->line_values.lvv_opid_value.value());
33✔
1070
                            if (iter != opids->los_opid_ranges.end()
33✔
1071
                                && iter->second.otr_description.lod_index)
33✔
1072
                            {
1073
                                const auto& opid_def
1074
                                    = (*lf->get_format_ptr()
29✔
1075
                                            ->lf_opid_description_def_vec)
29✔
1076
                                        [iter->second.otr_description.lod_index
29✔
1077
                                             .value()];
29✔
1078
                                to_sqlite(ctx, opid_def->od_name);
29✔
1079
                            } else {
1080
                                sqlite3_result_null(ctx);
4✔
1081
                            }
1082
                        } else {
33✔
1083
                            sqlite3_result_null(ctx);
81✔
1084
                        }
1085
                        break;
114✔
1086
                    }
1087
                    case log_footer_columns::format: {
143✔
1088
                        auto format_name = lf->get_format_name();
143✔
1089
                        sqlite3_result_text(ctx,
143✔
1090
                                            format_name.get(),
1091
                                            format_name.size(),
143✔
1092
                                            SQLITE_STATIC);
1093
                        break;
143✔
1094
                    }
1095
                    case log_footer_columns::format_regex: {
114✔
1096
                        auto pat_name = lf->get_format_ptr()->get_pattern_name(
228✔
1097
                            lf->get_format_file_state().lffs_pattern_locks,
114✔
1098
                            line_number);
1099
                        sqlite3_result_text(ctx,
114✔
1100
                                            pat_name.get(),
1101
                                            pat_name.size(),
114✔
1102
                                            SQLITE_STATIC);
1103
                        break;
114✔
1104
                    }
1105
                    case log_footer_columns::time_msecs: {
1,513✔
1106
                        sqlite3_result_int64(
1,513✔
1107
                            ctx,
1108
                            ll->get_time<std::chrono::milliseconds>().count());
1,513✔
1109
                        break;
1,513✔
1110
                    }
1111
                    case log_footer_columns::path: {
118✔
1112
                        const auto& fn = lf->get_filename();
118✔
1113

1114
                        sqlite3_result_text(ctx,
118✔
1115
                                            fn.c_str(),
1116
                                            fn.native().length(),
118✔
1117
                                            SQLITE_STATIC);
1118
                        break;
118✔
1119
                    }
1120
                    case log_footer_columns::unique_path: {
118✔
1121
                        const auto& fn = lf->get_unique_path();
118✔
1122

1123
                        sqlite3_result_text(ctx,
118✔
1124
                                            fn.c_str(),
1125
                                            fn.native().length(),
118✔
1126
                                            SQLITE_STATIC);
1127
                        break;
118✔
1128
                    }
1129
                    case log_footer_columns::text: {
120✔
1130
                        shared_buffer_ref line;
120✔
1131

1132
                        lf->read_full_message(ll, line);
120✔
1133
                        line.erase_ansi();
120✔
1134
                        sqlite3_result_text(ctx,
120✔
1135
                                            line.get_data(),
1136
                                            line.length(),
120✔
1137
                                            SQLITE_TRANSIENT);
1138
                        break;
120✔
1139
                    }
120✔
1140
                    case log_footer_columns::body: {
284✔
1141
                        if (vc->line_values.lvv_values.empty()) {
284✔
1142
                            vc->cache_msg(lf, ll);
115✔
1143
                            require(vc->line_values.lvv_sbr.get_data()
115✔
1144
                                    != nullptr);
1145
                            vt->vi->extract(
115✔
1146
                                lf, line_number, vc->attrs, vc->line_values);
115✔
1147
                        }
1148

1149
                        auto body_range
1150
                            = find_string_attr_range(vc->attrs, &SA_BODY);
284✔
1151
                        if (!body_range.is_valid()) {
284✔
1152
                            sqlite3_result_null(ctx);
23✔
1153
                        } else {
1154
                            const char* msg_start
1155
                                = vc->line_values.lvv_sbr.get_data();
261✔
1156

1157
                            sqlite3_result_text(ctx,
261✔
1158
                                                &msg_start[body_range.lr_start],
261✔
1159
                                                body_range.length(),
1160
                                                SQLITE_TRANSIENT);
1161
                        }
1162
                        break;
284✔
1163
                    }
1164
                    case log_footer_columns::raw_text: {
128✔
1165
                        auto read_res = lf->read_raw_message(ll);
128✔
1166

1167
                        if (read_res.isErr()) {
128✔
1168
                            auto msg = fmt::format(
1169
                                FMT_STRING("unable to read line -- {}"),
×
1170
                                read_res.unwrapErr());
×
1171
                            sqlite3_result_error(
×
1172
                                ctx, msg.c_str(), msg.length());
×
1173
                        } else {
×
1174
                            auto sbr = read_res.unwrap();
128✔
1175

1176
                            sqlite3_result_text(ctx,
128✔
1177
                                                sbr.get_data(),
1178
                                                sbr.length(),
128✔
1179
                                                SQLITE_TRANSIENT);
1180
                        }
128✔
1181
                        break;
128✔
1182
                    }
128✔
1183
                    case log_footer_columns::line_hash: {
119✔
1184
                        auto lw
1185
                            = vt->lss->window_at(vc->log_cursor.lc_curr_line);
119✔
1186
                        for (const auto& li : *lw) {
119✔
1187
                            auto hash_res = li.get_line_hash();
119✔
1188
                            if (hash_res.isErr()) {
119✔
1189
                                auto msg = fmt::format(
1190
                                    FMT_STRING("unable to read line -- {}"),
×
1191
                                    hash_res.unwrapErr());
×
1192
                                sqlite3_result_error(
×
1193
                                    ctx, msg.c_str(), msg.length());
×
1194
                            } else {
×
1195
                                to_sqlite(ctx,
119✔
1196
                                          text_auto_buffer{hash_res.unwrap()});
238✔
1197
                            }
1198
                            break;
119✔
1199
                        }
238✔
1200
                        break;
119✔
1201
                    }
119✔
1202
                    case log_footer_columns::line_link: {
119✔
1203
                        auto anchor_opt = vt->lss->anchor_for_row(
119✔
1204
                            vc->log_cursor.lc_curr_line);
119✔
1205
                        if (anchor_opt) {
119✔
1206
                            to_sqlite(ctx, anchor_opt.value());
119✔
1207
                        } else {
1208
                            sqlite3_result_null(ctx);
×
1209
                        }
1210
                        break;
119✔
1211
                    }
119✔
1212
                    case log_footer_columns::src_file: {
121✔
1213
                        if (vc->line_values.lvv_values.empty()) {
121✔
1214
                            vc->cache_msg(lf, ll);
7✔
1215
                            require(vc->line_values.lvv_sbr.get_data()
7✔
1216
                                    != nullptr);
1217
                            vt->vi->extract(
7✔
1218
                                lf, line_number, vc->attrs, vc->line_values);
7✔
1219
                        }
1220

1221
                        to_sqlite(ctx, vc->line_values.lvv_src_file_value);
121✔
1222
                        break;
121✔
1223
                    }
1224
                    case log_footer_columns::src_line: {
121✔
1225
                        if (vc->line_values.lvv_values.empty()) {
121✔
1226
                            vc->cache_msg(lf, ll);
×
1227
                            require(vc->line_values.lvv_sbr.get_data()
×
1228
                                    != nullptr);
1229
                            vt->vi->extract(
×
1230
                                lf, line_number, vc->attrs, vc->line_values);
×
1231
                        }
1232

1233
                        to_sqlite(ctx, vc->line_values.lvv_src_line_value);
121✔
1234
                        break;
121✔
1235
                    }
1236
                    case log_footer_columns::thread_id: {
119✔
1237
                        if (vc->line_values.lvv_values.empty()) {
119✔
1238
                            vc->cache_msg(lf, ll);
5✔
1239
                            require(vc->line_values.lvv_sbr.get_data()
5✔
1240
                                    != nullptr);
1241
                            vt->vi->extract(
5✔
1242
                                lf, line_number, vc->attrs, vc->line_values);
5✔
1243
                        }
1244

1245
                        to_sqlite(ctx, vc->line_values.lvv_thread_id_value);
119✔
1246
                        break;
119✔
1247
                    }
1248
                    case log_footer_columns::duration: {
112✔
1249
                        if (vc->line_values.lvv_values.empty()) {
112✔
1250
                            vc->cache_msg(lf, ll);
×
1251
                            require(vc->line_values.lvv_sbr.get_data()
×
1252
                                    != nullptr);
1253
                            vt->vi->extract(
×
1254
                                lf, line_number, vc->attrs, vc->line_values);
×
1255
                        }
1256

1257
                        std::optional<double> duration_opt;
112✔
1258
                        if (vc->line_values.lvv_duration_value) {
112✔
1259
                            auto duration_secs
1260
                                = (double) vc->line_values.lvv_duration_value
×
1261
                                      .value()
×
1262
                                      .count();
×
1263
                            duration_secs /= 1000000.0;
×
1264
                            duration_opt = duration_secs;
×
1265
                        }
1266
                        to_sqlite(ctx, duration_opt);
112✔
1267
                        break;
112✔
1268
                    }
1269
                }
1270
            } else {
1271
                if (vc->line_values.lvv_values.empty()) {
75,213✔
1272
                    vc->cache_msg(lf, ll);
30,945✔
1273
                    require(vc->line_values.lvv_sbr.get_data() != nullptr);
30,945✔
1274
                    vt->vi->extract(
30,945✔
1275
                        lf, line_number, vc->attrs, vc->line_values);
30,945✔
1276
                }
1277

1278
                auto sub_col = logline_value_meta::table_column{
1279
                    (size_t) (col - VT_COL_MAX)};
75,213✔
1280
                auto lv_iter = find_if(vc->line_values.lvv_values.begin(),
75,213✔
1281
                                       vc->line_values.lvv_values.end(),
1282
                                       logline_value_col_eq(sub_col));
1283

1284
                if (lv_iter != vc->line_values.lvv_values.end()) {
75,213✔
1285
                    if (!lv_iter->lv_meta.lvm_struct_name.empty()) {
74,485✔
1286
                        yajlpp_gen gen;
8✔
1287
                        yajl_gen_config(gen, yajl_gen_beautify, false);
8✔
1288

1289
                        {
1290
                            yajlpp_map root(gen);
8✔
1291

1292
                            for (const auto& lv_struct :
8✔
1293
                                 vc->line_values.lvv_values)
82✔
1294
                            {
1295
                                if (lv_struct.lv_meta.lvm_column != sub_col) {
66✔
1296
                                    continue;
39✔
1297
                                }
1298

1299
                                root.gen(lv_struct.lv_meta.lvm_name);
27✔
1300
                                switch (lv_struct.lv_meta.lvm_kind) {
27✔
1301
                                    case value_kind_t::VALUE_NULL:
2✔
1302
                                        root.gen();
2✔
1303
                                        break;
2✔
1304
                                    case value_kind_t::VALUE_BOOLEAN:
×
1305
                                        root.gen((bool) lv_struct.lv_value.i);
×
1306
                                        break;
×
1307
                                    case value_kind_t::VALUE_INTEGER:
×
1308
                                        root.gen(lv_struct.lv_value.i);
×
1309
                                        break;
×
1310
                                    case value_kind_t::VALUE_FLOAT:
×
1311
                                        root.gen(lv_struct.lv_value.d);
×
1312
                                        break;
×
1313
                                    case value_kind_t::VALUE_JSON: {
1✔
1314
                                        auto_mem<yajl_handle_t> parse_handle(
1315
                                            yajl_free);
1✔
1316
                                        json_ptr jp("");
1✔
1317
                                        json_op jo(jp);
1✔
1318

1319
                                        jo.jo_ptr_callbacks
1320
                                            = json_op::gen_callbacks;
1✔
1321
                                        jo.jo_ptr_data = gen;
1✔
1322
                                        parse_handle.reset(
1✔
1323
                                            yajl_alloc(&json_op::ptr_callbacks,
1324
                                                       nullptr,
1325
                                                       &jo));
1326

1327
                                        const auto* json_in
1328
                                            = (const unsigned char*)
1329
                                                  lv_struct.text_value();
1✔
1330
                                        auto json_len = lv_struct.text_length();
1✔
1331

1332
                                        if (yajl_parse(parse_handle.in(),
1✔
1333
                                                       json_in,
1334
                                                       json_len)
1335
                                                != yajl_status_ok
1336
                                            || yajl_complete_parse(
1✔
1337
                                                   parse_handle.in())
1338
                                                != yajl_status_ok)
1339
                                        {
1340
                                            log_error(
×
1341
                                                "failed to parse json value: "
1342
                                                "%.*s",
1343
                                                (int) lv_struct.text_length(),
1344
                                                lv_struct.text_value());
1345
                                            root.gen(lv_struct.to_string());
×
1346
                                        }
1347
                                        break;
1✔
1348
                                    }
1✔
1349
                                    default:
24✔
1350
                                        root.gen(lv_struct.to_string());
24✔
1351
                                        break;
24✔
1352
                                }
1353
                            }
1354
                        }
8✔
1355

1356
                        auto sf = gen.to_string_fragment();
8✔
1357
                        sqlite3_result_text(
8✔
1358
                            ctx, sf.data(), sf.length(), SQLITE_TRANSIENT);
1359
                        sqlite3_result_subtype(ctx, JSON_SUBTYPE);
8✔
1360
                    } else {
8✔
1361
                        switch (lv_iter->lv_meta.lvm_kind) {
74,477✔
1362
                            case value_kind_t::VALUE_NULL:
2,467✔
1363
                                sqlite3_result_null(ctx);
2,467✔
1364
                                break;
2,467✔
1365
                            case value_kind_t::VALUE_JSON: {
131✔
1366
                                sqlite3_result_text(ctx,
131✔
1367
                                                    lv_iter->text_value(),
1368
                                                    lv_iter->text_length(),
131✔
1369
                                                    SQLITE_TRANSIENT);
1370
                                sqlite3_result_subtype(ctx, JSON_SUBTYPE);
131✔
1371
                                break;
131✔
1372
                            }
1373
                            case value_kind_t::VALUE_ANY:
68,411✔
1374
                            case value_kind_t::VALUE_STRUCT:
1375
                            case value_kind_t::VALUE_TEXT:
1376
                            case value_kind_t::VALUE_XML: {
1377
                                sqlite3_result_text(ctx,
68,411✔
1378
                                                    lv_iter->text_value(),
1379
                                                    lv_iter->text_length(),
68,411✔
1380
                                                    SQLITE_TRANSIENT);
1381
                                break;
68,411✔
1382
                            }
1383
                            case value_kind_t::VALUE_TIMESTAMP: {
11✔
1384
                                auto* fmt = lf->get_format_ptr();
11✔
1385
                                auto dts = fmt->build_time_scanner();
11✔
1386
                                exttm tm;
11✔
1387
                                timeval tv;
1388

1389
                                if (dts.scan(lv_iter->text_value(),
11✔
1390
                                             lv_iter->text_length(),
1391
                                             fmt->get_timestamp_formats(),
1392
                                             &tm,
1393
                                             tv))
1394
                                {
1395
                                    char buffer[64];
1396
                                    sql_strftime(buffer, sizeof(buffer), tv);
5✔
1397
                                    sqlite3_result_text(ctx,
5✔
1398
                                                        buffer,
1399
                                                        strlen(buffer),
5✔
1400
                                                        SQLITE_TRANSIENT);
1401
                                } else {
1402
                                    sqlite3_result_text(ctx,
6✔
1403
                                                        lv_iter->text_value(),
1404
                                                        lv_iter->text_length(),
6✔
1405
                                                        SQLITE_TRANSIENT);
1406
                                }
1407
                                break;
11✔
1408
                            }
1409
                            case value_kind_t::VALUE_W3C_QUOTED:
20✔
1410
                            case value_kind_t::VALUE_QUOTED:
1411
                                if (lv_iter->text_length() == 0) {
20✔
1412
                                    sqlite3_result_text(
×
1413
                                        ctx, "", 0, SQLITE_STATIC);
1414
                                } else {
1415
                                    const char* text_value
1416
                                        = lv_iter->text_value();
20✔
1417
                                    size_t text_len = lv_iter->text_length();
20✔
1418

1419
                                    switch (text_value[0]) {
20✔
1420
                                        case '\'':
7✔
1421
                                        case '"': {
1422
                                            char* val = (char*) sqlite3_malloc(
7✔
1423
                                                text_len);
1424

1425
                                            if (val == nullptr) {
7✔
1426
                                                sqlite3_result_error_nomem(ctx);
×
1427
                                            } else {
1428
                                                auto unquote_func
1429
                                                    = lv_iter->lv_meta.lvm_kind
7✔
1430
                                                        == value_kind_t::
1431
                                                            VALUE_W3C_QUOTED
1432
                                                    ? unquote_w3c
7✔
1433
                                                    : unquote;
7✔
1434

1435
                                                size_t unquoted_len
1436
                                                    = unquote_func(val,
7✔
1437
                                                                   text_value,
1438
                                                                   text_len);
1439
                                                sqlite3_result_text(
7✔
1440
                                                    ctx,
1441
                                                    val,
1442
                                                    unquoted_len,
1443
                                                    sqlite3_free);
1444
                                            }
1445
                                            break;
7✔
1446
                                        }
1447
                                        default: {
13✔
1448
                                            sqlite3_result_text(
13✔
1449
                                                ctx,
1450
                                                text_value,
1451
                                                lv_iter->text_length(),
13✔
1452
                                                SQLITE_TRANSIENT);
1453
                                            break;
13✔
1454
                                        }
1455
                                    }
1456
                                }
1457
                                break;
20✔
1458

1459
                            case value_kind_t::VALUE_BOOLEAN:
2,845✔
1460
                            case value_kind_t::VALUE_INTEGER:
1461
                                sqlite3_result_int64(ctx, lv_iter->lv_value.i);
2,845✔
1462
                                break;
2,845✔
1463

1464
                            case value_kind_t::VALUE_FLOAT:
592✔
1465
                                sqlite3_result_double(ctx, lv_iter->lv_value.d);
592✔
1466
                                break;
592✔
1467

1468
                            case value_kind_t::VALUE_UNKNOWN:
×
1469
                            case value_kind_t::VALUE__MAX:
1470
                                require(0);
×
1471
                                break;
1472
                        }
1473
                    }
1474
                } else {
1475
                    sqlite3_result_null(ctx);
728✔
1476
                }
1477
            }
1478
            break;
82,152✔
1479
    }
1480

1481
    return SQLITE_OK;
86,849✔
1482
}
1483

1484
static int
1485
vt_rowid(sqlite3_vtab_cursor* cur, sqlite_int64* p_rowid)
227✔
1486
{
1487
    vtab_cursor* p_cur = (vtab_cursor*) cur;
227✔
1488

1489
    *p_rowid = (((uint64_t) p_cur->log_cursor.lc_curr_line) << 8)
227✔
1490
        | (p_cur->log_cursor.lc_sub_index & 0xff);
227✔
1491

1492
    return SQLITE_OK;
227✔
1493
}
1494

1495
void
1496
log_cursor::update(unsigned char op, vis_line_t vl, constraint_t cons)
136✔
1497
{
1498
    switch (op) {
136✔
1499
        case SQLITE_INDEX_CONSTRAINT_EQ:
110✔
1500
            if (vl < 0_vl) {
110✔
1501
                this->lc_curr_line = this->lc_end_line;
×
1502
            } else if (vl < this->lc_end_line) {
110✔
1503
                this->lc_curr_line = vl;
104✔
1504
                if (cons == constraint_t::unique) {
104✔
1505
                    this->lc_end_line = this->lc_curr_line + 1_vl;
104✔
1506
                }
1507
            }
1508
            break;
110✔
1509
        case SQLITE_INDEX_CONSTRAINT_GE:
3✔
1510
            if (vl < 0_vl) {
3✔
1511
                vl = 0_vl;
×
1512
            }
1513
            this->lc_curr_line = vl;
3✔
1514
            break;
3✔
1515
        case SQLITE_INDEX_CONSTRAINT_GT:
8✔
1516
            if (vl < 0_vl) {
8✔
1517
                this->lc_curr_line = 0_vl;
2✔
1518
            } else {
1519
                this->lc_curr_line
1520
                    = vl + (cons == constraint_t::unique ? 1_vl : 0_vl);
6✔
1521
            }
1522
            break;
8✔
1523
        case SQLITE_INDEX_CONSTRAINT_LE:
4✔
1524
            if (vl < 0_vl) {
4✔
1525
                this->lc_curr_line = this->lc_end_line;
×
1526
            } else if (vl < this->lc_end_line) {
4✔
1527
                this->lc_end_line
1528
                    = vl + (cons == constraint_t::unique ? 1_vl : 0_vl);
2✔
1529
            }
1530
            break;
4✔
1531
        case SQLITE_INDEX_CONSTRAINT_LT:
11✔
1532
            if (vl <= 0_vl) {
11✔
1533
                this->lc_curr_line = this->lc_end_line;
2✔
1534
            } else if (this->lc_direction > 0) {
9✔
1535
                if (vl < this->lc_end_line) {
1✔
1536
                    this->lc_end_line = vl;
1✔
1537
                }
1538
            } else if (this->lc_direction < 0) {
8✔
1539
                if (vl <= this->lc_curr_line) {
8✔
1540
                    this->lc_curr_line = vl - 1_vl;
8✔
1541
                }
1542
            }
1543
            break;
11✔
1544
    }
1545
#ifdef DEBUG_INDEXING
1546
    log_debug("log_cursor::update(%s, %d) -> (%d:%d:%d)",
1547
              sql_constraint_op_name(op),
1548
              vl,
1549
              this->lc_curr_line,
1550
              this->lc_end_line,
1551
              this->lc_direction);
1552
#endif
1553
}
136✔
1554

1555
log_cursor::string_constraint::string_constraint(unsigned char op,
215✔
1556
                                                 std::string value)
215✔
1557
    : sc_op(op), sc_value(std::move(value))
215✔
1558
{
1559
    if (op == SQLITE_INDEX_CONSTRAINT_REGEXP) {
215✔
1560
        auto compile_res = lnav::pcre2pp::code::from(this->sc_value);
×
1561

1562
        if (compile_res.isErr()) {
×
1563
            auto ce = compile_res.unwrapErr();
×
1564
            log_error("unable to compile regexp constraint: %s -- %s",
×
1565
                      this->sc_value.c_str(),
1566
                      ce.get_message().c_str());
1567
        } else {
×
1568
            this->sc_pattern = compile_res.unwrap().to_shared();
×
1569
        }
1570
    }
1571
}
215✔
1572

1573
bool
1574
log_cursor::string_constraint::matches(const std::string& sf) const
4✔
1575
{
1576
    switch (this->sc_op) {
4✔
1577
        case SQLITE_INDEX_CONSTRAINT_EQ:
×
1578
        case SQLITE_INDEX_CONSTRAINT_IS:
1579
            return sf == this->sc_value;
×
1580
        case SQLITE_INDEX_CONSTRAINT_NE:
×
1581
        case SQLITE_INDEX_CONSTRAINT_ISNOT:
1582
            return sf != this->sc_value;
×
1583
        case SQLITE_INDEX_CONSTRAINT_GT:
×
1584
            return sf > this->sc_value;
×
1585
        case SQLITE_INDEX_CONSTRAINT_LE:
×
1586
            return sf <= this->sc_value;
×
1587
        case SQLITE_INDEX_CONSTRAINT_LT:
×
1588
            return sf < this->sc_value;
×
1589
        case SQLITE_INDEX_CONSTRAINT_GE:
×
1590
            return sf >= this->sc_value;
×
1591
        case SQLITE_INDEX_CONSTRAINT_LIKE:
×
1592
            return sqlite3_strlike(this->sc_value.c_str(), sf.data(), 0) == 0;
×
1593
        case SQLITE_INDEX_CONSTRAINT_GLOB:
4✔
1594
            return sqlite3_strglob(this->sc_value.c_str(), sf.data()) == 0;
4✔
1595
        case SQLITE_INDEX_CONSTRAINT_REGEXP: {
×
1596
            if (this->sc_pattern != nullptr) {
×
1597
                return this->sc_pattern->find_in(sf, PCRE2_NO_UTF_CHECK)
×
1598
                    .ignore_error()
×
1599
                    .has_value();
×
1600
            }
1601
            // return true here so that the regexp is actually run and fails
1602
            return true;
×
1603
        }
1604
        case SQLITE_INDEX_CONSTRAINT_ISNOTNULL:
×
1605
            return true;
×
1606
        default:
×
1607
            return false;
×
1608
    }
1609
}
1610

1611
struct vtab_time_range {
1612
    std::optional<std::chrono::microseconds> vtr_begin;
1613
    std::optional<std::chrono::microseconds> vtr_end;
1614

1615
    bool empty() const { return !this->vtr_begin && !this->vtr_end; }
4✔
1616

1617
    void add(const std::chrono::microseconds& us)
7✔
1618
    {
1619
        if (!this->vtr_begin || us < this->vtr_begin) {
7✔
1620
            this->vtr_begin = us;
3✔
1621
        }
1622
        if (!this->vtr_end || this->vtr_end < us) {
7✔
1623
            this->vtr_end = us;
6✔
1624
        }
1625
    }
7✔
1626
};
1627

1628
static int
1629
vt_filter(sqlite3_vtab_cursor* p_vtc,
471✔
1630
          int idxNum,
1631
          const char* idxStr,
1632
          int argc,
1633
          sqlite3_value** argv)
1634
{
1635
    auto* p_cur = (vtab_cursor*) p_vtc;
471✔
1636
    auto* vt = (log_vtab*) p_vtc->pVtab;
471✔
1637
    sqlite3_index_info::sqlite3_index_constraint* index = nullptr;
471✔
1638

1639
    if (idxStr != nullptr) {
471✔
1640
        auto desc_len = strlen(idxStr);
460✔
1641
        auto index_len = idxNum * sizeof(*index);
460✔
1642
        auto storage_len = desc_len + 128 + index_len;
460✔
1643
        auto direction_storage
460✔
1644
            = static_cast<const char*>(idxStr) + desc_len + 1;
460✔
1645
        p_cur->log_cursor.lc_direction = vis_line_t(direction_storage[0]);
460✔
1646
        auto* remaining_storage = const_cast<void*>(
460✔
1647
            static_cast<const void*>(idxStr + desc_len + 1 + 1));
460✔
1648
        auto* index_storage
1649
            = std::align(alignof(sqlite3_index_info::sqlite3_index_constraint),
460✔
1650
                         index_len,
1651
                         remaining_storage,
1652
                         storage_len);
1653
        index = static_cast<sqlite3_index_info::sqlite3_index_constraint*>(
460✔
1654
            index_storage);
1655
    } else {
1656
        p_cur->log_cursor.lc_direction = 1_vl;
11✔
1657
    }
1658

1659
#ifdef DEBUG_INDEXING
1660
    log_info("vt_filter(%s, %d, direction=%d)",
1661
             vt->vi->get_name().get(),
1662
             idxNum,
1663
             p_cur->log_cursor.lc_direction);
1664
    log_info("  index storage: %p", index);
1665
#endif
1666
    p_cur->log_cursor.lc_format_name.clear();
471✔
1667
    p_cur->log_cursor.lc_pattern_name.clear();
471✔
1668
    p_cur->log_cursor.lc_opid_bloom_bits = std::nullopt;
471✔
1669
    p_cur->log_cursor.lc_tid_bloom_bits = std::nullopt;
471✔
1670
    p_cur->log_cursor.lc_level_constraint = std::nullopt;
471✔
1671
    p_cur->log_cursor.lc_log_path.clear();
471✔
1672
    p_cur->log_cursor.lc_last_log_path_match = nullptr;
471✔
1673
    p_cur->log_cursor.lc_last_log_path_mismatch = nullptr;
471✔
1674
    p_cur->log_cursor.lc_unique_path.clear();
471✔
1675
    p_cur->log_cursor.lc_last_unique_path_match = nullptr;
471✔
1676
    p_cur->log_cursor.lc_last_unique_path_mismatch = nullptr;
471✔
1677
    if (p_cur->log_cursor.lc_direction < 0) {
471✔
1678
        p_cur->log_cursor.lc_curr_line
1679
            = vis_line_t(vt->lss->text_line_count() - 1);
8✔
1680
        p_cur->log_cursor.lc_end_line = -1_vl;
8✔
1681
    } else {
1682
        p_cur->log_cursor.lc_curr_line = 0_vl;
463✔
1683
        p_cur->log_cursor.lc_end_line = vis_line_t(vt->lss->text_line_count());
463✔
1684
    }
1685
    p_cur->log_cursor.lc_scanned_rows = 0;
471✔
1686
    p_cur->log_cursor.lc_indexed_lines.clear();
471✔
1687
    p_cur->log_cursor.lc_indexed_lines_range = msg_range::empty();
471✔
1688

1689
    std::optional<vtab_time_range> log_time_range;
471✔
1690
    std::optional<uint64_t> opid_val;
471✔
1691
    std::optional<uint64_t> tid_val;
471✔
1692
    std::vector<log_cursor::string_constraint> log_path_constraints;
471✔
1693
    std::vector<log_cursor::string_constraint> log_unique_path_constraints;
471✔
1694

1695
    for (int lpc = 0; lpc < idxNum; lpc++) {
835✔
1696
        auto col = index[lpc].iColumn;
364✔
1697
        auto op = index[lpc].op;
364✔
1698
        switch (col) {
364✔
1699
            case VT_COL_LINE_NUMBER: {
134✔
1700
                auto vl = vis_line_t(sqlite3_value_int64(argv[lpc]));
134✔
1701

1702
                p_cur->log_cursor.update(
134✔
1703
                    op, vl, log_cursor::constraint_t::unique);
1704
                break;
134✔
1705
            }
1706
            case VT_COL_LEVEL: {
7✔
1707
                if (sqlite3_value_type(argv[lpc]) != SQLITE3_TEXT) {
7✔
1708
                    continue;
×
1709
                }
1710

1711
                auto sf = from_sqlite<string_fragment>()(argc, argv, lpc);
7✔
1712
                auto level = string2level(sf.data(), sf.length());
7✔
1713

1714
                p_cur->log_cursor.lc_level_constraint
1715
                    = log_cursor::level_constraint{
7✔
1716
                        op,
1717
                        level,
1718
                    };
7✔
1719
                break;
7✔
1720
            }
1721

1722
            case VT_COL_LOG_TIME:
2✔
1723
                if (sqlite3_value_type(argv[lpc]) == SQLITE3_TEXT) {
2✔
1724
                    const auto* datestr
1725
                        = (const char*) sqlite3_value_text(argv[lpc]);
1✔
1726
                    auto datelen = sqlite3_value_bytes(argv[lpc]);
1✔
1727
                    date_time_scanner dts;
1✔
1728
                    timeval tv;
1729
                    exttm mytm;
1✔
1730

1731
                    const auto* date_end
1732
                        = dts.scan(datestr, datelen, nullptr, &mytm, tv);
1✔
1733
                    auto us = to_us(tv);
1✔
1734
                    if (date_end != (datestr + datelen)) {
1✔
1735
                        log_warning(
×
1736
                            "  log_time constraint is not a valid datetime, "
1737
                            "index will not be applied: %s",
1738
                            datestr);
1739
                    } else {
1740
                        switch (op) {
1✔
1741
                            case SQLITE_INDEX_CONSTRAINT_EQ:
×
1742
                            case SQLITE_INDEX_CONSTRAINT_IS:
1743
                                if (!log_time_range) {
×
1744
                                    log_time_range = vtab_time_range{};
×
1745
                                }
1746
                                log_time_range->add(us);
×
1747
                                break;
×
1748
                            case SQLITE_INDEX_CONSTRAINT_GT:
1✔
1749
                            case SQLITE_INDEX_CONSTRAINT_GE:
1750
                                if (!log_time_range) {
1✔
1751
                                    log_time_range = vtab_time_range{};
1✔
1752
                                }
1753
                                log_time_range->vtr_begin = us;
1✔
1754
                                break;
1✔
1755
                            case SQLITE_INDEX_CONSTRAINT_LT:
×
1756
                            case SQLITE_INDEX_CONSTRAINT_LE:
1757
                                if (!log_time_range) {
×
1758
                                    log_time_range = vtab_time_range{};
×
1759
                                }
1760
                                log_time_range->vtr_end = us;
×
1761
                                break;
×
1762
                        }
1763
                    }
1764
                } else {
1765
                    log_warning(
1✔
1766
                        "  log_time constraint is not text, index will not be "
1767
                        "applied: value_type(%d)=%d",
1768
                        lpc,
1769
                        sqlite3_value_type(argv[lpc]));
1770
                }
1771
                break;
2✔
1772
            default: {
221✔
1773
                if (col > (VT_COL_MAX + vt->vi->vi_column_count - 1)) {
221✔
1774
                    auto footer_column = static_cast<log_footer_columns>(
1775
                        col - (VT_COL_MAX + vt->vi->vi_column_count - 1) - 1);
7✔
1776

1777
                    switch (footer_column) {
7✔
1778
                        case log_footer_columns::time_msecs: {
1✔
1779
                            auto msecs = std::chrono::milliseconds{
1780
                                sqlite3_value_int64(argv[lpc])};
1✔
1781
                            switch (op) {
1✔
1782
                                case SQLITE_INDEX_CONSTRAINT_EQ:
1✔
1783
                                case SQLITE_INDEX_CONSTRAINT_IS:
1784
                                    if (!log_time_range) {
1✔
1785
                                        log_time_range = vtab_time_range{};
1✔
1786
                                    }
1787
                                    log_time_range->add(msecs);
1✔
1788
                                    break;
1✔
1789
                                case SQLITE_INDEX_CONSTRAINT_GT:
×
1790
                                case SQLITE_INDEX_CONSTRAINT_GE:
1791
                                    if (!log_time_range) {
×
1792
                                        log_time_range = vtab_time_range{};
×
1793
                                    }
1794
                                    log_time_range->vtr_begin = msecs;
×
1795
                                    break;
×
1796
                                case SQLITE_INDEX_CONSTRAINT_LT:
×
1797
                                case SQLITE_INDEX_CONSTRAINT_LE:
1798
                                    if (!log_time_range) {
×
1799
                                        log_time_range = vtab_time_range{};
×
1800
                                    }
1801
                                    log_time_range->vtr_end = msecs;
×
1802
                                    break;
×
1803
                            }
1804
                            break;
1✔
1805
                        }
1806
                        case log_footer_columns::format: {
2✔
1807
                            const auto* format_name_str
1808
                                = (const char*) sqlite3_value_text(argv[lpc]);
2✔
1809

1810
                            if (format_name_str != nullptr) {
2✔
1811
                                p_cur->log_cursor.lc_format_name
1812
                                    = intern_string::lookup(format_name_str);
4✔
1813
                            }
1814
                            break;
2✔
1815
                        }
1816
                        case log_footer_columns::format_regex: {
×
1817
                            const auto* pattern_name_str
1818
                                = (const char*) sqlite3_value_text(argv[lpc]);
×
1819

1820
                            if (pattern_name_str != nullptr) {
×
1821
                                p_cur->log_cursor.lc_pattern_name
1822
                                    = intern_string::lookup(pattern_name_str);
×
1823
                            }
1824
                            break;
×
1825
                        }
1826
                        case log_footer_columns::opid:
1✔
1827
                        case log_footer_columns::user_opid: {
1828
                            if (sqlite3_value_type(argv[lpc]) != SQLITE3_TEXT) {
1✔
1829
                                continue;
×
1830
                            }
1831
                            auto opid = from_sqlite<string_fragment>()(
1✔
1832
                                argc, argv, lpc);
1833
                            if (!log_time_range) {
1✔
1834
                                log_time_range = vtab_time_range{};
1✔
1835
                            }
1836
                            for (const auto& file_data : *vt->lss) {
2✔
1837
                                if (file_data->get_file_ptr() == nullptr) {
1✔
1838
                                    continue;
×
1839
                                }
1840
                                safe::ReadAccess<logfile::safe_opid_state>
1841
                                    r_opid_map(
1842
                                        file_data->get_file_ptr()->get_opids());
1✔
1843
                                const auto& iter
1844
                                    = r_opid_map->los_opid_ranges.find(opid);
1✔
1845
                                if (iter == r_opid_map->los_opid_ranges.end()) {
1✔
1846
                                    continue;
×
1847
                                }
1848
                                log_time_range->add(
1✔
1849
                                    iter->second.otr_range.tr_begin);
1✔
1850
                                log_time_range->add(
1✔
1851
                                    iter->second.otr_range.tr_end);
1✔
1852
                            }
1✔
1853

1854
                            opid_val = opid.bloom_bits();
1✔
1855
                            break;
1✔
1856
                        }
1857
                        case log_footer_columns::path: {
1✔
1858
                            if (sqlite3_value_type(argv[lpc]) != SQLITE3_TEXT) {
1✔
1859
                                continue;
×
1860
                            }
1861

1862
                            const auto filename
1863
                                = from_sqlite<std::string>()(argc, argv, lpc);
1✔
1864
                            const auto fn_constraint
1865
                                = log_cursor::string_constraint{op, filename};
1✔
1866
                            auto found = false;
1✔
1867

1868
                            if (!log_time_range) {
1✔
1869
                                log_time_range = vtab_time_range{};
1✔
1870
                            }
1871
                            for (const auto& file_data : *vt->lss) {
3✔
1872
                                auto* lf = file_data->get_file_ptr();
2✔
1873
                                if (lf == nullptr) {
2✔
1874
                                    continue;
×
1875
                                }
1876
                                if (fn_constraint.matches(lf->get_filename())) {
2✔
1877
                                    found = true;
2✔
1878
                                    log_time_range->add(
4✔
1879
                                        lf->front().get_time<>());
2✔
1880
                                    log_time_range->add(
4✔
1881
                                        lf->back().get_time<>());
4✔
1882
                                }
1883
                            }
1884
                            if (found) {
1✔
1885
                                log_path_constraints.emplace_back(
1✔
1886
                                    fn_constraint);
1887
                            }
1888
                            break;
1✔
1889
                        }
1✔
UNCOV
1890
                        case log_footer_columns::unique_path: {
×
UNCOV
1891
                            if (sqlite3_value_type(argv[lpc]) != SQLITE3_TEXT) {
×
UNCOV
1892
                                continue;
×
1893
                            }
1894

1895
                            const auto filename
1896
                                = from_sqlite<std::string>()(argc, argv, lpc);
×
1897
                            const auto fn_constraint
UNCOV
1898
                                = log_cursor::string_constraint{op, filename};
×
UNCOV
1899
                            auto found = false;
×
1900

UNCOV
1901
                            if (!log_time_range) {
×
1902
                                log_time_range = vtab_time_range{};
×
1903
                            }
UNCOV
1904
                            for (const auto& file_data : *vt->lss) {
×
1905
                                auto* lf = file_data->get_file_ptr();
×
1906
                                if (lf == nullptr) {
×
UNCOV
1907
                                    continue;
×
1908
                                }
1909
                                if (fn_constraint.matches(
×
1910
                                        lf->get_unique_path()))
×
1911
                                {
UNCOV
1912
                                    found = true;
×
1913
                                    log_time_range->add(
×
1914
                                        lf->front().get_time<>());
×
UNCOV
1915
                                    log_time_range->add(
×
1916
                                        lf->back().get_time<>());
×
1917
                                }
1918
                            }
UNCOV
1919
                            if (found) {
×
1920
                                log_unique_path_constraints.emplace_back(
×
1921
                                    fn_constraint);
1922
                            }
UNCOV
1923
                            break;
×
1924
                        }
UNCOV
1925
                        case log_footer_columns::partition:
×
1926
                        case log_footer_columns::actual_time:
1927
                        case log_footer_columns::idle_msecs:
1928
                        case log_footer_columns::mark:
1929
                        case log_footer_columns::sticky_mark:
1930
                        case log_footer_columns::comment:
1931
                        case log_footer_columns::tags:
1932
                        case log_footer_columns::annotations:
1933
                        case log_footer_columns::filters:
1934
                        case log_footer_columns::text:
1935
                        case log_footer_columns::body:
1936
                        case log_footer_columns::raw_text:
1937
                        case log_footer_columns::line_hash:
1938
                        case log_footer_columns::opid_definition:
1939
                        case log_footer_columns::src_file:
1940
                        case log_footer_columns::src_line:
1941
                        case log_footer_columns::duration:
UNCOV
1942
                            break;
×
1943
                        case log_footer_columns::line_link: {
2✔
1944
                            if (sqlite3_value_type(argv[lpc]) != SQLITE3_TEXT) {
2✔
UNCOV
1945
                                continue;
×
1946
                            }
1947
                            auto permalink
1948
                                = from_sqlite<std::string>()(argc, argv, lpc);
2✔
1949
                            auto row_opt = vt->lss->row_for_anchor(permalink);
2✔
1950
                            if (row_opt) {
2✔
1951
                                p_cur->log_cursor.update(
4✔
1952
                                    op,
1953
                                    row_opt.value(),
2✔
1954
                                    log_cursor::constraint_t::unique);
1955
                            } else {
UNCOV
1956
                                log_trace("could not find link: %s",
×
1957
                                          permalink.c_str());
1958
                            }
1959
                            break;
2✔
1960
                        }
2✔
UNCOV
1961
                        case log_footer_columns::thread_id: {
×
UNCOV
1962
                            if (sqlite3_value_type(argv[lpc]) != SQLITE3_TEXT) {
×
UNCOV
1963
                                continue;
×
1964
                            }
UNCOV
1965
                            auto tid = from_sqlite<string_fragment>()(
×
1966
                                argc, argv, lpc);
UNCOV
1967
                            if (!log_time_range) {
×
UNCOV
1968
                                log_time_range = vtab_time_range{};
×
1969
                            }
1970
                            for (const auto& file_data : *vt->lss) {
×
1971
                                if (file_data->get_file_ptr() == nullptr) {
×
UNCOV
1972
                                    continue;
×
1973
                                }
1974
                                safe::ReadAccess<logfile::safe_thread_id_state>
1975
                                    r_tid_map(file_data->get_file_ptr()
1976
                                                  ->get_thread_ids());
×
1977
                                const auto& iter
1978
                                    = r_tid_map->ltis_tid_ranges.find(tid);
×
1979
                                if (iter == r_tid_map->ltis_tid_ranges.end()) {
×
1980
                                    continue;
×
1981
                                }
UNCOV
1982
                                log_time_range->add(
×
UNCOV
1983
                                    iter->second.titr_range.tr_begin);
×
1984
                                log_time_range->add(
×
UNCOV
1985
                                    iter->second.titr_range.tr_end);
×
1986
                            }
1987

1988
                            tid_val = tid.bloom_bits();
×
UNCOV
1989
                            break;
×
1990
                        }
1991
                    }
1992
                } else {
1993
                    const auto* value
1994
                        = (const char*) sqlite3_value_text(argv[lpc]);
214✔
1995

1996
                    if (value != nullptr) {
214✔
1997
                        auto value_len
1998
                            = (size_t) sqlite3_value_bytes(argv[lpc]);
214✔
1999

2000
#ifdef DEBUG_INDEXING
2001
                        log_debug("adding index request for column %d = %s",
2002
                                  col,
2003
                                  value);
2004
#endif
2005

2006
                        p_cur->log_cursor.lc_indexed_columns.emplace_back(
214✔
2007
                            col,
2008
                            log_cursor::string_constraint{
428✔
2009
                                op,
2010
                                std::string{value, value_len},
856✔
2011
                            });
2012
                    }
2013
                }
2014
                break;
221✔
2015
            }
2016
        }
2017
    }
2018

2019
    if (p_cur->log_cursor.lc_curr_line == p_cur->log_cursor.lc_end_line) {
471✔
2020
    } else if (!p_cur->log_cursor.lc_indexed_columns.empty()) {
464✔
2021
        auto min_index_range = msg_range::invalid();
214✔
2022
        auto scan_range
2023
            = msg_range::empty()
214✔
2024
                  .expand_to(p_cur->log_cursor.lc_curr_line)
214✔
2025
                  .expand_to(
214✔
2026
                      p_cur->log_cursor.lc_end_line
2027
                      - (p_cur->log_cursor.lc_direction > 0 ? 1_vl : -1_vl))
214✔
2028
                  .get_valid()
214✔
2029
                  .value();
214✔
2030
        for (const auto& icol : p_cur->log_cursor.lc_indexed_columns) {
19,734✔
2031
            auto& coli = vt->vi->vi_column_indexes[icol.cc_column];
19,520✔
2032
            if (coli.ci_index_generation != vt->lss->lss_index_generation) {
19,520✔
2033
                coli.ci_value_to_lines.clear();
8✔
2034
                coli.ci_index_generation = vt->lss->lss_index_generation;
8✔
2035
                coli.ci_indexed_range = msg_range::empty();
8✔
2036
                coli.ci_string_arena.reset();
8✔
2037
            }
2038

2039
            {
2040
                auto col_valid_opt = coli.ci_indexed_range.get_valid();
19,520✔
2041
                if (col_valid_opt) {
19,520✔
2042
                    log_debug("column %d valid range [%d:%d)",
19,512✔
2043
                              icol.cc_column,
2044
                              (int) col_valid_opt->v_min_line,
2045
                              (int) col_valid_opt->v_max_line);
2046
                }
2047
            }
2048

2049
            min_index_range.intersect(coli.ci_indexed_range);
19,520✔
2050
        }
2051

2052
        for (const auto& icol : p_cur->log_cursor.lc_indexed_columns) {
19,734✔
2053
            const auto& coli = vt->vi->vi_column_indexes[icol.cc_column];
19,520✔
2054

2055
            auto iter
2056
                = coli.ci_value_to_lines.find(icol.cc_constraint.sc_value);
19,520✔
2057
            if (iter != coli.ci_value_to_lines.end()) {
19,520✔
2058
                for (auto vl : iter->second) {
39,180✔
2059
                    if (!scan_range.contains(vl)) {
19,670✔
2060
#ifdef DEBUG_INDEXING
2061
                        log_debug(
2062
                            "indexed line %d is outside of scan range [%d:%d)",
2063
                            vl,
2064
                            scan_range.v_min_line,
2065
                            scan_range.v_max_line);
2066
#endif
2067
                        continue;
10✔
2068
                    }
2069

2070
                    if (!min_index_range.contains(vl)) {
19,660✔
2071
#ifdef DEBUG_INDEXING
2072
                        log_debug(
2073
                            "indexed line %d is outside of the min index range",
2074
                            vl);
2075
#endif
UNCOV
2076
                        continue;
×
2077
                    }
2078

2079
#ifdef DEBUG_INDEXING
2080
                    log_debug("adding indexed line %d", vl);
2081
#endif
2082
                    p_cur->log_cursor.lc_indexed_lines.push_back(vl);
19,660✔
2083
                }
2084
            }
2085
        }
2086
        p_cur->log_cursor.lc_indexed_lines_range = min_index_range;
214✔
2087

2088
#if 0
2089
        if (max_indexed_line && max_indexed_line.value() > 0_vl) {
2090
            p_cur->log_cursor.lc_indexed_lines.push_back(
2091
                max_indexed_line.value());
2092
        }
2093
#endif
2094

2095
        auto index_valid_opt = min_index_range.get_valid();
214✔
2096
        if (!min_index_range.contains(scan_range.v_min_line)
214✔
2097
            || !min_index_range.contains(scan_range.v_max_line - 1_vl))
214✔
2098
        {
2099
            log_debug(
209✔
2100
                "scan needed to populate index, clearing other indexes "
2101
                "scan_range[%d:%d)",
2102
                (int) scan_range.v_min_line,
2103
                (int) scan_range.v_max_line);
2104
            p_cur->log_cursor.lc_level_constraint = std::nullopt;
209✔
2105
            opid_val = std::nullopt;
209✔
2106
            tid_val = std::nullopt;
209✔
2107
            log_time_range = std::nullopt;
209✔
2108
            log_path_constraints.clear();
209✔
2109
            log_unique_path_constraints.clear();
209✔
2110

2111
            if (index_valid_opt) {
209✔
2112
                log_debug("  min_index_range[%d:%d)",
201✔
2113
                          (int) index_valid_opt->v_min_line,
2114
                          (int) index_valid_opt->v_max_line);
2115
                if (scan_range.v_min_line < index_valid_opt->v_min_line
201✔
2116
                    && index_valid_opt->v_max_line < scan_range.v_max_line)
201✔
2117
                {
UNCOV
2118
                    for (const auto& icol :
×
UNCOV
2119
                         p_cur->log_cursor.lc_indexed_columns)
×
2120
                    {
UNCOV
2121
                        vt->vi->vi_column_indexes.erase(icol.cc_column);
×
2122
                    }
UNCOV
2123
                    p_cur->log_cursor.lc_indexed_lines.clear();
×
2124
                    p_cur->log_cursor.lc_indexed_lines_range
UNCOV
2125
                        = msg_range::empty();
×
2126
                } else if (scan_range.v_max_line < index_valid_opt->v_min_line
201✔
2127
                           || scan_range.v_min_line
402✔
2128
                               >= index_valid_opt->v_max_line)
201✔
2129
                {
2130
                    p_cur->log_cursor.lc_indexed_lines.clear();
1✔
2131
                    p_cur->log_cursor.lc_indexed_lines_range
2132
                        = msg_range::empty();
1✔
2133
                    if (p_cur->log_cursor.lc_direction < 0) {
1✔
UNCOV
2134
                        if (scan_range.v_max_line < index_valid_opt->v_min_line)
×
2135
                        {
2136
                            p_cur->log_cursor.lc_curr_line
UNCOV
2137
                                = index_valid_opt->v_min_line - 1_vl;
×
2138
                        } else {
2139
                            p_cur->log_cursor.lc_end_line
UNCOV
2140
                                = index_valid_opt->v_max_line - 1_vl;
×
2141
                        }
2142
                    } else {
2143
                        if (scan_range.v_max_line < index_valid_opt->v_min_line)
1✔
2144
                        {
2145
                            p_cur->log_cursor.lc_end_line
UNCOV
2146
                                = index_valid_opt->v_min_line;
×
2147
                        } else {
2148
                            p_cur->log_cursor.lc_curr_line
2149
                                = index_valid_opt->v_max_line;
1✔
2150
                        }
2151
                    }
2152
                }
2153
            } else {
2154
                log_debug("  min_index_range::empty");
8✔
2155
                p_cur->log_cursor.lc_indexed_lines.clear();
8✔
2156
            }
2157
        } else if (index_valid_opt) {
5✔
2158
            log_info("using existing index over range [%d:%d)",
5✔
2159
                     (int) index_valid_opt->v_min_line,
2160
                     (int) index_valid_opt->v_max_line);
2161
            if (p_cur->log_cursor.lc_direction < 0) {
5✔
2162
                p_cur->log_cursor.lc_indexed_lines.push_back(
4✔
2163
                    index_valid_opt->v_min_line - 1_vl);
8✔
2164
            } else {
2165
                p_cur->log_cursor.lc_indexed_lines.push_back(
2✔
2166
                    index_valid_opt->v_max_line);
1✔
2167
            }
2168
        }
2169

2170
        if (p_cur->log_cursor.lc_direction < 0) {
214✔
2171
            log_debug("ORDER BY is DESC, reversing indexed lines");
8✔
2172
            std::sort(p_cur->log_cursor.lc_indexed_lines.begin(),
8✔
2173
                      p_cur->log_cursor.lc_indexed_lines.end(),
2174
                      std::less<>());
2175
        } else {
2176
            std::sort(p_cur->log_cursor.lc_indexed_lines.begin(),
206✔
2177
                      p_cur->log_cursor.lc_indexed_lines.end(),
2178
                      std::greater<>());
2179
        }
2180

2181
#ifdef DEBUG_INDEXING
2182
        log_debug("indexed lines:");
2183
        for (auto indline : p_cur->log_cursor.lc_indexed_lines) {
2184
            log_debug("  %d", (int) indline);
2185
        }
2186
#endif
2187
    }
214✔
2188

2189
    if (!log_time_range) {
471✔
2190
    } else if (log_time_range->empty()) {
4✔
2191
#ifdef DEBUG_INDEXING
2192
        log_warning("time range is empty");
2193
#endif
UNCOV
2194
        p_cur->log_cursor.lc_curr_line = p_cur->log_cursor.lc_end_line;
×
2195
    } else {
2196
        if (log_time_range->vtr_begin) {
4✔
2197
            auto vl_opt = vt->lss->row_for_time(
4✔
2198
                to_timeval(log_time_range->vtr_begin.value()));
4✔
2199
            if (!vl_opt) {
4✔
2200
#ifdef DEBUG_INDEXING
2201
                log_warning("cannot find row with begin time: %d",
2202
                            log_time_range->vtr_begin.value().count());
2203
#endif
UNCOV
2204
                p_cur->log_cursor.lc_curr_line = p_cur->log_cursor.lc_end_line;
×
2205
            } else {
2206
#ifdef DEBUG_INDEXING
2207
                log_debug("found row with begin time: %d -> %d",
2208
                          log_time_range->vtr_begin.value().count(),
2209
                          vl_opt.value());
2210
#endif
2211
                p_cur->log_cursor.lc_curr_line = vl_opt.value();
4✔
2212
            }
2213
        }
2214
        if (log_time_range->vtr_end) {
4✔
2215
            auto vl_max_opt = vt->lss->row_for_time(
3✔
2216
                to_timeval(log_time_range->vtr_end.value()));
3✔
2217
            if (vl_max_opt) {
3✔
2218
                p_cur->log_cursor.lc_end_line = vl_max_opt.value();
3✔
2219
                auto win = vt->lss->window_at(
3✔
2220
                    vl_max_opt.value(), vis_line_t(vt->lss->text_line_count()));
3✔
2221
                for (const auto& msg_info : *win) {
7✔
2222
                    if (log_time_range->vtr_end.value()
5✔
2223
                        < msg_info.get_logline().get_time<>())
10✔
2224
                    {
2225
                        break;
1✔
2226
                    }
2227
                    p_cur->log_cursor.lc_end_line
2228
                        = msg_info.get_vis_line() + 1_vl;
4✔
2229
                }
3✔
2230
            }
3✔
2231
        }
2232
    }
2233

2234
    p_cur->log_cursor.lc_opid_bloom_bits = opid_val;
471✔
2235
    p_cur->log_cursor.lc_tid_bloom_bits = tid_val;
471✔
2236
    p_cur->log_cursor.lc_log_path = std::move(log_path_constraints);
471✔
2237
    p_cur->log_cursor.lc_unique_path = std::move(log_unique_path_constraints);
471✔
2238

2239
#if 0
2240
    if (p_cur->log_cursor.lc_indexed_lines.empty()) {
2241
        p_cur->log_cursor.lc_indexed_lines.push_back(
2242
            p_cur->log_cursor.lc_curr_line);
2243
    }
2244
#endif
2245
    log_debug("before table filter [%d:%d)",
471✔
2246
              (int) p_cur->log_cursor.lc_curr_line,
2247
              (int) p_cur->log_cursor.lc_end_line);
2248
    vt->vi->filter(p_cur->log_cursor, *vt->lss);
471✔
2249

2250
    log_debug("before initial next [%d:%d)",
471✔
2251
              (int) p_cur->log_cursor.lc_curr_line,
2252
              (int) p_cur->log_cursor.lc_end_line);
2253
    if (vt->base.pModule->xNext != vt_next_no_rowid) {
471✔
2254
        p_cur->log_cursor.lc_curr_line -= p_cur->log_cursor.lc_direction;
458✔
2255
    }
2256
    vt->base.pModule->xNext(p_vtc);
471✔
2257

2258
#ifdef DEBUG_INDEXING
2259
    log_debug("vt_filter() -> cursor_range(%d:%d:%d)",
2260
              (int) p_cur->log_cursor.lc_curr_line,
2261
              (int) p_cur->log_cursor.lc_end_line,
2262
              p_cur->log_cursor.lc_direction);
2263
#endif
2264

2265
    return SQLITE_OK;
471✔
2266
}
471✔
2267

2268
static int
2269
vt_best_index(sqlite3_vtab* tab, sqlite3_index_info* p_info)
272✔
2270
{
2271
    std::vector<sqlite3_index_info::sqlite3_index_constraint> indexes;
272✔
2272
    std::vector<std::string> index_desc;
272✔
2273
    int argvInUse = 0;
272✔
2274
    auto* vt = (log_vtab*) tab;
272✔
2275
    char direction = 1;
272✔
2276

2277
    log_info("vt_best_index(%s, nConstraint=%d, nOrderBy=%d)",
272✔
2278
             vt->vi->get_name().get(),
2279
             p_info->nConstraint,
2280
             p_info->nOrderBy);
2281
    if (p_info->nOrderBy > 0) {
272✔
2282
        log_info("  ORDER BY");
22✔
2283
        for (int i = 0; i < p_info->nOrderBy; i++) {
44✔
2284
            const auto& orderby_info = p_info->aOrderBy[i];
22✔
2285
            log_info("    %d %s",
22✔
2286
                     orderby_info.iColumn,
2287
                     orderby_info.desc ? "DESC" : "ASC");
2288
        }
2289

2290
        if (p_info->aOrderBy[0].iColumn == 0) {
22✔
2291
            if (p_info->aOrderBy[0].desc) {
11✔
2292
                log_info("  consuming ORDER BY log_line DESC");
8✔
2293
                direction = -1;
8✔
2294
            } else {
2295
                log_info("  consuming ORDER BY log_line ASC");
3✔
2296
                direction = 1;
3✔
2297
            }
2298
            p_info->orderByConsumed = 1;
11✔
2299
        }
2300
    }
2301
    if (!vt->vi->vi_supports_indexes) {
272✔
2302
        p_info->orderByConsumed = 0;
10✔
2303
        return SQLITE_OK;
10✔
2304
    }
2305
    for (int lpc = 0; lpc < p_info->nConstraint; lpc++) {
472✔
2306
        const auto& constraint = p_info->aConstraint[lpc];
210✔
2307
        if (!constraint.usable || constraint.op == SQLITE_INDEX_CONSTRAINT_MATCH
210✔
2308
#ifdef SQLITE_INDEX_CONSTRAINT_OFFSET
2309
            || constraint.op == SQLITE_INDEX_CONSTRAINT_OFFSET
188✔
2310
            || constraint.op == SQLITE_INDEX_CONSTRAINT_LIMIT
188✔
2311
#endif
2312
        )
2313
        {
2314
            log_debug("  column %d: is not usable (usable=%d, op: %s)",
39✔
2315
                      lpc,
2316
                      constraint.usable,
2317
                      sql_constraint_op_name(constraint.op));
2318
            continue;
39✔
2319
        }
2320

2321
        auto col = constraint.iColumn;
171✔
2322
        auto op = constraint.op;
171✔
2323
        log_debug("  column %d: op: %s", col, sql_constraint_op_name(op));
171✔
2324
        switch (col) {
171✔
2325
            case VT_COL_LINE_NUMBER: {
94✔
2326
                argvInUse += 1;
94✔
2327
                indexes.push_back(constraint);
94✔
2328
                p_info->aConstraintUsage[lpc].argvIndex = argvInUse;
94✔
2329
                index_desc.emplace_back(fmt::format(
94✔
2330
                    FMT_STRING("log_line {} ?"), sql_constraint_op_name(op)));
282✔
2331
                break;
94✔
2332
            }
2333
            case VT_COL_LOG_TIME: {
2✔
2334
                argvInUse += 1;
2✔
2335
                indexes.push_back(p_info->aConstraint[lpc]);
2✔
2336
                p_info->aConstraintUsage[lpc].argvIndex = argvInUse;
2✔
2337
                index_desc.emplace_back(fmt::format(
2✔
2338
                    FMT_STRING("log_time {} ?"), sql_constraint_op_name(op)));
6✔
2339
                break;
2✔
2340
            }
2341
            case VT_COL_LEVEL: {
8✔
2342
                if (log_cursor::level_constraint::op_is_supported(op)) {
8✔
2343
                    argvInUse += 1;
8✔
2344
                    indexes.push_back(constraint);
8✔
2345
                    p_info->aConstraintUsage[lpc].argvIndex = argvInUse;
8✔
2346
                    index_desc.emplace_back(
8✔
2347
                        fmt::format(FMT_STRING("log_level {} ?"),
24✔
2348
                                    sql_constraint_op_name(op)));
16✔
2349
                }
2350
                break;
8✔
2351
            }
2352
            default: {
67✔
2353
                if (col > (VT_COL_MAX + vt->vi->vi_column_count - 1)) {
67✔
2354
                    auto footer_column = static_cast<log_footer_columns>(
2355
                        col - (VT_COL_MAX + vt->vi->vi_column_count - 1) - 1);
25✔
2356

2357
                    switch (footer_column) {
25✔
2358
                        case log_footer_columns::time_msecs: {
1✔
2359
                            argvInUse += 1;
1✔
2360
                            indexes.push_back(p_info->aConstraint[lpc]);
1✔
2361
                            p_info->aConstraintUsage[lpc].argvIndex = argvInUse;
1✔
2362
                            index_desc.emplace_back(
1✔
2363
                                fmt::format(FMT_STRING("log_time_msecs {} ?"),
3✔
2364
                                            sql_constraint_op_name(op)));
1✔
2365
                            break;
1✔
2366
                        }
2367
                        case log_footer_columns::format: {
3✔
2368
                            if (op == SQLITE_INDEX_CONSTRAINT_EQ) {
3✔
2369
                                argvInUse += 1;
3✔
2370
                                indexes.push_back(constraint);
3✔
2371
                                p_info->aConstraintUsage[lpc].argvIndex
3✔
2372
                                    = argvInUse;
3✔
2373
                                index_desc.emplace_back("log_format = ?");
3✔
2374
                            }
2375
                            break;
3✔
2376
                        }
UNCOV
2377
                        case log_footer_columns::format_regex: {
×
UNCOV
2378
                            if (op == SQLITE_INDEX_CONSTRAINT_EQ) {
×
UNCOV
2379
                                argvInUse += 1;
×
UNCOV
2380
                                indexes.push_back(constraint);
×
UNCOV
2381
                                p_info->aConstraintUsage[lpc].argvIndex
×
UNCOV
2382
                                    = argvInUse;
×
UNCOV
2383
                                index_desc.emplace_back("log_format_regex = ?");
×
2384
                            }
UNCOV
2385
                            break;
×
2386
                        }
2387
                        case log_footer_columns::opid:
1✔
2388
                        case log_footer_columns::user_opid: {
2389
                            if (op == SQLITE_INDEX_CONSTRAINT_EQ) {
1✔
2390
                                argvInUse += 1;
1✔
2391
                                indexes.push_back(constraint);
1✔
2392
                                p_info->aConstraintUsage[lpc].argvIndex
1✔
2393
                                    = argvInUse;
1✔
2394
                                index_desc.emplace_back("log_opid = ?");
1✔
2395
                            }
2396
                            break;
1✔
2397
                        }
2398
                        case log_footer_columns::path: {
2✔
2399
                            argvInUse += 1;
2✔
2400
                            indexes.push_back(constraint);
2✔
2401
                            p_info->aConstraintUsage[lpc].argvIndex = argvInUse;
2✔
2402
                            index_desc.emplace_back(
2✔
2403
                                fmt::format(FMT_STRING("log_path {} ?"),
6✔
2404
                                            sql_constraint_op_name(op)));
2✔
2405
                            break;
2✔
2406
                        }
UNCOV
2407
                        case log_footer_columns::unique_path: {
×
UNCOV
2408
                            argvInUse += 1;
×
UNCOV
2409
                            indexes.push_back(constraint);
×
UNCOV
2410
                            p_info->aConstraintUsage[lpc].argvIndex = argvInUse;
×
UNCOV
2411
                            index_desc.emplace_back(
×
UNCOV
2412
                                fmt::format(FMT_STRING("log_unique_path {} ?"),
×
UNCOV
2413
                                            sql_constraint_op_name(op)));
×
UNCOV
2414
                            break;
×
2415
                        }
2416
                        case log_footer_columns::partition:
16✔
2417
                        case log_footer_columns::actual_time:
2418
                        case log_footer_columns::idle_msecs:
2419
                        case log_footer_columns::mark:
2420
                        case log_footer_columns::sticky_mark:
2421
                        case log_footer_columns::comment:
2422
                        case log_footer_columns::tags:
2423
                        case log_footer_columns::annotations:
2424
                        case log_footer_columns::filters:
2425
                        case log_footer_columns::text:
2426
                        case log_footer_columns::body:
2427
                        case log_footer_columns::raw_text:
2428
                        case log_footer_columns::line_hash:
2429
                        case log_footer_columns::opid_definition:
2430
                        case log_footer_columns::src_file:
2431
                        case log_footer_columns::src_line:
2432
                        case log_footer_columns::duration:
2433
                            break;
16✔
2434
                        case log_footer_columns::line_link: {
2✔
2435
                            argvInUse += 1;
2✔
2436
                            indexes.push_back(constraint);
2✔
2437
                            p_info->aConstraintUsage[lpc].argvIndex = argvInUse;
2✔
2438
                            index_desc.emplace_back("log_line_link = ?");
2✔
2439
                            break;
2✔
2440
                        }
UNCOV
2441
                        case log_footer_columns::thread_id: {
×
UNCOV
2442
                            if (op == SQLITE_INDEX_CONSTRAINT_EQ) {
×
UNCOV
2443
                                argvInUse += 1;
×
UNCOV
2444
                                indexes.push_back(constraint);
×
UNCOV
2445
                                p_info->aConstraintUsage[lpc].argvIndex
×
UNCOV
2446
                                    = argvInUse;
×
UNCOV
2447
                                index_desc.emplace_back("log_thread_id = ?");
×
2448
                            }
UNCOV
2449
                            break;
×
2450
                        }
2451
                    }
2452
                } else if (op == SQLITE_INDEX_CONSTRAINT_EQ) {
42✔
2453
                    argvInUse += 1;
19✔
2454
                    indexes.push_back(constraint);
19✔
2455
                    p_info->aConstraintUsage[lpc].argvIndex = argvInUse;
19✔
2456
                    index_desc.emplace_back(
19✔
2457
                        fmt::format(FMT_STRING("col({}) {} ?"),
57✔
2458
                                    col,
2459
                                    sql_constraint_op_name(op)));
38✔
2460
                }
2461
                break;
67✔
2462
            }
2463
        }
2464
    }
2465

2466
    if (argvInUse) {
262✔
2467
        auto full_desc = fmt::format(FMT_STRING("SEARCH {} USING {}"),
238✔
2468
                                     vt->vi->get_name().get(),
119✔
2469
                                     fmt::join(index_desc, " AND "));
119✔
2470
        log_info("found index: %s", full_desc.c_str());
119✔
2471

2472
        sqlite3_index_info::sqlite3_index_constraint* index_copy;
2473
        auto index_len = indexes.size() * sizeof(*index_copy);
119✔
2474
        size_t len = full_desc.size() + 128 + index_len;
119✔
2475
        auto* storage = sqlite3_malloc(len);
119✔
2476
        if (!storage) {
119✔
UNCOV
2477
            return SQLITE_NOMEM;
×
2478
        }
2479
        auto* desc_storage = static_cast<char*>(storage);
119✔
2480
        memcpy(desc_storage, full_desc.c_str(), full_desc.size() + 1);
119✔
2481
        desc_storage[full_desc.size() + 1] = direction;
119✔
2482
        auto* remaining_storage
2483
            = static_cast<void*>(desc_storage + full_desc.size() + 1 + 1);
119✔
2484
        len -= 1 + full_desc.size() - 1;
119✔
2485
        auto* index_storage
2486
            = std::align(alignof(sqlite3_index_info::sqlite3_index_constraint),
119✔
2487
                         index_len,
2488
                         remaining_storage,
2489
                         len);
2490
        index_copy
2491
            = reinterpret_cast<sqlite3_index_info::sqlite3_index_constraint*>(
119✔
2492
                index_storage);
2493
        log_info("  index storage: %p", index_copy);
119✔
2494
        memcpy(index_copy, &indexes[0], index_len);
119✔
2495
        p_info->idxNum = argvInUse;
119✔
2496
        p_info->idxStr = static_cast<char*>(storage);
119✔
2497
        p_info->needToFreeIdxStr = 1;
119✔
2498
        p_info->estimatedCost = 10.0;
119✔
2499
    } else {
119✔
2500
        static char fullscan_asc[] = "fullscan\0\001";
2501
        static char fullscan_desc[] = "fullscan\0\377";
2502

2503
        p_info->idxStr = direction < 0 ? fullscan_desc : fullscan_asc;
143✔
2504
        p_info->estimatedCost = 1000000000.0;
143✔
2505
    }
2506

2507
    return SQLITE_OK;
262✔
2508
}
272✔
2509

2510
struct parsed_tags {
2511
    std::vector<std::string> pt_tags;
2512
};
2513

2514
static const struct json_path_container tags_handler = {
2515
    json_path_handler("#")
2516
        .with_synopsis("tag")
2517
        .with_description("A tag for the log line")
2518
        .with_pattern(R"(^#[^\s]+$)")
2519
        .for_field(&parsed_tags::pt_tags),
2520
};
2521

2522
static int
2523
vt_update(sqlite3_vtab* tab,
113✔
2524
          int argc,
2525
          sqlite3_value** argv,
2526
          sqlite_int64* rowid_out)
2527
{
2528
    auto* vt = (log_vtab*) tab;
113✔
2529
    int retval = SQLITE_READONLY;
113✔
2530

2531
    if (argc > 1 && sqlite3_value_type(argv[0]) != SQLITE_NULL
112✔
2532
        && sqlite3_value_int64(argv[0]) == sqlite3_value_int64(argv[1]))
225✔
2533
    {
2534
        int64_t rowid = sqlite3_value_int64(argv[0]) >> 8;
112✔
2535
        int val = sqlite3_value_int(
224✔
2536
            argv[2 + vt->footer_index(log_footer_columns::mark)]);
112✔
2537
        int sticky_val = sqlite3_value_int(
224✔
2538
            argv[2 + vt->footer_index(log_footer_columns::sticky_mark)]);
112✔
2539
        vis_line_t vrowid(rowid);
112✔
2540
        auto win = vt->lss->window_at(vrowid);
112✔
2541
        const auto msg_info = *win->begin();
112✔
2542
        const auto* part_name = sqlite3_value_text(
224✔
2543
            argv[2 + vt->footer_index(log_footer_columns::partition)]);
112✔
2544
        const auto* log_comment = sqlite3_value_text(
224✔
2545
            argv[2 + vt->footer_index(log_footer_columns::comment)]);
112✔
UNCOV
2546
        const auto log_tags = from_sqlite<std::optional<string_fragment>>()(
×
2547
            argc, argv, 2 + vt->footer_index(log_footer_columns::tags));
112✔
UNCOV
2548
        const auto log_annos = from_sqlite<std::optional<string_fragment>>()(
×
2549
            argc, argv, 2 + vt->footer_index(log_footer_columns::annotations));
112✔
UNCOV
2550
        auto log_opid = from_sqlite<std::optional<string_fragment>>()(
×
2551
            argc, argv, 2 + vt->footer_index(log_footer_columns::opid));
112✔
2552
        bookmark_metadata tmp_bm;
112✔
2553
        parsed_tags tmp_tags;
112✔
2554

2555
        if (log_tags) {
112✔
2556
            std::vector<lnav::console::user_message> errors;
8✔
2557
            yajlpp_parse_context ypc(vt->vi->get_tags_name(), &tags_handler);
8✔
2558
            auto_mem<yajl_handle_t> handle(yajl_free);
8✔
2559

2560
            handle = yajl_alloc(&ypc.ypc_callbacks, nullptr, &ypc);
8✔
2561
            ypc.ypc_userdata = &errors;
8✔
2562
            ypc.ypc_line_number = log_vtab_data.lvd_location.sl_line_number;
8✔
2563
            ypc.with_handle(handle)
8✔
2564
                .with_error_reporter([](const yajlpp_parse_context& ypc,
16✔
2565
                                        auto msg) {
2566
                    auto& errors = *((std::vector<lnav::console::user_message>*)
2✔
2567
                                         ypc.ypc_userdata);
2568
                    errors.emplace_back(msg);
2✔
2569
                })
2✔
2570
                .with_obj(tmp_tags);
8✔
2571
            ypc.parse_doc(log_tags.value());
8✔
2572
            if (!errors.empty()) {
8✔
2573
                auto top_error
UNCOV
2574
                    = lnav::console::user_message::error(
×
2575
                          attr_line_t("invalid value for ")
2✔
2576
                              .append_quoted("log_tags"_symbol)
2✔
2577
                              .append(" column of table ")
2✔
2578
                              .append_quoted(lnav::roles::symbol(
4✔
2579
                                  vt->vi->get_name().to_string())))
4✔
2580
                          .with_reason(errors[0].to_attr_line(
4✔
2581
                              lnav::console::user_message::render_flags::none))
2582
                          .move();
2✔
2583
                set_vtable_errmsg(tab, top_error);
2✔
2584
                return SQLITE_ERROR;
2✔
2585
            }
2✔
2586
        }
12✔
2587
        if (log_annos) {
110✔
2588
            static const intern_string_t SRC
2589
                = intern_string::lookup("log_annotations");
9✔
2590

2591
            auto parse_res = logmsg_annotations_handlers.parser_for(SRC).of(
6✔
2592
                log_annos.value());
3✔
2593
            if (parse_res.isErr()) {
3✔
2594
                set_vtable_errmsg(tab, parse_res.unwrapErr()[0]);
2✔
2595
                return SQLITE_ERROR;
2✔
2596
            }
2597

2598
            tmp_bm.bm_annotations = parse_res.unwrap();
1✔
2599
        }
3✔
2600

2601
        auto& bv_meta = vt->tc->get_bookmarks()[&textview_curses::BM_META];
108✔
2602
        bool has_meta = log_comment != nullptr || log_tags.has_value()
106✔
2603
            || log_annos.has_value();
214✔
2604

2605
        if (bv_meta.bv_tree.exists(vrowid) && !has_meta) {
108✔
2606
            vt->tc->set_user_mark(&textview_curses::BM_META, vrowid, false);
2✔
2607
            vt->lss->set_line_meta_changed();
2✔
2608
        }
2609

2610
        if (part_name) {
108✔
2611
            auto& line_meta = vt->lss->get_bookmark_metadata(vrowid);
3✔
2612
            line_meta.bm_name = std::string((const char*) part_name);
3✔
2613
            vt->tc->set_user_mark(&textview_curses::BM_PARTITION, vrowid, true);
3✔
2614
        } else {
2615
            vt->tc->set_user_mark(
105✔
2616
                &textview_curses::BM_PARTITION, vrowid, false);
2617
        }
2618

2619
        if (log_opid) {
108✔
2620
            auto& lvv = msg_info.get_values();
85✔
2621
            if (!lvv.lvv_opid_value
85✔
2622
                || lvv.lvv_opid_provenance
85✔
2623
                    == logline_value_vector::opid_provenance::user)
2624
            {
2625
                msg_info.get_file_ptr()->set_logline_opid(
124✔
2626
                    msg_info.get_file_line_number(), log_opid.value());
62✔
2627
                vt->lss->set_line_meta_changed();
62✔
2628
            }
2629
        } else if (msg_info.get_values().lvv_opid_provenance
23✔
2630
                   == logline_value_vector::opid_provenance::user)
23✔
2631
        {
2632
            msg_info.get_file_ptr()->clear_logline_opid(
3✔
2633
                msg_info.get_file_line_number());
2634
            vt->lss->set_line_meta_changed();
3✔
2635
        }
2636

2637
        if (!has_meta && part_name == nullptr
100✔
2638
            && (!log_opid
286✔
2639
                || msg_info.get_values().lvv_opid_provenance
78✔
2640
                    == logline_value_vector::opid_provenance::file))
2641
        {
2642
            vt->lss->erase_bookmark_metadata(vrowid);
35✔
2643
        }
2644

2645
        if (has_meta) {
108✔
2646
            auto& line_meta = vt->lss->get_bookmark_metadata(vrowid);
8✔
2647

2648
            vt->tc->set_user_mark(&textview_curses::BM_META, vrowid, true);
8✔
2649
            if (part_name == nullptr) {
8✔
2650
                line_meta.bm_name.clear();
8✔
2651
            }
2652
            if (log_comment) {
8✔
2653
                line_meta.bm_comment = std::string((const char*) log_comment);
4✔
2654
            } else {
2655
                line_meta.bm_comment.clear();
6✔
2656
            }
2657
            if (log_tags) {
8✔
2658
                line_meta.bm_tags.clear();
6✔
2659
                for (const auto& tag : tmp_tags.pt_tags) {
13✔
2660
                    line_meta.add_tag(tag);
7✔
2661
                }
2662

2663
                for (const auto& entry : line_meta.bm_tags) {
12✔
2664
                    bookmark_metadata::KNOWN_TAGS.insert(entry.te_tag);
6✔
2665
                }
2666
            } else {
2667
                line_meta.bm_tags.clear();
2✔
2668
            }
2669
            if (log_annos) {
8✔
2670
                line_meta.bm_annotations = std::move(tmp_bm.bm_annotations);
1✔
2671
            } else if (!sqlite3_value_nochange(
7✔
2672
                           argv[2
7✔
2673
                                + vt->footer_index(
7✔
2674
                                    log_footer_columns::annotations)]))
7✔
2675
            {
UNCOV
2676
                line_meta.bm_annotations.la_pairs.clear();
×
2677
            }
2678
            vt->lss->set_line_meta_changed();
8✔
2679
        }
2680

2681
        vt->tc->set_user_mark(&textview_curses::BM_USER, vrowid, val);
108✔
2682
        vt->tc->set_user_mark(&textview_curses::BM_STICKY, vrowid, sticky_val);
108✔
2683
        rowid += 1;
108✔
2684
        while ((size_t) rowid < vt->lss->text_line_count()) {
111✔
2685
            vis_line_t vl(rowid);
87✔
2686
            auto cl = vt->lss->at(vl);
87✔
2687
            auto* ll = vt->lss->find_line(cl);
87✔
2688
            if (ll->is_message()) {
87✔
2689
                break;
84✔
2690
            }
2691
            vt->tc->set_user_mark(&textview_curses::BM_USER, vl, val);
3✔
2692
            rowid += 1;
3✔
2693
        }
2694

2695
        if (retval != SQLITE_ERROR) {
108✔
2696
            retval = SQLITE_OK;
108✔
2697
        }
2698
    }
124✔
2699

2700
    return retval;
109✔
2701
}
2702

2703
static const sqlite3_module generic_vtab_module = {
2704
    0, /* iVersion */
2705
    vt_create, /* xCreate       - create a vtable */
2706
    vt_connect, /* xConnect      - associate a vtable with a connection */
2707
    vt_best_index, /* xBestIndex    - best index */
2708
    vt_disconnect, /* xDisconnect   - disassociate a vtable with a connection */
2709
    vt_destroy, /* xDestroy      - destroy a vtable */
2710
    vt_open, /* xOpen         - open a cursor */
2711
    vt_close, /* xClose        - close a cursor */
2712
    vt_filter, /* xFilter       - configure scan constraints */
2713
    vt_next, /* xNext         - advance a cursor */
2714
    vt_eof, /* xEof          - inidicate end of result set*/
2715
    vt_column, /* xColumn       - read data */
2716
    vt_rowid, /* xRowid        - read data */
2717
    vt_update, /* xUpdate       - write data */
2718
    nullptr, /* xBegin        - begin transaction */
2719
    nullptr, /* xSync         - sync transaction */
2720
    nullptr, /* xCommit       - commit transaction */
2721
    nullptr, /* xRollback     - rollback transaction */
2722
    nullptr, /* xFindFunction - function overloading */
2723
};
2724

2725
static const sqlite3_module no_rowid_vtab_module = {
2726
    0, /* iVersion */
2727
    vt_create, /* xCreate       - create a vtable */
2728
    vt_connect, /* xConnect      - associate a vtable with a connection */
2729
    vt_best_index, /* xBestIndex    - best index */
2730
    vt_disconnect, /* xDisconnect   - disassociate a vtable with a connection */
2731
    vt_destroy, /* xDestroy      - destroy a vtable */
2732
    vt_open, /* xOpen         - open a cursor */
2733
    vt_close, /* xClose        - close a cursor */
2734
    vt_filter, /* xFilter       - configure scan constraints */
2735
    vt_next_no_rowid, /* xNext         - advance a cursor */
2736
    vt_eof, /* xEof          - inidicate end of result set*/
2737
    vt_column, /* xColumn       - read data */
2738
    nullptr, /* xRowid        - read data */
2739
    nullptr, /* xUpdate       - write data */
2740
    nullptr, /* xBegin        - begin transaction */
2741
    nullptr, /* xSync         - sync transaction */
2742
    nullptr, /* xCommit       - commit transaction */
2743
    nullptr, /* xRollback     - rollback transaction */
2744
    nullptr, /* xFindFunction - function overloading */
2745
};
2746

2747
static int
2748
progress_callback(void* ptr)
1,238,063✔
2749
{
2750
    int retval = 0;
1,238,063✔
2751

2752
    if (log_vtab_data.lvd_progress != nullptr) {
1,238,063✔
2753
        retval = log_vtab_data.lvd_progress(log_cursor_latest);
11,795✔
2754
    }
2755
    if (!log_vtab_data.lvd_looping) {
1,238,063✔
UNCOV
2756
        retval = 1;
×
2757
    }
2758

2759
    return retval;
1,238,063✔
2760
}
2761

2762
log_vtab_manager::log_vtab_manager(auto_sqlite3& memdb, logfile_sub_source& lss)
924✔
2763
    : vm_db(memdb), vm_source(lss)
924✔
2764
{
2765
    sqlite3_create_module(
924✔
2766
        this->vm_db, "log_vtab_impl", &generic_vtab_module, this);
924✔
2767
    sqlite3_create_module(
924✔
2768
        this->vm_db, "log_vtab_no_rowid_impl", &no_rowid_vtab_module, this);
924✔
2769
    sqlite3_progress_handler(memdb, 32, progress_callback, nullptr);
924✔
2770
}
924✔
2771

2772
log_vtab_manager::~log_vtab_manager()
924✔
2773
{
2774
    while (!this->vm_impls.empty()) {
73,789✔
2775
        auto first_name = this->vm_impls.begin()->first;
72,865✔
2776

2777
        this->unregister_vtab(first_name);
72,865✔
2778
    }
2779
}
924✔
2780

2781
std::string
2782
log_vtab_manager::register_vtab(std::shared_ptr<log_vtab_impl> vi)
73,048✔
2783
{
2784
    std::string retval;
73,048✔
2785

2786
    if (this->vm_impls.find(vi->get_name().to_string_fragment())
73,048✔
2787
        == this->vm_impls.end())
146,096✔
2788
    {
2789
        std::vector<std::string> primary_keys;
73,048✔
2790
        auto_mem<char, sqlite3_free> errmsg;
73,048✔
2791
        auto_mem<char, sqlite3_free> sql;
73,048✔
2792
        int rc;
2793

2794
        this->vm_impls[vi->get_name().to_string_fragment()] = vi;
73,048✔
2795

2796
        vi->get_primary_keys(primary_keys);
73,048✔
2797

2798
        sql = sqlite3_mprintf(
2799
            "CREATE VIRTUAL TABLE lnav_db.%s "
2800
            "USING %s(%s)",
2801
            vi->get_name().get(),
73,048✔
2802
            primary_keys.empty() ? "log_vtab_impl" : "log_vtab_no_rowid_impl",
73,048✔
2803
            vi->get_name().get());
219,144✔
2804
        rc = sqlite3_exec(this->vm_db, sql, nullptr, nullptr, errmsg.out());
73,048✔
2805
        if (rc != SQLITE_OK) {
73,048✔
2806
            retval = errmsg;
1✔
2807
        }
2808
    } else {
73,048✔
UNCOV
2809
        retval = "a table with the given name already exists";
×
2810
    }
2811

2812
    return retval;
73,048✔
UNCOV
2813
}
×
2814

2815
std::string
2816
log_vtab_manager::unregister_vtab(string_fragment name)
73,365✔
2817
{
2818
    std::string retval;
73,365✔
2819

2820
    if (this->vm_impls.find(name) == this->vm_impls.end()) {
73,365✔
2821
        retval = fmt::format(FMT_STRING("unknown table -- {}"), name);
1,268✔
2822
    } else {
2823
        auto_mem<char, sqlite3_free> sql;
73,048✔
2824
        __attribute((unused)) int rc;
2825

2826
        sql = sqlite3_mprintf(
2827
            "DROP TABLE IF EXISTS %.*s", name.length(), name.data());
73,048✔
2828
        log_debug("unregister_vtab: %s", sql.in());
73,048✔
2829
        rc = sqlite3_exec(this->vm_db, sql, nullptr, nullptr, nullptr);
73,048✔
2830

2831
        this->vm_impls.erase(name);
73,048✔
2832
    }
73,048✔
2833

2834
    return retval;
73,365✔
UNCOV
2835
}
×
2836

2837
std::shared_ptr<log_vtab_impl>
2838
log_vtab_manager::lookup_impl(string_fragment name) const
74,064✔
2839
{
2840
    const auto iter = this->vm_impls.find(name);
74,064✔
2841
    if (iter != this->vm_impls.end()) {
74,064✔
2842
        return iter->second;
73,682✔
2843
    }
2844
    return nullptr;
382✔
2845
}
2846

2847
bool
2848
log_vtab_manager::has_log_backed_table(
1,808✔
2849
    const std::set<std::string>& table_names) const
2850
{
2851
    static const std::set<std::string> FIXED_LOG_TABLES = {
2852
        "all_logs",
2853
        "all_logs_vtab",
2854
        "all_opids",
2855
        "all_thread_ids",
2856
    };
1,808✔
2857

2858
    for (const auto& name : table_names) {
2,157✔
2859
        if (FIXED_LOG_TABLES.count(name) > 0) {
513✔
2860
            return true;
164✔
2861
        }
2862
        if (this->lookup_impl(string_fragment::from_str(name)) != nullptr) {
480✔
2863
            return true;
131✔
2864
        }
2865
    }
2866
    return false;
1,644✔
2867
}
2868

2869
log_format_vtab_impl::log_format_vtab_impl(
60,598✔
2870
    std::shared_ptr<const log_format> format)
60,598✔
2871
    : log_vtab_impl(format->get_name()), lfvi_format(format)
60,598✔
2872
{
2873
}
60,598✔
2874

2875
bool
2876
log_format_vtab_impl::next(log_cursor& lc, logfile_sub_source& lss)
50,276✔
2877
{
2878
    if (lc.is_eof()) {
50,276✔
UNCOV
2879
        return true;
×
2880
    }
2881

2882
    auto cl = content_line_t(lss.at(lc.lc_curr_line));
50,276✔
2883
    auto* lf = lss.find_file_ptr(cl);
50,276✔
2884
    auto lf_iter = lf->begin() + cl;
50,276✔
2885

2886
    if (!lf_iter->is_message()) {
50,276✔
UNCOV
2887
        return false;
×
2888
    }
2889

2890
    if (lf->get_format_name() == this->lfvi_format->get_name()) {
50,276✔
2891
        return true;
29,900✔
2892
    }
2893

2894
    return false;
20,376✔
2895
}
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