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

tstack / lnav / 19080893927-2636

04 Nov 2025 07:43PM UTC coverage: 68.931% (+0.03%) from 68.898%
19080893927-2636

push

github

tstack
[tidy] some more cleanup

9 of 29 new or added lines in 5 files covered. (31.03%)

1855 existing lines in 27 files now uncovered.

50572 of 73366 relevant lines covered (68.93%)

428466.05 hits per line

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

85.73
/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 "log_vtab_impl.hh"
31

32
#include "base/ansi_scrubber.hh"
33
#include "base/itertools.hh"
34
#include "base/lnav_log.hh"
35
#include "base/string_util.hh"
36
#include "bookmarks.json.hh"
37
#include "config.h"
38
#include "hasher.hh"
39
#include "lnav_util.hh"
40
#include "logfile_sub_source.hh"
41
#include "logline_window.hh"
42
#include "scn/ranges.h"
43
#include "sql_util.hh"
44
#include "vtab_module.hh"
45
#include "vtab_module_json.hh"
46
#include "yajlpp/json_op.hh"
47
#include "yajlpp/yajlpp_def.hh"
48

49
// #define DEBUG_INDEXING 1
50

51
using namespace lnav::roles::literals;
52

53
static auto intern_lifetime = intern_string::get_table_lifetime();
54

55
static log_cursor log_cursor_latest;
56

57
thread_local _log_vtab_data log_vtab_data;
58

59
const std::unordered_set<string_fragment, frag_hasher>
60
    log_vtab_impl::RESERVED_COLUMNS = {
61
        "log_line"_frag,        "log_time"_frag,        "log_level"_frag,
62
        "log_part"_frag,        "log_actual_time"_frag, "log_idle_msecs"_frag,
63
        "log_mark"_frag,        "log_comment"_frag,     "log_tags"_frag,
64
        "log_annotations"_frag, "log_filters"_frag,     "log_opid"_frag,
65
        "log_user_opid"_frag,   "log_format"_frag,      "log_format_regex"_frag,
66
        "log_time_msecs"_frag,  "log_path"_frag,        "log_unique_path"_frag,
67
        "log_text"_frag,        "log_body"_frag,        "log_raw_text"_frag,
68
        "log_line_hash"_frag,   "log_line_link"_frag,   "log_src_file"_frag,
69
        "log_src_line"_frag,
70
};
71

72
static const char* const LOG_COLUMNS = R"(  (
73
  log_line        INTEGER,                         -- The line number for the log message
74
  log_time        DATETIME,                        -- The adjusted timestamp for the log message
75
  log_level       TEXT     COLLATE loglevel,       -- The log message level
76
  -- BEGIN Format-specific fields:
77
)";
78

79
static const char* const LOG_FOOTER_COLUMNS = R"(
80
  -- END Format-specific fields
81
  log_part         TEXT     COLLATE naturalnocase,    -- The partition the message is in
82
  log_actual_time  DATETIME HIDDEN,                   -- The timestamp from the original log file for this message
83
  log_idle_msecs   INTEGER,                           -- The difference in time between this messages and the previous
84
  log_mark         BOOLEAN,                           -- True if the log message was marked
85
  log_comment      TEXT,                              -- The comment for this message
86
  log_tags         TEXT,                              -- A JSON list of tags for this message
87
  log_annotations  TEXT,                              -- A JSON object of annotations for this messages
88
  log_filters      TEXT,                              -- A JSON list of filter IDs that matched this message
89
  log_opid         TEXT HIDDEN,                       -- The message's OPID from the log message or user
90
  log_user_opid    TEXT HIDDEN,                       -- The message's OPID as set by the user
91
  log_format       TEXT HIDDEN,                       -- The name of the log file format
92
  log_format_regex TEXT HIDDEN,                       -- The name of the regex used to parse this log message
93
  log_time_msecs   INTEGER HIDDEN,                    -- The adjusted timestamp for the log message as the number of milliseconds from the epoch
94
  log_path         TEXT HIDDEN COLLATE naturalnocase, -- The path to the log file this message is from
95
  log_unique_path  TEXT HIDDEN COLLATE naturalnocase, -- The unique portion of the path this message is from
96
  log_text         TEXT HIDDEN,                       -- The full text of the log message
97
  log_body         TEXT HIDDEN,                       -- The body of the log message
98
  log_raw_text     TEXT HIDDEN,                       -- The raw text from the log file
99
  log_line_hash    TEXT HIDDEN,                       -- A hash of the first line of the log message
100
  log_line_link    TEXT HIDDEN,                       -- The permalink for the log message
101
  log_src_file     TEXT HIDDEN,                       -- The source file the log message came from
102
  log_src_line     TEXT HIDDEN                        -- The source line the log message came from
103
)";
104

105
enum class log_footer_columns : uint32_t {
106
    partition,
107
    actual_time,
108
    idle_msecs,
109
    mark,
110
    comment,
111
    tags,
112
    annotations,
113
    filters,
114
    opid,
115
    user_opid,
116
    format,
117
    format_regex,
118
    time_msecs,
119
    path,
120
    unique_path,
121
    text,
122
    body,
123
    raw_text,
124
    line_hash,
125
    line_link,
126
    src_file,
127
    src_line,
128
};
129

130
const std::string&
131
log_vtab_impl::get_table_statement()
52,167✔
132
{
133
    if (this->vi_table_statement.empty()) {
52,167✔
134
        std::vector<vtab_column> cols;
51,977✔
135
        std::ostringstream oss;
51,977✔
136
        size_t max_name_len = 15;
51,977✔
137

138
        oss << "CREATE TABLE " << this->get_name().to_string() << LOG_COLUMNS;
51,977✔
139
        this->get_columns(cols);
51,977✔
140
        this->vi_column_count = cols.size();
51,977✔
141
        for (const auto& col : cols) {
517,823✔
142
            max_name_len = std::max(max_name_len, col.vc_name.length());
465,846✔
143
        }
144
        for (const auto& col : cols) {
517,823✔
145
            std::string comment;
465,846✔
146

147
            require(!col.vc_name.empty());
465,846✔
148

149
            if (!col.vc_comment.empty()) {
465,846✔
150
                comment.append(" -- ").append(col.vc_comment);
54,261✔
151
            }
152

153
            auto colname = sql_quote_ident(col.vc_name.c_str());
465,846✔
154
            auto coldecl = lnav::sql::mprintf(
155
                "  %-*s %-7s %s COLLATE %-15Q,%s\n",
156
                max_name_len,
157
                colname.in(),
158
                sqlite3_type_to_string(col.vc_type),
465,846✔
159
                col.vc_hidden ? "hidden" : "",
465,846✔
160
                col.vc_collator.empty() ? "BINARY" : col.vc_collator.c_str(),
479,444✔
161
                comment.c_str());
945,290✔
162
            oss << coldecl;
465,846✔
163
        }
465,846✔
164
        oss << LOG_FOOTER_COLUMNS;
51,977✔
165

166
        {
167
            std::vector<std::string> primary_keys;
51,977✔
168

169
            this->get_primary_keys(primary_keys);
51,977✔
170
            if (!primary_keys.empty()) {
51,977✔
171
                auto first = true;
7,374✔
172

173
                oss << ", PRIMARY KEY (";
7,374✔
174
                for (const auto& pkey : primary_keys) {
22,122✔
175
                    if (!first) {
14,748✔
176
                        oss << ", ";
7,374✔
177
                    }
178
                    oss << pkey;
14,748✔
179
                    first = false;
14,748✔
180
                }
181
                oss << ")\n";
7,374✔
182
            } else {
183
                oss << ", PRIMARY KEY (log_line)\n";
44,603✔
184
            }
185
        }
51,977✔
186

187
        oss << ");\n";
51,977✔
188

189
        log_trace("log_vtab_impl.get_table_statement() -> %s",
51,977✔
190
                  oss.str().c_str());
191

192
        this->vi_table_statement = oss.str();
51,977✔
193
    }
51,977✔
194

195
    return this->vi_table_statement;
52,167✔
196
}
197

198
std::pair<int, unsigned int>
199
log_vtab_impl::logline_value_to_sqlite_type(value_kind_t kind)
439,078✔
200
{
201
    int type = 0;
439,078✔
202
    unsigned int subtype = 0;
439,078✔
203

204
    switch (kind) {
439,078✔
205
        case value_kind_t::VALUE_JSON:
10,655✔
206
            type = SQLITE3_TEXT;
10,655✔
207
            subtype = JSON_SUBTYPE;
10,655✔
208
            break;
10,655✔
209
        case value_kind_t::VALUE_NULL:
337,525✔
210
        case value_kind_t::VALUE_TEXT:
211
        case value_kind_t::VALUE_STRUCT:
212
        case value_kind_t::VALUE_QUOTED:
213
        case value_kind_t::VALUE_W3C_QUOTED:
214
        case value_kind_t::VALUE_TIMESTAMP:
215
        case value_kind_t::VALUE_XML:
216
            type = SQLITE3_TEXT;
337,525✔
217
            break;
337,525✔
218
        case value_kind_t::VALUE_FLOAT:
6,775✔
219
            type = SQLITE_FLOAT;
6,775✔
220
            break;
6,775✔
221
        case value_kind_t::VALUE_BOOLEAN:
84,123✔
222
        case value_kind_t::VALUE_INTEGER:
223
            type = SQLITE_INTEGER;
84,123✔
224
            break;
84,123✔
225
        case value_kind_t::VALUE_UNKNOWN:
×
226
        case value_kind_t::VALUE__MAX:
227
            ensure(0);
×
228
            break;
229
    }
230
    return std::make_pair(type, subtype);
878,156✔
231
}
232

233
void
234
log_vtab_impl::get_foreign_keys(
43,530✔
235
    std::unordered_set<std::string>& keys_inout) const
236
{
237
    keys_inout.emplace("id");
43,530✔
238
    keys_inout.emplace("parent");
43,530✔
239
    keys_inout.emplace("notused");
43,530✔
240

241
    keys_inout.emplace("log_line");
43,530✔
242
    keys_inout.emplace("min(log_line)");
43,530✔
243
    keys_inout.emplace("log_mark");
43,530✔
244
    keys_inout.emplace("log_time_msecs");
43,530✔
245
    keys_inout.emplace("log_top_line()");
43,530✔
246
    keys_inout.emplace("log_msg_line()");
43,530✔
247
    keys_inout.emplace("log_src_line");
43,530✔
248
    keys_inout.emplace("log_thread_id");
43,530✔
249
}
43,530✔
250

251
void
252
log_vtab_impl::extract(logfile* lf,
29,504✔
253
                       uint64_t line_number,
254
                       string_attrs_t& sa,
255
                       logline_value_vector& values)
256
{
257
    const auto* format = lf->get_format_ptr();
29,504✔
258

259
    format->annotate(lf, line_number, sa, values, false);
29,504✔
260
}
29,504✔
261

262
bool
263
log_vtab_impl::is_valid(log_cursor& lc, logfile_sub_source& lss)
55,000✔
264
{
265
    if (lc.lc_curr_line < 0_vl) {
55,000✔
266
        return false;
×
267
    }
268

269
    content_line_t cl(lss.at(lc.lc_curr_line));
55,000✔
270
    auto* lf = lss.find_file_ptr(cl);
55,000✔
271
    auto lf_iter = lf->begin() + cl;
55,000✔
272

273
    if (!lf_iter->is_message()) {
55,000✔
274
        return false;
366✔
275
    }
276

277
    if (!lc.lc_format_name.empty()
54,634✔
278
        && lc.lc_format_name != lf->get_format_name())
54,634✔
279
    {
280
        return false;
3✔
281
    }
282

283
    if (!lc.lc_pattern_name.empty()
54,631✔
284
        && lc.lc_pattern_name != lf->get_format_ptr()->get_pattern_name(cl))
54,631✔
285
    {
286
        return false;
×
287
    }
288

289
    if (lc.lc_level_constraint
54,631✔
290
        && !lc.lc_level_constraint->matches(lf_iter->get_msg_level()))
54,631✔
291
    {
292
        return false;
206✔
293
    }
294

295
    if (!lc.lc_log_path.empty()) {
54,425✔
296
        if (lf == lc.lc_last_log_path_match) {
4✔
297
        } else if (lf == lc.lc_last_log_path_mismatch) {
2✔
298
            return false;
×
299
        } else {
300
            for (const auto& path_cons : lc.lc_log_path) {
4✔
301
                if (!path_cons.matches(lf->get_filename())) {
2✔
302
                    lc.lc_last_log_path_mismatch = lf;
×
303
                    return false;
×
304
                }
305
            }
306
            lc.lc_last_log_path_match = lf;
2✔
307
        }
308
    }
309

310
    if (!lc.lc_unique_path.empty()) {
54,425✔
311
        if (lf == lc.lc_last_unique_path_match) {
×
312
        } else if (lf == lc.lc_last_unique_path_mismatch) {
×
313
            return false;
×
314
        } else {
315
            for (const auto& path_cons : lc.lc_unique_path) {
×
316
                if (!path_cons.matches(lf->get_unique_path())) {
×
317
                    lc.lc_last_unique_path_mismatch = lf;
×
318
                    return false;
×
319
                }
320
            }
321
            lc.lc_last_unique_path_match = lf;
×
322
        }
323
    }
324

325
    if (lc.lc_opid && !lf_iter->match_opid_hash(lc.lc_opid.value())) {
54,425✔
326
        return false;
×
327
    }
328

329
    return true;
54,425✔
330
}
331

332
struct log_vtab {
333
    sqlite3_vtab base;
334
    sqlite3* db;
335
    textview_curses* tc{nullptr};
336
    logfile_sub_source* lss{nullptr};
337
    std::shared_ptr<log_vtab_impl> vi;
338

339
    size_t footer_index(log_footer_columns col) const
360✔
340
    {
341
        return VT_COL_MAX + this->vi->vi_column_count
360✔
342
            + lnav::enums::to_underlying(col);
360✔
343
    }
344
};
345

346
struct vtab_cursor {
347
    void cache_msg(logfile* lf, logfile::const_iterator ll)
33,183✔
348
    {
349
        if (this->log_msg_line == this->log_cursor.lc_curr_line) {
33,183✔
350
            return;
×
351
        }
352
        auto& sbr = this->line_values.lvv_sbr;
33,183✔
353
        lf->read_full_message(ll,
33,183✔
354
                              sbr,
355
                              this->log_cursor.lc_direction < 0
33,183✔
356
                                  ? line_buffer::scan_direction::backward
357
                                  : line_buffer::scan_direction::forward);
358
        sbr.erase_ansi();
33,183✔
359
        this->log_msg_line = this->log_cursor.lc_curr_line;
33,183✔
360
    }
361

362
    void invalidate()
34,073✔
363
    {
364
        this->attrs.clear();
34,073✔
365
        this->line_values.clear();
34,073✔
366
        this->log_msg_line = -1_vl;
34,073✔
367
    }
34,073✔
368

369
    sqlite3_vtab_cursor base;
370
    struct log_cursor log_cursor;
371
    vis_line_t log_msg_line{-1_vl};
372
    string_attrs_t attrs;
373
    logline_value_vector line_values;
374
};
375

376
static int vt_destructor(sqlite3_vtab* p_svt);
377

378
static int
379
vt_create(sqlite3* db,
52,000✔
380
          void* pAux,
381
          int argc,
382
          const char* const* argv,
383
          sqlite3_vtab** pp_vt,
384
          char** pzErr)
