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

tstack / lnav / 19833402571-2724

01 Dec 2025 06:33PM UTC coverage: 68.86% (-0.001%) from 68.861%
19833402571-2724

push

github

tstack
[breadcrumb] add thread ID to breadcrumb bar

173 of 219 new or added lines in 14 files covered. (79.0%)

4 existing lines in 3 files now uncovered.

51293 of 74489 relevant lines covered (68.86%)

435674.56 hits per line

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

84.3
/src/log_vtab_impl.cc
1
/**
2
 * Copyright (c) 2007-2012, Timothy Stack
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 * * Redistributions of source code must retain the above copyright notice, this
10
 * list of conditions and the following disclaimer.
11
 * * Redistributions in binary form must reproduce the above copyright notice,
12
 * this list of conditions and the following disclaimer in the documentation
13
 * and/or other materials provided with the distribution.
14
 * * Neither the name of Timothy Stack nor the names of its contributors
15
 * may be used to endorse or promote products derived from this software
16
 * without specific prior written permission.
17
 *
18
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
19
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
22
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
 */
29

30
#include <cstdint>
31
#include <memory>
32
#include <string>
33
#include <unordered_set>
34
#include <vector>
35

36
#include "log_vtab_impl.hh"
37

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

54
// #define DEBUG_INDEXING 1
55

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

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

60
static log_cursor log_cursor_latest;
61

62
thread_local _log_vtab_data log_vtab_data;
63

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

77
static const char* const LOG_COLUMNS = R"(  (
78
  log_line        INTEGER,                         -- The line number for the log message
79
  log_time        DATETIME,                        -- The adjusted timestamp for the log message
80
  log_level       TEXT     COLLATE loglevel,       -- The log message level
81
  -- BEGIN Format-specific fields:
82
)";
83

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

111
enum class log_footer_columns : uint32_t {
112
    partition,
113
    actual_time,
114
    idle_msecs,
115
    mark,
116
    comment,
117
    tags,
118
    annotations,
119
    filters,
120
    opid,
121
    user_opid,
122
    format,
123
    format_regex,
124
    time_msecs,
125
    path,
126
    unique_path,
127
    text,
128
    body,
129
    raw_text,
130
    line_hash,
131
    line_link,
132
    src_file,
133
    src_line,
134
    thread_id,
135
};
136

137
const std::string&
138
log_vtab_impl::get_table_statement()
55,657✔
139
{
140
    if (this->vi_table_statement.empty()) {
55,657✔
141
        std::vector<vtab_column> cols;
55,460✔
142
        std::ostringstream oss;
55,460✔
143
        size_t max_name_len = 15;
55,460✔
144

145
        oss << "CREATE TABLE lnav_db." << this->get_name().to_string()
110,920✔
146
            << LOG_COLUMNS;
110,920✔
147
        this->get_columns(cols);
55,460✔
148
        this->vi_column_count = cols.size();
55,460✔
149
        for (const auto& col : cols) {
562,880✔
150
            max_name_len = std::max(max_name_len, col.vc_name.length());
507,420✔
151
        }
152
        for (const auto& col : cols) {
562,880✔
153
            std::string comment;
507,420✔
154

155
            require(!col.vc_name.empty());
507,420✔
156

157
            if (!col.vc_comment.empty()) {
507,420✔
158
                comment.append(" -- ").append(col.vc_comment);
57,748✔
159
            }
160

161
            auto colname = sql_quote_ident(col.vc_name.c_str());
507,420✔
162
            auto coldecl = lnav::sql::mprintf(
163
                "  %-*s %-7s %s COLLATE %-15Q,%s\n",
164
                max_name_len,
165
                colname.in(),
166
                sqlite3_type_to_string(col.vc_type),
507,420✔
167
                col.vc_hidden ? "hidden" : "",
507,420✔
168
                col.vc_collator.empty() ? "BINARY" : col.vc_collator.c_str(),
522,061✔
169
                comment.c_str());
1,029,481✔
170
            oss << coldecl;
507,420✔
171
        }
507,420✔
172
        oss << LOG_FOOTER_COLUMNS;
55,460✔
173

174
        {
175
            std::vector<std::string> primary_keys;
55,460✔
176

177
            this->get_primary_keys(primary_keys);
55,460✔
178
            if (!primary_keys.empty()) {
55,460✔
179
                auto first = true;
8,869✔
180

181
                oss << ", PRIMARY KEY (";
8,869✔
182
                for (const auto& pkey : primary_keys) {
26,607✔
183
                    if (!first) {
17,738✔
184
                        oss << ", ";
8,869✔
185
                    }
186
                    oss << pkey;
17,738✔
187
                    first = false;
17,738✔
188
                }
189
                oss << ")\n";
8,869✔
190
            } else {
191
                oss << ", PRIMARY KEY (log_line)\n";
46,591✔
192
            }
193
        }
55,460✔
194

195
        oss << ");\n";
55,460✔
196

197
        log_trace("log_vtab_impl.get_table_statement() -> %s",
55,460✔
198
                  oss.str().c_str());
199

200
        this->vi_table_statement = oss.str();
55,460✔
201
    }
55,460✔
202

203
    return this->vi_table_statement;
55,657✔
204
}
205

206
std::pair<int, unsigned int>
207
log_vtab_impl::logline_value_to_sqlite_type(value_kind_t kind)
474,082✔
208
{
209
    int type = 0;
474,082✔
210
    unsigned int subtype = 0;
474,082✔
211

212
    switch (kind) {
474,082✔
213
        case value_kind_t::VALUE_JSON:
10,978✔
214
            type = SQLITE3_TEXT;
10,978✔
215
            subtype = JSON_SUBTYPE;
10,978✔
216
            break;
10,978✔
217
        case value_kind_t::VALUE_NULL:
364,330✔
218
        case value_kind_t::VALUE_TEXT:
219
        case value_kind_t::VALUE_STRUCT:
220
        case value_kind_t::VALUE_QUOTED:
221
        case value_kind_t::VALUE_W3C_QUOTED:
222
        case value_kind_t::VALUE_TIMESTAMP:
223
        case value_kind_t::VALUE_XML:
224
            type = SQLITE3_TEXT;
364,330✔
225
            break;
364,330✔
226
        case value_kind_t::VALUE_FLOAT:
6,988✔
227
            type = SQLITE_FLOAT;
6,988✔
228
            break;
6,988✔
229
        case value_kind_t::VALUE_BOOLEAN:
91,786✔
230
        case value_kind_t::VALUE_INTEGER:
231
            type = SQLITE_INTEGER;
91,786✔
232
            break;
91,786✔
233
        case value_kind_t::VALUE_UNKNOWN:
×
234
        case value_kind_t::VALUE__MAX:
235
            ensure(0);
×
236
            break;
237
    }
238
    return std::make_pair(type, subtype);
948,164✔
239
}
240

241
void
242
log_vtab_impl::get_foreign_keys(
45,153✔
243
    std::unordered_set<std::string>& keys_inout) const
244
{
245
    keys_inout.emplace("id");
45,153✔
246
    keys_inout.emplace("parent");
45,153✔
247
    keys_inout.emplace("notused");
45,153✔
248

249
    keys_inout.emplace("log_line");
45,153✔
250
    keys_inout.emplace("min(log_line)");
45,153✔
251
    keys_inout.emplace("log_mark");
45,153✔
252
    keys_inout.emplace("log_time_msecs");
45,153✔
253
    keys_inout.emplace("log_top_line()");
45,153✔
254
    keys_inout.emplace("log_msg_line()");
45,153✔
255
    keys_inout.emplace("log_src_line");
45,153✔
256
    keys_inout.emplace("log_thread_id");
45,153✔
257
}
45,153✔
258

259
void
260
log_vtab_impl::extract(logfile* lf,
29,504✔
261
                       uint64_t line_number,
262
                       string_attrs_t& sa,
263
                       logline_value_vector& values)
264
{
265
    const auto* format = lf->get_format_ptr();
29,504✔
266

267
    format->annotate(lf, line_number, sa, values);
29,504✔
268
}
29,504✔
269

270
bool
271
log_vtab_impl::is_valid(log_cursor& lc, logfile_sub_source& lss)
55,023✔
272
{
273
    if (lc.lc_curr_line < 0_vl) {
55,023✔
274
        return false;
×
275
    }
276

277
    content_line_t cl(lss.at(lc.lc_curr_line));
55,023✔
278
    auto* lf = lss.find_file_ptr(cl);
55,023✔
279
    auto lf_iter = lf->begin() + cl;
55,023✔
280

281
    if (!lf_iter->is_message()) {
55,023✔
282
        return false;
373✔
283
    }
284

285
    if (!lc.lc_format_name.empty()
54,650✔
286
        && lc.lc_format_name != lf->get_format_name())
54,650✔
287
    {
288
        return false;
3✔
289
    }
290

291
    if (!lc.lc_pattern_name.empty()
54,647✔
292
        && lc.lc_pattern_name
54,647✔
293
            != lf->get_format_ptr()->get_pattern_name(
54,647✔
294
                lf->get_format_file_state().lffs_pattern_locks, cl))
×
295
    {
296
        return false;
×
297
    }
298

299
    if (lc.lc_level_constraint
54,647✔
300
        && !lc.lc_level_constraint->matches(lf_iter->get_msg_level()))
54,647✔
301
    {
302
        return false;
206✔
303
    }
304

305
    if (!lc.lc_log_path.empty()) {
54,441✔
306
        if (lf == lc.lc_last_log_path_match) {
4✔
307
        } else if (lf == lc.lc_last_log_path_mismatch) {
2✔
308
            return false;
×
309
        } else {
310
            for (const auto& path_cons : lc.lc_log_path) {
4✔
311
                if (!path_cons.matches(lf->get_filename())) {
2✔
312
                    lc.lc_last_log_path_mismatch = lf;
×
313
                    return false;
×
314
                }
315
            }
316
            lc.lc_last_log_path_match = lf;
2✔
317
        }
318
    }
319

320
    if (!lc.lc_unique_path.empty()) {
54,441✔
321
        if (lf == lc.lc_last_unique_path_match) {
×
322
        } else if (lf == lc.lc_last_unique_path_mismatch) {
×
323
            return false;
×
324
        } else {
325
            for (const auto& path_cons : lc.lc_unique_path) {
×
326
                if (!path_cons.matches(lf->get_unique_path())) {
×
327
                    lc.lc_last_unique_path_mismatch = lf;
×
328
                    return false;
×
329
                }
330
            }
331
            lc.lc_last_unique_path_match = lf;
×
332
        }
333
    }
334

335
    if (lc.lc_opid_bloom_bits
54,441✔
336
        && !lf_iter->match_bloom_bits(lc.lc_opid_bloom_bits.value()))
54,441✔
337
    {
NEW
338
        return false;
×
339
    }
340

341
    if (lc.lc_tid_bloom_bits
54,441✔
342
        && !lf_iter->match_bloom_bits(lc.lc_tid_bloom_bits.value()))
54,441✔
343
    {
UNCOV
344
        return false;
×
345
    }
346

347
    return true;
54,441✔
348
}
349

350
struct log_vtab {
351
    sqlite3_vtab base;
352
    sqlite3* db;
353
    textview_curses* tc{nullptr};
354
    logfile_sub_source* lss{nullptr};
355
    std::shared_ptr<log_vtab_impl> vi;
356

357
    size_t footer_index(log_footer_columns col) const
360✔
358
    {
359
        return VT_COL_MAX + this->vi->vi_column_count
360✔
360
            + lnav::enums::to_underlying(col);
360✔
361
    }
362
};
363

364
struct vtab_cursor {
365
    void cache_msg(logfile* lf, logfile::const_iterator ll)
33,202✔
366
    {
367
        if (this->log_msg_line == this->log_cursor.lc_curr_line) {
33,202✔
368
            return;
×
369
        }
370
        auto& sbr = this->line_values.lvv_sbr;
33,202✔
371
        lf->read_full_message(ll,
33,202✔
372
                              sbr,
373
                              this->log_cursor.lc_direction < 0
33,202✔
374
                                  ? line_buffer::scan_direction::backward
375
                                  : line_buffer::scan_direction::forward);
376
        sbr.erase_ansi();
33,202✔
377
        this->log_msg_line = this->log_cursor.lc_curr_line;
33,202✔
378
    }
379

380
    void invalidate()
34,093✔
381
    {
382
        this->attrs.clear();
34,093✔
383
        this->line_values.clear();
34,093✔
384
        this->log_msg_line = -1_vl;
34,093✔
385
    }
34,093✔
386

387
    sqlite3_vtab_cursor base;
388
    struct log_cursor log_cursor;
389
    vis_line_t log_msg_line{-1_vl};
390
    string_attrs_t attrs;
391
    logline_value_vector line_values;
392
};
393

394
static int vt_destructor(sqlite3_vtab* p_svt);
395

396
static int
397
vt_create(sqlite3* db,
55,484✔
398
          void* pAux,
399
          int argc,
400
          const char* const* argv,
401
          sqlite3_vtab** pp_vt,
402
          char** pzErr)
403
{
404
    auto* vm = (log_vtab_manager*) pAux;
55,484✔
405
    int rc = SQLITE_OK;
55,484✔
406
    /* Allocate the sqlite3_vtab/vtab structure itself */
407
    auto p_vt = std::make_unique<log_vtab>();
55,484✔
408

409
    p_vt->db = db;
55,484✔
410

411
    /* Declare the vtable's structure */
412
    p_vt->vi = vm->lookup_impl(intern_string::lookup(argv[3]));
110,968✔
413
    if (p_vt->vi == nullptr) {
55,484✔
414
        return SQLITE_ERROR;
×
415
    }
416
    p_vt->tc = vm->get_view();
55,484✔
417
    p_vt->lss = vm->get_source();
55,484✔
418
    rc = sqlite3_declare_vtab(db, p_vt->vi->get_table_statement().c_str());
55,484✔
419

420
    if (rc == SQLITE_OK) {
55,484✔
421
        /* Success. Set *pp_vt and return */
422
        auto loose_p_vt = p_vt.release();
55,483✔
423
        *pp_vt = &loose_p_vt->base;
55,483✔
424
        log_debug("creating log format table: %s = %p", argv[3], loose_p_vt);
55,483✔
425
    } else {
426
        log_error("sqlite3_declare_vtab(%s) failed: %s",
1✔
427
                  p_vt->vi->get_name().c_str(),
428
                  sqlite3_errmsg(db));
429
    }
430

431
    return rc;
55,484✔
432
}
55,484✔
433

434
static int
435
vt_destructor(sqlite3_vtab* p_svt)
55,483✔
436
{
437
    log_vtab* p_vt = (log_vtab*) p_svt;
55,483✔
438

439
    log_debug("deleting log format table: %p", p_vt);
55,483✔
440

441
    delete p_vt;
55,483✔
442

443
    return SQLITE_OK;
55,483✔
444
}
445

446
static int
447
vt_connect(sqlite3* db,
24✔
448
           void* p_aux,
449
           int argc,
450
           const char* const* argv,
451
           sqlite3_vtab** pp_vt,
452
           char** pzErr)
453
{
454
    return vt_create(db, p_aux, argc, argv, pp_vt, pzErr);
24✔
455
}
456

457
static int
458
vt_disconnect(sqlite3_vtab* pVtab)
24✔
459
{
460
    return vt_destructor(pVtab);
24✔
461
}
462

463
static int
464
vt_destroy(sqlite3_vtab* p_vt)
55,459✔
465
{
466
    return vt_destructor(p_vt);
55,459✔
467
}
468

