1 /**
2  * Copyright (c) 2015, 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 ON
25  * 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 "config.h"
31 
32 #include <assert.h>
33 #include <unistd.h>
34 #include <string.h>
35 
36 #include "lnav.hh"
37 #include "base/injector.bind.hh"
38 #include "base/lnav_log.hh"
39 #include "sql_util.hh"
40 #include "views_vtab.hh"
41 #include "view_curses.hh"
42 
43 using namespace std;
44 
45 template<>
46 struct from_sqlite<lnav_view_t> {
operator ()from_sqlite47     inline lnav_view_t operator()(int argc, sqlite3_value **val, int argi) {
48         const char *view_name = (const char *) sqlite3_value_text(val[argi]);
49         auto view_index_opt = view_from_string(view_name);
50 
51         if (!view_index_opt) {
52             throw from_sqlite_conversion_error("lnav view name", argi);
53         }
54 
55         return view_index_opt.value();
56     }
57 };
58 
59 template<>
60 struct from_sqlite<text_filter::type_t> {
operator ()from_sqlite61     inline text_filter::type_t operator()(int argc, sqlite3_value **val, int argi) {
62         const char *type_name = (const char *) sqlite3_value_text(val[argi]);
63 
64         if (strcasecmp(type_name, "in") == 0) {
65             return text_filter::INCLUDE;
66         } else if (strcasecmp(type_name, "out") == 0) {
67             return text_filter::EXCLUDE;
68         }
69 
70         throw from_sqlite_conversion_error("filter type", argi);
71     }
72 };
73 
74 template<>
75 struct from_sqlite<pair<string, auto_mem<pcre>>> {
operator ()from_sqlite76     inline pair<string, auto_mem<pcre>> operator()(int argc, sqlite3_value **val, int argi) {
77         const char *pattern = (const char *) sqlite3_value_text(val[argi]);
78         const char *errptr;
79         auto_mem<pcre> code;
80         int eoff;
81 
82         if (pattern == nullptr || pattern[0] == '\0') {
83             throw from_sqlite_conversion_error("non-empty pattern", argi);
84         }
85 
86         code = pcre_compile(pattern,
87                             PCRE_CASELESS,
88                             &errptr,
89                             &eoff,
90                             nullptr);
91 
92         if (code == nullptr) {
93             throw sqlite_func_error(
94                 "Invalid regular expression in column {}: {} at offset {}",
95                 argi, errptr, eoff);
96         }
97 
98         return make_pair(string(pattern), std::move(code));
99     }
100 };
101 
102 struct lnav_views : public tvt_iterator_cursor<lnav_views> {
103     static constexpr const char *NAME = "lnav_views";
104     static constexpr const char *CREATE_STMT = R"(
105 -- Access lnav's views through this table.
106 CREATE TABLE lnav_views (
107     name TEXT PRIMARY KEY,  -- The name of the view.
108     top INTEGER,            -- The number of the line at the top of the view, starting from zero.
109     left INTEGER,           -- The left position of the viewport.
110     height INTEGER,         -- The height of the viewport.
111     inner_height INTEGER,   -- The number of lines in the view.
112     top_time DATETIME,      -- The time of the top line in the view, if the content is time-based.
113     top_file TEXT,          -- The file the top line is from.
114     paused INTEGER,         -- Indicates if the view is paused and will not load new data.
115     search TEXT,            -- The text to search for in the view.
116     filtering INTEGER       -- Indicates if the view is applying filters.
117 );
118 )";
119 
120     using iterator = textview_curses *;
121 
beginlnav_views122     iterator begin() {
123         return std::begin(lnav_data.ld_views);
124     }
125 
endlnav_views126     iterator end() {
127         return std::end(lnav_data.ld_views);
128     }
129 
get_columnlnav_views130     int get_column(cursor &vc, sqlite3_context *ctx, int col) {
131         lnav_view_t view_index = (lnav_view_t) distance(std::begin(lnav_data.ld_views), vc.iter);
132         textview_curses &tc = *vc.iter;
133         unsigned long width;
134         vis_line_t height;
135 
136         tc.get_dimensions(height, width);
137         switch (col) {
138             case 0:
139                 sqlite3_result_text(ctx,
140                                     lnav_view_strings[view_index], -1,
141                                     SQLITE_STATIC);
142                 break;
143             case 1:
144                 sqlite3_result_int(ctx, (int) tc.get_top());
145                 break;
146             case 2:
147                 sqlite3_result_int(ctx, tc.get_left());
148                 break;
149             case 3:
150                 sqlite3_result_int(ctx, height);
151                 break;
152             case 4:
153                 sqlite3_result_int(ctx, tc.get_inner_height());
154                 break;
155             case 5: {
156                 auto *time_source = dynamic_cast<text_time_translator *>(tc.get_sub_source());
157 
158                 if (time_source != nullptr && tc.get_inner_height() > 0) {
159                     auto top_time_opt = time_source->time_for_row(tc.get_top());
160 
161                     if (top_time_opt) {
162                         char timestamp[64];
163 
164                         sql_strftime(timestamp, sizeof(timestamp), top_time_opt.value(), 'T');
165                         sqlite3_result_text(ctx, timestamp, -1, SQLITE_TRANSIENT);
166                     } else {
167                         sqlite3_result_null(ctx);
168                     }
169                 } else {
170                     sqlite3_result_null(ctx);
171                 }
172                 break;
173             }
174             case 6: {
175                 to_sqlite(ctx, tc.map_top_row([](const auto& al) {
176                     return get_string_attr(al.get_attrs(), &logline::L_FILE) | [](const auto* sa) {
177                         auto lf = (logfile *) sa->sa_value.sav_ptr;
178 
179                         return nonstd::make_optional(lf->get_filename());
180                     };
181                 }));
182                 break;
183             }
184             case 7:
185                 sqlite3_result_int(ctx, tc.is_paused());
186                 break;
187             case 8:
188                 to_sqlite(ctx, tc.get_current_search());
189                 break;
190             case 9: {
191                 auto tss = tc.get_sub_source();
192 
193                 if (tss != nullptr && tss->tss_supports_filtering) {
194                     sqlite3_result_int(ctx, tss->tss_apply_filters);
195                 } else {
196                     sqlite3_result_int(ctx, 0);
197                 }
198                 break;
199             }
200         }
201 
202         return SQLITE_OK;
203     }
204 
delete_rowlnav_views205     int delete_row(sqlite3_vtab *tab, sqlite3_int64 rowid) {
206         tab->zErrMsg = sqlite3_mprintf(
207             "Rows cannot be deleted from the lnav_views table");
208         return SQLITE_ERROR;
209     }
210 
insert_rowlnav_views211     int insert_row(sqlite3_vtab *tab, sqlite3_int64 &rowid_out) {
212         tab->zErrMsg = sqlite3_mprintf(
213             "Rows cannot be inserted into the lnav_views table");
214         return SQLITE_ERROR;
215     };
216 
update_rowlnav_views217     int update_row(sqlite3_vtab *tab,
218                    sqlite3_int64 &index,
219                    const char *name,
220                    int64_t top_row,
221                    int64_t left,
222                    int64_t height,
223                    int64_t inner_height,
224                    const char *top_time,
225                    const char *top_file,
226                    bool is_paused,
227                    const char *search,
228                    bool do_filtering) {
229         textview_curses &tc = lnav_data.ld_views[index];
230         text_time_translator *time_source = dynamic_cast<text_time_translator *>(tc.get_sub_source());
231 
232         if (tc.get_top() != top_row) {
233             tc.set_top(vis_line_t(top_row));
234         } else if (top_time != nullptr && time_source != nullptr) {
235             date_time_scanner dts;
236             struct timeval tv;
237 
238             if (dts.convert_to_timeval(top_time, -1, nullptr, tv)) {
239                 auto last_time_opt = time_source->time_for_row(tc.get_top());
240 
241                 if (last_time_opt) {
242                     auto last_time = last_time_opt.value();
243                     if (tv != last_time) {
244                         time_source->row_for_time(tv) | [&tc](auto row) {
245                             tc.set_top(row);
246                         };
247                     }
248                 }
249             } else {
250                 tab->zErrMsg = sqlite3_mprintf("Invalid time: %s", top_time);
251                 return SQLITE_ERROR;
252             }
253         }
254         tc.set_left(left);
255         tc.set_paused(is_paused);
256         tc.execute_search(search);
257         auto tss = tc.get_sub_source();
258         if (tss != nullptr &&
259             tss->tss_supports_filtering &&
260             tss->tss_apply_filters != do_filtering) {
261             tss->tss_apply_filters = do_filtering;
262             tss->text_filters_changed();
263         }
264 
265         return SQLITE_OK;
266     };
267 };
268 
269 struct lnav_view_stack : public tvt_iterator_cursor<lnav_view_stack> {
270     using iterator = vector<textview_curses *>::iterator;
271 
272     static constexpr const char *NAME = "lnav_view_stack";
273     static constexpr const char *CREATE_STMT = R"(
274 -- Access lnav's view stack through this table.
275 CREATE TABLE lnav_view_stack (
276     name TEXT
277 );
278 )";
279 
beginlnav_view_stack280     iterator begin() {
281         return lnav_data.ld_view_stack.begin();
282     }
283 
endlnav_view_stack284     iterator end() {
285         return lnav_data.ld_view_stack.end();
286     }
287 
get_columnlnav_view_stack288     int get_column(cursor &vc, sqlite3_context *ctx, int col) {
289         textview_curses *tc = *vc.iter;
290         auto view = lnav_view_t(tc - lnav_data.ld_views);
291 
292         switch (col) {
293             case 0:
294                 sqlite3_result_text(ctx,
295                                     lnav_view_strings[view], -1,
296                                     SQLITE_STATIC);
297                 break;
298         }
299 
300         return SQLITE_OK;
301     };
302 
delete_rowlnav_view_stack303     int delete_row(sqlite3_vtab *tab, sqlite3_int64 rowid) {
304         if ((size_t)rowid != lnav_data.ld_view_stack.size() - 1) {
305             tab->zErrMsg = sqlite3_mprintf(
306                 "Only the top view in the stack can be deleted");
307             return SQLITE_ERROR;
308         }
309 
310         lnav_data.ld_last_view = *lnav_data.ld_view_stack.top();
311         lnav_data.ld_view_stack.pop_back();
312         return SQLITE_OK;
313     };
314 
insert_rowlnav_view_stack315     int insert_row(sqlite3_vtab *tab,
316                    sqlite3_int64 &rowid_out,
317                    lnav_view_t view_index) {
318         textview_curses *tc = &lnav_data.ld_views[view_index];
319 
320         ensure_view(tc);
321         rowid_out = lnav_data.ld_view_stack.size() - 1;
322 
323         return SQLITE_OK;
324     };
325 
update_rowlnav_view_stack326     int update_row(sqlite3_vtab *tab, sqlite3_int64 &index) {
327         tab->zErrMsg = sqlite3_mprintf(
328             "The lnav_view_stack table cannot be updated");
329         return SQLITE_ERROR;
330     };
331 };
332 
333 struct lnav_view_filter_base {
334     struct iterator {
335         using difference_type = int;
336         using value_type = text_filter;
337         using pointer = text_filter *;
338         using reference = text_filter &;
339         using iterator_category = forward_iterator_tag;
340 
341         lnav_view_t i_view_index;
342         int i_filter_index;
343 
iteratorlnav_view_filter_base::iterator344         iterator(lnav_view_t view = LNV_LOG, int filter = -1)
345             : i_view_index(view), i_filter_index(filter) {
346         }
347 
operator ++lnav_view_filter_base::iterator348         iterator &operator++() {
349             while (this->i_view_index < LNV__MAX) {
350                 textview_curses &tc = lnav_data.ld_views[this->i_view_index];
351                 text_sub_source *tss = tc.get_sub_source();
352 
353                 if (tss == nullptr) {
354                     this->i_view_index = lnav_view_t(this->i_view_index + 1);
355                     continue;
356                 }
357 
358                 filter_stack &fs = tss->get_filters();
359 
360                 this->i_filter_index += 1;
361                 if (this->i_filter_index >= (ssize_t) fs.size()) {
362                     this->i_filter_index = -1;
363                     this->i_view_index = lnav_view_t(this->i_view_index + 1);
364                 } else {
365                     break;
366                 }
367             }
368 
369             return *this;
370         }
371 
operator ==lnav_view_filter_base::iterator372         bool operator==(const iterator &other) const {
373             return this->i_view_index == other.i_view_index &&
374                    this->i_filter_index == other.i_filter_index;
375         }
376 
operator !=lnav_view_filter_base::iterator377         bool operator!=(const iterator &other) const {
378             return !(*this == other);
379         }
380     };
381 
beginlnav_view_filter_base382     iterator begin() {
383         iterator retval = iterator();
384 
385         return ++retval;
386     }
387 
endlnav_view_filter_base388     iterator end() {
389         return {LNV__MAX, -1};
390     }
391 
get_rowidlnav_view_filter_base392     sqlite_int64 get_rowid(iterator iter) {
393         textview_curses &tc = lnav_data.ld_views[iter.i_view_index];
394         text_sub_source *tss = tc.get_sub_source();
395         filter_stack &fs = tss->get_filters();
396         auto &tf = *(fs.begin() + iter.i_filter_index);
397 
398         sqlite_int64 retval = iter.i_view_index;
399 
400         retval = retval << 32;
401         retval = retval | tf->get_index();
402 
403         return retval;
404     }
405 };
406 
407 struct lnav_view_filters : public tvt_iterator_cursor<lnav_view_filters>,
408     public lnav_view_filter_base {
409     static constexpr const char *NAME = "lnav_view_filters";
410     static constexpr const char *CREATE_STMT = R"(
411 -- Access lnav's filters through this table.
412 CREATE TABLE lnav_view_filters (
413     view_name TEXT,                   -- The name of the view.
414     filter_id INTEGER DEFAULT 0,      -- The filter identifier.
415     enabled   INTEGER DEFAULT 1,      -- Indicates if the filter is enabled/disabled.
416     type      TEXT    DEFAULT 'out',  -- The type of filter (i.e. in/out).
417     pattern   TEXT                    -- The filter pattern.
418 );
419 )";
420 
get_columnlnav_view_filters421     int get_column(cursor &vc, sqlite3_context *ctx, int col) {
422         textview_curses &tc = lnav_data.ld_views[vc.iter.i_view_index];
423         text_sub_source *tss = tc.get_sub_source();
424         filter_stack &fs = tss->get_filters();
425         auto tf = *(fs.begin() + vc.iter.i_filter_index);
426 
427         switch (col) {
428             case 0:
429                 sqlite3_result_text(ctx,
430                                     lnav_view_strings[vc.iter.i_view_index], -1,
431                                     SQLITE_STATIC);
432                 break;
433             case 1:
434                 to_sqlite(ctx, tf->get_index());
435                 break;
436             case 2:
437                 sqlite3_result_int(ctx, tf->is_enabled());
438                 break;
439             case 3:
440                 switch (tf->get_type()) {
441                     case text_filter::INCLUDE:
442                         sqlite3_result_text(ctx, "in", 2, SQLITE_STATIC);
443                         break;
444                     case text_filter::EXCLUDE:
445                         sqlite3_result_text(ctx, "out", 3, SQLITE_STATIC);
446                         break;
447                     default:
448                         ensure(0);
449                 }
450                 break;
451             case 4:
452                 sqlite3_result_text(ctx,
453                                     tf->get_id().c_str(),
454                                     -1,
455                                     SQLITE_TRANSIENT);
456                 break;
457         }
458 
459         return SQLITE_OK;
460     }
461 
insert_rowlnav_view_filters462     int insert_row(sqlite3_vtab *tab,
463                    sqlite3_int64 &rowid_out,
464                    lnav_view_t view_index,
465                    nonstd::optional<int64_t> _filter_id,
466                    nonstd::optional<bool> enabled,
467                    nonstd::optional<text_filter::type_t> type,
468                    pair<string, auto_mem<pcre>> pattern) {
469         textview_curses &tc = lnav_data.ld_views[view_index];
470         text_sub_source *tss = tc.get_sub_source();
471         filter_stack &fs = tss->get_filters();
472         auto filter_index = fs.next_index();
473         if (!filter_index) {
474             throw sqlite_func_error("Too many filters");
475         }
476         auto pf = make_shared<pcre_filter>(
477             type.value_or(text_filter::type_t::EXCLUDE),
478             pattern.first,
479             *filter_index,
480             pattern.second.release());
481         fs.add_filter(pf);
482         if (!enabled.value_or(true)) {
483             pf->disable();
484         }
485         tss->text_filters_changed();
486         tc.set_needs_update();
487 
488         return SQLITE_OK;
489     }
490 
delete_rowlnav_view_filters491     int delete_row(sqlite3_vtab *tab, sqlite3_int64 rowid) {
492         auto view_index = lnav_view_t(rowid >> 32);
493         size_t filter_index = rowid & 0xffffffffLL;
494         textview_curses &tc = lnav_data.ld_views[view_index];
495         text_sub_source *tss = tc.get_sub_source();
496         filter_stack &fs = tss->get_filters();
497 
498         for (const auto &iter : fs) {
499             if (iter->get_index() == filter_index) {
500                 fs.delete_filter(iter->get_id());
501                 tss->text_filters_changed();
502                 break;
503             }
504         }
505         tc.set_needs_update();
506 
507         return SQLITE_OK;
508     }
509 
update_rowlnav_view_filters510     int update_row(sqlite3_vtab *tab,
511                    sqlite3_int64 &rowid,
512                    lnav_view_t new_view_index,
513                    int64_t new_filter_id,
514                    bool enabled,
515                    text_filter::type_t type,
516                    pair<string, auto_mem<pcre>> pattern) {
517         auto view_index = lnav_view_t(rowid >> 32);
518         auto filter_index = rowid & 0xffffffffLL;
519         textview_curses &tc = lnav_data.ld_views[view_index];
520         text_sub_source *tss = tc.get_sub_source();
521         filter_stack &fs = tss->get_filters();
522         auto iter = fs.begin();
523         for (; iter != fs.end(); ++iter) {
524             if ((*iter)->get_index() == (size_t) filter_index) {
525                 break;
526             }
527         }
528 
529         shared_ptr<text_filter> tf = *iter;
530 
531         if (new_view_index != view_index) {
532             tab->zErrMsg = sqlite3_mprintf(
533                 "The view for a filter cannot be changed");
534             return SQLITE_ERROR;
535         }
536 
537         tf->lf_deleted = true;
538         tss->text_filters_changed();
539 
540         auto pf = make_shared<pcre_filter>(type,
541                                            pattern.first,
542                                            tf->get_index(),
543                                            pattern.second.release());
544 
545         if (!enabled) {
546             pf->disable();
547         }
548 
549         *iter = pf;
550         tss->text_filters_changed();
551         tc.set_needs_update();
552 
553         return SQLITE_OK;
554     };
555 };
556 
557 struct lnav_view_filter_stats : public tvt_iterator_cursor<lnav_view_filter_stats>,
558     public lnav_view_filter_base {
559     static constexpr const char *NAME = "lnav_view_filter_stats";
560     static constexpr const char *CREATE_STMT = R"(
561 -- Access statistics for filters through this table.
562 CREATE TABLE lnav_view_filter_stats (
563     view_name TEXT,     -- The name of the view.
564     filter_id INTEGER,  -- The filter identifier.
565     hits      INTEGER   -- The number of lines that matched this filter.
566 );
567 )";
568 
get_columnlnav_view_filter_stats569     int get_column(cursor &vc, sqlite3_context *ctx, int col) {
570         textview_curses &tc = lnav_data.ld_views[vc.iter.i_view_index];
571         text_sub_source *tss = tc.get_sub_source();
572         filter_stack &fs = tss->get_filters();
573         auto tf = *(fs.begin() + vc.iter.i_filter_index);
574 
575         switch (col) {
576             case 0:
577                 sqlite3_result_text(ctx,
578                                     lnav_view_strings[vc.iter.i_view_index], -1,
579                                     SQLITE_STATIC);
580                 break;
581             case 1:
582                 to_sqlite(ctx, tf->get_index());
583                 break;
584             case 2:
585                 to_sqlite(ctx, tss->get_filtered_count_for(tf->get_index()));
586                 break;
587         }
588 
589         return SQLITE_OK;
590     }
591 };
592 
593 struct lnav_view_files : public tvt_iterator_cursor<lnav_view_files> {
594     static constexpr const char *NAME = "lnav_view_files";
595     static constexpr const char *CREATE_STMT = R"(
596 --
597 CREATE TABLE lnav_view_files (
598     view_name TEXT,     -- The name of the view.
599     filepath  TEXT,     -- The path to the file.
600     visible   INTEGER   -- Indicates whether or not the file is shown.
601 );
602 )";
603 
604     using iterator = logfile_sub_source::iterator;
605 
beginlnav_view_files606     iterator begin() {
607         return lnav_data.ld_log_source.begin();
608     }
609 
endlnav_view_files610     iterator end() {
611         return lnav_data.ld_log_source.end();
612     }
613 
get_columnlnav_view_files614     int get_column(cursor &vc, sqlite3_context *ctx, int col) {
615         auto& ld = *vc.iter;
616 
617         switch (col) {
618             case 0:
619                 sqlite3_result_text(ctx,
620                                     lnav_view_strings[LNV_LOG], -1,
621                                     SQLITE_STATIC);
622                 break;
623             case 1:
624                 to_sqlite(ctx, ld->ld_filter_state.lfo_filter_state
625                     .tfs_logfile->get_filename());
626                 break;
627             case 2:
628                 to_sqlite(ctx, ld->ld_visible);
629                 break;
630         }
631 
632         return SQLITE_OK;
633     }
634 
delete_rowlnav_view_files635     int delete_row(sqlite3_vtab *tab, sqlite3_int64 rowid) {
636         tab->zErrMsg = sqlite3_mprintf(
637             "Rows cannot be deleted from the lnav_view_files table");
638         return SQLITE_ERROR;
639     }
640 
insert_rowlnav_view_files641     int insert_row(sqlite3_vtab *tab, sqlite3_int64 &rowid_out) {
642         tab->zErrMsg = sqlite3_mprintf(
643             "Rows cannot be inserted into the lnav_view_files table");
644         return SQLITE_ERROR;
645     };
646 
update_rowlnav_view_files647     int update_row(sqlite3_vtab *tab,
648                    sqlite3_int64 &rowid,
649                    const char *view_name,
650                    const char *file_path,
651                    bool visible) {
652         auto &lss = lnav_data.ld_log_source;
653         auto iter = this->begin();
654 
655         std::advance(iter, rowid);
656 
657         auto& ld = *iter;
658         if (ld->ld_visible != visible) {
659             ld->set_visibility(visible);
660             lss.text_filters_changed();
661         }
662 
663         return SQLITE_OK;
664     }
665 };
666 
667 static const char *CREATE_FILTER_VIEW = R"(
668 CREATE VIEW lnav_view_filters_and_stats AS
669   SELECT * FROM lnav_view_filters LEFT NATURAL JOIN lnav_view_filter_stats
670 )";
671 
672 static auto a = injector::bind_multiple<vtab_module_base>()
673     .add<vtab_module<lnav_views>>()
674     .add<vtab_module<lnav_view_stack>>()
675     .add<vtab_module<lnav_view_filters>>()
676     .add<vtab_module<tvt_no_update<lnav_view_filter_stats>>>()
677     .add<vtab_module<lnav_view_files>>();
678 
register_views_vtab(sqlite3 * db)679 int register_views_vtab(sqlite3 *db)
680 {
681     char *errmsg;
682     if (sqlite3_exec(db, CREATE_FILTER_VIEW, nullptr, nullptr, &errmsg) != SQLITE_OK) {
683         log_error("Unable to create filter view: %s", errmsg);
684     }
685 
686     return 0;
687 }
688