385
{
386
    auto* vm = (log_vtab_manager*) pAux;
52,000✔
387
    int rc = SQLITE_OK;
52,000✔
388
    /* Allocate the sqlite3_vtab/vtab structure itself */
389
    auto p_vt = std::make_unique<log_vtab>();
52,000✔
390

391
    p_vt->db = db;
52,000✔
392

393
    /* Declare the vtable's structure */
394
    p_vt->vi = vm->lookup_impl(intern_string::lookup(argv[3]));
104,000✔
395
    if (p_vt->vi == nullptr) {
52,000✔
396
        return SQLITE_ERROR;
×
397
    }
398
    p_vt->tc = vm->get_view();
52,000✔
399
    p_vt->lss = vm->get_source();
52,000✔
400
    rc = sqlite3_declare_vtab(db, p_vt->vi->get_table_statement().c_str());
52,000✔
401

402
    if (rc == SQLITE_OK) {
52,000✔
403
        /* Success. Set *pp_vt and return */
404
        auto loose_p_vt = p_vt.release();
51,999✔
405
        *pp_vt = &loose_p_vt->base;
51,999✔
406
        log_debug("creating log format table: %s = %p", argv[3], loose_p_vt);
51,999✔
407
    } else {
408
        log_error("sqlite3_declare_vtab(%s) failed: %s",
1✔
409
                  p_vt->vi->get_name().c_str(),
410
                  sqlite3_errmsg(db));
411
    }
412

413
    return rc;
52,000✔
414
}
52,000✔
415

416
static int
417
vt_destructor(sqlite3_vtab* p_svt)
51,999✔
418
{
419
    log_vtab* p_vt = (log_vtab*) p_svt;
51,999✔
420

421
    log_debug("deleting log format table: %p", p_vt);
51,999✔
422

423
    delete p_vt;
51,999✔
424

425
    return SQLITE_OK;
51,999✔
426
}
427

428
static int
429
vt_connect(sqlite3* db,
23✔
430
           void* p_aux,
431
           int argc,
432
           const char* const* argv,
433
           sqlite3_vtab** pp_vt,
434
           char** pzErr)
435
{
436
    return vt_create(db, p_aux, argc, argv, pp_vt, pzErr);
23✔
437
}
438

439
static int
440
vt_disconnect(sqlite3_vtab* pVtab)
23✔
441
{
442
    return vt_destructor(pVtab);
23✔
443
}
444

445
static int
446
vt_destroy(sqlite3_vtab* p_vt)
51,976✔
447
{
448
    return vt_destructor(p_vt);
51,976✔
449
}
450

451
static int vt_next(sqlite3_vtab_cursor* cur);
452

453
static int
454
vt_open(sqlite3_vtab* p_svt, sqlite3_vtab_cursor** pp_cursor)
194✔
455
{
456
    log_vtab* p_vt = (log_vtab*) p_svt;
194✔
457

458
    p_vt->base.zErrMsg = nullptr;
194✔
459

460
    vtab_cursor* p_cur = new vtab_cursor();
194✔
461

462
    *pp_cursor = (sqlite3_vtab_cursor*) p_cur;
194✔
463

464
    p_cur->base.pVtab = p_svt;
194✔
465
    p_cur->log_cursor.lc_opid = std::nullopt;
194✔
466
    p_cur->log_cursor.lc_curr_line = 0_vl;
194✔
467
    p_cur->log_cursor.lc_direction = 1_vl;
194✔
468
    p_cur->log_cursor.lc_end_line = vis_line_t(p_vt->lss->text_line_count());
194✔
469
    p_cur->log_cursor.lc_sub_index = 0;
194✔
470

471
    for (auto& ld : *p_vt->lss) {
399✔
472
        auto* lf = ld->get_file_ptr();
205✔
473

474
        if (lf == nullptr) {
205✔
475
            continue;
×
476
        }
477

478
        lf->enable_cache();
205✔
479
    }
480

481
    return SQLITE_OK;
194✔
482
}
483

484
static int
485
vt_close(sqlite3_vtab_cursor* cur)
194✔
486
{
487
    auto* p_cur = (vtab_cursor*) cur;
194✔
488

489
    /* Free cursor struct. */
490
    delete p_cur;
194✔
491

492
    return SQLITE_OK;
194✔
493
}
494

495
static int
496
vt_eof(sqlite3_vtab_cursor* cur)
34,073✔
497
{
498
    auto* vc = (vtab_cursor*) cur;
34,073✔
499

500
    return vc->log_cursor.is_eof();
34,073✔
501
}
502

503
static void
504
populate_indexed_columns(vtab_cursor* vc, log_vtab* vt)
33,701✔
505
{
506
    if (vc->log_cursor.is_eof() || vc->log_cursor.lc_indexed_columns.empty()) {
33,701✔
507
        return;
2,882✔
508
    }
509

510
    logfile* lf = nullptr;
30,819✔
511

512
    for (const auto& ic : vc->log_cursor.lc_indexed_columns) {
3,254,472✔
513
        auto& ci = vt->vi->vi_column_indexes[ic.cc_column];
3,223,653✔
514
        const auto vl = vc->log_cursor.lc_curr_line;
3,223,653✔
515

516
        if (ci.ci_indexed_range.contains(vl)) {
3,223,653✔
517
            // the index already contains this column, nothing to do
518
            continue;
3,222,558✔
519
        }
520

521
        if (lf == nullptr) {
2,129✔
522
            const auto cl = vt->lss->at(vl);
2,129✔
523
            uint64_t line_number;
524
            auto ld = vt->lss->find_data(cl, line_number);
2,129✔
525
            lf = (*ld)->get_file_ptr();
2,129✔
526
            auto ll = lf->begin() + line_number;
2,129✔
527

528
            vc->cache_msg(lf, ll);
2,129✔
529
            require(vc->line_values.lvv_sbr.get_data() != nullptr);
2,129✔
530
            vt->vi->extract(lf, line_number, vc->attrs, vc->line_values);
2,129✔
531
        }
532

533
        auto sub_col = logline_value_meta::table_column{
534
            (size_t) (ic.cc_column - VT_COL_MAX)};
2,129✔
535
        auto lv_iter = find_if(vc->line_values.lvv_values.begin(),
2,129✔
536
                               vc->line_values.lvv_values.end(),
537
                               logline_value_col_eq(sub_col));
538
        if (lv_iter == vc->line_values.lvv_values.end()
2,129✔
539
            || lv_iter->lv_meta.lvm_kind == value_kind_t::VALUE_NULL)
2,129✔
540
        {
541
            continue;
1,034✔
542
        }
543

544
        auto value = lv_iter->to_string_fragment(ci.ci_string_arena);
1,095✔
545

546
#ifdef DEBUG_INDEXING
547
        log_debug("updated index for column %d %.*s -> %d",
548
                  ic.cc_column,
549
                  value.length(),
550
                  value.data(),
551
                  (int) vc->log_cursor.lc_curr_line);
552
#endif
553

554
        auto& line_deq = ci.ci_value_to_lines[value];
1,095✔
555
        if (line_deq.empty()
1,095✔
556
            || (line_deq.front() != vl && line_deq.back() != vl))
1,095✔
557
        {
558
            if (vc->log_cursor.lc_direction < 0) {
1,095✔
559
                line_deq.push_front(vl);
774✔
560
            } else {
561
                line_deq.push_back(vl);
321✔
562
            }
563
        }
564
    }
565
}
566

567
static int
568
vt_next(sqlite3_vtab_cursor* cur)
33,904✔
569
{
570
    auto* vc = (vtab_cursor*) cur;
33,904✔
571
    auto* vt = (log_vtab*) cur->pVtab;
33,904✔
572
    auto done = false;
33,904✔
573

574
#ifdef DEBUG_INDEXING
575
    log_debug("vt_next([%d:%d:%d])",
576
              vc->log_cursor.lc_curr_line,
577
              vc->log_cursor.lc_end_line,
578
              vc->log_cursor.lc_direction);
579
#endif
580

581
    vc->invalidate();
33,904✔
582
    if (!vc->log_cursor.lc_indexed_lines.empty()
33,904✔
583
        && vc->log_cursor.lc_indexed_lines_range.contains(
33,904✔
584
            vc->log_cursor.lc_curr_line))
585
    {
586
        vc->log_cursor.lc_curr_line = vc->log_cursor.lc_indexed_lines.back();
19,534✔
587
        vc->log_cursor.lc_indexed_lines.pop_back();
19,534✔
588
    } else {
589
        vc->log_cursor.lc_curr_line += vc->log_cursor.lc_direction;
14,370✔
590
    }
591
    vc->log_cursor.lc_sub_index = 0;
33,904✔
592
    do {
593
        log_cursor_latest = vc->log_cursor;
54,336✔
594
        if (((log_cursor_latest.lc_curr_line % 1024) == 0)
54,336✔
595
            && (log_vtab_data.lvd_progress != nullptr
54,660✔
596
                && log_vtab_data.lvd_progress(log_cursor_latest)))
324✔
597
        {
598
            break;
×
599
        }
600

601
        while (vc->log_cursor.lc_curr_line != -1_vl && !vc->log_cursor.is_eof()
109,560✔
602
               && !vt->vi->is_valid(vc->log_cursor, *vt->lss))
109,560✔
603
        {
604
            vc->log_cursor.lc_curr_line += vc->log_cursor.lc_direction;
445✔
605
            vc->log_cursor.lc_sub_index = 0;
445✔
606
        }
607
        if (vc->log_cursor.is_eof()) {
54,336✔
608
            log_info("vt_next at EOF (%d:%d:%d), scanned rows %lu",
372✔
609
                     vc->log_cursor.lc_curr_line,
610
                     vc->log_cursor.lc_end_line,
611
                     vc->log_cursor.lc_direction,
612
                     vc->log_cursor.lc_scanned_rows);
613
            done = true;
372✔
614
        } else {
615
            done = vt->vi->next(vc->log_cursor, *vt->lss);
53,964✔
616
            if (done) {
53,964✔
617
                if (vc->log_cursor.lc_curr_line % 10000 == 0) {
33,532✔
618
                    log_debug("scanned %d", vc->log_cursor.lc_curr_line);
313✔
619
                }
620
#ifdef DEBUG_INDEXING
621
                log_debug("scanned %d", vc->log_cursor.lc_curr_line);
622
#endif
623
                vc->log_cursor.lc_scanned_rows += 1;
33,532✔
624
                populate_indexed_columns(vc, vt);
33,532✔
625
                vt->vi->expand_indexes_to(vc->log_cursor.lc_indexed_columns,
33,532✔
626
                                          vc->log_cursor.lc_curr_line);
627
            } else {
628
                if (!vc->log_cursor.lc_indexed_lines.empty()
20,432✔
629
                    && vc->log_cursor.lc_indexed_lines_range.contains(
20,432✔
630
                        vc->log_cursor.lc_curr_line))
631
                {
632
                    vc->log_cursor.lc_curr_line
633
                        = vc->log_cursor.lc_indexed_lines.back();
×
634
                    vc->log_cursor.lc_indexed_lines.pop_back();
×
635
                } else {
636
                    vc->log_cursor.lc_curr_line += vc->log_cursor.lc_direction;
20,432✔
637
                }
638
                vc->log_cursor.lc_sub_index = 0;
20,432✔
639
            }
640
        }
641
    } while (!done);
54,336✔
642

643
#ifdef DEBUG_INDEXING
644
    log_debug("vt_next() -> [%d:%d:%d]",
645
              vc->log_cursor.lc_curr_line,
646
              vc->log_cursor.lc_end_line,
647
              vc->log_cursor.lc_direction);
648
#endif
649

650
    return SQLITE_OK;
33,904✔
651
}
652

653
static int
654
vt_next_no_rowid(sqlite3_vtab_cursor* cur)
169✔
655
{
656
    auto* vc = (vtab_cursor*) cur;
169✔
657
    auto* vt = (log_vtab*) cur->pVtab;
169✔
658
    auto done = false;
169✔
659

660
    vc->invalidate();
169✔
661
    do {
662
        log_cursor_latest = vc->log_cursor;
760✔
663
        if (((log_cursor_latest.lc_curr_line % 1024) == 0)
760✔
664
            && (log_vtab_data.lvd_progress != nullptr
792✔
665
                && log_vtab_data.lvd_progress(log_cursor_latest)))
32✔
666
        {
667
            break;
×
668
        }
669

670
        auto vl_before = vc->log_cursor.lc_curr_line;
760✔
671
        done = vt->vi->next(vc->log_cursor, *vt->lss);
760✔
672
        if (vl_before != vc->log_cursor.lc_curr_line) {
760✔
673
            vt->vi->expand_indexes_to(vc->log_cursor.lc_indexed_columns,
×
674
                                      vl_before);
675
        }
676
        if (done) {
760✔
677
            populate_indexed_columns(vc, vt);
169✔
678
        } else if (vc->log_cursor.is_eof()) {
591✔
679
            done = true;
×
680
        } else {
681
            require(vc->log_cursor.lc_curr_line
591✔
682
                    < (ssize_t) vt->lss->text_line_count());
683

684
            if (!vc->log_cursor.lc_indexed_lines.empty()
591✔
685
                && vc->log_cursor.lc_indexed_lines_range.contains(
591✔
686
                    vc->log_cursor.lc_curr_line))
687
            {
688
                vt->vi->expand_indexes_to(vc->log_cursor.lc_indexed_columns,
3✔
689
                                          vc->log_cursor.lc_curr_line);
690
                vc->log_cursor.lc_curr_line
691
                    = vc->log_cursor.lc_indexed_lines.back();
3✔
692
                vc->log_cursor.lc_indexed_lines.pop_back();
3✔
693
#ifdef DEBUG_INDEXING
694
                log_debug("going to next line from index %d",
695
                          (int) vc->log_cursor.lc_curr_line);
696
#endif
697
            } else {
698
                vt->vi->expand_indexes_to(vc->log_cursor.lc_indexed_columns,
588✔
699
                                          vc->log_cursor.lc_curr_line);
700
                vc->log_cursor.lc_curr_line += vc->log_cursor.lc_direction;
588✔
701
            }
702
            vc->log_cursor.lc_sub_index = 0;
591✔
703
        }
704
    } while (!done);
760✔
705

706
#ifdef DEBUG_INDEXING
707
    log_debug("vt_next_no_rowid() -> %d:%d",
708
              vc->log_cursor.lc_curr_line,
709
              vc->log_cursor.lc_end_line);
710
#endif
711

712
    return SQLITE_OK;
169✔
713
}
714