469
static int vt_next(sqlite3_vtab_cursor* cur);
470

471
static int
472
vt_open(sqlite3_vtab* p_svt, sqlite3_vtab_cursor** pp_cursor)
195✔
473
{
474
    log_vtab* p_vt = (log_vtab*) p_svt;
195✔
475

476
    p_vt->base.zErrMsg = nullptr;
195✔
477

478
    vtab_cursor* p_cur = new vtab_cursor();
195✔
479

480
    *pp_cursor = (sqlite3_vtab_cursor*) p_cur;
195✔
481

482
    p_cur->base.pVtab = p_svt;
195✔
483
    p_cur->log_cursor.lc_opid_bloom_bits = std::nullopt;
195✔
484
    p_cur->log_cursor.lc_tid_bloom_bits = std::nullopt;
195✔
485
    p_cur->log_cursor.lc_curr_line = 0_vl;
195✔
486
    p_cur->log_cursor.lc_direction = 1_vl;
195✔
487
    p_cur->log_cursor.lc_end_line = vis_line_t(p_vt->lss->text_line_count());
195✔
488
    p_cur->log_cursor.lc_sub_index = 0;
195✔
489

490
    for (auto& ld : *p_vt->lss) {
400✔
491
        auto* lf = ld->get_file_ptr();
205✔
492

493
        if (lf == nullptr) {
205✔
494
            continue;
×
495
        }
496

497
        lf->enable_cache();
205✔
498
    }
499

500
    return SQLITE_OK;
195✔
501
}
502

503
static int
504
vt_close(sqlite3_vtab_cursor* cur)
195✔
505
{
506
    auto* p_cur = (vtab_cursor*) cur;
195✔
507

508
    /* Free cursor struct. */
509
    delete p_cur;
195✔
510

511
    return SQLITE_OK;
195✔
512
}
513

514
static int
515
vt_eof(sqlite3_vtab_cursor* cur)
34,093✔
516
{
517
    auto* vc = (vtab_cursor*) cur;
34,093✔
518

519
    return vc->log_cursor.is_eof();
34,093✔
520
}
521

522
static void
523
populate_indexed_columns(vtab_cursor* vc, log_vtab* vt)
33,720✔
524
{
525
    if (vc->log_cursor.is_eof() || vc->log_cursor.lc_indexed_columns.empty()) {
33,720✔
526
        return;
2,901✔
527
    }
528

529
    logfile* lf = nullptr;
30,819✔
530

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

535
        if (ci.ci_indexed_range.contains(vl)) {
3,223,653✔
536
            // the index already contains this column, nothing to do
537
            continue;
3,222,558✔
538
        }
539

540
        if (lf == nullptr) {
2,129✔
541
            const auto cl = vt->lss->at(vl);
2,129✔
542
            uint64_t line_number;
543
            auto ld = vt->lss->find_data(cl, line_number);
2,129✔
544
            lf = (*ld)->get_file_ptr();
2,129✔
545
            auto ll = lf->begin() + line_number;
2,129✔
546

547
            vc->cache_msg(lf, ll);
2,129✔
548
            require(vc->line_values.lvv_sbr.get_data() != nullptr);
2,129✔
549
            vt->vi->extract(lf, line_number, vc->attrs, vc->line_values);
2,129✔
550
        }
551

552
        auto sub_col = logline_value_meta::table_column{
553
            (size_t) (ic.cc_column - VT_COL_MAX)};
2,129✔
554
        auto lv_iter = find_if(vc->line_values.lvv_values.begin(),
2,129✔
555
                               vc->line_values.lvv_values.end(),
556
                               logline_value_col_eq(sub_col));
557
        if (lv_iter == vc->line_values.lvv_values.end()
2,129✔
558
            || lv_iter->lv_meta.lvm_kind == value_kind_t::VALUE_NULL)
2,129✔
559
        {
560
            continue;
1,034✔
561
        }
562

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

565
#ifdef DEBUG_INDEXING
566
        log_debug("updated index for column %d %.*s -> %d",
567
                  ic.cc_column,
568
                  value.length(),
569
                  value.data(),
570
                  (int) vc->log_cursor.lc_curr_line);
571
#endif
572

573
        auto& line_deq = ci.ci_value_to_lines[value];
1,095✔
574
        if (line_deq.empty()
1,095✔
575
            || (line_deq.front() != vl && line_deq.back() != vl))
1,095✔
576
        {
577
            if (vc->log_cursor.lc_direction < 0) {
1,095✔
578
                line_deq.push_front(vl);
774✔
579
            } else {
580
                line_deq.push_back(vl);
321✔
581
            }
582
        }
583
    }
584
}
585

586
static int
587
vt_next(sqlite3_vtab_cursor* cur)
33,924✔
588
{
589
    auto* vc = (vtab_cursor*) cur;
33,924✔
590
    auto* vt = (log_vtab*) cur->pVtab;
33,924✔
591
    auto done = false;
33,924✔
592

593
#ifdef DEBUG_INDEXING
594
    log_debug("vt_next([%d:%d:%d])",
595
              vc->log_cursor.lc_curr_line,
596
              vc->log_cursor.lc_end_line,
597
              vc->log_cursor.lc_direction);
598
#endif
599

600
    vc->invalidate();
33,924✔
601
    if (!vc->log_cursor.lc_indexed_lines.empty()
33,924✔
602
        && vc->log_cursor.lc_indexed_lines_range.contains(
33,924✔
603
            vc->log_cursor.lc_curr_line))
604
    {
605
        vc->log_cursor.lc_curr_line = vc->log_cursor.lc_indexed_lines.back();
19,534✔
606
        vc->log_cursor.lc_indexed_lines.pop_back();
19,534✔
607
    } else {
608
        vc->log_cursor.lc_curr_line += vc->log_cursor.lc_direction;
14,390✔
609
    }
610
    vc->log_cursor.lc_sub_index = 0;
33,924✔
611
    do {
612
        log_cursor_latest = vc->log_cursor;
54,353✔
613
        if (((log_cursor_latest.lc_curr_line % 1024) == 0)
54,353✔
614
            && (log_vtab_data.lvd_progress != nullptr
54,677✔
615
                && log_vtab_data.lvd_progress(log_cursor_latest)))
324✔
616
        {
617
            break;
×
618
        }
619

620
        while (vc->log_cursor.lc_curr_line != -1_vl && !vc->log_cursor.is_eof()
109,608✔
621
               && !vt->vi->is_valid(vc->log_cursor, *vt->lss))
109,608✔
622
        {
623
            vc->log_cursor.lc_curr_line += vc->log_cursor.lc_direction;
452✔
624
            vc->log_cursor.lc_sub_index = 0;
452✔
625
        }
626
        if (vc->log_cursor.is_eof()) {
54,353✔
627
            log_info("vt_next at EOF (%d:%d:%d), scanned rows %lu",
373✔
628
                     (int) vc->log_cursor.lc_curr_line,
629
                     (int) vc->log_cursor.lc_end_line,
630
                     (int) vc->log_cursor.lc_direction,
631
                     vc->log_cursor.lc_scanned_rows);
632
            done = true;
373✔
633
        } else {
634
            done = vt->vi->next(vc->log_cursor, *vt->lss);
53,980✔
635
            if (done) {
53,980✔
636
                if (vc->log_cursor.lc_curr_line % 10000 == 0) {
33,551✔
637
                    log_debug("scanned %d", (int) vc->log_cursor.lc_curr_line);
314✔
638
                }
639
#ifdef DEBUG_INDEXING
640
                log_debug("scanned %d", vc->log_cursor.lc_curr_line);
641
#endif
642
                vc->log_cursor.lc_scanned_rows += 1;
33,551✔
643
                populate_indexed_columns(vc, vt);
33,551✔
644
                vt->vi->expand_indexes_to(vc->log_cursor.lc_indexed_columns,
33,551✔
645
                                          vc->log_cursor.lc_curr_line);
646
            } else {
647
                if (!vc->log_cursor.lc_indexed_lines.empty()
20,429✔
648
                    && vc->log_cursor.lc_indexed_lines_range.contains(
20,429✔
649
                        vc->log_cursor.lc_curr_line))
650
                {
651
                    vc->log_cursor.lc_curr_line
652
                        = vc->log_cursor.lc_indexed_lines.back();
×
653
                    vc->log_cursor.lc_indexed_lines.pop_back();
×
654
                } else {
655
                    vc->log_cursor.lc_curr_line += vc->log_cursor.lc_direction;
20,429✔
656
                }
657
                vc->log_cursor.lc_sub_index = 0;
20,429✔
658
            }
659
        }
660
    } while (!done);
54,353✔
661

662
#ifdef DEBUG_INDEXING
663
    log_debug("vt_next() -> [%d:%d:%d]",
664
              vc->log_cursor.lc_curr_line,
665
              vc->log_cursor.lc_end_line,
666
              vc->log_cursor.lc_direction);
667
#endif
668

669
    return SQLITE_OK;
33,924✔
670
}
671

672
static int
673
vt_next_no_rowid(sqlite3_vtab_cursor* cur)
169✔
674
{
675
    auto* vc = (vtab_cursor*) cur;
169✔
676
    auto* vt = (log_vtab*) cur->pVtab;
169✔
677
    auto done = false;
169✔
678

679
    vc->invalidate();
169✔
680
    do {
681
        log_cursor_latest = vc->log_cursor;
760✔
682
        if (((log_cursor_latest.lc_curr_line % 1024) == 0)
760✔
683
            && (log_vtab_data.lvd_progress != nullptr
792✔
684
                && log_vtab_data.lvd_progress(log_cursor_latest)))
32✔
685
        {
686
            break;
×
687
        }
688

689
        auto vl_before = vc->log_cursor.lc_curr_line;
760✔
690
        done = vt->vi->next(vc->log_cursor, *vt->lss);
760✔
691
        if (vl_before != vc->log_cursor.lc_curr_line) {
760✔
692
            vt->vi->expand_indexes_to(vc->log_cursor.lc_indexed_columns,
×
693
                                      vl_before);
694
        }
695
        if (done) {
760✔
696
            populate_indexed_columns(vc, vt);
169✔
697
        } else if (vc->log_cursor.is_eof()) {
591✔
698
            done = true;
×
699
        } else {
700
            require(vc->log_cursor.lc_curr_line
591✔
701
                    < (ssize_t) vt->lss->text_line_count());
702

703
            if (!vc->log_cursor.lc_indexed_lines.empty()
591✔
704
                && vc->log_cursor.lc_indexed_lines_range.contains(
591✔
705
                    vc->log_cursor.lc_curr_line))
706
            {
707
                vt->vi->expand_indexes_to(vc->log_cursor.lc_indexed_columns,
3✔
708
                                          vc->log_cursor.lc_curr_line);
709
                vc->log_cursor.lc_curr_line
710
                    = vc->log_cursor.lc_indexed_lines.back();
3✔
711
                vc->log_cursor.lc_indexed_lines.pop_back();
3✔
712
#ifdef DEBUG_INDEXING
713
                log_debug("going to next line from index %d",
714
                          (int) vc->log_cursor.lc_curr_line);
715
#endif
716
            } else {
717
                vt->vi->expand_indexes_to(vc->log_cursor.lc_indexed_columns,
588✔
718
                                          vc->log_cursor.lc_curr_line);
719
                vc->log_cursor.lc_curr_line += vc->log_cursor.lc_direction;
588✔
720
            }
721
            vc->log_cursor.lc_sub_index = 0;
591✔
722
        }
723
    } while (!done);
760✔
724

725
#ifdef DEBUG_INDEXING
726
    log_debug("vt_next_no_rowid() -> %d:%d",
727
              vc->log_cursor.lc_curr_line,
728
              vc->log_cursor.lc_end_line);
729
#endif
730

731
    return SQLITE_OK;
169✔
732
}
733