715
static int
716
vt_column(sqlite3_vtab_cursor* cur, sqlite3_context* ctx, int col)
84,156✔
717
{
718
    auto* vc = (vtab_cursor*) cur;
84,156✔
719
    auto* vt = (log_vtab*) cur->pVtab;
84,156✔
720

721
#ifdef DEBUG_INDEXING
722
    log_debug("vt_column(%s, %d:%d)",
723
              vt->vi->get_name().get(),
724
              (int) vc->log_cursor.lc_curr_line,
725
              col);
726
#endif
727

728
    content_line_t cl(vt->lss->at(vc->log_cursor.lc_curr_line));
84,156✔
729
    uint64_t line_number;
730
    auto ld = vt->lss->find_data(cl, line_number);
84,156✔
731
    auto* lf = (*ld)->get_file_ptr();
84,156✔
732
    auto ll = lf->begin() + line_number;
84,156✔
733

734
    require(col >= 0);
84,156✔
735

736
    /* Just return the ordinal of the column requested. */
737
    switch (col) {
84,156✔
738
        case VT_COL_LINE_NUMBER: {
2,667✔
739
            sqlite3_result_int64(ctx, vc->log_cursor.lc_curr_line);
2,667✔
740
            break;
2,667✔
741
        }
742

743
        case VT_COL_LOG_TIME: {
375✔
744
            char buffer[64];
745

746
            sql_strftime(buffer, sizeof(buffer), ll->get_timeval());
375✔
747
            sqlite3_result_text(ctx, buffer, strlen(buffer), SQLITE_TRANSIENT);
375✔
748
            break;
375✔
749
        }
750

751
        case VT_COL_LEVEL: {
1,292✔
752
            const auto& level_name = ll->get_level_name();
1,292✔
753

754
            sqlite3_result_text(
1,292✔
755
                ctx, level_name.data(), level_name.length(), SQLITE_STATIC);
756
            break;
1,292✔
757
        }
758

759
        default:
79,822✔
760
            if (col > (VT_COL_MAX + vt->vi->vi_column_count - 1)) {
79,822✔
761
                auto footer_column = static_cast<log_footer_columns>(
762
                    col - (VT_COL_MAX + vt->vi->vi_column_count - 1) - 1);
4,996✔
763

764
                switch (footer_column) {
4,996✔
765
                    case log_footer_columns::partition: {
385✔
766
                        auto& vb = vt->tc->get_bookmarks();
385✔
767
                        const auto& bv = vb[&textview_curses::BM_PARTITION];
385✔
768

769
                        if (bv.empty()) {
385✔
770
                            sqlite3_result_null(ctx);
288✔
771
                        } else {
772
                            vis_line_t curr_line(vc->log_cursor.lc_curr_line);
97✔
773
                            auto iter
774
                                = bv.bv_tree.lower_bound(curr_line + 1_vl);
97✔
775

776
                            if (iter != bv.bv_tree.begin()) {
97✔
777
                                --iter;
69✔
778
                                auto line_meta_opt
779
                                    = vt->lss->find_bookmark_metadata(*iter);
69✔
780
                                if (line_meta_opt
69✔
781
                                    && !line_meta_opt.value()->bm_name.empty())
69✔
782
                                {
783
                                    sqlite3_result_text(
69✔
784
                                        ctx,
785
                                        line_meta_opt.value()->bm_name.c_str(),
69✔
786
                                        line_meta_opt.value()->bm_name.size(),
69✔
787
                                        SQLITE_TRANSIENT);
788
                                } else {
789
                                    sqlite3_result_null(ctx);
×
790
                                }
791
                            } else {
792
                                sqlite3_result_null(ctx);
28✔
793
                            }
794
                        }
795
                        break;
385✔
796
                    }
797
                    case log_footer_columns::actual_time: {
66✔
798
                        char buffer[64] = "";
66✔
799

800
                        if (ll->is_time_skewed()) {
66✔
801
                            if (vc->line_values.lvv_values.empty()) {
2✔
802
                                vc->cache_msg(lf, ll);
2✔
803
                                require(vc->line_values.lvv_sbr.get_data()
2✔
804
                                        != nullptr);
805
                                vt->vi->extract(lf,
2✔
806
                                                line_number,
807
                                                vc->attrs,
2✔
808
                                                vc->line_values);
2✔
809
                            }
810

811
                            struct line_range time_range;
2✔
812

813
                            time_range = find_string_attr_range(vc->attrs,
2✔
814
                                                                &L_TIMESTAMP);
815

816
                            const auto* time_src
817
                                = vc->line_values.lvv_sbr.get_data()
2✔
818
                                + time_range.lr_start;
2✔
819
                            struct timeval actual_tv;
820
                            struct exttm tm;
2✔
821

822
                            if (lf->get_format_ptr()->lf_date_time.scan(
4✔
823
                                    time_src,
824
                                    time_range.length(),
2✔
825
                                    lf->get_format_ptr()
826
                                        ->get_timestamp_formats(),
827
                                    &tm,
828
                                    actual_tv,
829
                                    false))
830
                            {
831
                                sql_strftime(buffer, sizeof(buffer), actual_tv);
2✔
832
                            }
833
                        } else {
834
                            sql_strftime(
64✔
835
                                buffer, sizeof(buffer), ll->get_timeval());
128✔
836
                        }
837
                        sqlite3_result_text(
66✔
838
                            ctx, buffer, strlen(buffer), SQLITE_TRANSIENT);
66✔
839
                        break;
66✔
840
                    }
841
                    case log_footer_columns::idle_msecs: {
284✔
842
                        if (vc->log_cursor.lc_curr_line == 0) {
284✔
843
                            sqlite3_result_int64(ctx, 0);
55✔
844
                        } else {
845
                            content_line_t prev_cl(vt->lss->at(
229✔
846
                                vis_line_t(vc->log_cursor.lc_curr_line - 1)));
229✔
847
                            auto prev_lf = vt->lss->find(prev_cl);
229✔
848
                            auto prev_ll = prev_lf->begin() + prev_cl;
229✔
849

850
                            auto prev_time
851
                                = prev_ll
852
                                      ->get_time<std::chrono::milliseconds>();
229✔
853
                            auto curr_line_time
854
                                = ll->get_time<std::chrono::milliseconds>();
229✔
855
                            // require(curr_line_time >= prev_time);
856
                            sqlite3_result_int64(
229✔
857
                                ctx,
858
                                curr_line_time.count() - prev_time.count());
229✔
859
                        }
229✔
860
                        break;
284✔
861
                    }
862
                    case log_footer_columns::mark: {
282✔
863
                        sqlite3_result_int(ctx, ll->is_marked());
282✔
864
                        break;
282✔
865
                    }
866
                    case log_footer_columns::comment: {
293✔
867
                        auto line_meta_opt = vt->lss->find_bookmark_metadata(
293✔
868
                            vc->log_cursor.lc_curr_line);
869
                        if (!line_meta_opt
293✔
870
                            || line_meta_opt.value()->bm_comment.empty())
293✔
871
                        {
872
                            sqlite3_result_null(ctx);
291✔
873
                        } else {
874
                            const auto& meta = *(line_meta_opt.value());
2✔
875
                            sqlite3_result_text(ctx,
2✔
876
                                                meta.bm_comment.c_str(),
877
                                                meta.bm_comment.length(),
2✔
878
                                                SQLITE_TRANSIENT);
879
                        }
880
                        break;
293✔
881
                    }
882
                    case log_footer_columns::tags: {
688✔
883
                        auto line_meta_opt = vt->lss->find_bookmark_metadata(
688✔
884
                            vc->log_cursor.lc_curr_line);
885
                        if (!line_meta_opt
688✔
886
                            || line_meta_opt.value()->bm_tags.empty())
688✔
887
                        {
888
                            sqlite3_result_null(ctx);
681✔
889
                        } else {
890
                            const auto& meta = *(line_meta_opt.value());
7✔
891

892
                            yajlpp_gen gen;
7✔
893

894
                            yajl_gen_config(gen, yajl_gen_beautify, false);
7✔
895

896
                            {
897
                                yajlpp_array arr(gen);
7✔
898

899
                                for (const auto& str : meta.bm_tags) {
14✔
900
                                    arr.gen(str);
7✔
901
                                }
902
                            }
7✔
903

904
                            to_sqlite(ctx, json_string(gen));
7✔
905
                        }
7✔
906
                        break;
688✔
907
                    }
908
                    case log_footer_columns::annotations: {
296✔
909
                        if (sqlite3_vtab_nochange(ctx)) {
296✔
910
                            return SQLITE_OK;
47✔
911
                        }
912

913
                        auto line_meta_opt = vt->lss->find_bookmark_metadata(
249✔
914
                            vc->log_cursor.lc_curr_line);
915
                        if (!line_meta_opt
249✔
916
                            || line_meta_opt.value()
253✔
917
                                   ->bm_annotations.la_pairs.empty())
4✔
918
                        {
919
                            sqlite3_result_null(ctx);
245✔
920
                        } else {
921
                            const auto& meta = *(line_meta_opt.value());
4✔
922
                            to_sqlite(
4✔
923
                                ctx,
924
                                logmsg_annotations_handlers.to_json_string(
4✔
925
                                    meta.bm_annotations));
4✔
926
                        }
927
                        break;
249✔
928
                    }
929
                    case log_footer_columns::filters: {
284✔
930
                        const auto& filter_mask
931
                            = (*ld)->ld_filter_state.lfo_filter_state.tfs_mask;
284✔
932

933
                        if (!filter_mask[line_number]) {
284✔
934
                            sqlite3_result_null(ctx);
283✔
935
                        } else {
936
                            const auto& filters = vt->lss->get_filters();
1✔
937
                            yajlpp_gen gen;
1✔
938

939
                            yajl_gen_config(gen, yajl_gen_beautify, false);
1✔
940

941
                            {
942
                                yajlpp_array arr(gen);
1✔
943

944
                                for (const auto& filter : filters) {
2✔
945
                                    if (filter->lf_deleted) {
1✔
946
                                        continue;
×
947
                                    }
948

949
                                    uint32_t mask
950
                                        = (1UL << filter->get_index());
1✔
951

952
                                    if (filter_mask[line_number] & mask) {
1✔
953
                                        arr.gen(filter->get_index());
1✔
954
                                    }
955
                                }
956
                            }
1✔
957

958
                            to_sqlite(ctx, gen.to_string_fragment());
1✔
959
                            sqlite3_result_subtype(ctx, JSON_SUBTYPE);
1✔
960
                        }
1✔
961
                        break;
284✔
962
                    }
963
                    case log_footer_columns::opid: {
85✔
964
                        if (vc->line_values.lvv_values.empty()) {
85✔
965
                            vc->cache_msg(lf, ll);
56✔
966
                            require(vc->line_values.lvv_sbr.get_data()
56✔
967
                                    != nullptr);
968
                            vt->vi->extract(
56✔
969
                                lf, line_number, vc->attrs, vc->line_values);
56✔
970
                        }
971

972
                        if (vc->line_values.lvv_opid_value) {
85✔
973
                            to_sqlite(ctx,
43✔
974
                                      vc->line_values.lvv_opid_value.value());
43✔
975
                        } else {
976
                            sqlite3_result_null(ctx);
42✔
977
                        }
978
                        break;
85✔
979
                    }
980
                    case log_footer_columns::user_opid: {
63✔
981
                        if (vc->line_values.lvv_values.empty()) {
63✔
982
                            vc->cache_msg(lf, ll);
11✔
983
                            require(vc->line_values.lvv_sbr.get_data()
11✔
984
                                    != nullptr);
985
                            vt->vi->extract(
11✔
986
                                lf, line_number, vc->attrs, vc->line_values);
11✔
987
                        }
988

989
                        if (vc->line_values.lvv_opid_value
63✔
990
                            && vc->line_values.lvv_opid_provenance
63✔
991
                                == logline_value_vector::opid_provenance::user)
63✔
992
                        {
993
                            to_sqlite(ctx,
×
994
                                      vc->line_values.lvv_opid_value.value());
×
995
                        } else {
996
                            sqlite3_result_null(ctx);
63✔
997
                        }
998
                        break;
63✔
999
                    }
1000
                    case log_footer_columns::format: {
76✔
1001
                        auto format_name = lf->get_format_name();
76✔
1002
                        sqlite3_result_text(ctx,
76✔
1003
                                            format_name.get(),
1004
                                            format_name.size(),
76✔
1005
                                            SQLITE_STATIC);
1006
                        break;
76✔
1007
                    }
1008
                    case log_footer_columns::format_regex: {
53✔
1009
                        auto pat_name = lf->get_format_ptr()->get_pattern_name(
53✔
1010
                            line_number);
1011
                        sqlite3_result_text(ctx,
53✔
1012
                                            pat_name.get(),
1013
                                            pat_name.size(),
53✔
1014
                                            SQLITE_STATIC);
1015
                        break;
53✔
1016
                    }
1017
                    case log_footer_columns::time_msecs: {
1,452✔
1018
                        sqlite3_result_int64(
1,452✔
1019
                            ctx,
1020
                            ll->get_time<std::chrono::milliseconds>().count());
1,452✔
1021
                        break;
1,452✔
1022
                    }
1023
                    case log_footer_columns::path: {
57✔
1024
                        const auto& fn = lf->get_filename();
57✔
1025

1026
                        sqlite3_result_text(ctx,
57✔
1027
                                            fn.c_str(),
1028
                                            fn.native().length(),
57✔
1029
                                            SQLITE_STATIC);
1030
                        break;
57✔
1031
                    }
1032
                    case log_footer_columns::unique_path: {
57✔
1033
                        const auto& fn = lf->get_unique_path();
57✔
1034

1035
                        sqlite3_result_text(ctx,
57✔
1036
                                            fn.c_str(),
1037
                                            fn.native().length(),
57✔
1038
                                            SQLITE_STATIC);
1039
                        break;
57✔
1040
                    }
1041
                    case log_footer_columns::text: {
59✔
1042
                        shared_buffer_ref line;
59✔
1043

1044
                        lf->read_full_message(ll, line);
59✔
1045
                        line.erase_ansi();
59✔
1046
                        sqlite3_result_text(ctx,
59✔
1047
                                            line.get_data(),
1048
                                            line.length(),
59✔
1049
                                            SQLITE_TRANSIENT);
1050
                        break;
59✔
1051
                    }
59✔
1052
                    case log_footer_columns::body: {
218✔
1053
                        if (vc->line_values.lvv_values.empty()) {
218✔
1054
                            vc->cache_msg(lf, ll);
110✔
1055
                            require(vc->line_values.lvv_sbr.get_data()
110✔
1056
                                    != nullptr);
1057
                            vt->vi->extract(
110✔
1058
                                lf, line_number, vc->attrs, vc->line_values);
110✔
1059
                        }
1060

1061
                        auto body_range
1062
                            = find_string_attr_range(vc->attrs, &SA_BODY);
218✔
1063
                        if (!body_range.is_valid()) {
218✔
1064
                            sqlite3_result_null(ctx);
×
1065
                        } else {
1066
                            const char* msg_start
1067
                                = vc->line_values.lvv_sbr.get_data();
218✔
1068

1069
                            sqlite3_result_text(ctx,
218✔
1070
                                                &msg_start[body_range.lr_start],
218✔
1071
                                                body_range.length(),
1072
                                                SQLITE_TRANSIENT);
1073
                        }
1074
                        break;
218✔
1075
                    }
1076
                    case log_footer_columns::raw_text: {
66✔
1077
                        auto read_res = lf->read_raw_message(ll);
66✔
1078

1079
                        if (read_res.isErr()) {
66✔
1080
                            auto msg = fmt::format(
1081
                                FMT_STRING("unable to read line -- {}"),
×
1082
                                read_res.unwrapErr());
×
1083
                            sqlite3_result_error(
×
1084
                                ctx, msg.c_str(), msg.length());
×
1085
                        } else {
×
1086
                            auto sbr = read_res.unwrap();
66✔
1087

1088
                            sqlite3_result_text(ctx,
66✔
1089
                                                sbr.get_data(),
1090
                                                sbr.length(),
66✔
1091
                                                SQLITE_TRANSIENT);
1092
                        }
66✔
1093
                        break;
66✔
1094
                    }
66✔
1095
                    case log_footer_columns::line_hash: {
58✔
1096
                        auto lw
1097
                            = vt->lss->window_at(vc->log_cursor.lc_curr_line);
58✔
1098
                        for (const auto& li : *lw) {
58✔
1099
                            auto hash_res = li.get_line_hash();
58✔
1100
                            if (hash_res.isErr()) {
58✔
1101
                                auto msg = fmt::format(
1102
                                    FMT_STRING("unable to read line -- {}"),
×
1103
                                    hash_res.unwrapErr());
×
1104
                                sqlite3_result_error(
×
1105
                                    ctx, msg.c_str(), msg.length());
×
1106
                            } else {
×
1107
                                to_sqlite(ctx,
58✔
1108
                                          text_auto_buffer{hash_res.unwrap()});
116✔
1109
                            }
1110
                            break;
58✔
1111
                        }
116✔
1112
                        break;
58✔
1113
                    }
58✔
1114
                    case log_footer_columns::line_link: {
58✔
1115
                        auto anchor_opt = vt->lss->anchor_for_row(
58✔
1116
                            vc->log_cursor.lc_curr_line);
58✔
1117
                        if (anchor_opt) {
58✔
1118
                            to_sqlite(ctx, anchor_opt.value());
58✔
1119
                        } else {
1120
                            sqlite3_result_null(ctx);
×
1121
                        }
1122
                        break;
58✔
1123
                    }
58✔
1124
                    case log_footer_columns::src_file: {
58✔
1125
                        if (vc->line_values.lvv_values.empty()) {
58✔
1126
                            vc->cache_msg(lf, ll);
7✔
1127
                            require(vc->line_values.lvv_sbr.get_data()
7✔
1128
                                    != nullptr);
1129
                            vt->vi->extract(
7✔
1130
                                lf, line_number, vc->attrs, vc->line_values);
7✔
1131
                        }
1132

1133
                        auto src_range
1134
                            = find_string_attr_range(vc->attrs, &SA_SRC_FILE);
58✔
1135
                        if (!src_range.is_valid()) {
58✔
1136
                            sqlite3_result_null(ctx);
34✔
1137
                        } else {
1138
                            const char* msg_start
1139
                                = vc->line_values.lvv_sbr.get_data();
24✔
1140

1141
                            sqlite3_result_text(ctx,
24✔
1142
                                                &msg_start[src_range.lr_start],
24✔
1143
                                                src_range.length(),
1144
                                                SQLITE_TRANSIENT);
1145
                        }
1146
                        break;
58✔
1147
                    }
1148
                    case log_footer_columns::src_line: {
58✔
1149
                        if (vc->line_values.lvv_values.empty()) {
58✔
1150
                            vc->cache_msg(lf, ll);
×
1151
                            require(vc->line_values.lvv_sbr.get_data()
×
1152
                                    != nullptr);
1153
                            vt->vi->extract(
×
1154
                                lf, line_number, vc->attrs, vc->line_values);
×
1155
                        }
1156

1157
                        auto src_range
1158
                            = find_string_attr_range(vc->attrs, &SA_SRC_LINE);
58✔
1159
                        if (!src_range.is_valid()) {
58✔
1160
                            sqlite3_result_null(ctx);
34✔
1161
                        } else {
1162
                            const char* msg_start
1163
                                = vc->line_values.lvv_sbr.get_data();
24✔
1164

1165
                            sqlite3_result_text(ctx,
24✔
1166
                                                &msg_start[src_range.lr_start],
24✔
1167
                                                src_range.length(),
1168
                                                SQLITE_TRANSIENT);
1169
                        }
1170
                        break;
58✔
1171
                    }
1172
                }
1173
            } else {
1174
                if (vc->line_values.lvv_values.empty()) {
74,826✔
1175
                    vc->cache_msg(lf, ll);
30,868✔
1176
                    require(vc->line_values.lvv_sbr.get_data() != nullptr);
30,868✔
1177
                    vt->vi->extract(
30,868✔
1178
                        lf, line_number, vc->attrs, vc->line_values);
30,868✔
1179
                }
1180

1181
                auto sub_col = logline_value_meta::table_column{
1182
                    (size_t) (col - VT_COL_MAX)};
74,826✔
1183
                auto lv_iter = find_if(vc->line_values.lvv_values.begin(),
74,826✔
1184
                                       vc->line_values.lvv_values.end(),
1185
                                       logline_value_col_eq(sub_col));
1186

1187
                if (lv_iter != vc->line_values.lvv_values.end()) {
74,826✔
1188
                    if (!lv_iter->lv_meta.lvm_struct_name.empty()) {
74,307✔
1189
                        yajlpp_gen gen;
8✔
1190
                        yajl_gen_config(gen, yajl_gen_beautify, false);
8✔
1191

1192
                        {
1193
                            yajlpp_map root(gen);
8✔
1194

1195
                            for (const auto& lv_struct :
8✔
1196
                                 vc->line_values.lvv_values)
82✔
1197
                            {
1198
                                if (lv_struct.lv_meta.lvm_column != sub_col) {
66✔
1199
                                    continue;
39✔
1200
                                }
1201

1202
                                root.gen(lv_struct.lv_meta.lvm_name);
27✔
1203
                                switch (lv_struct.lv_meta.lvm_kind) {
27✔
1204
                                    case value_kind_t::VALUE_NULL:
2✔
1205
                                        root.gen();
2✔
1206
                                        break;
2✔
1207
                                    case value_kind_t::VALUE_BOOLEAN:
×
1208
                                        root.gen((bool) lv_struct.lv_value.i);
×
1209
                                        break;
×
1210
                                    case value_kind_t::VALUE_INTEGER:
×
1211
                                        root.gen(lv_struct.lv_value.i);
×
1212
                                        break;
×
1213
                                    case value_kind_t::VALUE_FLOAT:
×
1214
                                        root.gen(lv_struct.lv_value.d);
×
1215
                                        break;
×
1216
                                    case value_kind_t::VALUE_JSON: {
1✔
1217
                                        auto_mem<yajl_handle_t> parse_handle(
1218
                                            yajl_free);
1✔
1219
                                        json_ptr jp("");
1✔
1220
                                        json_op jo(jp);
1✔
1221

1222
                                        jo.jo_ptr_callbacks
1223
                                            = json_op::gen_callbacks;
1✔
1224
                                        jo.jo_ptr_data = gen;
1✔
1225
                                        parse_handle.reset(
1✔
1226
                                            yajl_alloc(&json_op::ptr_callbacks,
1227
                                                       nullptr,
1228
                                                       &jo));
1229

1230
                                        const auto* json_in
1231
                                            = (const unsigned char*)
1232
                                                  lv_struct.text_value();
1✔
1233
                                        auto json_len = lv_struct.text_length();
1✔
1234

1235
                                        if (yajl_parse(parse_handle.in(),
1✔
1236
                                                       json_in,
1237
                                                       json_len)
1238
                                                != yajl_status_ok
1239
                                            || yajl_complete_parse(
1✔
1240
                                                   parse_handle.in())
1241
                                                != yajl_status_ok)
1242
                                        {
1243
                                            log_error(
×
1244
                                                "failed to parse json value: "
1245
                                                "%.*s",
1246
                                                lv_struct.text_length(),
1247
                                                lv_struct.text_value());
1248
                                            root.gen(lv_struct.to_string());
×
1249
                                        }
1250
                                        break;
1✔
1251
                                    }
1✔
1252
                                    default:
24✔
1253
                                        root.gen(lv_struct.to_string());
24✔
1254
                                        break;
24✔
1255
                                }
1256
                            }
1257
                        }
8✔
1258

1259
                        auto sf = gen.to_string_fragment();
8✔
1260
                        sqlite3_result_text(
8✔
1261
                            ctx, sf.data(), sf.length(), SQLITE_TRANSIENT);
1262
                        sqlite3_result_subtype(ctx, JSON_SUBTYPE);
8✔
1263
                    } else {
8✔
1264
                        switch (lv_iter->lv_meta.lvm_kind) {
74,299✔
1265
                            case value_kind_t::VALUE_NULL:
2,487✔
1266
                                sqlite3_result_null(ctx);
2,487✔
1267
                                break;
2,487✔
1268
                            case value_kind_t::VALUE_JSON: {
65✔
1269
                                sqlite3_result_text(ctx,
65✔
1270
                                                    lv_iter->text_value(),
1271
                                                    lv_iter->text_length(),
65✔
1272
                                                    SQLITE_TRANSIENT);
1273
                                sqlite3_result_subtype(ctx, JSON_SUBTYPE);
65✔
1274
                                break;
65✔
1275
                            }
1276
                            case value_kind_t::VALUE_STRUCT:
68,306✔
1277
                            case value_kind_t::VALUE_TEXT:
1278
                            case value_kind_t::VALUE_XML:
1279
                            case value_kind_t::VALUE_TIMESTAMP: {
1280
                                sqlite3_result_text(ctx,
68,306✔
1281
                                                    lv_iter->text_value(),
1282
                                                    lv_iter->text_length(),
68,306✔
1283
                                                    SQLITE_TRANSIENT);
1284
                                break;
68,306✔
1285
                            }
1286
                            case value_kind_t::VALUE_W3C_QUOTED:
20✔
1287
                            case value_kind_t::VALUE_QUOTED:
1288
                                if (lv_iter->text_length() == 0) {
20✔
1289
                                    sqlite3_result_text(
×
1290
                                        ctx, "", 0, SQLITE_STATIC);
1291
                                } else {
1292
                                    const char* text_value
1293
                                        = lv_iter->text_value();
20✔
1294
                                    size_t text_len = lv_iter->text_length();
20✔
1295

1296
                                    switch (text_value[0]) {
20✔
1297
                                        case '\'':
7✔
1298
                                        case '"': {
1299
                                            char* val = (char*) sqlite3_malloc(
7✔
1300
                                                text_len);
1301

1302
                                            if (val == nullptr) {
7✔
1303
                                                sqlite3_result_error_nomem(ctx);
×
1304
                                            } else {
1305
                                                auto unquote_func
1306
                                                    = lv_iter->lv_meta.lvm_kind
7✔
1307
                                                        == value_kind_t::
1308
                                                            VALUE_W3C_QUOTED
1309
                                                    ? unquote_w3c
7✔
1310
                                                    : unquote;
7✔
1311

1312
                                                size_t unquoted_len
1313
                                                    = unquote_func(val,
7✔
1314
                                                                   text_value,
1315
                                                                   text_len);
1316
                                                sqlite3_result_text(
7✔
1317
                                                    ctx,
1318
                                                    val,
1319
                                                    unquoted_len,
1320
                                                    sqlite3_free);
1321
                                            }
1322
                                            break;
7✔
1323
                                        }
1324
                                        default: {
13✔
1325
                                            sqlite3_result_text(
13✔
1326
                                                ctx,
1327
                                                text_value,
1328
                                                lv_iter->text_length(),
13✔
1329
                                                SQLITE_TRANSIENT);
1330
                                            break;
13✔
1331
                                        }
1332
                                    }
1333
                                }
1334
                                break;
20✔
1335

1336
                            case value_kind_t::VALUE_BOOLEAN:
2,834✔
1337
                            case value_kind_t::VALUE_INTEGER:
1338
                                sqlite3_result_int64(ctx, lv_iter->lv_value.i);
2,834✔
1339
                                break;
2,834✔
1340

1341
                            case value_kind_t::VALUE_FLOAT:
587✔
1342
                                sqlite3_result_double(ctx, lv_iter->lv_value.d);
587✔
1343
                                break;
587✔
1344

1345
                            case value_kind_t::VALUE_UNKNOWN:
×
1346
                            case value_kind_t::VALUE__MAX:
1347
                                require(0);
×
1348
                                break;
1349
                        }
1350
                    }
1351
                } else {
1352
                    sqlite3_result_null(ctx);
519✔
1353
                }
1354
            }
1355
            break;
79,775✔
1356
    }
1357

1358
    return SQLITE_OK;
84,109✔
1359
}
1360