734
static int
735
vt_column(sqlite3_vtab_cursor* cur, sqlite3_context* ctx, int col)
84,230✔
736
{
737
    auto* vc = (vtab_cursor*) cur;
84,230✔
738
    auto* vt = (log_vtab*) cur->pVtab;
84,230✔
739

740
#ifdef DEBUG_INDEXING
741
    log_debug("vt_column(%s, %d:%d)",
742
              vt->vi->get_name().get(),
743
              (int) vc->log_cursor.lc_curr_line,
744
              col);
745
#endif
746

747
    content_line_t cl(vt->lss->at(vc->log_cursor.lc_curr_line));
84,230✔
748
    uint64_t line_number;
749
    auto ld = vt->lss->find_data(cl, line_number);
84,230✔
750
    auto* lf = (*ld)->get_file_ptr();
84,230✔
751
    auto ll = lf->begin() + line_number;
84,230✔
752

753
    require(col >= 0);
84,230✔
754

755
    /* Just return the ordinal of the column requested. */
756
    switch (col) {
84,230✔
757
        case VT_COL_LINE_NUMBER: {
2,665✔
758
            sqlite3_result_int64(ctx, vc->log_cursor.lc_curr_line);
2,665✔
759
            break;
2,665✔
760
        }
761

762
        case VT_COL_LOG_TIME: {
373✔
763
            char buffer[64];
764

765
            sql_strftime(buffer, sizeof(buffer), ll->get_timeval());
373✔
766
            sqlite3_result_text(ctx, buffer, strlen(buffer), SQLITE_TRANSIENT);
373✔
767
            break;
373✔
768
        }
769

770
        case VT_COL_LEVEL: {
1,290✔
771
            const auto& level_name = ll->get_level_name();
1,290✔
772

773
            sqlite3_result_text(
1,290✔
774
                ctx, level_name.data(), level_name.length(), SQLITE_STATIC);
775
            break;
1,290✔
776
        }
777

778
        default:
79,902✔
779
            if (col > (VT_COL_MAX + vt->vi->vi_column_count - 1)) {
79,902✔
780
                auto footer_column = static_cast<log_footer_columns>(
781
                    col - (VT_COL_MAX + vt->vi->vi_column_count - 1) - 1);
5,143✔
782

783
                switch (footer_column) {
5,143✔
784
                    case log_footer_columns::partition: {
383✔
785
                        auto& vb = vt->tc->get_bookmarks();
383✔
786
                        const auto& bv = vb[&textview_curses::BM_PARTITION];
383✔
787

788
                        if (bv.empty()) {
383✔
789
                            sqlite3_result_null(ctx);
286✔
790
                        } else {
791
                            vis_line_t curr_line(vc->log_cursor.lc_curr_line);
97✔
792
                            auto iter
793
                                = bv.bv_tree.lower_bound(curr_line + 1_vl);
97✔
794

795
                            if (iter != bv.bv_tree.begin()) {
97✔
796
                                --iter;
69✔
797
                                auto line_meta_opt
798
                                    = vt->lss->find_bookmark_metadata(*iter);
69✔
799
                                if (line_meta_opt
69✔
800
                                    && !line_meta_opt.value()->bm_name.empty())
69✔
801
                                {
802
                                    sqlite3_result_text(
69✔
803
                                        ctx,
804
                                        line_meta_opt.value()->bm_name.c_str(),
69✔
805
                                        line_meta_opt.value()->bm_name.size(),
69✔
806
                                        SQLITE_TRANSIENT);
807
                                } else {
808
                                    sqlite3_result_null(ctx);
×
809
                                }
810
                            } else {
811
                                sqlite3_result_null(ctx);
28✔
812
                            }
813
                        }
814
                        break;
383✔
815
                    }
816
                    case log_footer_columns::actual_time: {
66✔
817
                        char buffer[64] = "";
66✔
818

819
                        if (ll->is_time_skewed()) {
66✔
820
                            if (vc->line_values.lvv_values.empty()) {
2✔
821
                                vc->cache_msg(lf, ll);
2✔
822
                                require(vc->line_values.lvv_sbr.get_data()
2✔
823
                                        != nullptr);
824
                                vt->vi->extract(lf,
2✔
825
                                                line_number,
826
                                                vc->attrs,
2✔
827
                                                vc->line_values);
2✔
828
                            }
829

830
                            struct line_range time_range;
2✔
831

832
                            time_range = find_string_attr_range(vc->attrs,
2✔
833
                                                                &L_TIMESTAMP);
834

835
                            const auto* time_src
836
                                = vc->line_values.lvv_sbr.get_data()
2✔
837
                                + time_range.lr_start;
2✔
838
                            struct timeval actual_tv;
839
                            struct exttm tm;
2✔
840

841
                            if (lf->get_format_ptr()->lf_date_time.scan(
4✔
842
                                    time_src,
843
                                    time_range.length(),
2✔
844
                                    lf->get_format_ptr()
845
                                        ->get_timestamp_formats(),
846
                                    &tm,
847
                                    actual_tv,
848
                                    false))
849
                            {
850
                                sql_strftime(buffer, sizeof(buffer), actual_tv);
2✔
851
                            }
852
                        } else {
853
                            sql_strftime(
64✔
854
                                buffer, sizeof(buffer), ll->get_timeval());
128✔
855
                        }
856
                        sqlite3_result_text(
66✔
857
                            ctx, buffer, strlen(buffer), SQLITE_TRANSIENT);
66✔
858
                        break;
66✔
859
                    }
860
                    case log_footer_columns::idle_msecs: {
282✔
861
                        if (vc->log_cursor.lc_curr_line == 0) {
282✔
862
                            sqlite3_result_int64(ctx, 0);
55✔
863
                        } else {
864
                            content_line_t prev_cl(vt->lss->at(
227✔
865
                                vis_line_t(vc->log_cursor.lc_curr_line - 1)));
227✔
866
                            auto prev_lf = vt->lss->find(prev_cl);
227✔
867
                            auto prev_ll = prev_lf->begin() + prev_cl;
227✔
868

869
                            auto prev_time
870
                                = prev_ll
871
                                      ->get_time<std::chrono::milliseconds>();
227✔
872
                            auto curr_line_time
873
                                = ll->get_time<std::chrono::milliseconds>();
227✔
874
                            // require(curr_line_time >= prev_time);
875
                            sqlite3_result_int64(
227✔
876
                                ctx,
877
                                curr_line_time.count() - prev_time.count());
227✔
878
                        }
227✔
879
                        break;
282✔
880
                    }
881
                    case log_footer_columns::mark: {
301✔
882
                        sqlite3_result_int(ctx, ll->is_marked());
301✔
883
                        break;
301✔
884
                    }
885
                    case log_footer_columns::comment: {
312✔
886
                        auto line_meta_opt = vt->lss->find_bookmark_metadata(
312✔
887
                            vc->log_cursor.lc_curr_line);
888
                        if (!line_meta_opt
312✔
889
                            || line_meta_opt.value()->bm_comment.empty())
312✔
890
                        {
891
                            sqlite3_result_null(ctx);
310✔
892
                        } else {
893
                            const auto& meta = *(line_meta_opt.value());
2✔
894
                            sqlite3_result_text(ctx,
2✔
895
                                                meta.bm_comment.c_str(),
896
                                                meta.bm_comment.length(),
2✔
897
                                                SQLITE_TRANSIENT);
898
                        }
899
                        break;
312✔
900
                    }
901
                    case log_footer_columns::tags: {
707✔
902
                        auto line_meta_opt = vt->lss->find_bookmark_metadata(
707✔
903
                            vc->log_cursor.lc_curr_line);
904
                        if (!line_meta_opt
707✔
905
                            || line_meta_opt.value()->bm_tags.empty())
707✔
906
                        {
907
                            sqlite3_result_null(ctx);
700✔
908
                        } else {
909
                            const auto& meta = *(line_meta_opt.value());
7✔
910

911
                            yajlpp_gen gen;
7✔
912

913
                            yajl_gen_config(gen, yajl_gen_beautify, false);
7✔
914

915
                            {
916
                                yajlpp_array arr(gen);
7✔
917

918
                                for (const auto& str : meta.bm_tags) {
14✔
919
                                    arr.gen(str);
7✔
920
                                }
921
                            }
7✔
922

923
                            to_sqlite(ctx, json_string(gen));
7✔
924
                        }
7✔
925
                        break;
707✔
926
                    }
927
                    case log_footer_columns::annotations: {
315✔
928
                        if (sqlite3_vtab_nochange(ctx)) {
315✔
929
                            return SQLITE_OK;
47✔
930
                        }
931

932
                        auto line_meta_opt = vt->lss->find_bookmark_metadata(
268✔
933
                            vc->log_cursor.lc_curr_line);
934
                        if (!line_meta_opt
268✔
935
                            || line_meta_opt.value()
272✔
936
                                   ->bm_annotations.la_pairs.empty())
4✔
937
                        {
938
                            sqlite3_result_null(ctx);
264✔
939
                        } else {
940
                            const auto& meta = *(line_meta_opt.value());
4✔
941
                            to_sqlite(
4✔
942
                                ctx,
943
                                logmsg_annotations_handlers.to_json_string(
4✔
944
                                    meta.bm_annotations));
4✔
945
                        }
946
                        break;
268✔
947
                    }
948
                    case log_footer_columns::filters: {
282✔
949
                        const auto& filter_mask
950
                            = (*ld)->ld_filter_state.lfo_filter_state.tfs_mask;
282✔
951

952
                        if (!filter_mask[line_number]) {
282✔
953
                            sqlite3_result_null(ctx);
281✔
954
                        } else {
955
                            const auto& filters = vt->lss->get_filters();
1✔
956
                            yajlpp_gen gen;
1✔
957

958
                            yajl_gen_config(gen, yajl_gen_beautify, false);
1✔
959

960
                            {
961
                                yajlpp_array arr(gen);
1✔
962

963
                                for (const auto& filter : filters) {
2✔
964
                                    if (filter->lf_deleted) {
1✔
965
                                        continue;
×
966
                                    }
967

968
                                    uint32_t mask
969
                                        = (1UL << filter->get_index());
1✔
970

971
                                    if (filter_mask[line_number] & mask) {
1✔
972
                                        arr.gen(filter->get_index());
1✔
973
                                    }
974
                                }
975
                            }
1✔
976

977
                            to_sqlite(ctx, gen.to_string_fragment());
1✔
978
                            sqlite3_result_subtype(ctx, JSON_SUBTYPE);
1✔
979
                        }
1✔
980
                        break;
282✔
981
                    }
982
                    case log_footer_columns::opid: {
85✔
983
                        if (vc->line_values.lvv_values.empty()) {
85✔
984
                            vc->cache_msg(lf, ll);
56✔
985
                            require(vc->line_values.lvv_sbr.get_data()
56✔
986
                                    != nullptr);
987
                            vt->vi->extract(
56✔
988
                                lf, line_number, vc->attrs, vc->line_values);
56✔
989
                        }
990

991
                        if (vc->line_values.lvv_opid_value) {
85✔
992
                            to_sqlite(ctx,
43✔
993
                                      vc->line_values.lvv_opid_value.value());
43✔
994
                        } else {
995
                            sqlite3_result_null(ctx);
42✔
996
                        }
997
                        break;
85✔
998
                    }
999
                    case log_footer_columns::user_opid: {
84✔
1000
                        if (vc->line_values.lvv_values.empty()) {
84✔
1001
                            vc->cache_msg(lf, ll);
32✔
1002
                            require(vc->line_values.lvv_sbr.get_data()
32✔
1003
                                    != nullptr);
1004
                            vt->vi->extract(
32✔
1005
                                lf, line_number, vc->attrs, vc->line_values);
32✔
1006
                        }
1007

1008
                        if (vc->line_values.lvv_opid_value
84✔
1009
                            && vc->line_values.lvv_opid_provenance
84✔
1010
                                == logline_value_vector::opid_provenance::user)
84✔
1011
                        {
1012
                            to_sqlite(ctx,
×
1013
                                      vc->line_values.lvv_opid_value.value());
×
1014
                        } else {
1015
                            sqlite3_result_null(ctx);
84✔
1016
                        }
1017
                        break;
84✔
1018
                    }
1019
                    case log_footer_columns::format: {
76✔
1020
                        auto format_name = lf->get_format_name();
76✔
1021
                        sqlite3_result_text(ctx,
76✔
1022
                                            format_name.get(),
1023
                                            format_name.size(),
76✔
1024
                                            SQLITE_STATIC);
1025
                        break;
76✔
1026
                    }
1027
                    case log_footer_columns::format_regex: {
53✔
1028
                        auto pat_name = lf->get_format_ptr()->get_pattern_name(
106✔
1029
                            lf->get_format_file_state().lffs_pattern_locks,
53✔
1030
                            line_number);
1031
                        sqlite3_result_text(ctx,
53✔
1032
                                            pat_name.get(),
1033
                                            pat_name.size(),
53✔
1034
                                            SQLITE_STATIC);
1035
                        break;
53✔
1036
                    }
1037
                    case log_footer_columns::time_msecs: {
1,452✔
1038
                        sqlite3_result_int64(
1,452✔
1039
                            ctx,
1040
                            ll->get_time<std::chrono::milliseconds>().count());
1,452✔
1041
                        break;
1,452✔
1042
                    }
1043
                    case log_footer_columns::path: {
57✔
1044
                        const auto& fn = lf->get_filename();
57✔
1045

1046
                        sqlite3_result_text(ctx,
57✔
1047
                                            fn.c_str(),
1048
                                            fn.native().length(),
57✔
1049
                                            SQLITE_STATIC);
1050
                        break;
57✔
1051
                    }
1052
                    case log_footer_columns::unique_path: {
57✔
1053
                        const auto& fn = lf->get_unique_path();
57✔
1054

1055
                        sqlite3_result_text(ctx,
57✔
1056
                                            fn.c_str(),
1057
                                            fn.native().length(),
57✔
1058
                                            SQLITE_STATIC);
1059
                        break;
57✔
1060
                    }
1061
                    case log_footer_columns::text: {
59✔
1062
                        shared_buffer_ref line;
59✔
1063

1064
                        lf->read_full_message(ll, line);
59✔
1065
                        line.erase_ansi();
59✔
1066
                        sqlite3_result_text(ctx,
59✔
1067
                                            line.get_data(),
1068
                                            line.length(),
59✔
1069
                                            SQLITE_TRANSIENT);
1070
                        break;
59✔
1071
                    }
59✔
1072
                    case log_footer_columns::body: {
218✔
1073
                        if (vc->line_values.lvv_values.empty()) {
218✔
1074
                            vc->cache_msg(lf, ll);
110✔
1075
                            require(vc->line_values.lvv_sbr.get_data()
110✔
1076
                                    != nullptr);
1077
                            vt->vi->extract(
110✔
1078
                                lf, line_number, vc->attrs, vc->line_values);
110✔
1079
                        }
1080

1081
                        auto body_range
1082
                            = find_string_attr_range(vc->attrs, &SA_BODY);
218✔
1083
                        if (!body_range.is_valid()) {
218✔
1084
                            sqlite3_result_null(ctx);
×
1085
                        } else {
1086
                            const char* msg_start
1087
                                = vc->line_values.lvv_sbr.get_data();
218✔
1088

1089
                            sqlite3_result_text(ctx,
218✔
1090
                                                &msg_start[body_range.lr_start],
218✔
1091
                                                body_range.length(),
1092
                                                SQLITE_TRANSIENT);
1093
                        }
1094
                        break;
218✔
1095
                    }
1096
                    case log_footer_columns::raw_text: {
66✔
1097
                        auto read_res = lf->read_raw_message(ll);
66✔
1098

1099
                        if (read_res.isErr()) {
66✔
1100
                            auto msg = fmt::format(
1101
                                FMT_STRING("unable to read line -- {}"),
×
1102
                                read_res.unwrapErr());
×
1103
                            sqlite3_result_error(
×
1104
                                ctx, msg.c_str(), msg.length());
×
1105
                        } else {
×
1106
                            auto sbr = read_res.unwrap();
66✔
1107

1108
                            sqlite3_result_text(ctx,
66✔
1109
                                                sbr.get_data(),
1110
                                                sbr.length(),
66✔
1111
                                                SQLITE_TRANSIENT);
1112
                        }
66✔
1113
                        break;
66✔
1114
                    }
66✔
1115
                    case log_footer_columns::line_hash: {
58✔
1116
                        auto lw
1117
                            = vt->lss->window_at(vc->log_cursor.lc_curr_line);
58✔
1118
                        for (const auto& li : *lw) {
58✔
1119
                            auto hash_res = li.get_line_hash();
58✔
1120
                            if (hash_res.isErr()) {
58✔
1121
                                auto msg = fmt::format(
1122
                                    FMT_STRING("unable to read line -- {}"),
×
1123
                                    hash_res.unwrapErr());
×
1124
                                sqlite3_result_error(
×
1125
                                    ctx, msg.c_str(), msg.length());
×
1126
                            } else {
×
1127
                                to_sqlite(ctx,
58✔
1128
                                          text_auto_buffer{hash_res.unwrap()});
116✔
1129
                            }
1130
                            break;
58✔
1131
                        }
116✔
1132
                        break;
58✔
1133
                    }
58✔
1134
                    case log_footer_columns::line_link: {
58✔
1135
                        auto anchor_opt = vt->lss->anchor_for_row(
58✔
1136
                            vc->log_cursor.lc_curr_line);
58✔
1137
                        if (anchor_opt) {
58✔
1138
                            to_sqlite(ctx, anchor_opt.value());
58✔
1139
                        } else {
1140
                            sqlite3_result_null(ctx);
×
1141
                        }
1142
                        break;
58✔
1143
                    }
58✔
1144
                    case log_footer_columns::src_file: {
58✔
1145
                        if (vc->line_values.lvv_values.empty()) {
58✔
1146
                            vc->cache_msg(lf, ll);
7✔
1147
                            require(vc->line_values.lvv_sbr.get_data()
7✔
1148
                                    != nullptr);
1149
                            vt->vi->extract(
7✔
1150
                                lf, line_number, vc->attrs, vc->line_values);
7✔
1151
                        }
1152

1153
                        auto src_range
1154
                            = find_string_attr_range(vc->attrs, &SA_SRC_FILE);
58✔
1155
                        if (!src_range.is_valid()) {
58✔
1156
                            sqlite3_result_null(ctx);
34✔
1157
                        } else {
1158
                            const char* msg_start
1159
                                = vc->line_values.lvv_sbr.get_data();
24✔
1160

1161
                            sqlite3_result_text(ctx,
24✔
1162
                                                &msg_start[src_range.lr_start],
24✔
1163
                                                src_range.length(),
1164
                                                SQLITE_TRANSIENT);
1165
                        }
1166
                        break;
58✔
1167
                    }
1168
                    case log_footer_columns::src_line: {
58✔
1169
                        if (vc->line_values.lvv_values.empty()) {
58✔
1170
                            vc->cache_msg(lf, ll);
×
1171
                            require(vc->line_values.lvv_sbr.get_data()
×
1172
                                    != nullptr);
1173
                            vt->vi->extract(
×
1174
                                lf, line_number, vc->attrs, vc->line_values);
×
1175
                        }
1176

1177
                        auto src_range
1178
                            = find_string_attr_range(vc->attrs, &SA_SRC_LINE);
58✔
1179
                        if (!src_range.is_valid()) {
58✔
1180
                            sqlite3_result_null(ctx);
34✔
1181
                        } else {
1182
                            const char* msg_start
1183
                                = vc->line_values.lvv_sbr.get_data();
24✔
1184

1185
                            sqlite3_result_text(ctx,
24✔
1186
                                                &msg_start[src_range.lr_start],
24✔
1187
                                                src_range.length(),
1188
                                                SQLITE_TRANSIENT);
1189
                        }
1190
                        break;
58✔
1191
                    }
1192
                    case log_footer_columns::thread_id: {
56✔
1193
                        if (vc->line_values.lvv_values.empty()) {
56✔
1194
                            vc->cache_msg(lf, ll);
5✔
1195
                            require(vc->line_values.lvv_sbr.get_data()
5✔
1196
                                    != nullptr);
1197
                            vt->vi->extract(
5✔
1198
                                lf, line_number, vc->attrs, vc->line_values);
5✔
1199
                        }
1200

1201
                        if (vc->line_values.lvv_thread_id_value) {
56✔
1202
                            to_sqlite(
23✔
1203
                                ctx,
1204
                                vc->line_values.lvv_thread_id_value.value());
23✔
1205
                        } else {
1206
                            sqlite3_result_null(ctx);
33✔
1207
                        }
1208
                        break;
56✔
1209
                    }
1210
                }
1211
            } else {
1212
                if (vc->line_values.lvv_values.empty()) {
74,759✔
1213
                    vc->cache_msg(lf, ll);
30,861✔
1214
                    require(vc->line_values.lvv_sbr.get_data() != nullptr);
30,861✔
1215
                    vt->vi->extract(
30,861✔
1216
                        lf, line_number, vc->attrs, vc->line_values);
30,861✔
1217
                }
1218

1219
                auto sub_col = logline_value_meta::table_column{
1220
                    (size_t) (col - VT_COL_MAX)};
74,759✔
1221
                auto lv_iter = find_if(vc->line_values.lvv_values.begin(),
74,759✔
1222
                                       vc->line_values.lvv_values.end(),
1223
                                       logline_value_col_eq(sub_col));
1224

1225
                if (lv_iter != vc->line_values.lvv_values.end()) {
74,759✔
1226
                    if (!lv_iter->lv_meta.lvm_struct_name.empty()) {
74,242✔
1227
                        yajlpp_gen gen;
8✔
1228
                        yajl_gen_config(gen, yajl_gen_beautify, false);
8✔
1229

1230
                        {
1231
                            yajlpp_map root(gen);
8✔
1232

1233
                            for (const auto& lv_struct :
8✔
1234
                                 vc->line_values.lvv_values)
82✔
1235
                            {
1236
                                if (lv_struct.lv_meta.lvm_column != sub_col) {
66✔
1237
                                    continue;
39✔
1238
                                }
1239

1240
                                root.gen(lv_struct.lv_meta.lvm_name);
27✔
1241
                                switch (lv_struct.lv_meta.lvm_kind) {
27✔
1242
                                    case value_kind_t::VALUE_NULL:
2✔
1243
                                        root.gen();
2✔
1244
                                        break;
2✔
1245
                                    case value_kind_t::VALUE_BOOLEAN:
×
1246
                                        root.gen((bool) lv_struct.lv_value.i);
×
1247
                                        break;
×
1248
                                    case value_kind_t::VALUE_INTEGER:
×
1249
                                        root.gen(lv_struct.lv_value.i);
×
1250
                                        break;
×
1251
                                    case value_kind_t::VALUE_FLOAT:
×
1252
                                        root.gen(lv_struct.lv_value.d);
×
1253
                                        break;
×
1254
                                    case value_kind_t::VALUE_JSON: {
1✔
1255
                                        auto_mem<yajl_handle_t> parse_handle(
1256
                                            yajl_free);
1✔
1257
                                        json_ptr jp("");
1✔
1258
                                        json_op jo(jp);
1✔
1259

1260
                                        jo.jo_ptr_callbacks
1261
                                            = json_op::gen_callbacks;
1✔
1262
                                        jo.jo_ptr_data = gen;
1✔
1263
                                        parse_handle.reset(
1✔
1264
                                            yajl_alloc(&json_op::ptr_callbacks,
1265
                                                       nullptr,
1266
                                                       &jo));
1267

1268
                                        const auto* json_in
1269
                                            = (const unsigned char*)
1270
                                                  lv_struct.text_value();
1✔
1271
                                        auto json_len = lv_struct.text_length();
1✔
1272

1273
                                        if (yajl_parse(parse_handle.in(),
1✔
1274
                                                       json_in,
1275
                                                       json_len)
1276
                                                != yajl_status_ok
1277
                                            || yajl_complete_parse(
1✔
1278
                                                   parse_handle.in())
1279
                                                != yajl_status_ok)
1280
                                        {
1281
                                            log_error(
×
1282
                                                "failed to parse json value: "
1283
                                                "%.*s",
1284
                                                (int) lv_struct.text_length(),
1285
                                                lv_struct.text_value());
1286
                                            root.gen(lv_struct.to_string());
×
1287
                                        }
1288
                                        break;
1✔
1289
                                    }
1✔
1290
                                    default:
24✔
1291
                                        root.gen(lv_struct.to_string());
24✔
1292
                                        break;
24✔
1293
                                }
1294
                            }
1295
                        }
8✔
1296

1297
                        auto sf = gen.to_string_fragment();
8✔
1298
                        sqlite3_result_text(
8✔
1299
                            ctx, sf.data(), sf.length(), SQLITE_TRANSIENT);
1300
                        sqlite3_result_subtype(ctx, JSON_SUBTYPE);
8✔
1301
                    } else {
8✔
1302
                        switch (lv_iter->lv_meta.lvm_kind) {
74,234✔
1303
                            case value_kind_t::VALUE_NULL:
2,461✔
1304
                                sqlite3_result_null(ctx);
2,461✔
1305
                                break;
2,461✔
1306
                            case value_kind_t::VALUE_JSON: {
65✔
1307
                                sqlite3_result_text(ctx,
65✔
1308
                                                    lv_iter->text_value(),
1309
                                                    lv_iter->text_length(),
65✔
1310
                                                    SQLITE_TRANSIENT);
1311
                                sqlite3_result_subtype(ctx, JSON_SUBTYPE);
65✔
1312
                                break;
65✔
1313
                            }
1314
                            case value_kind_t::VALUE_STRUCT:
68,271✔
1315
                            case value_kind_t::VALUE_TEXT:
1316
                            case value_kind_t::VALUE_XML:
1317
                            case value_kind_t::VALUE_TIMESTAMP: {
1318
                                sqlite3_result_text(ctx,
68,271✔
1319
                                                    lv_iter->text_value(),
1320
                                                    lv_iter->text_length(),
68,271✔
1321
                                                    SQLITE_TRANSIENT);
1322
                                break;
68,271✔
1323
                            }
1324
                            case value_kind_t::VALUE_W3C_QUOTED:
20✔
1325
                            case value_kind_t::VALUE_QUOTED:
1326
                                if (lv_iter->text_length() == 0) {
20✔
1327
                                    sqlite3_result_text(
×
1328
                                        ctx, "", 0, SQLITE_STATIC);
1329
                                } else {
1330
                                    const char* text_value
1331
                                        = lv_iter->text_value();
20✔
1332
                                    size_t text_len = lv_iter->text_length();
20✔
1333

1334
                                    switch (text_value[0]) {
20✔
1335
                                        case '\'':
7✔
1336
                                        case '"': {
1337
                                            char* val = (char*) sqlite3_malloc(
7✔
1338
                                                text_len);
1339

1340
                                            if (val == nullptr) {
7✔
1341
                                                sqlite3_result_error_nomem(ctx);
×
1342
                                            } else {
1343
                                                auto unquote_func
1344
                                                    = lv_iter->lv_meta.lvm_kind
7✔
1345
                                                        == value_kind_t::
1346
                                                            VALUE_W3C_QUOTED
1347
                                                    ? unquote_w3c
7✔
1348
                                                    : unquote;
7✔
1349

1350
                                                size_t unquoted_len
1351
                                                    = unquote_func(val,
7✔
1352
                                                                   text_value,
1353
                                                                   text_len);
1354
                                                sqlite3_result_text(
7✔
1355
                                                    ctx,
1356
                                                    val,
1357
                                                    unquoted_len,
1358
                                                    sqlite3_free);
1359
                                            }
1360
                                            break;
7✔
1361
                                        }
1362
                                        default: {
13✔
1363
                                            sqlite3_result_text(
13✔
1364
                                                ctx,
1365
                                                text_value,
1366
                                                lv_iter->text_length(),
13✔
1367
                                                SQLITE_TRANSIENT);
1368
                                            break;
13✔
1369
                                        }
1370
                                    }
1371
                                }
1372
                                break;
20✔
1373

1374
                            case value_kind_t::VALUE_BOOLEAN:
2,830✔
1375
                            case value_kind_t::VALUE_INTEGER:
1376
                                sqlite3_result_int64(ctx, lv_iter->lv_value.i);
2,830✔
1377
                                break;
2,830✔
1378

1379
                            case value_kind_t::VALUE_FLOAT:
587✔
1380
                                sqlite3_result_double(ctx, lv_iter->lv_value.d);
587✔
1381
                                break;
587✔
1382

1383
                            case value_kind_t::VALUE_UNKNOWN:
×
1384
                            case value_kind_t::VALUE__MAX:
1385
                                require(0);
×
1386
                                break;
1387
                        }
1388
                    }
1389
                } else {
1390
                    sqlite3_result_null(ctx);
517✔
1391
                }
1392
            }
1393
            break;
79,855✔
1394
    }
1395

1396
    return SQLITE_OK;
84,183✔
1397
}
1398