1361
static int
1362
vt_rowid(sqlite3_vtab_cursor* cur, sqlite_int64* p_rowid)
105✔
1363
{
1364
    vtab_cursor* p_cur = (vtab_cursor*) cur;
105✔
1365

1366
    *p_rowid = (((uint64_t) p_cur->log_cursor.lc_curr_line) << 8)
105✔
1367
        | (p_cur->log_cursor.lc_sub_index & 0xff);
105✔
1368

1369
    return SQLITE_OK;
105✔
1370
}
1371

1372
void
1373
log_cursor::update(unsigned char op, vis_line_t vl, constraint_t cons)
80✔
1374
{
1375
    switch (op) {
80✔
1376
        case SQLITE_INDEX_CONSTRAINT_EQ:
55✔
1377
            if (vl < 0_vl) {
55✔
1378
                this->lc_curr_line = this->lc_end_line;
×
1379
            } else if (vl < this->lc_end_line) {
55✔
1380
                this->lc_curr_line = vl;
50✔
1381
                if (cons == constraint_t::unique) {
50✔
1382
                    this->lc_end_line = this->lc_curr_line + 1_vl;
50✔
1383
                }
1384
            }
1385
            break;
55✔
1386
        case SQLITE_INDEX_CONSTRAINT_GE:
3✔
1387
            if (vl < 0_vl) {
3✔
1388
                vl = 0_vl;
×
1389
            }
1390
            this->lc_curr_line = vl;
3✔
1391
            break;
3✔
1392
        case SQLITE_INDEX_CONSTRAINT_GT:
8✔
1393
            if (vl < 0_vl) {
8✔
1394
                this->lc_curr_line = 0_vl;
2✔
1395
            } else {
1396
                this->lc_curr_line
1397
                    = vl + (cons == constraint_t::unique ? 1_vl : 0_vl);
6✔
1398
            }
1399
            break;
8✔
1400
        case SQLITE_INDEX_CONSTRAINT_LE:
4✔
1401
            if (vl < 0_vl) {
4✔
1402
                this->lc_curr_line = this->lc_end_line;
×
1403
            } else if (vl < this->lc_end_line) {
4✔
1404
                this->lc_end_line
1405
                    = vl + (cons == constraint_t::unique ? 1_vl : 0_vl);
2✔
1406
            }
1407
            break;
4✔
1408
        case SQLITE_INDEX_CONSTRAINT_LT:
10✔
1409
            if (vl <= 0_vl) {
10✔
1410
                this->lc_curr_line = this->lc_end_line;
2✔
1411
            } else if (this->lc_direction > 0) {
8✔
1412
                if (vl < this->lc_end_line) {
×
1413
                    this->lc_end_line = vl;
×
1414
                }
1415
            } else if (this->lc_direction < 0) {
8✔
1416
                if (vl <= this->lc_curr_line) {
8✔
1417
                    this->lc_curr_line = vl - 1_vl;
8✔
1418
                }
1419
            }
1420
            break;
10✔
1421
    }
1422
#ifdef DEBUG_INDEXING
1423
    log_debug("log_cursor::update(%s, %d) -> (%d:%d:%d)",
1424
              sql_constraint_op_name(op),
1425
              vl,
1426
              this->lc_curr_line,
1427
              this->lc_end_line,
1428
              this->lc_direction);
1429
#endif
1430
}
80✔
1431

1432
log_cursor::string_constraint::string_constraint(unsigned char op,
215✔
1433
                                                 std::string value)
215✔
1434
    : sc_op(op), sc_value(std::move(value))