1399
static int
1400
vt_rowid(sqlite3_vtab_cursor* cur, sqlite_int64* p_rowid)
105✔
1401
{
1402
    vtab_cursor* p_cur = (vtab_cursor*) cur;
105✔
1403

1404
    *p_rowid = (((uint64_t) p_cur->log_cursor.lc_curr_line) << 8)
105✔
1405
        | (p_cur->log_cursor.lc_sub_index & 0xff);
105✔
1406

1407
    return SQLITE_OK;
105✔
1408
}
1409

1410
void
1411
log_cursor::update(unsigned char op, vis_line_t vl, constraint_t cons)
80✔
1412
{
1413
    switch (op) {
80✔
1414
        case SQLITE_INDEX_CONSTRAINT_EQ:
55✔
1415
            if (vl < 0_vl) {
55✔
1416
                this->lc_curr_line = this->lc_end_line;
×
1417
            } else if (vl < this->lc_end_line) {
55✔
1418
                this->lc_curr_line = vl;
50✔
1419
                if (cons == constraint_t::unique) {
50✔
1420
                    this->lc_end_line = this->lc_curr_line + 1_vl;
50✔
1421
                }
1422
            }
1423
            break;
55✔
1424
        case SQLITE_INDEX_CONSTRAINT_GE:
3✔
1425
            if (vl < 0_vl) {
3✔
1426
                vl = 0_vl;
×
1427
            }
1428
            this->lc_curr_line = vl;
3✔
1429
            break;
3✔
1430
        case SQLITE_INDEX_CONSTRAINT_GT:
8✔
1431
            if (vl < 0_vl) {
8✔
1432
                this->lc_curr_line = 0_vl;
2✔
1433
            } else {
1434
                this->lc_curr_line
1435
                    = vl + (cons == constraint_t::unique ? 1_vl : 0_vl);
6✔
1436
            }
1437
            break;
8✔
1438
        case SQLITE_INDEX_CONSTRAINT_LE:
4✔
1439
            if (vl < 0_vl) {
4✔
1440
                this->lc_curr_line = this->lc_end_line;
×
1441
            } else if (vl < this->lc_end_line) {
4✔
1442
                this->lc_end_line
1443
                    = vl + (cons == constraint_t::unique ? 1_vl : 0_vl);
2✔
1444
            }
1445
            break;
4✔
1446
        case SQLITE_INDEX_CONSTRAINT_LT:
10✔
1447
            if (vl <= 0_vl) {
10✔
1448
                this->lc_curr_line = this->lc_end_line;
2✔
1449
            } else if (this->lc_direction > 0) {
8✔
1450
                if (vl < this->lc_end_line) {
×
1451
                    this->lc_end_line = vl;
×
1452
                }
1453
            } else if (this->lc_direction < 0) {
8✔
1454
                if (vl <= this->lc_curr_line) {
8✔
1455
                    this->lc_curr_line = vl - 1_vl;
8✔
1456
                }
1457
            }
1458
            break;
10✔
1459
    }
1460
#ifdef DEBUG_INDEXING
1461
    log_debug("log_cursor::update(%s, %d) -> (%d:%d:%d)",
1462
              sql_constraint_op_name(op),
1463
              vl,
1464
              this->lc_curr_line,
1465
              this->lc_end_line,
1466
              this->lc_direction);
1467
#endif
1468
}
80✔
1469

1470
log_cursor::string_constraint::string_constraint(unsigned char op,
215✔
1471
                                                 std::string value)
215✔
1472
    : sc_op(op), sc_value(std::move(value))
215✔
1473
{
1474
    if (op == SQLITE_INDEX_CONSTRAINT_REGEXP) {
215✔
1475
        auto compile_res = lnav::pcre2pp::code::from(this->sc_value);
×
1476

1477
        if (compile_res.isErr()) {
×
1478
            auto ce = compile_res.unwrapErr();
×
1479
            log_error("unable to compile regexp constraint: %s -- %s",
×
1480
                      this->sc_value.c_str(),
1481
                      ce.get_message().c_str());
1482
        } else {
×
1483
            this->sc_pattern = compile_res.unwrap().to_shared();
×
1484
        }
1485
    }
1486
}
215✔
1487

1488
bool
1489
log_cursor::string_constraint::matches(const std::string& sf) const
4✔
1490
{
1491
    switch (this->sc_op) {
4✔
1492
        case SQLITE_INDEX_CONSTRAINT_EQ:
×
1493
        case SQLITE_INDEX_CONSTRAINT_IS:
1494
            return sf == this->sc_value;
×
1495
        case SQLITE_INDEX_CONSTRAINT_NE:
×
1496
        case SQLITE_INDEX_CONSTRAINT_ISNOT:
1497
            return sf != this->sc_value;
×
1498
        case SQLITE_INDEX_CONSTRAINT_GT:
×
1499
            return sf > this->sc_value;
×
1500
        case SQLITE_INDEX_CONSTRAINT_LE:
×
1501
            return sf <= this->sc_value;
×
1502
        case SQLITE_INDEX_CONSTRAINT_LT:
×
1503
            return sf < this->sc_value;
×
1504
        case SQLITE_INDEX_CONSTRAINT_GE:
×
1505
            return sf >= this->sc_value;
×
1506
        case SQLITE_INDEX_CONSTRAINT_LIKE:
×
1507
            return sqlite3_strlike(this->sc_value.c_str(), sf.data(), 0) == 0;
×
1508
        case SQLITE_INDEX_CONSTRAINT_GLOB:
4✔
1509
            return sqlite3_strglob(this->sc_value.c_str(), sf.data()) == 0;
4✔
1510
        case SQLITE_INDEX_CONSTRAINT_REGEXP: {
×
1511
            if (this->sc_pattern != nullptr) {
×
1512
                return this->sc_pattern->find_in(sf, PCRE2_NO_UTF_CHECK)
×
1513
                    .ignore_error()
×
1514
                    .has_value();
×
1515
            }
1516
            // return true here so that the regexp is actually run and fails
1517
            return true;
×
1518
        }
1519
        case SQLITE_INDEX_CONSTRAINT_ISNOTNULL:
×
1520
            return true;
×
1521
        default:
×
1522
            return false;
×
1523
    }
1524
}
1525

1526
struct vtab_time_range {
1527
    std::optional<std::chrono::microseconds> vtr_begin;
1528
    std::optional<std::chrono::microseconds> vtr_end;
1529

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

1532
    void add(const std::chrono::microseconds& us)
7✔
1533
    {
1534
        if (!this->vtr_begin || us < this->vtr_begin) {
7✔
1535
            this->vtr_begin = us;
3✔
1536
        }
1537
        if (!this->vtr_end || this->vtr_end < us) {
7✔
1538
            this->vtr_end = us;
6✔
1539
        }
1540
    }
7✔
1541
};
1542

1543
static int
1544
vt_filter(sqlite3_vtab_cursor* p_vtc,
402✔
1545
          int idxNum,
1546
          const char* idxStr,
1547
          int argc,
1548
          sqlite3_value** argv)
1549
{
1550
    auto* p_cur = (vtab_cursor*) p_vtc;
402✔
1551
    auto* vt = (log_vtab*) p_vtc->pVtab;
402✔
1552
    sqlite3_index_info::sqlite3_index_constraint* index = nullptr;
402✔
1553

1554
    if (idxStr != nullptr) {
402✔
1555
        auto desc_len = strlen(idxStr);
391✔
1556
        auto index_len = idxNum * sizeof(*index);
391✔
1557
        auto storage_len = desc_len + 128 + index_len;
391✔
1558
        auto direction_storage
391✔
1559
            = static_cast<const char*>(idxStr) + desc_len + 1;
391✔
1560
        p_cur->log_cursor.lc_direction = vis_line_t(direction_storage[0]);
391✔
1561
        auto* remaining_storage = const_cast<void*>(
391✔
1562
            static_cast<const void*>(idxStr + desc_len + 1 + 1));
391✔
1563
        auto* index_storage
1564
            = std::align(alignof(sqlite3_index_info::sqlite3_index_constraint),
391✔
1565
                         index_len,
1566
                         remaining_storage,
1567
                         storage_len);
1568
        index = static_cast<sqlite3_index_info::sqlite3_index_constraint*>(
391✔
1569
            index_storage);
1570
    } else {
1571
        p_cur->log_cursor.lc_direction = 1_vl;
11✔
1572
    }
1573

1574
#ifdef DEBUG_INDEXING
1575
    log_info("vt_filter(%s, %d, direction=%d)",
1576
             vt->vi->get_name().get(),
1577
             idxNum,
1578
             p_cur->log_cursor.lc_direction);
1579
    log_info("  index storage: %p", index);
1580
#endif
1581
    p_cur->log_cursor.lc_format_name.clear();
402✔
1582
    p_cur->log_cursor.lc_pattern_name.clear();
402✔
1583
    p_cur->log_cursor.lc_opid_bloom_bits = std::nullopt;
402✔
1584
    p_cur->log_cursor.lc_tid_bloom_bits = std::nullopt;
402✔
1585
    p_cur->log_cursor.lc_level_constraint = std::nullopt;
402✔
1586
    p_cur->log_cursor.lc_log_path.clear();
402✔
1587
    p_cur->log_cursor.lc_last_log_path_match = nullptr;
402✔
1588
    p_cur->log_cursor.lc_last_log_path_mismatch = nullptr;
402✔
1589
    p_cur->log_cursor.lc_unique_path.clear();
402✔
1590
    p_cur->log_cursor.lc_last_unique_path_match = nullptr;
402✔
1591
    p_cur->log_cursor.lc_last_unique_path_mismatch = nullptr;
402✔
1592
    if (p_cur->log_cursor.lc_direction < 0) {
402✔
1593
        p_cur->log_cursor.lc_curr_line
1594
            = vis_line_t(vt->lss->text_line_count() - 1);
8✔
1595
        p_cur->log_cursor.lc_end_line = -1_vl;
8✔
1596
    } else {
1597
        p_cur->log_cursor.lc_curr_line = 0_vl;
394✔
1598
        p_cur->log_cursor.lc_end_line = vis_line_t(vt->lss->text_line_count());
394✔
1599
    }
1600
    p_cur->log_cursor.lc_scanned_rows = 0;
402✔
1601
    p_cur->log_cursor.lc_indexed_lines.clear();
402✔
1602
    p_cur->log_cursor.lc_indexed_lines_range = msg_range::empty();
402✔
1603

1604
    std::optional<vtab_time_range> log_time_range;
402✔
1605
    std::optional<uint64_t> opid_val;
402✔
1606
    std::optional<uint64_t> tid_val;
402✔
1607
    std::vector<log_cursor::string_constraint> log_path_constraints;
402✔
1608
    std::vector<log_cursor::string_constraint> log_unique_path_constraints;
402✔
1609

1610
    for (int lpc = 0; lpc < idxNum; lpc++) {
710✔
1611
        auto col = index[lpc].iColumn;
308✔
1612
        auto op = index[lpc].op;
308✔
1613
        switch (col) {
308✔
1614
            case VT_COL_LINE_NUMBER: {
78✔
1615
                auto vl = vis_line_t(sqlite3_value_int64(argv[lpc]));
78✔
1616

1617
                p_cur->log_cursor.update(
78✔
1618
                    op, vl, log_cursor::constraint_t::unique);
1619
                break;
78✔
1620
            }
1621
            case VT_COL_LEVEL: {
7✔
1622
                if (sqlite3_value_type(argv[lpc]) != SQLITE3_TEXT) {
7✔
1623
                    continue;
×
1624
                }
1625

1626
                auto sf = from_sqlite<string_fragment>()(argc, argv, lpc);
7✔
1627
                auto level = string2level(sf.data(), sf.length());
7✔
1628

1629
                p_cur->log_cursor.lc_level_constraint
1630
                    = log_cursor::level_constraint{
7✔
1631
                        op,
1632
                        level,
1633
                    };
7✔
1634
                break;
7✔
1635
            }
1636

1637
            case VT_COL_LOG_TIME:
2✔
1638
                if (sqlite3_value_type(argv[lpc]) == SQLITE3_TEXT) {
2✔
1639
                    const auto* datestr
1640
                        = (const char*) sqlite3_value_text(argv[lpc]);
1✔
1641
                    auto datelen = sqlite3_value_bytes(argv[lpc]);
1✔
1642
                    date_time_scanner dts;
1✔
1643
                    timeval tv;
1644
                    exttm mytm;
1✔
1645

1646
                    const auto* date_end
1647
                        = dts.scan(datestr, datelen, nullptr, &mytm, tv);
1✔
1648
                    auto us = to_us(tv);
1✔
1649
                    if (date_end != (datestr + datelen)) {
1✔
1650
                        log_warning(
×
1651
                            "  log_time constraint is not a valid datetime, "
1652
                            "index will not be applied: %s",
1653
                            datestr);
1654
                    } else {
1655
                        switch (op) {
1✔
1656
                            case SQLITE_INDEX_CONSTRAINT_EQ:
×
1657
                            case SQLITE_INDEX_CONSTRAINT_IS:
1658
                                if (!log_time_range) {
×
1659
                                    log_time_range = vtab_time_range{};
×
1660
                                }
1661
                                log_time_range->add(us);
×
1662
                                break;
×
1663
                            case SQLITE_INDEX_CONSTRAINT_GT:
1✔
1664
                            case SQLITE_INDEX_CONSTRAINT_GE:
1665
                                if (!log_time_range) {
1✔
1666
                                    log_time_range = vtab_time_range{};
1✔
1667
                                }
1668
                                log_time_range->vtr_begin = us;
1✔
1669
                                break;
1✔
1670
                            case SQLITE_INDEX_CONSTRAINT_LT:
×
1671
                            case SQLITE_INDEX_CONSTRAINT_LE:
1672
                                if (!log_time_range) {
×
1673
                                    log_time_range = vtab_time_range{};
×
1674
                                }
1675
                                log_time_range->vtr_end = us;
×
1676
                                break;
×
1677
                        }
1678
                    }
1679
                } else {
1680
                    log_warning(
1✔
1681
                        "  log_time constraint is not text, index will not be "
1682
                        "applied: value_type(%d)=%d",
1683
                        lpc,
1684
                        sqlite3_value_type(argv[lpc]));
1685
                }
1686
                break;
2✔
1687
            default: {
221✔
1688
                if (col > (VT_COL_MAX + vt->vi->vi_column_count - 1)) {
221✔
1689
                    auto footer_column = static_cast<log_footer_columns>(
1690
                        col - (VT_COL_MAX + vt->vi->vi_column_count - 1) - 1);
7✔
1691

1692
                    switch (footer_column) {
7✔
1693
                        case log_footer_columns::time_msecs: {
1✔
1694
                            auto msecs = std::chrono::milliseconds{
1695
                                sqlite3_value_int64(argv[lpc])};
1✔
1696
                            switch (op) {
1✔
1697
                                case SQLITE_INDEX_CONSTRAINT_EQ:
1✔
1698
                                case SQLITE_INDEX_CONSTRAINT_IS:
1699
                                    if (!log_time_range) {
1✔
1700
                                        log_time_range = vtab_time_range{};
1✔
1701
                                    }
1702
                                    log_time_range->add(msecs);
1✔
1703
                                    break;
1✔
1704
                                case SQLITE_INDEX_CONSTRAINT_GT:
×
1705
                                case SQLITE_INDEX_CONSTRAINT_GE:
1706
                                    if (!log_time_range) {
×
1707
                                        log_time_range = vtab_time_range{};
×
1708
                                    }
1709
                                    log_time_range->vtr_begin = msecs;
×
1710
                                    break;
×
1711
                                case SQLITE_INDEX_CONSTRAINT_LT:
×
1712
                                case SQLITE_INDEX_CONSTRAINT_LE:
1713
                                    if (!log_time_range) {
×
1714
                                        log_time_range = vtab_time_range{};
×
1715
                                    }
1716
                                    log_time_range->vtr_end = msecs;
×
1717
                                    break;
×
1718
                            }
1719
                            break;
1✔
1720
                        }
1721
                        case log_footer_columns::format: {
2✔
1722
                            const auto* format_name_str
1723
                                = (const char*) sqlite3_value_text(argv[lpc]);
2✔
1724

1725
                            if (format_name_str != nullptr) {
2✔
1726
                                p_cur->log_cursor.lc_format_name
1727
                                    = intern_string::lookup(format_name_str);
4✔
1728
                            }
1729
                            break;
2✔
1730
                        }
1731
                        case log_footer_columns::format_regex: {
×
1732
                            const auto* pattern_name_str
1733
                                = (const char*) sqlite3_value_text(argv[lpc]);
×
1734

1735
                            if (pattern_name_str != nullptr) {
×
1736
                                p_cur->log_cursor.lc_pattern_name
1737
                                    = intern_string::lookup(pattern_name_str);
×
1738
                            }
1739
                            break;
×
1740
                        }
1741
                        case log_footer_columns::opid:
1✔
1742
                        case log_footer_columns::user_opid: {
1743
                            if (sqlite3_value_type(argv[lpc]) != SQLITE3_TEXT) {
1✔
1744
                                continue;
×
1745
                            }
1746
                            auto opid = from_sqlite<string_fragment>()(
1✔
1747
                                argc, argv, lpc);
1748
                            if (!log_time_range) {
1✔
1749
                                log_time_range = vtab_time_range{};
1✔
1750
                            }
1751
                            for (const auto& file_data : *vt->lss) {
2✔
1752
                                if (file_data->get_file_ptr() == nullptr) {
1✔
1753
                                    continue;
×
1754
                                }
1755
                                safe::ReadAccess<logfile::safe_opid_state>
1756
                                    r_opid_map(
1757
                                        file_data->get_file_ptr()->get_opids());
1✔
1758
                                const auto& iter
1759
                                    = r_opid_map->los_opid_ranges.find(opid);
1✔
1760
                                if (iter == r_opid_map->los_opid_ranges.end()) {
1✔
1761
                                    continue;
×
1762
                                }
1763
                                log_time_range->add(
1✔
1764
                                    iter->second.otr_range.tr_begin);
1✔
1765
                                log_time_range->add(
1✔
1766
                                    iter->second.otr_range.tr_end);
1✔
1767
                            }
1✔
1768

1769
                            opid_val = opid.bloom_bits();
1✔
1770
                            break;
1✔
1771
                        }
1772
                        case log_footer_columns::path: {
1✔
1773
                            if (sqlite3_value_type(argv[lpc]) != SQLITE3_TEXT) {
1✔
1774
                                continue;
×
1775
                            }
1776

1777
                            const auto filename
1778
                                = from_sqlite<std::string>()(argc, argv, lpc);
1✔
1779
                            const auto fn_constraint
1780
                                = log_cursor::string_constraint{op, filename};
1✔
1781
                            auto found = false;
1✔
1782

1783
                            if (!log_time_range) {
1✔
1784
                                log_time_range = vtab_time_range{};
1✔
1785
                            }
1786
                            for (const auto& file_data : *vt->lss) {
3✔
1787
                                auto* lf = file_data->get_file_ptr();
2✔
1788
                                if (lf == nullptr) {
2✔
1789
                                    continue;
×
1790
                                }
1791
                                if (fn_constraint.matches(lf->get_filename())) {
2✔
1792
                                    found = true;
2✔
1793
                                    log_time_range->add(
4✔
1794
                                        lf->front()
2✔
1795
                                            .get_time<
1796
                                                std::chrono::microseconds>());
2✔
1797
                                    log_time_range->add(
4✔
1798
                                        lf->back()
2✔
1799
                                            .get_time<
1800
                                                std::chrono::microseconds>());
4✔
1801
                                }
1802
                            }
1803
                            if (found) {
1✔
1804
                                log_path_constraints.emplace_back(
1✔
1805
                                    fn_constraint);
1806
                            }
1807
                            break;
1✔
1808
                        }
1✔
1809
                        case log_footer_columns::unique_path: {
×
1810
                            if (sqlite3_value_type(argv[lpc]) != SQLITE3_TEXT) {
×
1811
                                continue;
×
1812
                            }
1813

1814
                            const auto filename
1815
                                = from_sqlite<std::string>()(argc, argv, lpc);
×
1816
                            const auto fn_constraint
1817
                                = log_cursor::string_constraint{op, filename};
×
1818
                            auto found = false;
×
1819

1820
                            if (!log_time_range) {
×
1821
                                log_time_range = vtab_time_range{};
×
1822
                            }
1823
                            for (const auto& file_data : *vt->lss) {
×
1824
                                auto* lf = file_data->get_file_ptr();
×
1825
                                if (lf == nullptr) {
×
1826
                                    continue;
×
1827
                                }
1828
                                if (fn_constraint.matches(
×
1829
                                        lf->get_unique_path()))
×
1830
                                {
1831
                                    found = true;
×
1832
                                    log_time_range->add(
×
1833
                                        lf->front()
×
1834
                                            .get_time<
1835
                                                std::chrono::microseconds>());
×
1836
                                    log_time_range->add(
×
1837
                                        lf->back()
×
1838
                                            .get_time<
1839
                                                std::chrono::microseconds>());
×
1840
                                }
1841
                            }
1842
                            if (found) {
×
1843
                                log_unique_path_constraints.emplace_back(
×
1844
                                    fn_constraint);
1845
                            }
1846
                            break;
×
1847
                        }
1848
                        case log_footer_columns::partition:
×
1849
                        case log_footer_columns::actual_time:
1850
                        case log_footer_columns::idle_msecs:
1851
                        case log_footer_columns::mark:
1852
                        case log_footer_columns::comment:
1853
                        case log_footer_columns::tags:
1854
                        case log_footer_columns::annotations:
1855
                        case log_footer_columns::filters:
1856
                        case log_footer_columns::text:
1857
                        case log_footer_columns::body:
1858
                        case log_footer_columns::raw_text:
1859
                        case log_footer_columns::line_hash:
1860
                        case log_footer_columns::src_file:
1861
                        case log_footer_columns::src_line:
1862
                            break;
×
1863
                        case log_footer_columns::line_link: {
2✔
1864
                            if (sqlite3_value_type(argv[lpc]) != SQLITE3_TEXT) {
2✔
1865
                                continue;
×
1866
                            }
1867
                            auto permalink
1868
                                = from_sqlite<std::string>()(argc, argv, lpc);
2✔
1869
                            auto row_opt = vt->lss->row_for_anchor(permalink);
2✔
1870
                            if (row_opt) {
2✔
1871
                                p_cur->log_cursor.update(
4✔
1872
                                    op,
1873
                                    row_opt.value(),
2✔
1874
                                    log_cursor::constraint_t::unique);
1875
                            } else {
1876
                                log_trace("could not find link: %s",
×
1877
                                          permalink.c_str());
1878
                            }
1879
                            break;
2✔
1880
                        }
2✔
NEW
1881
                        case log_footer_columns::thread_id: {
×
NEW
1882
                            if (sqlite3_value_type(argv[lpc]) != SQLITE3_TEXT) {
×
NEW
1883
                                continue;
×
1884
                            }
NEW
1885
                            auto tid = from_sqlite<string_fragment>()(
×
1886
                                argc, argv, lpc);
NEW
1887
                            if (!log_time_range) {
×
NEW
1888
                                log_time_range = vtab_time_range{};
×
1889
                            }
NEW
1890
                            for (const auto& file_data : *vt->lss) {
×
NEW
1891
                                if (file_data->get_file_ptr() == nullptr) {
×
NEW
1892
                                    continue;
×
1893
                                }
1894
                                safe::ReadAccess<logfile::safe_thread_id_state>
1895
                                    r_tid_map(file_data->get_file_ptr()
NEW
1896
                                                  ->get_thread_ids());
×
1897
                                const auto& iter
NEW
1898
                                    = r_tid_map->ltis_tid_ranges.find(tid);
×
NEW
1899
                                if (iter == r_tid_map->ltis_tid_ranges.end()) {
×
NEW
1900
                                    continue;
×
1901
                                }
NEW
1902
                                log_time_range->add(
×
NEW
1903
                                    iter->second.titr_range.tr_begin);
×
NEW
1904
                                log_time_range->add(
×
NEW
1905
                                    iter->second.titr_range.tr_end);
×
1906
                            }
1907

NEW
1908
                            tid_val = tid.bloom_bits();
×
NEW
1909
                            break;
×
1910
                        }
1911
                    }
1912
                } else {
1913
                    const auto* value
1914
                        = (const char*) sqlite3_value_text(argv[lpc]);
214✔
1915

1916
                    if (value != nullptr) {
214✔
1917
                        auto value_len
1918
                            = (size_t) sqlite3_value_bytes(argv[lpc]);
214✔
1919

1920
#ifdef DEBUG_INDEXING
1921
                        log_debug("adding index request for column %d = %s",
1922
                                  col,
1923
                                  value);
1924
#endif
1925

1926
                        p_cur->log_cursor.lc_indexed_columns.emplace_back(
214✔
1927
                            col,
1928
                            log_cursor::string_constraint{
428✔
1929
                                op,
1930
                                std::string{value, value_len},
856✔
1931
                            });
1932
                    }
1933
                }
1934
                break;
221✔
1935
            }
1936
        }
1937
    }
1938

1939
    if (p_cur->log_cursor.lc_curr_line == p_cur->log_cursor.lc_end_line) {
402✔
1940
    } else if (!p_cur->log_cursor.lc_indexed_columns.empty()) {
396✔
1941
        auto min_index_range = msg_range::invalid();
214✔
1942
        auto scan_range
1943
            = msg_range::empty()
214✔
1944
                  .expand_to(p_cur->log_cursor.lc_curr_line)
214✔
1945
                  .expand_to(
214✔
1946
                      p_cur->log_cursor.lc_end_line
1947
                      - (p_cur->log_cursor.lc_direction > 0 ? 1_vl : -1_vl))
214✔
1948
                  .get_valid()
214✔
1949
                  .value();
214✔
1950
        for (const auto& icol : p_cur->log_cursor.lc_indexed_columns) {
19,734✔
1951
            auto& coli = vt->vi->vi_column_indexes[icol.cc_column];
19,520✔
1952
            if (coli.ci_index_generation != vt->lss->lss_index_generation) {
19,520✔
1953
                coli.ci_value_to_lines.clear();
8✔
1954
                coli.ci_index_generation = vt->lss->lss_index_generation;
8✔
1955
                coli.ci_indexed_range = msg_range::empty();
8✔
1956
                coli.ci_string_arena.reset();
8✔
1957
            }
1958

1959
            {
1960
                auto col_valid_opt = coli.ci_indexed_range.get_valid();
19,520✔
1961
                if (col_valid_opt) {
19,520✔
1962
                    log_debug("column %d valid range [%d:%d)",
19,512✔
1963
                              icol.cc_column,
1964
                              (int) col_valid_opt->v_min_line,
1965
                              (int) col_valid_opt->v_max_line);
1966
                }
1967
            }
1968

1969
            min_index_range.intersect(coli.ci_indexed_range);
19,520✔
1970
        }
1971

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

1975
            auto iter
1976
                = coli.ci_value_to_lines.find(icol.cc_constraint.sc_value);
19,520✔
1977
            if (iter != coli.ci_value_to_lines.end()) {
19,520✔
1978
                for (auto vl : iter->second) {
39,180✔
1979
                    if (!scan_range.contains(vl)) {
19,670✔
1980
#ifdef DEBUG_INDEXING
1981
                        log_debug(
1982
                            "indexed line %d is outside of scan range [%d:%d)",
1983
                            vl,
1984
                            scan_range.v_min_line,
1985
                            scan_range.v_max_line);
1986
#endif
1987
                        continue;
10✔
1988
                    }
1989

1990
                    if (!min_index_range.contains(vl)) {
19,660✔
1991
#ifdef DEBUG_INDEXING
1992
                        log_debug(
1993
                            "indexed line %d is outside of the min index range",
1994
                            vl);
1995
#endif
1996
                        continue;
×
1997
                    }
1998

1999
#ifdef DEBUG_INDEXING
2000
                    log_debug("adding indexed line %d", vl);
2001
#endif
2002
                    p_cur->log_cursor.lc_indexed_lines.push_back(vl);
19,660✔
2003
                }
2004
            }
2005
        }
2006
        p_cur->log_cursor.lc_indexed_lines_range = min_index_range;
214✔
2007

2008
#if 0
2009
        if (max_indexed_line && max_indexed_line.value() > 0_vl) {
2010
            p_cur->log_cursor.lc_indexed_lines.push_back(
2011
                max_indexed_line.value());
2012
        }
2013
#endif
2014

2015
        auto index_valid_opt = min_index_range.get_valid();
214✔
2016
        if (!min_index_range.contains(scan_range.v_min_line)
214✔
2017
            || !min_index_range.contains(scan_range.v_max_line - 1_vl))
214✔
2018
        {
2019
            log_debug(
209✔
2020
                "scan needed to populate index, clearing other indexes "
2021
                "scan_range[%d:%d)",
2022
                (int) scan_range.v_min_line,
2023
                (int) scan_range.v_max_line);
2024
            p_cur->log_cursor.lc_level_constraint = std::nullopt;
209✔
2025
            opid_val = std::nullopt;
209✔
2026
            tid_val = std::nullopt;
209✔
2027
            log_time_range = std::nullopt;
209✔
2028
            log_path_constraints.clear();
209✔
2029
            log_unique_path_constraints.clear();
209✔
2030

2031
            if (index_valid_opt) {
209✔
2032
                log_debug("  min_index_range[%d:%d)",
201✔
2033
                          (int) index_valid_opt->v_min_line,
2034
                          (int) index_valid_opt->v_max_line);
2035
                if (scan_range.v_min_line < index_valid_opt->v_min_line
201✔
2036
                    && index_valid_opt->v_max_line < scan_range.v_max_line)
201✔
2037
                {
2038
                    for (const auto& icol :
×
2039
                         p_cur->log_cursor.lc_indexed_columns)
×
2040
                    {
2041
                        vt->vi->vi_column_indexes.erase(icol.cc_column);
×
2042
                    }
2043
                    p_cur->log_cursor.lc_indexed_lines.clear();
×
2044
                    p_cur->log_cursor.lc_indexed_lines_range
2045
                        = msg_range::empty();
×
2046
                } else if (scan_range.v_max_line < index_valid_opt->v_min_line
201✔
2047
                           || scan_range.v_min_line
402✔
2048
                               >= index_valid_opt->v_max_line)
201✔
2049
                {
2050
                    p_cur->log_cursor.lc_indexed_lines.clear();
1✔
2051
                    p_cur->log_cursor.lc_indexed_lines_range
2052
                        = msg_range::empty();
1✔
2053
                    if (p_cur->log_cursor.lc_direction < 0) {
1✔
2054
                        if (scan_range.v_max_line < index_valid_opt->v_min_line)
×
2055
                        {
2056
                            p_cur->log_cursor.lc_curr_line
2057
                                = index_valid_opt->v_min_line - 1_vl;
×
2058
                        } else {
2059
                            p_cur->log_cursor.lc_end_line
2060
                                = index_valid_opt->v_max_line - 1_vl;
×
2061
                        }
2062
                    } else {
2063
                        if (scan_range.v_max_line < index_valid_opt->v_min_line)
1✔
2064
                        {
2065
                            p_cur->log_cursor.lc_end_line
2066
                                = index_valid_opt->v_min_line;
×
2067
                        } else {
2068
                            p_cur->log_cursor.lc_curr_line
2069
                                = index_valid_opt->v_max_line;
1✔
2070
                        }
2071
                    }
2072
                }
2073
            } else {
2074
                log_debug("  min_index_range::empty");
8✔
2075
                p_cur->log_cursor.lc_indexed_lines.clear();
8✔
2076
            }
2077
        } else if (index_valid_opt) {
5✔
2078
            log_info("using existing index over range [%d:%d)",
5✔
2079
                     (int) index_valid_opt->v_min_line,
2080
                     (int) index_valid_opt->v_max_line);
2081
            if (p_cur->log_cursor.lc_direction < 0) {
5✔
2082
                p_cur->log_cursor.lc_indexed_lines.push_back(
4✔
2083
                    index_valid_opt->v_min_line - 1_vl);
8✔
2084
            } else {
2085
                p_cur->log_cursor.lc_indexed_lines.push_back(
2✔
2086
                    index_valid_opt->v_max_line);
1✔
2087
            }
2088
        }
2089

2090
        if (p_cur->log_cursor.lc_direction < 0) {
214✔
2091
            log_debug("ORDER BY is DESC, reversing indexed lines");
8✔
2092
            std::sort(p_cur->log_cursor.lc_indexed_lines.begin(),
8✔
2093
                      p_cur->log_cursor.lc_indexed_lines.end(),
2094
                      std::less<>());
2095
        } else {
2096
            std::sort(p_cur->log_cursor.lc_indexed_lines.begin(),
206✔
2097
                      p_cur->log_cursor.lc_indexed_lines.end(),
2098
                      std::greater<>());
2099
        }
2100

2101
#ifdef DEBUG_INDEXING
2102
        log_debug("indexed lines:");
2103
        for (auto indline : p_cur->log_cursor.lc_indexed_lines) {
2104
            log_debug("  %d", (int) indline);
2105
        }
2106
#endif
2107
    }