215✔
1435
{
1436
    if (op == SQLITE_INDEX_CONSTRAINT_REGEXP) {
215✔
1437
        auto compile_res = lnav::pcre2pp::code::from(this->sc_value);
×
1438

1439
        if (compile_res.isErr()) {
×
1440
            auto ce = compile_res.unwrapErr();
×
1441
            log_error("unable to compile regexp constraint: %s -- %s",
×
1442
                      this->sc_value.c_str(),
1443
                      ce.get_message().c_str());
1444
        } else {
×
1445
            this->sc_pattern = compile_res.unwrap().to_shared();
×
1446
        }
1447
    }
1448
}
215✔
1449

1450
bool
1451
log_cursor::string_constraint::matches(const std::string& sf) const
4✔
1452
{
1453
    switch (this->sc_op) {
4✔
1454
        case SQLITE_INDEX_CONSTRAINT_EQ:
×
1455
        case SQLITE_INDEX_CONSTRAINT_IS:
1456
            return sf == this->sc_value;
×
1457
        case SQLITE_INDEX_CONSTRAINT_NE:
×
1458
        case SQLITE_INDEX_CONSTRAINT_ISNOT:
1459
            return sf != this->sc_value;
×
1460
        case SQLITE_INDEX_CONSTRAINT_GT:
×
1461
            return sf > this->sc_value;
×
1462
        case SQLITE_INDEX_CONSTRAINT_LE:
×
1463
            return sf <= this->sc_value;
×
1464
        case SQLITE_INDEX_CONSTRAINT_LT:
×
1465
            return sf < this->sc_value;
×
1466
        case SQLITE_INDEX_CONSTRAINT_GE:
×
1467
            return sf >= this->sc_value;
×
1468
        case SQLITE_INDEX_CONSTRAINT_LIKE:
×
1469
            return sqlite3_strlike(this->sc_value.c_str(), sf.data(), 0) == 0;
×
1470
        case SQLITE_INDEX_CONSTRAINT_GLOB:
4✔
1471
            return sqlite3_strglob(this->sc_value.c_str(), sf.data()) == 0;
4✔
1472
        case SQLITE_INDEX_CONSTRAINT_REGEXP: {
×
1473
            if (this->sc_pattern != nullptr) {
×
1474
                return this->sc_pattern->find_in(sf, PCRE2_NO_UTF_CHECK)
×
1475
                    .ignore_error()
×
1476
                    .has_value();
×
1477
            }
1478
            // return true here so that the regexp is actually run and fails
1479
            return true;
×
1480
        }
1481
        case SQLITE_INDEX_CONSTRAINT_ISNOTNULL:
×
1482
            return true;
×
1483
        default:
×
1484
            return false;
×
1485
    }
1486
}
1487

1488
struct vtab_time_range {
1489
    std::optional<std::chrono::microseconds> vtr_begin;
1490
    std::optional<std::chrono::microseconds> vtr_end;
1491

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

1494
    void add(const std::chrono::microseconds& us)
7✔
1495
    {
1496
        if (!this->vtr_begin || us < this->vtr_begin) {
7✔
1497
            this->vtr_begin = us;
3✔
1498
        }
1499
        if (!this->vtr_end || this->vtr_end < us) {
7✔
1500
            this->vtr_end = us;
6✔
1501
        }
1502
    }
7✔
1503
};
1504

1505
static int
1506
vt_filter(sqlite3_vtab_cursor* p_vtc,
401✔
1507
          int idxNum,
1508
          const char* idxStr,
1509
          int argc,
1510
          sqlite3_value** argv)