214✔
2108

2109
    if (!log_time_range) {
402✔
2110
    } else if (log_time_range->empty()) {
4✔
2111
#ifdef DEBUG_INDEXING
2112
        log_warning("time range is empty");
2113
#endif
2114
        p_cur->log_cursor.lc_curr_line = p_cur->log_cursor.lc_end_line;
×
2115
    } else {
2116
        if (log_time_range->vtr_begin) {
4✔
2117
            auto vl_opt = vt->lss->row_for_time(
4✔
2118
                to_timeval(log_time_range->vtr_begin.value()));
4✔
2119
            if (!vl_opt) {
4✔
2120
#ifdef DEBUG_INDEXING
2121
                log_warning("cannot find row with begin time: %d",
2122
                            log_time_range->vtr_begin.value().tv_sec);
2123
#endif
2124
                p_cur->log_cursor.lc_curr_line = p_cur->log_cursor.lc_end_line;
×
2125
            } else {
2126
#ifdef DEBUG_INDEXING
2127
                log_debug("found row with begin time: %d -> %d",
2128
                          log_time_range->vtr_begin.value(),
2129
                          vl_opt.value());
2130
#endif
2131
                p_cur->log_cursor.lc_curr_line = vl_opt.value();
4✔
2132
            }
2133
        }
2134
        if (log_time_range->vtr_end) {
4✔
2135
            auto vl_max_opt = vt->lss->row_for_time(
3✔
2136
                to_timeval(log_time_range->vtr_end.value()));
3✔
2137
            if (vl_max_opt) {
3✔
2138
                p_cur->log_cursor.lc_end_line = vl_max_opt.value();
3✔
2139
                auto win = vt->lss->window_at(
3✔
2140
                    vl_max_opt.value(), vis_line_t(vt->lss->text_line_count()));
3✔
2141
                for (const auto& msg_info : *win) {
7✔
2142
                    if (log_time_range->vtr_end.value()
5✔
2143
                        < msg_info.get_logline()
10✔
2144
                              .get_time<std::chrono::microseconds>())
10✔
2145
                    {
2146
                        break;
1✔
2147
                    }
2148
                    p_cur->log_cursor.lc_end_line
2149
                        = msg_info.get_vis_line() + 1_vl;
4✔
2150
                }
3✔
2151
            }
3✔
2152
        }
2153
    }
2154

2155
    p_cur->log_cursor.lc_opid_bloom_bits = opid_val;
402✔
2156
    p_cur->log_cursor.lc_tid_bloom_bits = tid_val;
402✔
2157
    p_cur->log_cursor.lc_log_path = std::move(log_path_constraints);
402✔
2158
    p_cur->log_cursor.lc_unique_path = std::move(log_unique_path_constraints);
402✔
2159

2160
#if 0
2161
    if (p_cur->log_cursor.lc_indexed_lines.empty()) {
2162
        p_cur->log_cursor.lc_indexed_lines.push_back(
2163
            p_cur->log_cursor.lc_curr_line);
2164
    }
2165
#endif
2166
    log_debug("before table filter [%d:%d)",
402✔
2167
              (int) p_cur->log_cursor.lc_curr_line,
2168
              (int) p_cur->log_cursor.lc_end_line);
2169
    vt->vi->filter(p_cur->log_cursor, *vt->lss);
402✔
2170

2171
    log_debug("before initial next [%d:%d)",
402✔
2172
              (int) p_cur->log_cursor.lc_curr_line,
2173
              (int) p_cur->log_cursor.lc_end_line);
2174
    if (vt->base.pModule->xNext != vt_next_no_rowid) {
402✔
2175
        p_cur->log_cursor.lc_curr_line -= p_cur->log_cursor.lc_direction;
389✔
2176
    }
2177
    vt->base.pModule->xNext(p_vtc);
402✔
2178

2179
#ifdef DEBUG_INDEXING
2180
    log_debug("vt_filter() -> cursor_range(%d:%d:%d)",
2181
              (int) p_cur->log_cursor.lc_curr_line,
2182
              (int) p_cur->log_cursor.lc_end_line,
2183
              p_cur->log_cursor.lc_direction);
2184
#endif
2185

2186
    return SQLITE_OK;
402✔
2187
}
402✔
2188

2189
static int
2190
vt_best_index(sqlite3_vtab* tab, sqlite3_index_info* p_info)
216✔
2191
{
2192
    std::vector<sqlite3_index_info::sqlite3_index_constraint> indexes;
216✔
2193
    std::vector<std::string> index_desc;
216✔
2194
    int argvInUse = 0;
216✔
2195
    auto* vt = (log_vtab*) tab;
216✔
2196
    char direction = 1;
216✔
2197

2198
    log_info("vt_best_index(%s, nConstraint=%d, nOrderBy=%d)",
216✔
2199
             vt->vi->get_name().get(),
2200
             p_info->nConstraint,
2201
             p_info->nOrderBy);
2202
    if (p_info->nOrderBy > 0) {
216✔
2203
        log_info("  ORDER BY");
19✔
2204
        for (int i = 0; i < p_info->nOrderBy; i++) {
38✔
2205
            const auto& orderby_info = p_info->aOrderBy[i];
19✔
2206
            log_info("    %d %s",
19✔
2207
                     orderby_info.iColumn,
2208
                     orderby_info.desc ? "DESC" : "ASC");
2209
        }
2210

2211
        if (p_info->aOrderBy[0].iColumn == 0) {
19✔
2212
            if (p_info->aOrderBy[0].desc) {
11✔
2213
                log_info("  consuming ORDER BY log_line DESC");
8✔
2214
                direction = -1;
8✔
2215
            } else {
2216
                log_info("  consuming ORDER BY log_line ASC");
3✔
2217
                direction = 1;
3✔
2218
            }
2219
            p_info->orderByConsumed = 1;
11✔
2220
        }
2221
    }
2222
    if (!vt->vi->vi_supports_indexes) {
216✔
2223
        p_info->orderByConsumed = 0;
10✔
2224
        return SQLITE_OK;
10✔
2225
    }
2226
    for (int lpc = 0; lpc < p_info->nConstraint; lpc++) {
376✔
2227
        const auto& constraint = p_info->aConstraint[lpc];
170✔
2228
        if (!constraint.usable || constraint.op == SQLITE_INDEX_CONSTRAINT_MATCH
170✔
2229
#ifdef SQLITE_INDEX_CONSTRAINT_OFFSET
2230
            || constraint.op == SQLITE_INDEX_CONSTRAINT_OFFSET
161✔
2231
            || constraint.op == SQLITE_INDEX_CONSTRAINT_LIMIT
161✔
2232
#endif
2233
        )
2234
        {
2235
            log_debug("  column %d: is not usable (usable=%d, op: %s)",
25✔
2236
                      lpc,
2237
                      constraint.usable,
2238
                      sql_constraint_op_name(constraint.op));
2239
            continue;
25✔
2240
        }
2241

2242
        auto col = constraint.iColumn;
145✔
2243
        auto op = constraint.op;
145✔
2244
        log_debug("  column %d: op: %s", col, sql_constraint_op_name(op));
145✔
2245
        switch (col) {
145✔
2246
            case VT_COL_LINE_NUMBER: {
69✔
2247
                argvInUse += 1;
69✔
2248
                indexes.push_back(constraint);
69✔
2249
                p_info->aConstraintUsage[lpc].argvIndex = argvInUse;
69✔
2250
                index_desc.emplace_back(fmt::format(
69✔
2251
                    FMT_STRING("log_line {} ?"), sql_constraint_op_name(op)));
207✔
2252
                break;
69✔
2253
            }
2254
            case VT_COL_LOG_TIME: {
2✔
2255
                argvInUse += 1;
2✔
2256
                indexes.push_back(p_info->aConstraint[lpc]);
2✔
2257
                p_info->aConstraintUsage[lpc].argvIndex = argvInUse;
2✔
2258
                index_desc.emplace_back(fmt::format(
2✔
2259
                    FMT_STRING("log_time {} ?"), sql_constraint_op_name(op)));
6✔
2260
                break;
2✔
2261
            }
2262
            case VT_COL_LEVEL: {
8✔
2263
                if (log_cursor::level_constraint::op_is_supported(op)) {
8✔
2264
                    argvInUse += 1;
8✔
2265
                    indexes.push_back(constraint);
8✔
2266
                    p_info->aConstraintUsage[lpc].argvIndex = argvInUse;
8✔
2267
                    index_desc.emplace_back(
8✔
2268
                        fmt::format(FMT_STRING("log_level {} ?"),
24✔
2269
                                    sql_constraint_op_name(op)));
16✔
2270
                }
2271
                break;
8✔
2272
            }
2273
            default: {
66✔
2274
                if (col > (VT_COL_MAX + vt->vi->vi_column_count - 1)) {
66✔
2275
                    auto footer_column = static_cast<log_footer_columns>(
2276
                        col - (VT_COL_MAX + vt->vi->vi_column_count - 1) - 1);
24✔
2277

2278
                    switch (footer_column) {
24✔
2279
                        case log_footer_columns::time_msecs: {
1✔
2280
                            argvInUse += 1;
1✔
2281
                            indexes.push_back(p_info->aConstraint[lpc]);
1✔
2282
                            p_info->aConstraintUsage[lpc].argvIndex = argvInUse;
1✔
2283
                            index_desc.emplace_back(
1✔
2284
                                fmt::format(FMT_STRING("log_time_msecs {} ?"),
3✔
2285
                                            sql_constraint_op_name(op)));
1✔
2286
                            break;
1✔
2287
                        }
2288
                        case log_footer_columns::format: {
3✔
2289
                            if (op == SQLITE_INDEX_CONSTRAINT_EQ) {
3✔
2290
                                argvInUse += 1;
3✔
2291
                                indexes.push_back(constraint);
3✔
2292
                                p_info->aConstraintUsage[lpc].argvIndex
3✔
2293
                                    = argvInUse;
3✔
2294
                                index_desc.emplace_back("log_format = ?");
3✔
2295
                            }
2296
                            break;
3✔
2297
                        }
2298
                        case log_footer_columns::format_regex: {
×
2299
                            if (op == SQLITE_INDEX_CONSTRAINT_EQ) {
×
2300
                                argvInUse += 1;
×
2301
                                indexes.push_back(constraint);
×
2302
                                p_info->aConstraintUsage[lpc].argvIndex
×
2303
                                    = argvInUse;
×
2304
                                index_desc.emplace_back("log_format_regex = ?");
×
2305
                            }
2306
                            break;
×
2307
                        }
2308
                        case log_footer_columns::opid:
1✔
2309
                        case log_footer_columns::user_opid: {
2310
                            if (op == SQLITE_INDEX_CONSTRAINT_EQ) {
1✔
2311
                                argvInUse += 1;
1✔
2312
                                indexes.push_back(constraint);
1✔
2313
                                p_info->aConstraintUsage[lpc].argvIndex
1✔
2314
                                    = argvInUse;
1✔
2315
                                index_desc.emplace_back("log_opid = ?");
1✔
2316
                            }
2317
                            break;
1✔
2318
                        }
2319
                        case log_footer_columns::path: {
2✔
2320
                            argvInUse += 1;
2✔
2321
                            indexes.push_back(constraint);
2✔
2322
                            p_info->aConstraintUsage[lpc].argvIndex = argvInUse;
2✔
2323
                            index_desc.emplace_back(
2✔
2324
                                fmt::format(FMT_STRING("log_path {} ?"),
6✔
2325
                                            sql_constraint_op_name(op)));
2✔
2326
                            break;
2✔
2327
                        }
2328
                        case log_footer_columns::unique_path: {
×
2329
                            argvInUse += 1;
×
2330
                            indexes.push_back(constraint);
×
2331
                            p_info->aConstraintUsage[lpc].argvIndex = argvInUse;
×
2332
                            index_desc.emplace_back(
×
2333
                                fmt::format(FMT_STRING("log_unique_path {} ?"),
×
2334
                                            sql_constraint_op_name(op)));
×
2335
                            break;
×
2336
                        }
2337
                        case log_footer_columns::partition:
15✔
2338
                        case log_footer_columns::actual_time:
2339
                        case log_footer_columns::idle_msecs:
2340
                        case log_footer_columns::mark:
2341
                        case log_footer_columns::comment:
2342
                        case log_footer_columns::tags:
2343
                        case log_footer_columns::annotations:
2344
                        case log_footer_columns::filters:
2345
                        case log_footer_columns::text:
2346
                        case log_footer_columns::body:
2347
                        case log_footer_columns::raw_text:
2348
                        case log_footer_columns::line_hash:
2349
                        case log_footer_columns::src_file:
2350
                        case log_footer_columns::src_line:
2351
                            break;
15✔
2352
                        case log_footer_columns::line_link: {
2✔
2353
                            argvInUse += 1;
2✔
2354
                            indexes.push_back(constraint);
2✔
2355
                            p_info->aConstraintUsage[lpc].argvIndex = argvInUse;
2✔
2356
                            index_desc.emplace_back("log_line_link = ?");
2✔
2357
                            break;
2✔
2358
                        }
NEW
2359
                        case log_footer_columns::thread_id: {
×
NEW
2360
                            if (op == SQLITE_INDEX_CONSTRAINT_EQ) {
×
NEW
2361
                                argvInUse += 1;
×
NEW
2362
                                indexes.push_back(constraint);
×
NEW
2363
                                p_info->aConstraintUsage[lpc].argvIndex
×
NEW
2364
                                    = argvInUse;
×
NEW
2365
                                index_desc.emplace_back("log_thread_id = ?");
×
2366
                            }
NEW
2367
                            break;
×
2368
                        }
2369
                    }
2370
                } else if (op == SQLITE_INDEX_CONSTRAINT_EQ) {
42✔
2371
                    argvInUse += 1;
19✔
2372
                    indexes.push_back(constraint);
19✔
2373
                    p_info->aConstraintUsage[lpc].argvIndex = argvInUse;
19✔
2374
                    index_desc.emplace_back(
19✔
2375
                        fmt::format(FMT_STRING("col({}) {} ?"),
57✔
2376
                                    col,
2377
                                    sql_constraint_op_name(op)));
38✔
2378
                }
2379
                break;
66✔
2380
            }
2381
        }
2382
    }
2383

2384
    if (argvInUse) {
206✔
2385
        auto full_desc = fmt::format(FMT_STRING("SEARCH {} USING {}"),
186✔
2386
                                     vt->vi->get_name().get(),
93✔
2387
                                     fmt::join(index_desc, " AND "));
93✔
2388
        log_info("found index: %s", full_desc.c_str());
93✔
2389

2390
        sqlite3_index_info::sqlite3_index_constraint* index_copy;
2391
        auto index_len = indexes.size() * sizeof(*index_copy);
93✔
2392
        size_t len = full_desc.size() + 128 + index_len;
93✔
2393
        auto* storage = sqlite3_malloc(len);
93✔
2394
        if (!storage) {
93✔
2395
            return SQLITE_NOMEM;
×
2396
        }
2397
        auto* desc_storage = static_cast<char*>(storage);
93✔
2398
        memcpy(desc_storage, full_desc.c_str(), full_desc.size() + 1);
93✔
2399
        desc_storage[full_desc.size() + 1] = direction;
93✔
2400
        auto* remaining_storage
2401
            = static_cast<void*>(desc_storage + full_desc.size() + 1 + 1);
93✔
2402
        len -= 1 + full_desc.size() - 1;
93✔
2403
        auto* index_storage
2404
            = std::align(alignof(sqlite3_index_info::sqlite3_index_constraint),
93✔
2405
                         index_len,
2406
                         remaining_storage,
2407
                         len);
2408
        index_copy
2409
            = reinterpret_cast<sqlite3_index_info::sqlite3_index_constraint*>(
93✔
2410
                index_storage);
2411
        log_info("  index storage: %p", index_copy);
93✔
2412
        memcpy(index_copy, &indexes[0], index_len);
93✔
2413
        p_info->idxNum = argvInUse;
93✔
2414
        p_info->idxStr = static_cast<char*>(storage);
93✔
2415
        p_info->needToFreeIdxStr = 1;
93✔
2416
        p_info->estimatedCost = 10.0;
93✔
2417
    } else {
93✔
2418
        static char fullscan_asc[] = "fullscan\0\001";
2419
        static char fullscan_desc[] = "fullscan\0\377";
2420

2421
        p_info->idxStr = direction < 0 ? fullscan_desc : fullscan_asc;
113✔
2422
        p_info->estimatedCost = 1000000000.0;
113✔
2423
    }
2424

2425
    return SQLITE_OK;
206✔
2426
}
216✔
2427

2428
static const struct json_path_container tags_handler = {
2429
    json_path_handler("#")
2430
        .with_synopsis("tag")
2431
        .with_description("A tag for the log line")
2432
        .with_pattern(R"(^#[^\s]+$)")
2433
        .for_field(&bookmark_metadata::bm_tags),
2434
};
2435

2436
static int
2437
vt_update(sqlite3_vtab* tab,
52✔
2438
          int argc,
2439
          sqlite3_value** argv,
2440
          sqlite_int64* rowid_out)