1511
{
1512
    auto* p_cur = (vtab_cursor*) p_vtc;
401✔
1513
    auto* vt = (log_vtab*) p_vtc->pVtab;
401✔
1514
    sqlite3_index_info::sqlite3_index_constraint* index = nullptr;
401✔
1515

1516
    if (idxStr != nullptr) {
401✔
1517
        auto desc_len = strlen(idxStr);
390✔
1518
        auto index_len = idxNum * sizeof(*index);
390✔
1519
        auto storage_len = desc_len + 128 + index_len;
390✔
1520
        auto direction_storage
390✔
1521
            = static_cast<const char*>(idxStr) + desc_len + 1;
390✔
1522
        p_cur->log_cursor.lc_direction = vis_line_t(direction_storage[0]);
390✔
1523
        auto* remaining_storage = const_cast<void*>(
390✔
1524
            static_cast<const void*>(idxStr + desc_len + 1 + 1));
390✔
1525
        auto* index_storage
1526
            = std::align(alignof(sqlite3_index_info::sqlite3_index_constraint),
390✔
1527
                         index_len,
1528
                         remaining_storage,
1529
                         storage_len);
1530
        index = static_cast<sqlite3_index_info::sqlite3_index_constraint*>(
390✔
1531
            index_storage);
1532
    } else {
1533
        p_cur->log_cursor.lc_direction = 1_vl;
11✔
1534
    }
1535

1536
#ifdef DEBUG_INDEXING
1537
    log_info("vt_filter(%s, %d, direction=%d)",
1538
             vt->vi->get_name().get(),
1539
             idxNum,
1540
             p_cur->log_cursor.lc_direction);
1541
    log_info("  index storage: %p", index);
1542
#endif
1543
    p_cur->log_cursor.lc_format_name.clear();
401✔
1544
    p_cur->log_cursor.lc_pattern_name.clear();
401✔
1545
    p_cur->log_cursor.lc_opid = std::nullopt;
401✔
1546
    p_cur->log_cursor.lc_level_constraint = std::nullopt;
401✔
1547
    p_cur->log_cursor.lc_log_path.clear();
401✔
1548
    p_cur->log_cursor.lc_last_log_path_match = nullptr;
401✔
1549
    p_cur->log_cursor.lc_last_log_path_mismatch = nullptr;
401✔
1550
    p_cur->log_cursor.lc_unique_path.clear();
401✔
1551
    p_cur->log_cursor.lc_last_unique_path_match = nullptr;
401✔
1552
    p_cur->log_cursor.lc_last_unique_path_mismatch = nullptr;
401✔
1553
    if (p_cur->log_cursor.lc_direction < 0) {
401✔
1554
        p_cur->log_cursor.lc_curr_line
1555
            = vis_line_t(vt->lss->text_line_count() - 1);
8✔
1556
        p_cur->log_cursor.lc_end_line = -1_vl;
8✔
1557
    } else {
1558
        p_cur->log_cursor.lc_curr_line = 0_vl;
393✔
1559
        p_cur->log_cursor.lc_end_line = vis_line_t(vt->lss->text_line_count());
393✔
1560
    }
1561
    p_cur->log_cursor.lc_scanned_rows = 0;
401✔
1562
    p_cur->log_cursor.lc_indexed_lines.clear();
401✔
1563
    p_cur->log_cursor.lc_indexed_lines_range = msg_range::empty();
401✔
1564

1565
    std::optional<vtab_time_range> log_time_range;
401✔
1566
    std::optional<uint16_t> opid_val;
401✔
1567
    std::vector<log_cursor::string_constraint> log_path_constraints;
401✔
1568
    std::vector<log_cursor::string_constraint> log_unique_path_constraints;
401✔
1569

1570
    for (int lpc = 0; lpc < idxNum; lpc++) {
708✔
1571
        auto col = index[lpc].iColumn;
307✔
1572
        auto op = index[lpc].op;
307✔
1573
        switch (col) {
307✔
1574
            case VT_COL_LINE_NUMBER: {
78✔
1575
                auto vl = vis_line_t(sqlite3_value_int64(argv[lpc]));
78✔
1576

1577
                p_cur->log_cursor.update(
78✔
1578
                    op, vl, log_cursor::constraint_t::unique);
1579
                break;
78✔
1580
            }
1581
            case VT_COL_LEVEL: {
6✔
1582
                if (sqlite3_value_type(argv[lpc]) != SQLITE3_TEXT) {
6✔
1583
                    continue;
×
1584
                }
1585

1586
                auto sf = from_sqlite<string_fragment>()(argc, argv, lpc);
6✔
1587
                auto level = string2level(sf.data(), sf.length());
6✔
1588

1589
                p_cur->log_cursor.lc_level_constraint
1590
                    = log_cursor::level_constraint{
6✔
1591
                        op,
1592
                        level,
1593
                    };
6✔
1594
                break;
6✔
1595
            }
1596

1597
            case VT_COL_LOG_TIME:
2✔
1598
                if (sqlite3_value_type(argv[lpc]) == SQLITE3_TEXT) {
2✔
1599
                    const auto* datestr
1600
                        = (const char*) sqlite3_value_text(argv[lpc]);
1✔
1601
                    auto datelen = sqlite3_value_bytes(argv[lpc]);
1✔
1602
                    date_time_scanner dts;
1✔
1603
                    timeval tv;
1604
                    exttm mytm;
1✔
1605

1606
                    const auto* date_end
1607
                        = dts.scan(datestr, datelen, nullptr, &mytm, tv);
1✔
1608
                    auto us = to_us(tv);
1✔
1609
                    if (date_end != (datestr + datelen)) {
1✔
1610
                        log_warning(
×
1611
                            "  log_time constraint is not a valid datetime, "
1612
                            "index will not be applied: %s",
1613
                            datestr);
1614
                    } else {
1615
                        switch (op) {
1✔
1616
                            case SQLITE_INDEX_CONSTRAINT_EQ:
×
1617
                            case SQLITE_INDEX_CONSTRAINT_IS:
1618
                                if (!log_time_range) {
×
1619
                                    log_time_range = vtab_time_range{};
×
1620
                                }
1621
                                log_time_range->add(us);
×
1622
                                break;
×
1623
                            case SQLITE_INDEX_CONSTRAINT_GT:
1✔
1624
                            case SQLITE_INDEX_CONSTRAINT_GE:
1625
                                if (!log_time_range) {
1✔
1626
                                    log_time_range = vtab_time_range{};
1✔
1627
                                }
1628
                                log_time_range->vtr_begin = us;
1✔
1629
                                break;
1✔
1630
                            case SQLITE_INDEX_CONSTRAINT_LT:
×
1631
                            case SQLITE_INDEX_CONSTRAINT_LE:
1632
                                if (!log_time_range) {
×
1633
                                    log_time_range = vtab_time_range{};
×
1634
                                }
1635
                                log_time_range->vtr_end = us;
×
1636
                                break;
×
1637
                        }
1638
                    }
1639
                } else {
1640
                    log_warning(
1✔
1641
                        "  log_time constraint is not text, index will not be "
1642
                        "applied: value_type(%d)=%d",
1643
                        lpc,
1644
                        sqlite3_value_type(argv[lpc]));
1645
                }
1646
                break;
2✔
1647
            default: {
221✔
1648
                if (col > (VT_COL_MAX + vt->vi->vi_column_count - 1)) {
221✔
1649
                    auto footer_column = static_cast<log_footer_columns>(
1650
                        col - (VT_COL_MAX + vt->vi->vi_column_count - 1) - 1);
7✔
1651

1652
                    switch (footer_column) {
7✔
1653
                        case log_footer_columns::time_msecs: {
1✔
1654
                            auto msecs = std::chrono::milliseconds{
1655
                                sqlite3_value_int64(argv[lpc])};
1✔
1656
                            switch (op) {
1✔
1657
                                case SQLITE_INDEX_CONSTRAINT_EQ:
1✔
1658
                                case SQLITE_INDEX_CONSTRAINT_IS:
1659
                                    if (!log_time_range) {
1✔
1660
                                        log_time_range = vtab_time_range{};
1✔
1661
                                    }
1662
                                    log_time_range->add(msecs);
1✔
1663
                                    break;
1✔
1664
                                case SQLITE_INDEX_CONSTRAINT_GT:
×
1665
                                case SQLITE_INDEX_CONSTRAINT_GE:
1666
                                    if (!log_time_range) {
×
1667
                                        log_time_range = vtab_time_range{};
×
1668
                                    }
1669
                                    log_time_range->vtr_begin = msecs;
×
1670
                                    break;
×
1671
                                case SQLITE_INDEX_CONSTRAINT_LT:
×
1672
                                case SQLITE_INDEX_CONSTRAINT_LE:
1673
                                    if (!log_time_range) {
×
1674
                                        log_time_range = vtab_time_range{};
×
1675
                                    }
1676
                                    log_time_range->vtr_end = msecs;
×
1677
                                    break;
×
1678
                            }
1679
                            break;
1✔
1680
                        }
1681
                        case log_footer_columns::format: {
2✔
1682
                            const auto* format_name_str
1683
                                = (const char*) sqlite3_value_text(argv[lpc]);
2✔
1684

1685
                            if (format_name_str != nullptr) {
2✔
1686
                                p_cur->log_cursor.lc_format_name
1687
                                    = intern_string::lookup(format_name_str);
4✔
1688
                            }
1689
                            break;
2✔
1690
                        }
1691
                        case log_footer_columns::format_regex: {
×
1692
                            const auto* pattern_name_str
1693
                                = (const char*) sqlite3_value_text(argv[lpc]);
×
1694

1695
                            if (pattern_name_str != nullptr) {
×
1696
                                p_cur->log_cursor.lc_pattern_name
1697
                                    = intern_string::lookup(pattern_name_str);
×
1698
                            }
1699
                            break;
×
1700
                        }
1701
                        case log_footer_columns::opid:
1✔
1702
                        case log_footer_columns::user_opid: {
1703
                            if (sqlite3_value_type(argv[lpc]) != SQLITE3_TEXT) {
1✔
1704
                                continue;
×
1705
                            }
1706
                            auto opid = from_sqlite<string_fragment>()(
1✔
1707
                                argc, argv, lpc);
1708
                            if (!log_time_range) {
1✔
1709
                                log_time_range = vtab_time_range{};
1✔
1710
                            }
1711
                            for (const auto& file_data : *vt->lss) {
2✔
1712
                                if (file_data->get_file_ptr() == nullptr) {
1✔
1713
                                    continue;
×
1714
                                }
1715
                                safe::ReadAccess<logfile::safe_opid_state>
1716
                                    r_opid_map(
1717
                                        file_data->get_file_ptr()->get_opids());
1✔
1718
                                const auto& iter
1719
                                    = r_opid_map->los_opid_ranges.find(opid);
1✔
1720
                                if (iter == r_opid_map->los_opid_ranges.end()) {
1✔
1721
                                    continue;
×
1722
                                }
1723
                                log_time_range->add(
1✔
1724
                                    iter->second.otr_range.tr_begin);
1✔
1725
                                log_time_range->add(
1✔
1726
                                    iter->second.otr_range.tr_end);
1✔
1727
                            }
1✔
1728

1729
                            opid_val = opid.hash();
1✔
1730
                            break;
1✔
1731
                        }
1732
                        case log_footer_columns::path: {
1✔
1733
                            if (sqlite3_value_type(argv[lpc]) != SQLITE3_TEXT) {
1✔
1734
                                continue;
×
1735
                            }
1736

1737
                            const auto filename
1738
                                = from_sqlite<std::string>()(argc, argv, lpc);
1✔
1739
                            const auto fn_constraint
1740
                                = log_cursor::string_constraint{op, filename};
1✔
1741
                            auto found = false;
1✔
1742

1743
                            if (!log_time_range) {
1✔
1744
                                log_time_range = vtab_time_range{};
1✔
1745
                            }
1746
                            for (const auto& file_data : *vt->lss) {
3✔
1747
                                auto* lf = file_data->get_file_ptr();
2✔
1748
                                if (lf == nullptr) {
2✔
1749
                                    continue;
×
1750
                                }
1751
                                if (fn_constraint.matches(lf->get_filename())) {
2✔
1752
                                    found = true;
2✔
1753
                                    log_time_range->add(
4✔
1754
                                        lf->front()
2✔
1755
                                            .get_time<
1756
                                                std::chrono::microseconds>());
2✔
1757
                                    log_time_range->add(
4✔
1758
                                        lf->back()
2✔
1759
                                            .get_time<
1760
                                                std::chrono::microseconds>());
4✔
1761
                                }
1762
                            }
1763
                            if (found) {
1✔
1764
                                log_path_constraints.emplace_back(
1✔
1765
                                    fn_constraint);
1766
                            }
1767
                            break;
1✔
1768
                        }
1✔
1769
                        case log_footer_columns::unique_path: {
×
1770
                            if (sqlite3_value_type(argv[lpc]) != SQLITE3_TEXT) {
×
1771
                                continue;
×
1772
                            }
1773

1774
                            const auto filename
1775
                                = from_sqlite<std::string>()(argc, argv, lpc);
×
1776
                            const auto fn_constraint
1777
                                = log_cursor::string_constraint{op, filename};
×
1778
                            auto found = false;
×
1779

1780
                            if (!log_time_range) {
×
1781
                                log_time_range = vtab_time_range{};
×
1782
                            }
1783
                            for (const auto& file_data : *vt->lss) {
×
1784
                                auto* lf = file_data->get_file_ptr();
×
1785
                                if (lf == nullptr) {
×
1786
                                    continue;
×
1787
                                }
1788
                                if (fn_constraint.matches(
×
1789
                                        lf->get_unique_path()))
×
1790
                                {
1791
                                    found = true;
×
1792
                                    log_time_range->add(
×
1793
                                        lf->front()
×
1794
                                            .get_time<
1795
                                                std::chrono::microseconds>());
×
1796
                                    log_time_range->add(
×
1797
                                        lf->back()
×
1798
                                            .get_time<
1799
                                                std::chrono::microseconds>());
×
1800
                                }
1801
                            }
1802
                            if (found) {
×
1803
                                log_unique_path_constraints.emplace_back(
×
1804
                                    fn_constraint);
1805
                            }
1806
                            break;
×
1807
                        }
1808
                        case log_footer_columns::partition:
×
1809
                        case log_footer_columns::actual_time:
1810
                        case log_footer_columns::idle_msecs:
1811
                        case log_footer_columns::mark:
1812
                        case log_footer_columns::comment:
1813
                        case log_footer_columns::tags:
1814
                        case log_footer_columns::annotations:
1815
                        case log_footer_columns::filters:
1816
                        case log_footer_columns::text:
1817
                        case log_footer_columns::body:
1818
                        case log_footer_columns::raw_text:
1819
                        case log_footer_columns::line_hash:
1820
                        case log_footer_columns::src_file:
1821
                        case log_footer_columns::src_line:
1822
                            break;
×
1823
                        case log_footer_columns::line_link: {
2✔
1824
                            if (sqlite3_value_type(argv[lpc]) != SQLITE3_TEXT) {
2✔
1825
                                continue;
×
1826
                            }
1827
                            auto permalink
1828
                                = from_sqlite<std::string>()(argc, argv, lpc);
2✔
1829
                            auto row_opt = vt->lss->row_for_anchor(permalink);
2✔
1830
                            if (row_opt) {
2✔
1831
                                p_cur->log_cursor.update(
4✔
1832
                                    op,
1833
                                    row_opt.value(),
2✔
1834
                                    log_cursor::constraint_t::unique);
1835
                            } else {
1836
                                log_trace("could not find link: %s",
×
1837
                                          permalink.c_str());
1838
                            }
1839
                            break;
2✔
1840
                        }
2✔
1841
                    }
1842
                } else {
1843
                    const auto* value
1844
                        = (const char*) sqlite3_value_text(argv[lpc]);
214✔
1845

1846
                    if (value != nullptr) {
214✔
1847
                        auto value_len
1848
                            = (size_t) sqlite3_value_bytes(argv[lpc]);
214✔
1849

1850
#ifdef DEBUG_INDEXING
1851
                        log_debug("adding index request for column %d = %s",
1852
                                  col,
1853
                                  value);
1854
#endif
1855

1856
                        p_cur->log_cursor.lc_indexed_columns.emplace_back(
214✔
1857
                            col,
1858
                            log_cursor::string_constraint{
428✔
1859
                                op,
1860
                                std::string{value, value_len},
856✔
1861
                            });
1862
                    }
1863
                }
1864
                break;
221✔
1865
            }
1866
        }
1867
    }
1868

1869
    if (p_cur->log_cursor.lc_curr_line == p_cur->log_cursor.lc_end_line) {
401✔
1870
    } else if (!p_cur->log_cursor.lc_indexed_columns.empty()) {
396✔
1871
        auto min_index_range = msg_range::invalid();
214✔
1872
        auto scan_range
1873
            = msg_range::empty()
214✔
1874
                  .expand_to(p_cur->log_cursor.lc_curr_line)
214✔
1875
                  .expand_to(
214✔
1876
                      p_cur->log_cursor.lc_end_line
1877
                      - (p_cur->log_cursor.lc_direction > 0 ? 1_vl : -1_vl))
214✔
1878
                  .get_valid()
214✔
1879
                  .value();
214✔
1880
        for (const auto& icol : p_cur->log_cursor.lc_indexed_columns) {
19,734✔
1881
            auto& coli = vt->vi->vi_column_indexes[icol.cc_column];
19,520✔
1882
            if (coli.ci_index_generation != vt->lss->lss_index_generation) {
19,520✔
1883
                coli.ci_value_to_lines.clear();
8✔
1884
                coli.ci_index_generation = vt->lss->lss_index_generation;
8✔
1885
                coli.ci_indexed_range = msg_range::empty();
8✔
1886
                coli.ci_string_arena.reset();
8✔
1887
            }
1888

1889
            {
1890
                auto col_valid_opt = coli.ci_indexed_range.get_valid();
19,520✔
1891
                if (col_valid_opt) {
19,520✔
1892
                    log_debug("column %d valid range [%d:%d)",
19,512✔
1893
                              icol.cc_column,
1894
                              col_valid_opt->v_min_line,
1895
                              col_valid_opt->v_max_line);
1896
                }
1897
            }
1898

1899
            min_index_range.intersect(coli.ci_indexed_range);
19,520✔
1900
        }
1901

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

1905
            auto iter
1906
                = coli.ci_value_to_lines.find(icol.cc_constraint.sc_value);
19,520✔
1907
            if (iter != coli.ci_value_to_lines.end()) {
19,520✔
1908
                for (auto vl : iter->second) {
39,180✔
1909
                    if (!scan_range.contains(vl)) {
19,670✔
1910
#ifdef DEBUG_INDEXING
1911
                        log_debug(
1912
                            "indexed line %d is outside of scan range [%d:%d)",
1913
                            vl,
1914
                            scan_range.v_min_line,
1915
                            scan_range.v_max_line);
1916
#endif
1917
                        continue;
10✔
1918
                    }
1919

1920
                    if (!min_index_range.contains(vl)) {
19,660✔
1921
#ifdef DEBUG_INDEXING
1922
                        log_debug(
1923
                            "indexed line %d is outside of the min index range",
1924
                            vl);
1925
#endif
1926
                        continue;
×
1927
                    }
1928

1929
#ifdef DEBUG_INDEXING
1930
                    log_debug("adding indexed line %d", vl);
1931
#endif
1932
                    p_cur->log_cursor.lc_indexed_lines.push_back(vl);
19,660✔
1933
                }
1934
            }
1935
        }
1936
        p_cur->log_cursor.lc_indexed_lines_range = min_index_range;
214✔
1937

1938
#if 0
1939
        if (max_indexed_line && max_indexed_line.value() > 0_vl) {
1940
            p_cur->log_cursor.lc_indexed_lines.push_back(
1941
                max_indexed_line.value());
1942
        }
1943
#endif
1944

1945
        auto index_valid_opt = min_index_range.get_valid();
214✔
1946
        if (!min_index_range.contains(scan_range.v_min_line)
214✔
1947
            || !min_index_range.contains(scan_range.v_max_line - 1_vl))
214✔
1948
        {
1949
            log_debug(
209✔
1950
                "scan needed to populate index, clearing other indexes "
1951
                "scan_range[%d:%d)",
1952
                scan_range.v_min_line,
1953
                scan_range.v_max_line);
1954
            p_cur->log_cursor.lc_level_constraint = std::nullopt;
209✔
1955
            opid_val = std::nullopt;
209✔
1956
            log_time_range = std::nullopt;
209✔
1957
            log_path_constraints.clear();
209✔
1958
            log_unique_path_constraints.clear();
209✔
1959

1960
            if (index_valid_opt) {
209✔
1961
                log_debug("  min_index_range[%d:%d)",
201✔
1962
                          index_valid_opt->v_min_line,
1963
                          index_valid_opt->v_max_line);
1964
                if (scan_range.v_min_line < index_valid_opt->v_min_line
201✔
1965
                    && index_valid_opt->v_max_line < scan_range.v_max_line)
201✔
1966
                {
1967
                    for (const auto& icol :
×
1968
                         p_cur->log_cursor.lc_indexed_columns)
×
1969
                    {
1970
                        vt->vi->vi_column_indexes.erase(icol.cc_column);
×
1971
                    }
1972
                    p_cur->log_cursor.lc_indexed_lines.clear();
×
1973
                    p_cur->log_cursor.lc_indexed_lines_range
1974
                        = msg_range::empty();
×
1975
                } else if (scan_range.v_max_line < index_valid_opt->v_min_line
201✔
1976
                           || scan_range.v_min_line
402✔
1977
                               >= index_valid_opt->v_max_line)
201✔
1978
                {
1979
                    p_cur->log_cursor.lc_indexed_lines.clear();
1✔
1980
                    p_cur->log_cursor.lc_indexed_lines_range
1981
                        = msg_range::empty();
1✔
1982
                    if (p_cur->log_cursor.lc_direction < 0) {
1✔
1983
                        if (scan_range.v_max_line < index_valid_opt->v_min_line)
×
1984
                        {
1985
                            p_cur->log_cursor.lc_curr_line
1986
                                = index_valid_opt->v_min_line - 1_vl;
×
1987
                        } else {
1988
                            p_cur->log_cursor.lc_end_line
1989
                                = index_valid_opt->v_max_line - 1_vl;
×
1990
                        }
1991
                    } else {
1992
                        if (scan_range.v_max_line < index_valid_opt->v_min_line)
1✔
1993
                        {
1994
                            p_cur->log_cursor.lc_end_line
1995
                                = index_valid_opt->v_min_line;
×
1996
                        } else {
1997
                            p_cur->log_cursor.lc_curr_line
1998
                                = index_valid_opt->v_max_line;
1✔
1999
                        }
2000
                    }
2001
                }
2002
            } else {
2003
                log_debug("  min_index_range::empty");
8✔
2004
                p_cur->log_cursor.lc_indexed_lines.clear();
8✔
2005
            }
2006
        } else if (index_valid_opt) {
5✔
2007
            log_info("using existing index over range [%d:%d)",
5✔
2008
                     index_valid_opt->v_min_line,
2009
                     index_valid_opt->v_max_line);
2010
            if (p_cur->log_cursor.lc_direction < 0) {
5✔
2011
                p_cur->log_cursor.lc_indexed_lines.push_back(
4✔
2012
                    index_valid_opt->v_min_line - 1_vl);
8✔
2013
            } else {
2014
                p_cur->log_cursor.lc_indexed_lines.push_back(
2✔
2015
                    index_valid_opt->v_max_line);
1✔
2016
            }
2017
        }
2018

2019
        if (p_cur->log_cursor.lc_direction < 0) {
214✔
2020
            log_debug("ORDER BY is DESC, reversing indexed lines");
8✔
2021
            std::sort(p_cur->log_cursor.lc_indexed_lines.begin(),
8✔
2022
                      p_cur->log_cursor.lc_indexed_lines.end(),
2023
                      std::less<>());
2024
        } else {
2025
            std::sort(p_cur->log_cursor.lc_indexed_lines.begin(),
206✔
2026
                      p_cur->log_cursor.lc_indexed_lines.end(),
2027
                      std::greater<>());
2028
        }
2029

2030
#ifdef DEBUG_INDEXING
2031
        log_debug("indexed lines:");
2032
        for (auto indline : p_cur->log_cursor.lc_indexed_lines) {
2033
            log_debug("  %d", (int) indline);
2034
        }
2035
#endif
2036
    }
214✔
2037

2038
    if (!log_time_range) {
401✔
2039
    } else if (log_time_range->empty()) {
4✔
2040
#ifdef DEBUG_INDEXING
2041
        log_warning("time range is empty");
2042
#endif
2043
        p_cur->log_cursor.lc_curr_line = p_cur->log_cursor.lc_end_line;
×
2044
    } else {
2045
        if (log_time_range->vtr_begin) {
4✔
2046
            auto vl_opt = vt->lss->row_for_time(
4✔
2047
                to_timeval(log_time_range->vtr_begin.value()));
4✔
2048
            if (!vl_opt) {
4✔
2049
#ifdef DEBUG_INDEXING
2050
                log_warning("cannot find row with begin time: %d",
2051
                            log_time_range->vtr_begin.value().tv_sec);
2052
#endif
2053
                p_cur->log_cursor.lc_curr_line = p_cur->log_cursor.lc_end_line;
×
2054
            } else {
2055
#ifdef DEBUG_INDEXING
2056
                log_debug("found row with begin time: %d -> %d",
2057
                          log_time_range->vtr_begin.value(),
2058
                          vl_opt.value());
2059
#endif
2060
                p_cur->log_cursor.lc_curr_line = vl_opt.value();
4✔
2061
            }
2062
        }
2063
        if (log_time_range->vtr_end) {
4✔
2064
            auto vl_max_opt = vt->lss->row_for_time(
3✔
2065
                to_timeval(log_time_range->vtr_end.value()));
3✔
2066
            if (vl_max_opt) {
3✔
2067
                p_cur->log_cursor.lc_end_line = vl_max_opt.value();
3✔
2068
                auto win = vt->lss->window_at(
3✔
2069
                    vl_max_opt.value(), vis_line_t(vt->lss->text_line_count()));
3✔
2070
                for (const auto& msg_info : *win) {
7✔
2071
                    if (log_time_range->vtr_end.value()
5✔
2072
                        < msg_info.get_logline()
10✔
2073
                              .get_time<std::chrono::microseconds>())
10✔
2074
                    {
2075
                        break;
1✔
2076
                    }
2077
                    p_cur->log_cursor.lc_end_line
2078
                        = msg_info.get_vis_line() + 1_vl;
4✔
2079
                }
3✔
2080
            }
3✔
2081
        }
2082
    }
2083

2084
    p_cur->log_cursor.lc_opid = opid_val;
401✔
2085
    p_cur->log_cursor.lc_log_path = std::move(log_path_constraints);
401✔
2086
    p_cur->log_cursor.lc_unique_path = std::move(log_unique_path_constraints);
401✔
2087

2088
#if 0
2089
    if (p_cur->log_cursor.lc_indexed_lines.empty()) {
2090
        p_cur->log_cursor.lc_indexed_lines.push_back(
2091
            p_cur->log_cursor.lc_curr_line);
2092
    }