2441
{
2442
    auto* vt = (log_vtab*) tab;
52✔
2443
    int retval = SQLITE_READONLY;
52✔
2444

2445
    if (argc > 1 && sqlite3_value_type(argv[0]) != SQLITE_NULL
51✔
2446
        && sqlite3_value_int64(argv[0]) == sqlite3_value_int64(argv[1]))
103✔
2447
    {
2448
        int64_t rowid = sqlite3_value_int64(argv[0]) >> 8;
51✔
2449
        int val = sqlite3_value_int(
102✔
2450
            argv[2 + vt->footer_index(log_footer_columns::mark)]);
51✔
2451
        vis_line_t vrowid(rowid);
51✔
2452
        auto win = vt->lss->window_at(vrowid);
51✔
2453
        const auto msg_info = *win->begin();
51✔
2454
        const auto* part_name = sqlite3_value_text(
102✔
2455
            argv[2 + vt->footer_index(log_footer_columns::partition)]);
51✔
2456
        const auto* log_comment = sqlite3_value_text(
102✔
2457
            argv[2 + vt->footer_index(log_footer_columns::comment)]);
51✔
2458
        const auto log_tags = from_sqlite<std::optional<string_fragment>>()(
×
2459
            argc, argv, 2 + vt->footer_index(log_footer_columns::tags));
51✔
2460
        const auto log_annos = from_sqlite<std::optional<string_fragment>>()(
×
2461
            argc, argv, 2 + vt->footer_index(log_footer_columns::annotations));
51✔
2462
        auto log_opid = from_sqlite<std::optional<string_fragment>>()(
×
2463
            argc, argv, 2 + vt->footer_index(log_footer_columns::opid));
51✔
2464
        const auto log_user_opid
2465
            = from_sqlite<std::optional<string_fragment>>()(
×
2466
                argc,
2467
                argv,
2468
                2 + vt->footer_index(log_footer_columns::user_opid));
51✔
2469
        bookmark_metadata tmp_bm;
51✔
2470

2471
        if (log_user_opid) {
51✔
2472
            log_opid = log_user_opid;
×
2473
        }
2474
        if (log_tags) {
51✔
2475
            std::vector<lnav::console::user_message> errors;
4✔
2476
            yajlpp_parse_context ypc(vt->vi->get_tags_name(), &tags_handler);
4✔
2477
            auto_mem<yajl_handle_t> handle(yajl_free);
4✔
2478

2479
            handle = yajl_alloc(&ypc.ypc_callbacks, nullptr, &ypc);
4✔
2480
            ypc.ypc_userdata = &errors;
4✔
2481
            ypc.ypc_line_number = log_vtab_data.lvd_location.sl_line_number;
4✔
2482
            ypc.with_handle(handle)
4✔
2483
                .with_error_reporter([](const yajlpp_parse_context& ypc,
8✔
2484
                                        auto msg) {
2485
                    auto& errors = *((std::vector<lnav::console::user_message>*)
2✔
2486
                                         ypc.ypc_userdata);
2487
                    errors.emplace_back(msg);
2✔
2488
                })
2✔
2489
                .with_obj(tmp_bm);
4✔
2490
            ypc.parse_doc(log_tags.value());
4✔
2491
            if (!errors.empty()) {
4✔
2492
                auto top_error
2493
                    = lnav::console::user_message::error(
×
2494
                          attr_line_t("invalid value for ")
2✔
2495
                              .append_quoted("log_tags"_symbol)
2✔
2496
                              .append(" column of table ")
2✔
2497
                              .append_quoted(lnav::roles::symbol(
4✔
2498
                                  vt->vi->get_name().to_string())))
4✔
2499
                          .with_reason(errors[0].to_attr_line(
4✔
2500
                              lnav::console::user_message::render_flags::none))
2501
                          .move();
2✔
2502
                set_vtable_errmsg(tab, top_error);
2✔
2503
                return SQLITE_ERROR;
2✔
2504
            }
2✔
2505
        }
8✔
2506
        if (log_annos) {
49✔
2507
            static const intern_string_t SRC
2508
                = intern_string::lookup("log_annotations");
9✔
2509

2510
            auto parse_res = logmsg_annotations_handlers.parser_for(SRC).of(
6✔
2511
                log_annos.value());
3✔
2512
            if (parse_res.isErr()) {
3✔
2513
                set_vtable_errmsg(tab, parse_res.unwrapErr()[0]);
2✔
2514
                return SQLITE_ERROR;
2✔
2515
            }
2516

2517
            tmp_bm.bm_annotations = parse_res.unwrap();
1✔
2518
        }
3✔
2519

2520
        auto& bv_meta = vt->tc->get_bookmarks()[&textview_curses::BM_META];
47✔
2521
        bool has_meta = log_comment != nullptr || log_tags.has_value()
45✔
2522
            || log_annos.has_value();
92✔
2523

2524
        if (bv_meta.bv_tree.exists(vrowid) && !has_meta) {
47✔
2525
            vt->tc->set_user_mark(&textview_curses::BM_META, vrowid, false);
2✔
2526
            vt->lss->set_line_meta_changed();
2✔
2527
        }
2528

2529
        if (!has_meta && part_name == nullptr
43✔
2530
            && (!log_opid
123✔
2531
                || msg_info.get_values().lvv_opid_provenance
33✔
2532
                    == logline_value_vector::opid_provenance::file))
2533
        {
2534
            vt->lss->erase_bookmark_metadata(vrowid);
23✔
2535
        }
2536

2537
        if (part_name) {
47✔
2538
            auto& line_meta = vt->lss->get_bookmark_metadata(vrowid);
3✔
2539
            line_meta.bm_name = std::string((const char*) part_name);
3✔
2540
            vt->tc->set_user_mark(&textview_curses::BM_PARTITION, vrowid, true);
3✔
2541
        } else {
2542
            vt->tc->set_user_mark(
44✔
2543
                &textview_curses::BM_PARTITION, vrowid, false);
2544
        }
2545

2546
        if (log_opid) {
47✔
2547
            auto& lvv = msg_info.get_values();
40✔
2548
            if (!lvv.lvv_opid_value
40✔
2549
                || lvv.lvv_opid_provenance
40✔
2550
                    == logline_value_vector::opid_provenance::user)
2551
            {
2552
                msg_info.get_file_ptr()->set_logline_opid(
34✔
2553
                    msg_info.get_file_line_number(), log_opid.value());
17✔
2554
                vt->lss->set_line_meta_changed();
17✔
2555
            }
2556
        } else if (msg_info.get_values().lvv_opid_provenance
7✔
2557
                   == logline_value_vector::opid_provenance::user)
7✔
2558
        {
2559
            msg_info.get_file_ptr()->clear_logline_opid(
×
2560
                msg_info.get_file_line_number());
2561
        }
2562

2563
        if (has_meta) {
47✔
2564
            auto& line_meta = vt->lss->get_bookmark_metadata(vrowid);
4✔
2565

2566
            vt->tc->set_user_mark(&textview_curses::BM_META, vrowid, true);
4✔
2567
            if (part_name == nullptr) {
4✔
2568
                line_meta.bm_name.clear();
4✔
2569
            }
2570
            if (log_comment) {
4✔
2571
                line_meta.bm_comment = std::string((const char*) log_comment);
4✔
2572
            } else {
2573
                line_meta.bm_comment.clear();
2✔
2574
            }
2575
            if (log_tags) {
4✔
2576
                line_meta.bm_tags.clear();
2✔
2577
                for (const auto& tag : tmp_bm.bm_tags) {
5✔
2578
                    line_meta.add_tag(tag);
3✔
2579
                }
2580

2581
                for (const auto& tag : line_meta.bm_tags) {
4✔
2582
                    bookmark_metadata::KNOWN_TAGS.insert(tag);
2✔
2583
                }
2584
            } else {
2585
                line_meta.bm_tags.clear();
2✔
2586
            }
2587
            if (log_annos) {
4✔
2588
                line_meta.bm_annotations = std::move(tmp_bm.bm_annotations);
1✔
2589
            } else if (!sqlite3_value_nochange(
3✔
2590
                           argv[2
3✔
2591
                                + vt->footer_index(
3✔
2592
                                    log_footer_columns::annotations)]))
3✔
2593
            {
2594
                line_meta.bm_annotations.la_pairs.clear();
×
2595
            }
2596
            vt->lss->set_line_meta_changed();
4✔
2597
        }
2598

2599
        vt->tc->set_user_mark(&textview_curses::BM_USER, vrowid, val);
47✔
2600
        rowid += 1;
47✔
2601
        while ((size_t) rowid < vt->lss->text_line_count()) {
50✔
2602
            vis_line_t vl(rowid);
34✔
2603
            auto cl = vt->lss->at(vl);
34✔
2604
            auto* ll = vt->lss->find_line(cl);
34✔
2605
            if (ll->is_message()) {
34✔
2606
                break;
31✔
2607
            }
2608
            vt->tc->set_user_mark(&textview_curses::BM_USER, vl, val);
3✔
2609
            rowid += 1;
3✔
2610
        }
2611

2612
        if (retval != SQLITE_ERROR) {
47✔
2613
            retval = SQLITE_OK;
47✔
2614
        }
2615
    }
59✔
2616

2617
    return retval;
48✔
2618
}
2619

2620
static const sqlite3_module generic_vtab_module = {
2621
    0, /* iVersion */
2622
    vt_create, /* xCreate       - create a vtable */
2623
    vt_connect, /* xConnect      - associate a vtable with a connection */
2624
    vt_best_index, /* xBestIndex    - best index */
2625
    vt_disconnect, /* xDisconnect   - disassociate a vtable with a connection */
2626
    vt_destroy, /* xDestroy      - destroy a vtable */
2627
    vt_open, /* xOpen         - open a cursor */
2628
    vt_close, /* xClose        - close a cursor */
2629
    vt_filter, /* xFilter       - configure scan constraints */
2630
    vt_next, /* xNext         - advance a cursor */
2631
    vt_eof, /* xEof          - inidicate end of result set*/
2632
    vt_column, /* xColumn       - read data */
2633
    vt_rowid, /* xRowid        - read data */
2634
    vt_update, /* xUpdate       - write data */
2635
    nullptr, /* xBegin        - begin transaction */
2636
    nullptr, /* xSync         - sync transaction */
2637
    nullptr, /* xCommit       - commit transaction */
2638
    nullptr, /* xRollback     - rollback transaction */
2639
    nullptr, /* xFindFunction - function overloading */
2640
};
2641

2642
static const sqlite3_module no_rowid_vtab_module = {
2643
    0, /* iVersion */
2644
    vt_create, /* xCreate       - create a vtable */
2645
    vt_connect, /* xConnect      - associate a vtable with a connection */
2646
    vt_best_index, /* xBestIndex    - best index */
2647
    vt_disconnect, /* xDisconnect   - disassociate a vtable with a connection */
2648
    vt_destroy, /* xDestroy      - destroy a vtable */
2649
    vt_open, /* xOpen         - open a cursor */
2650
    vt_close, /* xClose        - close a cursor */
2651
    vt_filter, /* xFilter       - configure scan constraints */
2652
    vt_next_no_rowid, /* xNext         - advance a cursor */
2653
    vt_eof, /* xEof          - inidicate end of result set*/
2654
    vt_column, /* xColumn       - read data */
2655
    nullptr, /* xRowid        - read data */
2656
    nullptr, /* xUpdate       - write data */
2657
    nullptr, /* xBegin        - begin transaction */
2658
    nullptr, /* xSync         - sync transaction */
2659
    nullptr, /* xCommit       - commit transaction */
2660
    nullptr, /* xRollback     - rollback transaction */
2661
    nullptr, /* xFindFunction - function overloading */
2662
};
2663

2664
static int
2665
progress_callback(void* ptr)
921,962✔
2666
{
2667
    int retval = 0;
921,962✔
2668

2669
    if (log_vtab_data.lvd_progress != nullptr) {
921,962✔
2670
        retval = log_vtab_data.lvd_progress(log_cursor_latest);
11,389✔
2671
    }
2672
    if (!log_vtab_data.lvd_looping) {
921,962✔
2673
        retval = 1;
×
2674
    }
2675

2676
    return retval;
921,962✔
2677
}
2678

2679
log_vtab_manager::log_vtab_manager(sqlite3* memdb,
633✔
2680
                                   textview_curses& tc,
2681
                                   logfile_sub_source& lss)
633✔
2682
    : vm_db(memdb), vm_textview(tc), vm_source(lss)
633✔
2683
{
2684
    sqlite3_create_module(
633✔
2685
        this->vm_db, "log_vtab_impl", &generic_vtab_module, this);
2686
    sqlite3_create_module(
633✔
2687
        this->vm_db, "log_vtab_no_rowid_impl", &no_rowid_vtab_module, this);
2688
    sqlite3_progress_handler(memdb, 32, progress_callback, nullptr);
633✔
2689
}
633✔
2690

2691
log_vtab_manager::~log_vtab_manager()
633✔
2692
{
2693
    while (!this->vm_impls.empty()) {
55,921✔
2694
        auto first_name = this->vm_impls.begin()->first;
55,288✔
2695

2696
        this->unregister_vtab(first_name);
55,288✔
2697
    }
2698
}
633✔
2699

2700
std::string
2701
log_vtab_manager::register_vtab(std::shared_ptr<log_vtab_impl> vi)
55,460✔
2702
{
2703
    std::string retval;
55,460✔
2704

2705
    if (this->vm_impls.find(vi->get_name().to_string_fragment())
55,460✔
2706
        == this->vm_impls.end())
110,920✔
2707
    {
2708
        std::vector<std::string> primary_keys;
55,460✔
2709
        auto_mem<char, sqlite3_free> errmsg;
55,460✔
2710
        auto_mem<char, sqlite3_free> sql;
55,460✔
2711
        int rc;
2712

2713
        this->vm_impls[vi->get_name().to_string_fragment()] = vi;
55,460✔
2714

2715
        vi->get_primary_keys(primary_keys);
55,460✔
2716

2717
        sql = sqlite3_mprintf(
2718
            "CREATE VIRTUAL TABLE lnav_db.%s "
2719
            "USING %s(%s)",
2720
            vi->get_name().get(),
55,460✔
2721
            primary_keys.empty() ? "log_vtab_impl" : "log_vtab_no_rowid_impl",
55,460✔
2722
            vi->get_name().get());
166,380✔
2723
        rc = sqlite3_exec(this->vm_db, sql, nullptr, nullptr, errmsg.out());
55,460✔
2724
        if (rc != SQLITE_OK) {
55,460✔
2725
            retval = errmsg;
1✔
2726
        }
2727
    } else {
55,460✔
2728
        retval = "a table with the given name already exists";
×
2729
    }
2730

2731
    return retval;
55,460✔
2732
}
×
2733

2734
std::string
2735
log_vtab_manager::unregister_vtab(string_fragment name)
55,708✔
2736
{
2737
    std::string retval;
55,708✔
2738

2739
    if (this->vm_impls.find(name) == this->vm_impls.end()) {
55,708✔
2740
        retval = fmt::format(FMT_STRING("unknown table -- {}"), name);
992✔
2741
    } else {
2742
        auto_mem<char, sqlite3_free> sql;
55,460✔
2743
        __attribute((unused)) int rc;
2744

2745
        sql = sqlite3_mprintf(
2746
            "DROP TABLE IF EXISTS %.*s", name.length(), name.data());
55,460✔
2747
        log_debug("unregister_vtab: %s", sql.in());
55,460✔
2748
        rc = sqlite3_exec(this->vm_db, sql, nullptr, nullptr, nullptr);
55,460✔
2749

2750
        this->vm_impls.erase(name);
55,460✔
2751
    }
55,460✔
2752

2753
    return retval;
55,708✔
2754
}
×
2755

2756
std::shared_ptr<log_vtab_impl>
2757
log_vtab_manager::lookup_impl(string_fragment name) const
55,915✔
2758
{
2759
    const auto iter = this->vm_impls.find(name);
55,915✔
2760
    if (iter != this->vm_impls.end()) {
55,915✔
2761
        return iter->second;
55,907✔
2762
    }
2763
    return nullptr;
8✔
2764
}
2765

2766
log_format_vtab_impl::log_format_vtab_impl(
45,536✔
2767
    std::shared_ptr<const log_format> format)
45,536✔
2768
    : log_vtab_impl(format->get_name()), lfvi_format(format)
45,536✔
2769
{
2770
}
45,536✔
2771

2772
bool
2773
log_format_vtab_impl::next(log_cursor& lc, logfile_sub_source& lss)
50,276✔
2774
{
2775
    if (lc.is_eof()) {
50,276✔
2776
        return true;
×
2777
    }
2778

2779
    auto cl = content_line_t(lss.at(lc.lc_curr_line));
50,276✔
2780
    auto* lf = lss.find_file_ptr(cl);
50,276✔
2781
    auto lf_iter = lf->begin() + cl;
50,276✔
2782

2783
    if (!lf_iter->is_message()) {
50,276✔
2784
        return false;
×
2785
    }
2786

2787
    if (lf->get_format_name() == this->lfvi_format->get_name()) {
50,276✔
2788
        return true;
29,900✔
2789
    }
2790

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