2093
#endif
2094
    log_debug("before table filter [%d:%d)",
401✔
2095
              p_cur->log_cursor.lc_curr_line,
2096
              p_cur->log_cursor.lc_end_line);
2097
    vt->vi->filter(p_cur->log_cursor, *vt->lss);
401✔
2098

2099
    log_debug("before initial next [%d:%d)",
401✔
2100
              p_cur->log_cursor.lc_curr_line,
2101
              p_cur->log_cursor.lc_end_line);
2102
    if (vt->base.pModule->xNext != vt_next_no_rowid) {
401✔
2103
        p_cur->log_cursor.lc_curr_line -= p_cur->log_cursor.lc_direction;
388✔
2104
    }
2105
    vt->base.pModule->xNext(p_vtc);
401✔
2106

2107
#ifdef DEBUG_INDEXING
2108
    log_debug("vt_filter() -> cursor_range(%d:%d:%d)",
2109
              (int) p_cur->log_cursor.lc_curr_line,
2110
              (int) p_cur->log_cursor.lc_end_line,
2111
              p_cur->log_cursor.lc_direction);
2112
#endif
2113

2114
    return SQLITE_OK;
401✔
2115
}
401✔
2116

2117
static int
2118
vt_best_index(sqlite3_vtab* tab, sqlite3_index_info* p_info)
214✔
2119
{
2120
    std::vector<sqlite3_index_info::sqlite3_index_constraint> indexes;
214✔
2121
    std::vector<std::string> index_desc;
214✔
2122
    int argvInUse = 0;
214✔
2123
    auto* vt = (log_vtab*) tab;
214✔
2124
    char direction = 1;
214✔
2125

2126
    log_info("vt_best_index(%s, nConstraint=%d, nOrderBy=%d)",
214✔
2127
             vt->vi->get_name().get(),
2128
             p_info->nConstraint,
2129
             p_info->nOrderBy);
2130
    if (p_info->nOrderBy > 0) {
214✔
2131
        log_info("  ORDER BY");
19✔
2132
        for (int i = 0; i < p_info->nOrderBy; i++) {
38✔
2133
            const auto& orderby_info = p_info->aOrderBy[i];
19✔
2134
            log_info("    %d %s",
19✔
2135
                     orderby_info.iColumn,
2136
                     orderby_info.desc ? "DESC" : "ASC");
2137
        }
2138

2139
        if (p_info->aOrderBy[0].iColumn == 0) {
19✔
2140
            if (p_info->aOrderBy[0].desc) {
11✔
2141
                log_info("  consuming ORDER BY log_line DESC");
8✔
2142
                direction = -1;
8✔
2143
            } else {
2144
                log_info("  consuming ORDER BY log_line ASC");
3✔
2145
                direction = 1;
3✔
2146
            }
2147
            p_info->orderByConsumed = 1;
11✔
2148
        }
2149
    }
2150
    if (!vt->vi->vi_supports_indexes) {
214✔
2151
        p_info->orderByConsumed = 0;
10✔
2152
        return SQLITE_OK;
10✔
2153
    }
2154
    for (int lpc = 0; lpc < p_info->nConstraint; lpc++) {
372✔
2155
        const auto& constraint = p_info->aConstraint[lpc];
168✔
2156
        if (!constraint.usable || constraint.op == SQLITE_INDEX_CONSTRAINT_MATCH
168✔
2157
#ifdef SQLITE_INDEX_CONSTRAINT_OFFSET
2158
            || constraint.op == SQLITE_INDEX_CONSTRAINT_OFFSET
159✔
2159
            || constraint.op == SQLITE_INDEX_CONSTRAINT_LIMIT
159✔
2160
#endif
2161
        )
2162
        {
2163
            log_debug("  column %d: is not usable (usable=%d, op: %s)",
25✔
2164
                      lpc,
2165
                      constraint.usable,
2166
                      sql_constraint_op_name(constraint.op));
2167
            continue;
25✔
2168
        }
2169

2170
        auto col = constraint.iColumn;
143✔
2171
        auto op = constraint.op;
143✔
2172
        log_debug("  column %d: op: %s", col, sql_constraint_op_name(op));
143✔
2173
        switch (col) {
143✔
2174
            case VT_COL_LINE_NUMBER: {
69✔
2175
                argvInUse += 1;
69✔
2176
                indexes.push_back(constraint);
69✔
2177
                p_info->aConstraintUsage[lpc].argvIndex = argvInUse;
69✔
2178
                index_desc.emplace_back(fmt::format(
69✔
2179
                    FMT_STRING("log_line {} ?"), sql_constraint_op_name(op)));
207✔
2180
                break;
69✔
2181
            }
2182
            case VT_COL_LOG_TIME: {
2✔
2183
                argvInUse += 1;
2✔
2184
                indexes.push_back(p_info->aConstraint[lpc]);
2✔
2185
                p_info->aConstraintUsage[lpc].argvIndex = argvInUse;
2✔
2186
                index_desc.emplace_back(fmt::format(
2✔
2187
                    FMT_STRING("log_time {} ?"), sql_constraint_op_name(op)));
6✔
2188
                break;
2✔
2189
            }
2190
            case VT_COL_LEVEL: {
7✔
2191
                if (log_cursor::level_constraint::op_is_supported(op)) {
7✔
2192
                    argvInUse += 1;
7✔
2193
                    indexes.push_back(constraint);
7✔
2194
                    p_info->aConstraintUsage[lpc].argvIndex = argvInUse;
7✔
2195
                    index_desc.emplace_back(
7✔
2196
                        fmt::format(FMT_STRING("log_level {} ?"),
21✔
2197
                                    sql_constraint_op_name(op)));
14✔
2198
                }
2199
                break;
7✔
2200
            }
2201
            default: {
65✔
2202
                if (col > (VT_COL_MAX + vt->vi->vi_column_count - 1)) {
65✔
2203
                    auto footer_column = static_cast<log_footer_columns>(
2204
                        col - (VT_COL_MAX + vt->vi->vi_column_count - 1) - 1);
23✔
2205

2206
                    switch (footer_column) {
23✔
2207
                        case log_footer_columns::time_msecs: {
1✔
2208
                            argvInUse += 1;
1✔
2209
                            indexes.push_back(p_info->aConstraint[lpc]);
1✔
2210
                            p_info->aConstraintUsage[lpc].argvIndex = argvInUse;
1✔
2211
                            index_desc.emplace_back(
1✔
2212
                                fmt::format(FMT_STRING("log_time_msecs {} ?"),
3✔
2213
                                            sql_constraint_op_name(op)));
1✔
2214
                            break;
1✔
2215
                        }
2216
                        case log_footer_columns::format: {
3✔
2217
                            if (op == SQLITE_INDEX_CONSTRAINT_EQ) {
3✔
2218
                                argvInUse += 1;
3✔
2219
                                indexes.push_back(constraint);
3✔
2220
                                p_info->aConstraintUsage[lpc].argvIndex
3✔
2221
                                    = argvInUse;
3✔
2222
                                index_desc.emplace_back("log_format = ?");
3✔
2223
                            }
2224
                            break;
3✔
2225
                        }
2226
                        case log_footer_columns::format_regex: {
×
2227
                            if (op == SQLITE_INDEX_CONSTRAINT_EQ) {
×
2228
                                argvInUse += 1;
×
2229
                                indexes.push_back(constraint);
×
2230
                                p_info->aConstraintUsage[lpc].argvIndex
×
2231
                                    = argvInUse;
×
2232
                                index_desc.emplace_back("log_format_regex = ?");
×
2233
                            }
2234
                            break;
×
2235
                        }
2236
                        case log_footer_columns::opid:
1✔
2237
                        case log_footer_columns::user_opid: {
2238
                            if (op == SQLITE_INDEX_CONSTRAINT_EQ) {
1✔
2239
                                argvInUse += 1;
1✔
2240
                                indexes.push_back(constraint);
1✔
2241
                                p_info->aConstraintUsage[lpc].argvIndex
1✔
2242
                                    = argvInUse;
1✔
2243
                                index_desc.emplace_back("log_opid = ?");
1✔
2244
                            }
2245
                            break;
1✔
2246
                        }
2247
                        case log_footer_columns::path: {
2✔
2248
                            argvInUse += 1;
2✔
2249
                            indexes.push_back(constraint);
2✔
2250
                            p_info->aConstraintUsage[lpc].argvIndex = argvInUse;
2✔
2251
                            index_desc.emplace_back(
2✔
2252
                                fmt::format(FMT_STRING("log_path {} ?"),
6✔
2253
                                            sql_constraint_op_name(op)));
2✔
2254
                            break;
2✔
2255
                        }
2256
                        case log_footer_columns::unique_path: {
×
2257
                            argvInUse += 1;
×
2258
                            indexes.push_back(constraint);
×
2259
                            p_info->aConstraintUsage[lpc].argvIndex = argvInUse;
×
2260
                            index_desc.emplace_back(
×
2261
                                fmt::format(FMT_STRING("log_unique_path {} ?"),
×
2262
                                            sql_constraint_op_name(op)));
×
2263
                            break;
×
2264
                        }
2265
                        case log_footer_columns::partition:
14✔
2266
                        case log_footer_columns::actual_time:
2267
                        case log_footer_columns::idle_msecs:
2268
                        case log_footer_columns::mark:
2269
                        case log_footer_columns::comment:
2270
                        case log_footer_columns::tags:
2271
                        case log_footer_columns::annotations:
2272
                        case log_footer_columns::filters:
2273
                        case log_footer_columns::text:
2274
                        case log_footer_columns::body:
2275
                        case log_footer_columns::raw_text:
2276
                        case log_footer_columns::line_hash:
2277
                        case log_footer_columns::src_file:
2278
                        case log_footer_columns::src_line:
2279
                            break;
14✔
2280
                        case log_footer_columns::line_link: {
2✔
2281
                            argvInUse += 1;
2✔
2282
                            indexes.push_back(constraint);
2✔
2283
                            p_info->aConstraintUsage[lpc].argvIndex = argvInUse;
2✔
2284
                            index_desc.emplace_back("log_line_link = ?");
2✔
2285
                            break;
2✔
2286
                        }
2287
                    }
2288
                } else if (op == SQLITE_INDEX_CONSTRAINT_EQ) {
42✔
2289
                    argvInUse += 1;
19✔
2290
                    indexes.push_back(constraint);
19✔
2291
                    p_info->aConstraintUsage[lpc].argvIndex = argvInUse;
19✔
2292
                    index_desc.emplace_back(
19✔
2293
                        fmt::format(FMT_STRING("col({}) {} ?"),
57✔
2294
                                    col,
2295
                                    sql_constraint_op_name(op)));
38✔
2296
                }
2297
                break;
65✔
2298
            }
2299
        }
2300
    }
2301

2302
    if (argvInUse) {
204✔
2303
        auto full_desc = fmt::format(FMT_STRING("SEARCH {} USING {}"),
184✔
2304
                                     vt->vi->get_name().get(),
92✔
2305
                                     fmt::join(index_desc, " AND "));
92✔
2306
        log_info("found index: %s", full_desc.c_str());
92✔
2307

2308
        sqlite3_index_info::sqlite3_index_constraint* index_copy;
2309
        auto index_len = indexes.size() * sizeof(*index_copy);
92✔
2310
        size_t len = full_desc.size() + 128 + index_len;
92✔
2311
        auto* storage = sqlite3_malloc(len);
92✔
2312
        if (!storage) {
92✔
2313
            return SQLITE_NOMEM;
×
2314
        }
2315
        auto* desc_storage = static_cast<char*>(storage);
92✔
2316
        memcpy(desc_storage, full_desc.c_str(), full_desc.size() + 1);
92✔
2317
        desc_storage[full_desc.size() + 1] = direction;
92✔
2318
        auto* remaining_storage
2319
            = static_cast<void*>(desc_storage + full_desc.size() + 1 + 1);
92✔
2320
        len -= 1 + full_desc.size() - 1;
92✔
2321
        auto* index_storage
2322
            = std::align(alignof(sqlite3_index_info::sqlite3_index_constraint),
92✔
2323
                         index_len,
2324
                         remaining_storage,
2325
                         len);
2326
        index_copy
2327
            = reinterpret_cast<sqlite3_index_info::sqlite3_index_constraint*>(
92✔
2328
                index_storage);
2329
        log_info("  index storage: %p", index_copy);
92✔
2330
        memcpy(index_copy, &indexes[0], index_len);
92✔
2331
        p_info->idxNum = argvInUse;
92✔
2332
        p_info->idxStr = static_cast<char*>(storage);
92✔
2333
        p_info->needToFreeIdxStr = 1;
92✔
2334
        p_info->estimatedCost = 10.0;
92✔
2335
    } else {
92✔
2336
        static char fullscan_asc[] = "fullscan\0\001";
2337
        static char fullscan_desc[] = "fullscan\0\377";
2338

2339
        p_info->idxStr = direction < 0 ? fullscan_desc : fullscan_asc;
112✔
2340
        p_info->estimatedCost = 1000000000.0;
112✔
2341
    }
2342

2343
    return SQLITE_OK;
204✔
2344
}
214✔
2345

2346
static const struct json_path_container tags_handler = {
2347
    json_path_handler("#")
2348
        .with_synopsis("tag")
2349
        .with_description("A tag for the log line")
2350
        .with_pattern(R"(^#[^\s]+$)")
2351
        .for_field(&bookmark_metadata::bm_tags),
2352
};
2353

2354
static int
2355
vt_update(sqlite3_vtab* tab,
52✔
2356
          int argc,
2357
          sqlite3_value** argv,
2358
          sqlite_int64* rowid_out)
2359
{
2360
    auto* vt = (log_vtab*) tab;
52✔
2361
    int retval = SQLITE_READONLY;
52✔
2362

2363
    if (argc > 1 && sqlite3_value_type(argv[0]) != SQLITE_NULL
51✔
2364
        && sqlite3_value_int64(argv[0]) == sqlite3_value_int64(argv[1]))
103✔
2365
    {
2366
        int64_t rowid = sqlite3_value_int64(argv[0]) >> 8;
51✔
2367
        int val = sqlite3_value_int(
102✔
2368
            argv[2 + vt->footer_index(log_footer_columns::mark)]);
51✔
2369
        vis_line_t vrowid(rowid);
51✔
2370
        auto win = vt->lss->window_at(vrowid);
51✔
2371
        const auto msg_info = *win->begin();
51✔
2372
        const auto* part_name = sqlite3_value_text(
102✔
2373
            argv[2 + vt->footer_index(log_footer_columns::partition)]);
51✔
2374
        const auto* log_comment = sqlite3_value_text(
102✔
2375
            argv[2 + vt->footer_index(log_footer_columns::comment)]);
51✔
2376
        const auto log_tags = from_sqlite<std::optional<string_fragment>>()(
×
2377
            argc, argv, 2 + vt->footer_index(log_footer_columns::tags));
51✔
2378
        const auto log_annos = from_sqlite<std::optional<string_fragment>>()(
×
2379
            argc, argv, 2 + vt->footer_index(log_footer_columns::annotations));
51✔
2380
        auto log_opid = from_sqlite<std::optional<string_fragment>>()(
×
2381
            argc, argv, 2 + vt->footer_index(log_footer_columns::opid));
51✔
2382
        const auto log_user_opid
2383
            = from_sqlite<std::optional<string_fragment>>()(
×
2384
                argc,
2385
                argv,
2386
                2 + vt->footer_index(log_footer_columns::user_opid));
51✔
2387
        bookmark_metadata tmp_bm;
51✔
2388

2389
        if (log_user_opid) {
51✔
2390
            log_opid = log_user_opid;
×
2391
        }
2392
        if (log_tags) {
51✔
2393
            std::vector<lnav::console::user_message> errors;
4✔
2394
            yajlpp_parse_context ypc(vt->vi->get_tags_name(), &tags_handler);
4✔
2395
            auto_mem<yajl_handle_t> handle(yajl_free);
4✔
2396

2397
            handle = yajl_alloc(&ypc.ypc_callbacks, nullptr, &ypc);
4✔
2398
            ypc.ypc_userdata = &errors;
4✔
2399
            ypc.ypc_line_number = log_vtab_data.lvd_location.sl_line_number;
4✔
2400
            ypc.with_handle(handle)
4✔
2401
                .with_error_reporter([](const yajlpp_parse_context& ypc,
8✔
2402
                                        auto msg) {
2403
                    auto& errors = *((std::vector<lnav::console::user_message>*)
2✔
2404
                                         ypc.ypc_userdata);
2405
                    errors.emplace_back(msg);
2✔
2406
                })
2✔
2407
                .with_obj(tmp_bm);
4✔
2408
            ypc.parse_doc(log_tags.value());
4✔
2409
            if (!errors.empty()) {
4✔
2410
                auto top_error
UNCOV
2411
                    = lnav::console::user_message::error(
×
2412
                          attr_line_t("invalid value for ")
2✔
2413
                              .append_quoted("log_tags"_symbol)
2✔
2414
                              .append(" column of table ")
2✔
2415
                              .append_quoted(lnav::roles::symbol(
4✔
2416
                                  vt->vi->get_name().to_string())))
4✔
2417
                          .with_reason(errors[0].to_attr_line(
4✔
2418
                              lnav::console::user_message::render_flags::none))
2419
                          .move();
2✔
2420
                set_vtable_errmsg(tab, top_error);
2✔
2421
                return SQLITE_ERROR;
2✔
2422
            }
2✔
2423
        }
8✔
2424
        if (log_annos) {
49✔
2425
            static const intern_string_t SRC
2426
                = intern_string::lookup("log_annotations");
9✔
2427

2428
            auto parse_res = logmsg_annotations_handlers.parser_for(SRC).of(
6✔
2429
                log_annos.value());
3✔
2430
            if (parse_res.isErr()) {
3✔
2431
                set_vtable_errmsg(tab, parse_res.unwrapErr()[0]);
2✔
2432
                return SQLITE_ERROR;
2✔
2433
            }
2434

2435
            tmp_bm.bm_annotations = parse_res.unwrap();
1✔
2436
        }
3✔
2437

2438
        auto& bv_meta = vt->tc->get_bookmarks()[&textview_curses::BM_META];
47✔
2439
        bool has_meta = log_comment != nullptr || log_tags.has_value()
45✔
2440
            || log_annos.has_value();
92✔
2441

2442
        if (bv_meta.bv_tree.exists(vrowid) && !has_meta) {
47✔
2443
            vt->tc->set_user_mark(&textview_curses::BM_META, vrowid, false);
2✔
2444
            vt->lss->set_line_meta_changed();
2✔
2445
        }
2446

2447
        if (!has_meta && part_name == nullptr
43✔
2448
            && (!log_opid
123✔
2449
                || msg_info.get_values().lvv_opid_provenance
33✔
2450
                    == logline_value_vector::opid_provenance::file))
2451
        {
2452
            vt->lss->erase_bookmark_metadata(vrowid);
23✔
2453
        }
2454

2455
        if (part_name) {
47✔
2456
            auto& line_meta = vt->lss->get_bookmark_metadata(vrowid);
3✔
2457
            line_meta.bm_name = std::string((const char*) part_name);
3✔
2458
            vt->tc->set_user_mark(&textview_curses::BM_PARTITION, vrowid, true);
3✔
2459
        } else {
2460
            vt->tc->set_user_mark(
44✔
2461
                &textview_curses::BM_PARTITION, vrowid, false);
2462
        }
2463

2464
        if (log_opid) {
47✔
2465
            auto& lvv = msg_info.get_values();
40✔
2466
            if (!lvv.lvv_opid_value
40✔
2467
                || lvv.lvv_opid_provenance
40✔
2468
                    == logline_value_vector::opid_provenance::user)
2469
            {
2470
                msg_info.get_file_ptr()->set_logline_opid(
34✔
2471
                    msg_info.get_file_line_number(), log_opid.value());
17✔
2472
                vt->lss->set_line_meta_changed();
17✔
2473
            }
2474
        } else if (msg_info.get_values().lvv_opid_provenance
7✔
2475
                   == logline_value_vector::opid_provenance::user)
7✔
2476
        {
UNCOV
2477
            msg_info.get_file_ptr()->clear_logline_opid(
×
2478
                msg_info.get_file_line_number());
2479
        }
2480

2481
        if (has_meta) {
47✔
2482
            auto& line_meta = vt->lss->get_bookmark_metadata(vrowid);
4✔
2483

2484
            vt->tc->set_user_mark(&textview_curses::BM_META, vrowid, true);
4✔
2485
            if (part_name == nullptr) {
4✔
2486
                line_meta.bm_name.clear();
4✔
2487
            }
2488
            if (log_comment) {
4✔
2489
                line_meta.bm_comment = std::string((const char*) log_comment);
4✔
2490
            } else {
2491
                line_meta.bm_comment.clear();
2✔
2492
            }
2493
            if (log_tags) {
4✔
2494
                line_meta.bm_tags.clear();
2✔
2495
                for (const auto& tag : tmp_bm.bm_tags) {
5✔
2496
                    line_meta.add_tag(tag);
3✔
2497
                }
2498

2499
                for (const auto& tag : line_meta.bm_tags) {
4✔
2500
                    bookmark_metadata::KNOWN_TAGS.insert(tag);
2✔
2501
                }
2502
            } else {
2503
                line_meta.bm_tags.clear();
2✔
2504
            }
2505
            if (log_annos) {
4✔
2506
                line_meta.bm_annotations = std::move(tmp_bm.bm_annotations);
1✔
2507
            } else if (!sqlite3_value_nochange(
3✔
2508
                           argv[2
3✔
2509
                                + vt->footer_index(
3✔
2510
                                    log_footer_columns::annotations)]))
3✔
2511
            {
UNCOV
2512
                line_meta.bm_annotations.la_pairs.clear();
×
2513
            }
2514
            vt->lss->set_line_meta_changed();
4✔
2515
        }
2516

2517
        vt->tc->set_user_mark(&textview_curses::BM_USER, vrowid, val);
47✔
2518
        rowid += 1;
47✔
2519
        while ((size_t) rowid < vt->lss->text_line_count()) {
50✔
2520
            vis_line_t vl(rowid);
34✔
2521
            auto cl = vt->lss->at(vl);
34✔
2522
            auto* ll = vt->lss->find_line(cl);
34✔
2523
            if (ll->is_message()) {
34✔
2524
                break;
31✔
2525
            }
2526
            vt->tc->set_user_mark(&textview_curses::BM_USER, vl, val);
3✔
2527
            rowid += 1;
3✔
2528
        }
2529

2530
        if (retval != SQLITE_ERROR) {
47✔
2531
            retval = SQLITE_OK;
47✔
2532
        }
2533
    }
59✔
2534

2535
    return retval;
48✔
2536
}
2537

2538
static const sqlite3_module generic_vtab_module = {
2539
    0, /* iVersion */
2540
    vt_create, /* xCreate       - create a vtable */
2541
    vt_connect, /* xConnect      - associate a vtable with a connection */
2542
    vt_best_index, /* xBestIndex    - best index */
2543
    vt_disconnect, /* xDisconnect   - disassociate a vtable with a connection */
2544
    vt_destroy, /* xDestroy      - destroy a vtable */
2545
    vt_open, /* xOpen         - open a cursor */
2546
    vt_close, /* xClose        - close a cursor */
2547
    vt_filter, /* xFilter       - configure scan constraints */
2548
    vt_next, /* xNext         - advance a cursor */
2549
    vt_eof, /* xEof          - inidicate end of result set*/
2550
    vt_column, /* xColumn       - read data */
2551
    vt_rowid, /* xRowid        - read data */
2552
    vt_update, /* xUpdate       - write data */
2553
    nullptr, /* xBegin        - begin transaction */
2554
    nullptr, /* xSync         - sync transaction */
2555
    nullptr, /* xCommit       - commit transaction */
2556
    nullptr, /* xRollback     - rollback transaction */
2557
    nullptr, /* xFindFunction - function overloading */
2558
};
2559

2560
static const sqlite3_module no_rowid_vtab_module = {
2561
    0, /* iVersion */
2562
    vt_create, /* xCreate       - create a vtable */
2563
    vt_connect, /* xConnect      - associate a vtable with a connection */
2564
    vt_best_index, /* xBestIndex    - best index */
2565
    vt_disconnect, /* xDisconnect   - disassociate a vtable with a connection */
2566
    vt_destroy, /* xDestroy      - destroy a vtable */
2567
    vt_open, /* xOpen         - open a cursor */
2568
    vt_close, /* xClose        - close a cursor */
2569
    vt_filter, /* xFilter       - configure scan constraints */
2570
    vt_next_no_rowid, /* xNext         - advance a cursor */
2571
    vt_eof, /* xEof          - inidicate end of result set*/
2572
    vt_column, /* xColumn       - read data */
2573
    nullptr, /* xRowid        - read data */
2574
    nullptr, /* xUpdate       - write data */
2575
    nullptr, /* xBegin        - begin transaction */
2576
    nullptr, /* xSync         - sync transaction */
2577
    nullptr, /* xCommit       - commit transaction */
2578
    nullptr, /* xRollback     - rollback transaction */
2579
    nullptr, /* xFindFunction - function overloading */
2580
};
2581

2582
static int
2583
progress_callback(void* ptr)
852,297✔
2584
{
2585
    int retval = 0;
852,297✔
2586

2587
    if (log_vtab_data.lvd_progress != nullptr) {
852,297✔
2588
        retval = log_vtab_data.lvd_progress(log_cursor_latest);
11,388✔
2589
    }
2590
    if (!log_vtab_data.lvd_looping) {
852,297✔
UNCOV
2591
        retval = 1;
×
2592
    }
2593

2594
    return retval;
852,297✔
2595
}
2596

2597
log_vtab_manager::log_vtab_manager(sqlite3* memdb,
614✔
2598
                                   textview_curses& tc,
2599
                                   logfile_sub_source& lss)
614✔
2600
    : vm_db(memdb), vm_textview(tc), vm_source(lss)
614✔
2601
{
2602
    sqlite3_create_module(
614✔
2603
        this->vm_db, "log_vtab_impl", &generic_vtab_module, this);
2604
    sqlite3_create_module(
614✔
2605
        this->vm_db, "log_vtab_no_rowid_impl", &no_rowid_vtab_module, this);
2606
    sqlite3_progress_handler(memdb, 32, progress_callback, nullptr);
614✔
2607
}
614✔
2608

2609
log_vtab_manager::~log_vtab_manager()
614✔
2610
{
2611
    while (!this->vm_impls.empty()) {
52,419✔
2612
        auto first_name = this->vm_impls.begin()->first;
51,805✔
2613

2614
        this->unregister_vtab(first_name);
51,805✔
2615
    }
2616
}
614✔
2617

2618
std::string
2619
log_vtab_manager::register_vtab(std::shared_ptr<log_vtab_impl> vi)
51,977✔
2620
{
2621
    std::string retval;
51,977✔
2622

2623
    if (this->vm_impls.find(vi->get_name().to_string_fragment())
51,977✔
2624
        == this->vm_impls.end())
103,954✔
2625
    {
2626
        std::vector<std::string> primary_keys;
51,977✔
2627
        auto_mem<char, sqlite3_free> errmsg;
51,977✔
2628
        auto_mem<char, sqlite3_free> sql;
51,977✔
2629
        int rc;
2630

2631
        this->vm_impls[vi->get_name().to_string_fragment()] = vi;
51,977✔
2632

2633
        vi->get_primary_keys(primary_keys);
51,977✔
2634

2635
        sql = sqlite3_mprintf(
2636
            "CREATE VIRTUAL TABLE lnav_db.%s "
2637
            "USING %s(%s)",
2638
            vi->get_name().get(),
51,977✔
2639
            primary_keys.empty() ? "log_vtab_impl" : "log_vtab_no_rowid_impl",
51,977✔
2640
            vi->get_name().get());
155,931✔
2641
        rc = sqlite3_exec(this->vm_db, sql, nullptr, nullptr, errmsg.out());
51,977✔
2642
        if (rc != SQLITE_OK) {
51,977✔
2643
            retval = errmsg;
1✔
2644
        }
2645
    } else {
51,977✔
UNCOV
2646
        retval = "a table with the given name already exists";
×
2647
    }
2648

2649
    return retval;
51,977✔
UNCOV
2650
}
×
2651

2652
std::string
2653
log_vtab_manager::unregister_vtab(string_fragment name)
52,225✔
2654
{
2655
    std::string retval;
52,225✔
2656

2657
    if (this->vm_impls.find(name) == this->vm_impls.end()) {
52,225✔
2658
        retval = fmt::format(FMT_STRING("unknown table -- {}"), name);
992✔
2659
    } else {
2660
        auto_mem<char, sqlite3_free> sql;
51,977✔
2661
        __attribute((unused)) int rc;
2662

2663
        sql = sqlite3_mprintf(
2664
            "DROP TABLE IF EXISTS %.*s", name.length(), name.data());
51,977✔
2665
        log_debug("unregister_vtab: %s", sql.in());
51,977✔
2666
        rc = sqlite3_exec(this->vm_db, sql, nullptr, nullptr, nullptr);
51,977✔
2667

2668
        this->vm_impls.erase(name);
51,977✔
2669
    }
51,977✔
2670

2671
    return retval;
52,225✔
UNCOV
2672
}
×
2673

2674
std::shared_ptr<log_vtab_impl>
2675
log_vtab_manager::lookup_impl(string_fragment name) const
52,430✔
2676
{
2677
    const auto iter = this->vm_impls.find(name);
52,430✔
2678
    if (iter != this->vm_impls.end()) {
52,430✔
2679
        return iter->second;
52,423✔
2680
    }
2681
    return nullptr;
7✔
2682
}
2683

2684
bool
2685
log_format_vtab_impl::next(log_cursor& lc, logfile_sub_source& lss)
50,276✔
2686
{
2687
    if (lc.is_eof()) {
50,276✔
UNCOV
2688
        return true;
×
2689
    }
2690

2691
    auto cl = content_line_t(lss.at(lc.lc_curr_line));
50,276✔
2692
    auto* lf = lss.find_file_ptr(cl);
50,276✔
2693
    auto lf_iter = lf->begin() + cl;
50,276✔
2694
    uint8_t mod_id = lf_iter->get_module_id();
50,276✔
2695

2696
    if (!lf_iter->is_message()) {
50,276✔
UNCOV
2697
        return false;
×
2698
    }
2699

2700
    auto format = lf->get_format_ptr();
50,276✔
2701
    if (format->get_name() == this->lfvi_format.get_name()) {
50,276✔
2702
        return true;
29,900✔
2703
    }
2704
    if (mod_id && mod_id == this->lfvi_format.lf_mod_index) {
20,376✔
2705
        // XXX
UNCOV
2706
        return true;
×
2707
    }
2708

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