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

tstack / lnav / 11872214087-1756

16 Nov 2024 06:12PM UTC coverage: 70.243% (+0.5%) from 69.712%
11872214087-1756

push

github

tstack
[build] disable regex101

46266 of 65866 relevant lines covered (70.24%)

467515.63 hits per line

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

74.96
/src/sql_util.cc
1
/**
2
 * Copyright (c) 2013, 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
 * @file sql_util.cc
30
 */
31

32
#include <regex>
33
#include <vector>
34

35
#include "sql_util.hh"
36

37
#include <ctype.h>
38
#include <stdarg.h>
39
#include <string.h>
40

41
#include "base/auto_mem.hh"
42
#include "base/injector.hh"
43
#include "base/lnav_log.hh"
44
#include "base/string_util.hh"
45
#include "bound_tags.hh"
46
#include "config.h"
47
#include "lnav_util.hh"
48
#include "pcrepp/pcre2pp.hh"
49
#include "readline_context.hh"
50
#include "readline_highlighters.hh"
51
#include "sql_execute.hh"
52
#include "sql_help.hh"
53
#include "sqlitepp.hh"
54

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

57
/**
58
 * Copied from -- http://www.sqlite.org/lang_keywords.html
59
 */
60
const char* sql_keywords[] = {
61
    "ABORT",
62
    "ACTION",
63
    "ADD",
64
    "AFTER",
65
    "ALL",
66
    "ALTER",
67
    "ALWAYS",
68
    "ANALYZE",
69
    "AND",
70
    "AS",
71
    "ASC",
72
    "ATTACH",
73
    "AUTOINCREMENT",
74
    "BEFORE",
75
    "BEGIN",
76
    "BETWEEN",
77
    "BY",
78
    "CASCADE",
79
    "CASE",
80
    "CAST",
81
    "CHECK",
82
    "COLLATE",
83
    "COLUMN",
84
    "COMMIT",
85
    "CONFLICT",
86
    "CONSTRAINT",
87
    "CREATE",
88
    "CROSS",
89
    "CURRENT",
90
    "CURRENT_DATE",
91
    "CURRENT_TIME",
92
    "CURRENT_TIMESTAMP",
93
    "DATABASE",
94
    "DEFAULT",
95
    "DEFERRABLE",
96
    "DEFERRED",
97
    "DELETE",
98
    "DESC",
99
    "DETACH",
100
    "DISTINCT",
101
    "DO",
102
    "DROP",
103
    "EACH",
104
    "ELSE",
105
    "END",
106
    "ESCAPE",
107
    "EXCEPT",
108
    "EXCLUDE",
109
    "EXCLUSIVE",
110
    "EXISTS",
111
    "EXPLAIN",
112
    "FAIL",
113
    "FILTER",
114
    "FIRST",
115
    "FOLLOWING",
116
    "FOR",
117
    "FOREIGN",
118
    "FROM",
119
    "FULL",
120
    "GENERATED",
121
    "GLOB",
122
    "GROUP",
123
    "GROUPS",
124
    "HAVING",
125
    "IF",
126
    "IGNORE",
127
    "IMMEDIATE",
128
    "IN",
129
    "INDEX",
130
    "INDEXED",
131
    "INITIALLY",
132
    "INNER",
133
    "INSERT",
134
    "INSTEAD",
135
    "INTERSECT",
136
    "INTO",
137
    "IS",
138
    "ISNULL",
139
    "JOIN",
140
    "KEY",
141
    "LAST",
142
    "LEFT",
143
    "LIKE",
144
    "LIMIT",
145
    "MATCH",
146
    "NATURAL",
147
    "NO",
148
    "NOT",
149
    "NOTHING",
150
    "NOTNULL",
151
    "NULL",
152
    "NULLS",
153
    "OF",
154
    "OFFSET",
155
    "ON",
156
    "OR",
157
    "ORDER",
158
    "OTHERS",
159
    "OUTER",
160
    "OVER",
161
    "PARTITION",
162
    "PLAN",
163
    "PRAGMA",
164
    "PRECEDING",
165
    "PRIMARY",
166
    "QUERY",
167
    "RAISE",
168
    "RANGE",
169
    "RECURSIVE",
170
    "REFERENCES",
171
    "REGEXP",
172
    "REINDEX",
173
    "RELEASE",
174
    "RENAME",
175
    "REPLACE",
176
    "RESTRICT",
177
    "RIGHT",
178
    "ROLLBACK",
179
    "ROW",
180
    "ROWS",
181
    "SAVEPOINT",
182
    "SELECT",
183
    "SET",
184
    "TABLE",
185
    "TEMP",
186
    "TEMPORARY",
187
    "THEN",
188
    "TIES",
189
    "TO",
190
    "TRANSACTION",
191
    "TRIGGER",
192
    "UNBOUNDED",
193
    "UNION",
194
    "UNIQUE",
195
    "UPDATE",
196
    "USING",
197
    "VACUUM",
198
    "VALUES",
199
    "VIEW",
200
    "VIRTUAL",
201
    "WHEN",
202
    "WHERE",
203
    "WINDOW",
204
    "WITH",
205
    "WITHOUT",
206
};
207

208
const char* sql_function_names[] = {
209
    /* http://www.sqlite.org/lang_aggfunc.html */
210
    "avg(",
211
    "count(",
212
    "group_concat(",
213
    "max(",
214
    "min(",
215
    "sum(",
216
    "total(",
217

218
    /* http://www.sqlite.org/lang_corefunc.html */
219
    "abs(",
220
    "changes()",
221
    "char(",
222
    "coalesce(",
223
    "glob(",
224
    "ifnull(",
225
    "instr(",
226
    "hex(",
227
    "last_insert_rowid()",
228
    "length(",
229
    "like(",
230
    "load_extension(",
231
    "lower(",
232
    "ltrim(",
233
    "nullif(",
234
    "printf(",
235
    "quote(",
236
    "random()",
237
    "randomblob(",
238
    "replace(",
239
    "round(",
240
    "rtrim(",
241
    "soundex(",
242
    "sqlite_compileoption_get(",
243
    "sqlite_compileoption_used(",
244
    "sqlite_source_id()",
245
    "sqlite_version()",
246
    "substr(",
247
    "total_changes()",
248
    "trim(",
249
    "typeof(",
250
    "unicode(",
251
    "upper(",
252
    "zeroblob(",
253

254
    /* http://www.sqlite.org/lang_datefunc.html */
255
    "date(",
256
    "time(",
257
    "datetime(",
258
    "julianday(",
259
    "strftime(",
260

261
    nullptr,
262
};
263

264
const std::unordered_map<unsigned char, const char*> sql_constraint_names = {
265
    {SQLITE_INDEX_CONSTRAINT_EQ, "="},
266
    {SQLITE_INDEX_CONSTRAINT_GT, ">"},
267
    {SQLITE_INDEX_CONSTRAINT_LE, "<="},
268
    {SQLITE_INDEX_CONSTRAINT_LT, "<"},
269
    {SQLITE_INDEX_CONSTRAINT_GE, ">="},
270
    {SQLITE_INDEX_CONSTRAINT_MATCH, "MATCH"},
271
    {SQLITE_INDEX_CONSTRAINT_LIKE, "LIKE"},
272
    {SQLITE_INDEX_CONSTRAINT_GLOB, "GLOB"},
273
    {SQLITE_INDEX_CONSTRAINT_REGEXP, "REGEXP"},
274
    {SQLITE_INDEX_CONSTRAINT_NE, "!="},
275
    {SQLITE_INDEX_CONSTRAINT_ISNOT, "IS NOT"},
276
    {SQLITE_INDEX_CONSTRAINT_ISNOTNULL, "IS NOT NULL"},
277
    {SQLITE_INDEX_CONSTRAINT_ISNULL, "IS NULL"},
278
    {SQLITE_INDEX_CONSTRAINT_IS, "IS"},
279
#if defined(SQLITE_INDEX_CONSTRAINT_LIMIT)
280
    {SQLITE_INDEX_CONSTRAINT_LIMIT, "LIMIT"},
281
    {SQLITE_INDEX_CONSTRAINT_OFFSET, "OFFSET"},
282
#endif
283
#if defined(SQLITE_INDEX_CONSTRAINT_FUNCTION)
284
    {SQLITE_INDEX_CONSTRAINT_FUNCTION, "function"},
285
#endif
286
};
287

288
std::multimap<std::string, const help_text*> sqlite_function_help;
289

290
static int
291
handle_db_list(void* ptr, int ncols, char** colvalues, char** colnames)
1✔
292
{
293
    struct sqlite_metadata_callbacks* smc;
294

295
    smc = (struct sqlite_metadata_callbacks*) ptr;
1✔
296

297
    smc->smc_db_list[colvalues[1]] = std::vector<std::string>();
1✔
298
    if (!smc->smc_database_list) {
1✔
299
        return 0;
×
300
    }
301

302
    return smc->smc_database_list(ptr, ncols, colvalues, colnames);
1✔
303
}
304

305
struct table_list_data {
306
    struct sqlite_metadata_callbacks* tld_callbacks;
307
    db_table_map_t::iterator* tld_iter;
308
};
309

310
static int
311
handle_table_list(void* ptr, int ncols, char** colvalues, char** colnames)
91✔
312
{
313
    struct table_list_data* tld = (struct table_list_data*) ptr;
91✔
314

315
    (*tld->tld_iter)->second.emplace_back(colvalues[0]);
91✔
316
    if (!tld->tld_callbacks->smc_table_list) {
91✔
317
        return 0;
×
318
    }
319

320
    return tld->tld_callbacks->smc_table_list(
182✔
321
        tld->tld_callbacks, ncols, colvalues, colnames);
91✔
322
}
323

324
int
325
walk_sqlite_metadata(sqlite3* db, struct sqlite_metadata_callbacks& smc)
1✔
326
{
327
    auto_mem<char, sqlite3_free> errmsg;
1✔
328
    int retval;
329

330
    if (smc.smc_collation_list) {
1✔
331
        retval = sqlite3_exec(db,
1✔
332
                              "pragma collation_list",
333
                              smc.smc_collation_list,
334
                              &smc,
335
                              errmsg.out());
336
        if (retval != SQLITE_OK) {
1✔
337
            log_error("could not get collation list -- %s", errmsg.in());
×
338
            return retval;
×
339
        }
340
    }
341

342
    retval = sqlite3_exec(
1✔
343
        db, "pragma database_list", handle_db_list, &smc, errmsg.out());
344
    if (retval != SQLITE_OK) {
1✔
345
        log_error("could not get DB list -- %s", errmsg.in());
×
346
        return retval;
×
347
    }
348

349
    for (auto iter = smc.smc_db_list.begin(); iter != smc.smc_db_list.end();
2✔
350
         ++iter)
1✔
351
    {
352
        struct table_list_data tld = {&smc, &iter};
1✔
353
        auto_mem<char, sqlite3_free> query;
1✔
354

355
        query = sqlite3_mprintf(
356
            "SELECT name,sql FROM %Q.sqlite_master "
357
            "WHERE type in ('table', 'view')",
358
            iter->first.c_str());
1✔
359

360
        retval = sqlite3_exec(db, query, handle_table_list, &tld, errmsg.out());
1✔
361
        if (retval != SQLITE_OK) {
1✔
362
            log_error("could not get table list -- %s", errmsg.in());
×
363
            return retval;
×
364
        }
365

366
        for (auto table_iter = iter->second.begin();
1✔
367
             table_iter != iter->second.end();
92✔
368
             ++table_iter)
91✔
369
        {
370
            auto_mem<char, sqlite3_free> table_query;
91✔
371
            std::string& table_name = *table_iter;
91✔
372

373
            table_query = sqlite3_mprintf("pragma %Q.table_xinfo(%Q)",
374
                                          iter->first.c_str(),
91✔
375
                                          table_name.c_str());
182✔
376
            if (table_query == nullptr) {
91✔
377
                return SQLITE_NOMEM;
×
378
            }
379

380
            if (smc.smc_table_info) {
91✔
381
                retval = sqlite3_exec(
91✔
382
                    db, table_query, smc.smc_table_info, &smc, errmsg.out());
383
                if (retval != SQLITE_OK) {
91✔
384
                    log_error("could not get table info -- %s", errmsg.in());
×
385
                    return retval;
×
386
                }
387
            }
388

389
            table_query = sqlite3_mprintf("pragma %Q.foreign_key_list(%Q)",
390
                                          iter->first.c_str(),
91✔
391
                                          table_name.c_str());
182✔
392
            if (table_query == nullptr) {
91✔
393
                return SQLITE_NOMEM;
×
394
            }
395

396
            if (smc.smc_foreign_key_list) {
91✔
397
                retval = sqlite3_exec(db,
91✔
398
                                      table_query,
399
                                      smc.smc_foreign_key_list,
400
                                      &smc,
401
                                      errmsg.out());
402
                if (retval != SQLITE_OK) {
91✔
403
                    log_error("could not get foreign key list -- %s",
×
404
                              errmsg.in());
405
                    return retval;
×
406
                }
407
            }
408
        }
91✔
409
    }
1✔
410

411
    return retval;
1✔
412
}
1✔
413

414
static int
415
schema_collation_list(void* ptr, int ncols, char** colvalues, char** colnames)
7✔
416
{
417
    return 0;
7✔
418
}
419

420
static int
421
schema_db_list(void* ptr, int ncols, char** colvalues, char** colnames)
1✔
422
{
423
    struct sqlite_metadata_callbacks* smc = (sqlite_metadata_callbacks*) ptr;
1✔
424
    std::string& schema_out = *((std::string*) smc->smc_userdata);
1✔
425
    auto_mem<char, sqlite3_free> attach_sql;
1✔
426

427
    attach_sql = sqlite3_mprintf(
428
        "ATTACH DATABASE %Q AS %Q;\n", colvalues[2], colvalues[1]);
1✔
429

430
    schema_out += attach_sql;
1✔
431

432
    return 0;
1✔
433
}
1✔
434

435
static int
436
schema_table_list(void* ptr, int ncols, char** colvalues, char** colnames)
91✔
437
{
438
    struct sqlite_metadata_callbacks* smc = (sqlite_metadata_callbacks*) ptr;
91✔
439
    std::string& schema_out = *((std::string*) smc->smc_userdata);
91✔
440
    auto_mem<char, sqlite3_free> create_sql;
91✔
441

442
    create_sql = sqlite3_mprintf("%s;\n", colvalues[1]);
91✔
443

444
    schema_out += create_sql;
91✔
445

446
    return 0;
91✔
447
}
91✔
448

449
static int
450
schema_table_info(void* ptr, int ncols, char** colvalues, char** colnames)
2,294✔
451
{
452
    return 0;
2,294✔
453
}
454

455
static int
456
schema_foreign_key_list(void* ptr, int ncols, char** colvalues, char** colnames)
1✔
457
{
458
    return 0;
1✔
459
}
460

461
void
462
dump_sqlite_schema(sqlite3* db, std::string& schema_out)
1✔
463
{
464
    struct sqlite_metadata_callbacks schema_sql_meta_callbacks = {
1✔
465
        schema_collation_list,
466
        schema_db_list,
467
        schema_table_list,
468
        schema_table_info,
469
        schema_foreign_key_list,
470
        &schema_out,
471
        {},
472
    };
1✔
473

474
    walk_sqlite_metadata(db, schema_sql_meta_callbacks);
1✔
475
}
1✔
476

477
void
478
attach_sqlite_db(sqlite3* db, const std::string& filename)
1✔
479
{
480
    static const std::regex db_name_converter("[^\\w]");
1✔
481

482
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
1✔
483

484
    if (sqlite3_prepare_v2(db, "ATTACH DATABASE ? as ?", -1, stmt.out(), NULL)
1✔
485
        != SQLITE_OK)
1✔
486
    {
487
        log_error("could not prepare DB attach statement -- %s",
×
488
                  sqlite3_errmsg(db));
489
        return;
×
490
    }
491

492
    if (sqlite3_bind_text(
1✔
493
            stmt.in(), 1, filename.c_str(), filename.length(), SQLITE_TRANSIENT)
1✔
494
        != SQLITE_OK)
1✔
495
    {
496
        log_error("could not bind DB attach statement -- %s",
×
497
                  sqlite3_errmsg(db));
498
        return;
×
499
    }
500

501
    size_t base_start = filename.find_last_of("/\\");
1✔
502
    std::string db_name;
1✔
503

504
    if (base_start == std::string::npos) {
1✔
505
        db_name = filename;
×
506
    } else {
507
        db_name = filename.substr(base_start + 1);
1✔
508
    }
509

510
    db_name = std::regex_replace(db_name, db_name_converter, "_");
1✔
511

512
    if (sqlite3_bind_text(
1✔
513
            stmt.in(), 2, db_name.c_str(), db_name.length(), SQLITE_TRANSIENT)
1✔
514
        != SQLITE_OK)
1✔
515
    {
516
        log_error("could not bind DB attach statement -- %s",
×
517
                  sqlite3_errmsg(db));
518
        return;
×
519
    }
520

521
    if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
1✔
522
        log_error("could not execute DB attach statement -- %s",
×
523
                  sqlite3_errmsg(db));
524
        return;
×
525
    }
526
}
1✔
527

528
static void
529
sqlite_logger(void* dummy, int code, const char* msg)
279✔
530
{
531
    lnav_log_level_t level;
532

533
    switch (code) {
279✔
534
        case SQLITE_OK:
×
535
            level = lnav_log_level_t::DEBUG;
×
536
            break;
×
537
#ifdef SQLITE_NOTICE
538
        case SQLITE_NOTICE:
×
539
            level = lnav_log_level_t::INFO;
×
540
            break;
×
541
#endif
542
#ifdef SQLITE_WARNING
543
        case SQLITE_WARNING:
×
544
            level = lnav_log_level_t::WARNING;
×
545
            break;
×
546
#endif
547
        default:
279✔
548
            level = lnav_log_level_t::ERROR;
279✔
549
            break;
279✔
550
    }
551

552
    log_msg(level, __FILE__, __LINE__, "(%d) %s", code, msg);
279✔
553

554
    ensure(code != 21);
279✔
555
}
279✔
556

557
void
558
sql_install_logger()
636✔
559
{
560
#ifdef SQLITE_CONFIG_LOG
561
    sqlite3_config(SQLITE_CONFIG_LOG, sqlite_logger, NULL);
636✔
562
#endif
563
}
636✔
564

565
bool
566
sql_ident_needs_quote(const char* ident)
3,406✔
567
{
568
    for (int lpc = 0; ident[lpc]; lpc++) {
28,750✔
569
        if (!isalnum(ident[lpc]) && ident[lpc] != '_') {
27,100✔
570
            return true;
1,756✔
571
        }
572
    }
573

574
    return false;
1,650✔
575
}
576

577
auto_mem<char, sqlite3_free>
578
sql_quote_ident(const char* ident)
325,592✔
579
{
580
    bool needs_quote = false;
325,592✔
581
    size_t quote_count = 0, alloc_size;
325,592✔
582
    auto_mem<char, sqlite3_free> retval;
325,592✔
583

584
    for (int lpc = 0; ident[lpc]; lpc++) {
3,144,696✔
585
        if ((lpc == 0 && isdigit(ident[lpc]))
2,819,104✔
586
            || (!isalnum(ident[lpc]) && ident[lpc] != '_'))
2,819,104✔
587
        {
588
            needs_quote = true;
21,243✔
589
        }
590
        if (ident[lpc] == '"') {
2,819,104✔
591
            quote_count += 1;
×
592
        }
593
    }
594

595
    alloc_size = strlen(ident) + quote_count * 2 + (needs_quote ? 2 : 0) + 1;
325,592✔
596
    if ((retval = (char*) sqlite3_malloc(alloc_size)) == nullptr) {
325,592✔
597
        retval = nullptr;
×
598
    } else {
599
        char* curr = retval;
325,592✔
600

601
        if (needs_quote) {
325,592✔
602
            curr[0] = '"';
13,836✔
603
            curr += 1;
13,836✔
604
        }
605
        for (size_t lpc = 0; ident[lpc] != '\0'; lpc++) {
3,144,696✔
606
            switch (ident[lpc]) {
2,819,104✔
607
                case '"':
×
608
                    curr[0] = '"';
×
609
                    curr += 1;
×
610
                default:
2,819,104✔
611
                    curr[0] = ident[lpc];
2,819,104✔
612
                    break;
2,819,104✔
613
            }
614
            curr += 1;
2,819,104✔
615
        }
616
        if (needs_quote) {
325,592✔
617
            curr[0] = '"';
13,836✔
618
            curr += 1;
13,836✔
619
        }
620

621
        *curr = '\0';
325,592✔
622
    }
623

624
    return retval;
325,592✔
625
}
×
626

627
std::string
628
sql_safe_ident(const string_fragment& ident)
16,924✔
629
{
630
    std::string retval = std::to_string(ident);
16,924✔
631

632
    for (size_t lpc = 0; lpc < retval.size(); lpc++) {
170,298✔
633
        char ch = retval[lpc];
153,374✔
634

635
        if (isalnum(ch) || ch == '_') {
153,374✔
636
            retval[lpc] = tolower(ch);
134,930✔
637
        } else {
638
            retval[lpc] = '_';
18,444✔
639
        }
640
    }
641

642
    return retval;
16,924✔
643
}
×
644

645
attr_line_t
646
annotate_sql_with_error(sqlite3* db, const char* sql, const char* tail)
7✔
647
{
648
    const auto* errmsg = sqlite3_errmsg(db);
7✔
649
    attr_line_t retval;
7✔
650
    int erroff = -1;
7✔
651

652
#if defined(HAVE_SQLITE3_ERROR_OFFSET)
653
    erroff = sqlite3_error_offset(db);
654
#endif
655
    if (tail != nullptr) {
7✔
656
        const auto* tail_lf = strchr(tail, '\n');
7✔
657
        if (tail_lf == nullptr) {
7✔
658
            tail = tail + strlen(tail);
7✔
659
        } else {
660
            tail = tail_lf;
×
661
        }
662
        retval.append(string_fragment::from_bytes(sql, tail - sql));
7✔
663
    } else {
664
        retval.append(sql);
×
665
    }
666
    if (erroff >= retval.length()) {
7✔
667
        erroff -= 1;
×
668
    }
669
    if (erroff != -1 && !endswith(retval.get_string(), "\n")) {
7✔
670
        retval.append("\n");
×
671
    }
672
    retval.with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE));
7✔
673
    readline_sqlite_highlighter(retval, retval.length());
7✔
674

675
    if (erroff != -1) {
7✔
676
        auto line_with_error
677
            = string_fragment::from_str(retval.get_string())
×
678
                  .find_boundaries_around(erroff, string_fragment::tag1{'\n'});
×
679
        auto erroff_in_line = erroff - line_with_error.sf_begin;
×
680

681
        attr_line_t pointer;
×
682

683
        pointer.append(erroff_in_line, ' ')
×
684
            .append("^ "_snippet_border)
×
685
            .append(lnav::roles::error(errmsg))
×
686
            .append("\n");
×
687

688
        retval.insert(line_with_error.sf_end + 1, pointer).rtrim();
×
689
    }
690

691
    return retval;
7✔
692
}
×
693

694
static void
695
sql_execute_script(sqlite3* db,
610✔
696
                   const std::map<std::string, scoped_value_t>& global_vars,
697
                   const char* src_name,
698
                   sqlite3_stmt* stmt,
699
                   std::vector<lnav::console::user_message>& errors)
700
{
701
    std::map<std::string, scoped_value_t> lvars;
610✔
702
    bool done = false;
610✔
703
    int param_count;
704

705
    sqlite3_clear_bindings(stmt);
610✔
706

707
    param_count = sqlite3_bind_parameter_count(stmt);
610✔
708
    for (int lpc = 0; lpc < param_count; lpc++) {
610✔
709
        const char* name;
710

711
        name = sqlite3_bind_parameter_name(stmt, lpc + 1);
×
712
        if (name[0] == '$') {
×
713
            const char* env_value;
714
            auto iter = lvars.find(&name[1]);
×
715
            if (iter != lvars.end()) {
×
716
                mapbox::util::apply_visitor(
×
717
                    sqlitepp::bind_visitor(stmt, lpc + 1), iter->second);
×
718
            } else {
719
                auto giter = global_vars.find(&name[1]);
×
720
                if (giter != global_vars.end()) {
×
721
                    mapbox::util::apply_visitor(
×
722
                        sqlitepp::bind_visitor(stmt, lpc + 1), giter->second);
×
723
                } else if ((env_value = getenv(&name[1])) != nullptr) {
×
724
                    sqlite3_bind_text(
×
725
                        stmt, lpc + 1, env_value, -1, SQLITE_TRANSIENT);
726
                } else {
727
                    sqlite3_bind_null(stmt, lpc + 1);
×
728
                }
729
            }
730
        } else {
731
            sqlite3_bind_null(stmt, lpc + 1);
×
732
        }
733
    }
734
    while (!done) {
1,220✔
735
        int retcode = sqlite3_step(stmt);
610✔
736
        switch (retcode) {
610✔
737
            case SQLITE_OK:
610✔
738
            case SQLITE_DONE:
739
                done = true;
610✔
740
                break;
610✔
741

742
            case SQLITE_ROW: {
×
743
                int ncols = sqlite3_column_count(stmt);
×
744

745
                for (int lpc = 0; lpc < ncols; lpc++) {
×
746
                    const char* name = sqlite3_column_name(stmt, lpc);
×
747
                    auto* raw_value = sqlite3_column_value(stmt, lpc);
×
748
                    auto value_type = sqlite3_value_type(raw_value);
×
749
                    scoped_value_t value;
×
750

751
                    switch (value_type) {
752
                        case SQLITE_INTEGER:
×
753
                            value = (int64_t) sqlite3_value_int64(raw_value);
×
754
                            break;
×
755
                        case SQLITE_FLOAT:
×
756
                            value = sqlite3_value_double(raw_value);
×
757
                            break;
×
758
                        case SQLITE_NULL:
×
759
                            value = null_value_t{};
×
760
                            break;
×
761
                        default:
×
762
                            value = string_fragment::from_bytes(
×
763
                                sqlite3_value_text(raw_value),
764
                                sqlite3_value_bytes(raw_value));
×
765
                            break;
×
766
                    }
767
                    lvars[name] = value;
×
768
                }
769
                break;
×
770
            }
771

772
            default: {
×
773
                const auto* sql_str = sqlite3_sql(stmt);
×
774
                auto sql_content
775
                    = annotate_sql_with_error(db, sql_str, nullptr);
×
776

777
                errors.emplace_back(
×
778
                    lnav::console::user_message::error(
×
779
                        "failed to execute SQL statement")
780
                        .with_reason(sqlite3_errmsg_to_attr_line(db))
×
781
                        .with_snippet(lnav::console::snippet::from(
×
782
                            intern_string::lookup(src_name), sql_content)));
783
                done = true;
×
784
                break;
×
785
            }
786
        }
787
    }
788

789
    sqlite3_reset(stmt);
610✔
790
}
610✔
791

792
static void
793
sql_compile_script(sqlite3* db,
564✔
794
                   const std::map<std::string, scoped_value_t>& global_vars,
795
                   const char* src_name,
796
                   const char* script_orig,
797
                   std::vector<lnav::console::user_message>& errors)
798
{
799
    const char* script = script_orig;
564✔
800

801
    while (script != nullptr && script[0]) {
1,174✔
802
        auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
1,174✔
803
        int line_number = 1;
1,174✔
804
        const char* tail;
805
        int retcode;
806

807
        while (isspace(*script) && script[0]) {
2,394✔
808
            script += 1;
1,220✔
809
        }
810
        for (const char* ch = script_orig; ch < script && ch[0]; ch++) {
101,376✔
811
            if (*ch == '\n') {
100,202✔
812
                line_number += 1;
3,568✔
813
            }
814
        }
815

816
        retcode = sqlite3_prepare_v2(db, script, -1, stmt.out(), &tail);
1,174✔
817
        log_debug("retcode %d  %p %p", retcode, script, tail);
1,174✔
818
        if (retcode != SQLITE_OK) {
1,174✔
819
            const auto* errmsg = sqlite3_errmsg(db);
×
820
            auto sql_content = annotate_sql_with_error(db, script, tail);
×
821

822
            errors.emplace_back(
×
823
                lnav::console::user_message::error(
×
824
                    "failed to compile SQL statement")
825
                    .with_reason(errmsg)
×
826
                    .with_snippet(
827
                        lnav::console::snippet::from(
×
828
                            intern_string::lookup(src_name), sql_content)
829
                            .with_line(line_number)));
×
830
            break;
×
831
        }
832
        if (script == tail) {
1,174✔
833
            break;
564✔
834
        }
835
        if (stmt == nullptr) {
610✔
836
        } else {
837
            sql_execute_script(db, global_vars, src_name, stmt.in(), errors);
610✔
838
        }
839

840
        script = tail;
610✔
841
    }
1,174✔
842
}
564✔
843

844
void
845
sql_execute_script(sqlite3* db,
564✔
846
                   const std::map<std::string, scoped_value_t>& global_vars,
847
                   const char* src_name,
848
                   const char* script,
849
                   std::vector<lnav::console::user_message>& errors)
850
{
851
    sql_compile_script(db, global_vars, src_name, script, errors);
564✔
852
}
564✔
853

854
static struct {
855
    int sqlite_type;
856
    const char* collator;
857
    const char* sample;
858
} TYPE_TEST_VALUE[] = {
859
    {SQLITE3_TEXT, "", "foobar"},
860
    {SQLITE_INTEGER, "", "123"},
861
    {SQLITE_FLOAT, "", "123.0"},
862
    {SQLITE_TEXT, "ipaddress", "127.0.0.1"},
863
};
864

865
int
866
guess_type_from_pcre(const std::string& pattern, std::string& collator)
30,049✔
867
{
868
    static const std::vector<int> number_matches = {1, 2};
30,049✔
869

870
    auto compile_res = lnav::pcre2pp::code::from(pattern);
30,049✔
871
    if (compile_res.isErr()) {
30,049✔
872
        return SQLITE3_TEXT;
×
873
    }
874

875
    auto re = compile_res.unwrap();
30,049✔
876
    std::vector<int> matches;
30,049✔
877
    int retval = SQLITE3_TEXT;
30,049✔
878
    int index = 0;
30,049✔
879

880
    collator.clear();
30,049✔
881
    for (const auto& test_value : TYPE_TEST_VALUE) {
150,245✔
882
        auto find_res
883
            = re.find_in(string_fragment::from_c_str(test_value.sample),
240,392✔
884
                         PCRE2_ANCHORED)
885
                  .ignore_error();
120,196✔
886
        if (find_res && find_res->f_all.sf_begin == 0
219,153✔
887
            && find_res->f_remaining.empty())
219,153✔
888
        {
889
            matches.push_back(index);
81,337✔
890
        }
891

892
        index += 1;
120,196✔
893
    }
894

895
    if (matches.size() == 1) {
30,049✔
896
        retval = TYPE_TEST_VALUE[matches.front()].sqlite_type;
7,771✔
897
        collator = TYPE_TEST_VALUE[matches.front()].collator;
7,771✔
898
    } else if (matches == number_matches) {
22,278✔
899
        retval = SQLITE_FLOAT;
1,036✔
900
        collator = "";
1,036✔
901
    }
902

903
    return retval;
30,049✔
904
}
30,049✔
905

906
const char*
907
sqlite3_type_to_string(int type)
325,588✔
908
{
909
    switch (type) {
325,588✔
910
        case SQLITE_FLOAT:
5,796✔
911
            return "FLOAT";
5,796✔
912
        case SQLITE_INTEGER:
79,285✔
913
            return "INTEGER";
79,285✔
914
        case SQLITE_TEXT:
240,506✔
915
            return "TEXT";
240,506✔
916
        case SQLITE_NULL:
1✔
917
            return "NULL";
1✔
918
        case SQLITE_BLOB:
×
919
            return "BLOB";
×
920
    }
921

922
    ensure("Invalid sqlite type");
923

924
    return nullptr;
×
925
}
926

927
/* XXX figure out how to do this with the template */
928
void
929
sqlite_close_wrapper(void* mem)
1,333✔
930
{
931
    sqlite3_close_v2((sqlite3*) mem);
1,333✔
932
}
1,333✔
933

934
int
935
sqlite_authorizer(void* pUserData,
8,167✔
936
                  int action_code,
937
                  const char* detail1,
938
                  const char* detail2,
939
                  const char* detail3,
940
                  const char* detail4)
941
{
942
    if (action_code == SQLITE_ATTACH) {
8,167✔
943
        return SQLITE_DENY;
4✔
944
    }
945
    return SQLITE_OK;
8,163✔
946
}
947

948
attr_line_t
949
sqlite3_errmsg_to_attr_line(sqlite3* db)
×
950
{
951
    const auto* errmsg = sqlite3_errmsg(db);
×
952
    if (startswith(errmsg, sqlitepp::ERROR_PREFIX)) {
×
953
        auto from_res = lnav::from_json<lnav::console::user_message>(
954
            &errmsg[strlen(sqlitepp::ERROR_PREFIX)]);
×
955

956
        if (from_res.isOk()) {
×
957
            return from_res.unwrap().to_attr_line();
×
958
        }
959

960
        return from_res.unwrapErr()[0].um_message.get_string();
×
961
    }
962

963
    return attr_line_t(errmsg);
×
964
}
965

966
std::string
967
sql_keyword_re()
305✔
968
{
969
    std::string retval = "(?:";
305✔
970
    bool first = true;
305✔
971

972
    for (const char* kw : sql_keywords) {
44,530✔
973
        if (!first) {
44,225✔
974
            retval.append("|");
43,920✔
975
        } else {
976
            first = false;
305✔
977
        }
978
        retval.append("\\b");
44,225✔
979
        retval.append(kw);
44,225✔
980
        retval.append("\\b");
44,225✔
981
    }
982
    retval += ")";
305✔
983

984
    return retval;
305✔
985
}
×
986

987
string_attr_type<void> SQL_COMMAND_ATTR("sql_command");
988
string_attr_type<void> SQL_KEYWORD_ATTR("sql_keyword");
989
string_attr_type<void> SQL_IDENTIFIER_ATTR("sql_ident");
990
string_attr_type<void> SQL_FUNCTION_ATTR("sql_func");
991
string_attr_type<void> SQL_STRING_ATTR("sql_string");
992
string_attr_type<void> SQL_NUMBER_ATTR("sql_number");
993
string_attr_type<void> SQL_UNTERMINATED_STRING_ATTR("sql_unstring");
994
string_attr_type<void> SQL_OPERATOR_ATTR("sql_oper");
995
string_attr_type<void> SQL_PAREN_ATTR("sql_paren");
996
string_attr_type<void> SQL_COMMA_ATTR("sql_comma");
997
string_attr_type<void> SQL_GARBAGE_ATTR("sql_garbage");
998
string_attr_type<void> SQL_COMMENT_ATTR("sql_comment");
999

1000
void
1001
annotate_sql_statement(attr_line_t& al)
1,609✔
1002
{
1003
    static const std::string keyword_re_str = R"(\A)" + sql_keyword_re();
1,609✔
1004

1005
    static const struct {
1006
        lnav::pcre2pp::code re;
1007
        string_attr_type<void>* type;
1008
    } PATTERNS[] = {
1009
        {
1010
            lnav::pcre2pp::code::from_const(R"(\A,)"),
1011
            &SQL_COMMA_ATTR,
1012
        },
1013
        {
1014
            lnav::pcre2pp::code::from_const(R"(\A\(|\A\))"),
1015
            &SQL_PAREN_ATTR,
1016
        },
1017
        {
1018
            lnav::pcre2pp::code::from(keyword_re_str, PCRE2_CASELESS).unwrap(),
305✔
1019
            &SQL_KEYWORD_ATTR,
1020
        },
1021
        {
1022
            lnav::pcre2pp::code::from_const(R"(\A'[^']*('(?:'[^']*')*|$))"),
1023
            &SQL_STRING_ATTR,
1024
        },
1025
        {
1026
            lnav::pcre2pp::code::from_const(R"(\A0x[0-9a-fA-F]+)"),
1027
            &SQL_NUMBER_ATTR,
1028
        },
1029
        {
1030
            lnav::pcre2pp::code::from_const(
1031
                R"(\A-?\d+(?:\.\d+)?(?:[eE][\-\+]?\d+)?)"),
1032
            &SQL_NUMBER_ATTR,
1033
        },
1034
        {
1035
            lnav::pcre2pp::code::from_const(
1036
                R"(\A(((\$|:|@)?\b[a-z_]\w*)|\"([^\"]+)\"|\[([^\]]+)]))",
1037
                PCRE2_CASELESS),
1038
            &SQL_IDENTIFIER_ATTR,
1039
        },
1040
        {
1041
            lnav::pcre2pp::code::from_const(R"(\A--.*)"),
1042
            &SQL_COMMENT_ATTR,
1043
        },
1044
        {
1045
            lnav::pcre2pp::code::from_const(
1046
                R"(\A(\*|\->{1,2}|<|>|=|!|\-|\+|\|\|))"),
1047
            &SQL_OPERATOR_ATTR,
1048
        },
1049
        {
1050
            lnav::pcre2pp::code::from_const(R"(\A.)"),
1051
            &SQL_GARBAGE_ATTR,
1052
        },
1053
    };
1,914✔
1054

1055
    static const auto cmd_pattern
1056
        = lnav::pcre2pp::code::from_const(R"(^;?(\.\w+))");
1,609✔
1057
    static const auto ws_pattern = lnav::pcre2pp::code::from_const(R"(\A\s+)");
1,609✔
1058

1059
    auto& line = al.get_string();
1,609✔
1060
    auto& sa = al.get_attrs();
1,609✔
1061

1062
    if (lnav::sql::is_prql(line)) {
1,609✔
1063
        lnav::sql::annotate_prql_statement(al);
11✔
1064
        return;
17✔
1065
    }
1066

1067
    auto cmd_find_res
1068
        = cmd_pattern.find_in(line, PCRE2_ANCHORED).ignore_error();
1,598✔
1069
    if (cmd_find_res) {
1,598✔
1070
        auto cap = cmd_find_res->f_all;
6✔
1071
        sa.emplace_back(line_range(cap.sf_begin, cap.sf_end),
6✔
1072
                        SQL_COMMAND_ATTR.value());
12✔
1073
        return;
6✔
1074
    }
1075

1076
    auto remaining = string_fragment::from_str(line);
1,592✔
1077
    while (!remaining.empty()) {
20,178✔
1078
        auto ws_find_res = ws_pattern.find_in(remaining).ignore_error();
18,586✔
1079
        if (ws_find_res) {
18,586✔
1080
            remaining = ws_find_res->f_remaining;
5,736✔
1081
            continue;
5,736✔
1082
        }
1083
        for (const auto& pat : PATTERNS) {
51,822✔
1084
            auto pat_find_res = pat.re.find_in(remaining).ignore_error();
51,822✔
1085
            if (pat_find_res) {
51,822✔
1086
                sa.emplace_back(to_line_range(pat_find_res->f_all),
12,850✔
1087
                                pat.type->value());
25,700✔
1088
                remaining = pat_find_res->f_remaining;
12,850✔
1089
                break;
12,850✔
1090
            }
1091
        }
1092
    }
1093

1094
    string_attrs_t::const_iterator iter;
1,592✔
1095
    int start = 0;
1,592✔
1096

1097
    while ((iter = find_string_attr(sa, &SQL_IDENTIFIER_ATTR, start))
5,923✔
1098
           != sa.end())
8,662✔
1099
    {
1100
        string_attrs_t::const_iterator piter;
2,739✔
1101
        bool found_open = false;
2,739✔
1102
        ssize_t lpc;
1103

1104
        start = iter->sa_range.lr_end;
2,739✔
1105
        for (lpc = iter->sa_range.lr_end; lpc < (int) line.length(); lpc++) {
3,634✔
1106
            if (line[lpc] == '(') {
3,422✔
1107
                found_open = true;
1,274✔
1108
                break;
1,274✔
1109
            }
1110
            if (!isspace(line[lpc])) {
2,148✔
1111
                break;
1,253✔
1112
            }
1113
        }
1114

1115
        if (found_open) {
2,739✔
1116
            ssize_t pstart = lpc + 1;
1,274✔
1117
            int depth = 1;
1,274✔
1118

1119
            while (depth > 0
1,274✔
1120
                   && (piter = find_string_attr(sa, &SQL_PAREN_ATTR, pstart))
5,461✔
1121
                       != sa.end())
5,461✔
1122
            {
1123
                if (line[piter->sa_range.lr_start] == '(') {
1,395✔
1124
                    depth += 1;
61✔
1125
                } else {
1126
                    depth -= 1;
1,334✔
1127
                }
1128
                pstart = piter->sa_range.lr_end;
1,395✔
1129
            }
1130

1131
            line_range func_range{iter->sa_range.lr_start};
1,274✔
1132
            if (piter == sa.end()) {
1,274✔
1133
                func_range.lr_end = line.length();
1✔
1134
            } else {
1135
                func_range.lr_end = piter->sa_range.lr_end - 1;
1,273✔
1136
            }
1137
            sa.emplace_back(func_range, SQL_FUNCTION_ATTR.value());
1,274✔
1138
        }
1139
    }
1140

1141
    remove_string_attr(sa, &SQL_PAREN_ATTR);
1,592✔
1142
    stable_sort(sa.begin(), sa.end());
1,592✔
1143
}
1144

1145
std::vector<const help_text*>
1146
find_sql_help_for_line(const attr_line_t& al, size_t x)
10✔
1147
{
1148
    static const auto* sql_cmd_map
1149
        = injector::get<readline_context::command_map_t*, sql_cmd_map_tag>();
10✔
1150

1151
    std::vector<const help_text*> retval;
10✔
1152
    const auto& sa = al.get_attrs();
10✔
1153
    std::string name;
10✔
1154

1155
    x = al.nearest_text(x);
10✔
1156

1157
    {
1158
        auto sa_opt = get_string_attr(al.get_attrs(), &SQL_COMMAND_ATTR);
10✔
1159
        if (sa_opt) {
10✔
1160
            auto cmd_name = al.get_substring((*sa_opt)->sa_range);
×
1161
            auto cmd_iter = sql_cmd_map->find(cmd_name);
×
1162

1163
            if (cmd_iter != sql_cmd_map->end()) {
×
1164
                return {&cmd_iter->second->c_help};
×
1165
            }
1166
        }
1167

1168
        auto prql_trans_iter = find_string_attr_containing(
10✔
1169
            al.get_attrs(), &lnav::sql::PRQL_TRANSFORM_ATTR, x);
1170
        if (prql_trans_iter != al.get_attrs().end()) {
10✔
1171
            auto cmd_name = al.get_substring(prql_trans_iter->sa_range);
1✔
1172
            auto cmd_iter = sql_cmd_map->find(cmd_name);
1✔
1173

1174
            if (cmd_iter != sql_cmd_map->end()) {
1✔
1175
                return {&cmd_iter->second->c_help};
1✔
1176
            }
1177
        }
1✔
1178
    }
1179

1180
    auto prql_fqid_iter = find_string_attr_containing(
9✔
1181
        al.get_attrs(), &lnav::sql ::PRQL_FQID_ATTR, x);
1182
    if (prql_fqid_iter != al.get_attrs().end()) {
9✔
1183
        auto fqid = al.get_substring(prql_fqid_iter->sa_range);
1✔
1184
        auto cmd_iter = sql_cmd_map->find(fqid);
1✔
1185
        if (cmd_iter != sql_cmd_map->end()) {
1✔
1186
            return {&cmd_iter->second->c_help};
1✔
1187
        }
1188

1189
        auto func_pair = lnav::sql::prql_functions.equal_range(fqid);
×
1190

1191
        for (auto func_iter = func_pair.first; func_iter != func_pair.second;
×
1192
             ++func_iter)
1193
        {
1194
            retval.emplace_back(func_iter->second);
×
1195
            return retval;
×
1196
        }
1197
    }
1✔
1198

1199
    std::vector<std::string> kw;
8✔
1200
    auto iter = rfind_string_attr_if(sa, x, [&al, &name, &kw, x](auto sa) {
8✔
1201
        if (sa.sa_type != &SQL_FUNCTION_ATTR && sa.sa_type != &SQL_KEYWORD_ATTR)
26✔
1202
        {
1203
            return false;
11✔
1204
        }
1205

1206
        const std::string& str = al.get_string();
15✔
1207
        const line_range& lr = sa.sa_range;
15✔
1208
        int lpc;
1209

1210
        if (sa.sa_type == &SQL_FUNCTION_ATTR) {
15✔
1211
            if (!sa.sa_range.contains(x)) {
7✔
1212
                return false;
2✔
1213
            }
1214
        }
1215

1216
        for (lpc = lr.lr_start; lpc < lr.lr_end; lpc++) {
86✔
1217
            if (!isalnum(str[lpc]) && str[lpc] != '_') {
78✔
1218
                break;
5✔
1219
            }
1220
        }
1221

1222
        auto tmp_name = str.substr(lr.lr_start, lpc - lr.lr_start);
13✔
1223
        if (sa.sa_type == &SQL_KEYWORD_ATTR) {
13✔
1224
            tmp_name = toupper(tmp_name);
8✔
1225
        }
1226
        bool retval = sqlite_function_help.count(tmp_name) > 0;
13✔
1227

1228
        if (retval) {
13✔
1229
            kw.push_back(tmp_name);
13✔
1230
            name = tmp_name;
13✔
1231
        }
1232
        return retval;
13✔
1233
    });
13✔
1234

1235
    if (iter != sa.end()) {
8✔
1236
        auto func_pair = sqlite_function_help.equal_range(name);
8✔
1237
        size_t help_count = std::distance(func_pair.first, func_pair.second);
8✔
1238

1239
        if (help_count > 1 && name != func_pair.first->second->ht_name) {
8✔
1240
            while (func_pair.first != func_pair.second) {
×
1241
                if (find(kw.begin(), kw.end(), func_pair.first->second->ht_name)
×
1242
                    == kw.end())
×
1243
                {
1244
                    ++func_pair.first;
×
1245
                } else {
1246
                    func_pair.second = next(func_pair.first);
×
1247
                    break;
×
1248
                }
1249
            }
1250
        }
1251
        for (auto func_iter = func_pair.first; func_iter != func_pair.second;
17✔
1252
             ++func_iter)
9✔
1253
        {
1254
            retval.emplace_back(func_iter->second);
9✔
1255
        }
1256
    }
1257

1258
    return retval;
8✔
1259
}
10✔
1260

1261
namespace lnav {
1262
namespace sql {
1263

1264
auto_mem<char, sqlite3_free>
1265
mprintf(const char* fmt, ...)
325,596✔
1266
{
1267
    auto_mem<char, sqlite3_free> retval;
325,596✔
1268
    va_list args;
1269

1270
    va_start(args, fmt);
325,596✔
1271
    retval = sqlite3_vmprintf(fmt, args);
325,596✔
1272
    va_end(args);
325,596✔
1273

1274
    return retval;
651,192✔
1275
}
×
1276

1277
bool
1278
is_prql(const string_fragment& sf)
4,028✔
1279
{
1280
    auto trimmed = sf.trim().skip(string_fragment::tag1{';'});
4,028✔
1281

1282
    return (trimmed.startswith("let ") || trimmed.startswith("from"));
4,028✔
1283
}
1284

1285
const char* prql_transforms[] = {
1286
    "aggregate",
1287
    "append",
1288
    "derive",
1289
    "filter",
1290
    "from",
1291
    "group",
1292
    "join",
1293
    "loop",
1294
    "select",
1295
    "sort",
1296
    "take",
1297
    "window",
1298

1299
    nullptr,
1300
};
1301

1302
const char* prql_keywords[] = {
1303
    "average", "avg", "case", "count", "count_distinct", "false", "func",
1304
    "into",    "let", "max",  "min",   "module",         "null",  "prql",
1305
    "stddev",  "sum", "true", "type",
1306

1307
    nullptr,
1308
};
1309

1310
std::string
1311
prql_keyword_re()
10✔
1312
{
1313
    std::string retval = "(?:";
10✔
1314
    bool first = true;
10✔
1315

1316
    for (const char* kw : prql_keywords) {
190✔
1317
        if (kw == nullptr) {
190✔
1318
            break;
10✔
1319
        }
1320
        if (!first) {
180✔
1321
            retval.append("|");
170✔
1322
        } else {
1323
            first = false;
10✔
1324
        }
1325
        retval.append("\\b");
180✔
1326
        retval.append(kw);
180✔
1327
        retval.append("\\b");
180✔
1328
    }
1329
    retval += ")";
10✔
1330

1331
    return retval;
10✔
1332
}
×
1333

1334
std::string
1335
prql_transform_re()
10✔
1336
{
1337
    std::string retval = "(?:";
10✔
1338
    bool first = true;
10✔
1339

1340
    for (const char* kw : prql_transforms) {
130✔
1341
        if (kw == nullptr) {
130✔
1342
            break;
10✔
1343
        }
1344
        if (!first) {
120✔
1345
            retval.append("|");
110✔
1346
        } else {
1347
            first = false;
10✔
1348
        }
1349
        retval.append("\\b");
120✔
1350
        retval.append(kw);
120✔
1351
        retval.append("\\b");
120✔
1352
    }
1353
    retval += ")";
10✔
1354

1355
    return retval;
10✔
1356
}
×
1357

1358
string_attr_type<void> PRQL_STAGE_ATTR("prql_stage");
1359
string_attr_type<void> PRQL_TRANSFORM_ATTR("prql_transform");
1360
string_attr_type<void> PRQL_KEYWORD_ATTR("prql_keyword");
1361
string_attr_type<void> PRQL_IDENTIFIER_ATTR("prql_ident");
1362
string_attr_type<void> PRQL_FQID_ATTR("prql_fqid");
1363
string_attr_type<void> PRQL_DOT_ATTR("prql_dot");
1364
string_attr_type<void> PRQL_PIPE_ATTR("prql_pipe");
1365
string_attr_type<void> PRQL_STRING_ATTR("prql_string");
1366
string_attr_type<void> PRQL_NUMBER_ATTR("prql_number");
1367
string_attr_type<void> PRQL_OPERATOR_ATTR("prql_oper");
1368
string_attr_type<void> PRQL_PAREN_ATTR("prql_paren");
1369
string_attr_type<void> PRQL_UNTERMINATED_PAREN_ATTR("prql_unterminated_paren");
1370
string_attr_type<void> PRQL_GARBAGE_ATTR("prql_garbage");
1371
string_attr_type<void> PRQL_COMMENT_ATTR("prql_comment");
1372

1373
void
1374
annotate_prql_statement(attr_line_t& al)
11✔
1375
{
1376
    static const std::string keyword_re_str = R"(\A)" + prql_keyword_re();
11✔
1377
    static const std::string transform_re_str = R"(\A)" + prql_transform_re();
11✔
1378

1379
    static const struct {
1380
        lnav::pcre2pp::code re;
1381
        string_attr_type<void>* type;
1382
    } PATTERNS[] = {
1383
        {
1384
            lnav::pcre2pp::code::from_const(R"(\A(?:\[|\]|\{|\}|\(|\)))"),
1385
            &PRQL_PAREN_ATTR,
1386
        },
1387
        {
1388
            lnav::pcre2pp::code::from(transform_re_str).unwrap(),
10✔
1389
            &PRQL_TRANSFORM_ATTR,
1390
        },
1391
        {
1392
            lnav::pcre2pp::code::from(keyword_re_str).unwrap(),
10✔
1393
            &PRQL_KEYWORD_ATTR,
1394
        },
1395
        {
1396
            lnav::pcre2pp::code::from_const(R"(\A(?:f|r|s)?'([^']|\\.)*')"),
1397
            &PRQL_STRING_ATTR,
1398
        },
1399
        {
1400
            lnav::pcre2pp::code::from_const(R"(\A(?:f|r|s)?"([^\"]|\\.)*")"),
1401
            &PRQL_STRING_ATTR,
1402
        },
1403
        {
1404
            lnav::pcre2pp::code::from_const(R"(\A0x[0-9a-fA-F]+)"),
1405
            &PRQL_NUMBER_ATTR,
1406
        },
1407
        {
1408
            lnav::pcre2pp::code::from_const(
1409
                R"(\A-?\d+(?:\.\d+)?(?:[eE][\-\+]?\d+)?)"),
1410
            &PRQL_NUMBER_ATTR,
1411
        },
1412
        {
1413
            lnav::pcre2pp::code::from_const(
1414
                R"(\A(?:(?:(?:\$)?\b[a-z_]\w*)|`([^`]+)`))", PCRE2_CASELESS),
1415
            &PRQL_IDENTIFIER_ATTR,
1416
        },
1417
        {
1418
            lnav::pcre2pp::code::from_const(R"(\A#.*)"),
1419
            &PRQL_COMMENT_ATTR,
1420
        },
1421
        {
1422
            lnav::pcre2pp::code::from_const(
1423
                R"(\A(\*|\->{1,2}|<|>|=>|={1,2}|\|\||&&|!|\-|\+|~=|\.\.|,|\?\?))"),
1424
            &PRQL_OPERATOR_ATTR,
1425
        },
1426
        {
1427
            lnav::pcre2pp::code::from_const(R"(\A\|)"),
1428
            &PRQL_PIPE_ATTR,
1429
        },
1430
        {
1431
            lnav::pcre2pp::code::from_const(R"(\A\.)"),
1432
            &PRQL_DOT_ATTR,
1433
        },
1434
        {
1435
            lnav::pcre2pp::code::from_const(R"(\A.)"),
1436
            &PRQL_GARBAGE_ATTR,
1437
        },
1438
    };
31✔
1439

1440
    static const auto ws_pattern = lnav::pcre2pp::code::from_const(R"(\A\s+)");
11✔
1441

1442
    const auto& line = al.get_string();
11✔
1443
    auto& sa = al.get_attrs();
11✔
1444
    auto remaining = string_fragment::from_str(line);
11✔
1445
    while (!remaining.empty()) {
179✔
1446
        auto ws_find_res = ws_pattern.find_in(remaining).ignore_error();
168✔
1447
        if (ws_find_res) {
168✔
1448
            remaining = ws_find_res->f_remaining;
73✔
1449
            continue;
73✔
1450
        }
1451
        for (const auto& pat : PATTERNS) {
695✔
1452
            auto pat_find_res = pat.re.find_in(remaining).ignore_error();
695✔
1453
            if (pat_find_res) {
695✔
1454
                sa.emplace_back(to_line_range(pat_find_res->f_all),
95✔
1455
                                pat.type->value());
190✔
1456
                remaining = pat_find_res->f_remaining;
95✔
1457
                break;
95✔
1458
            }
1459
        }
1460
    }
1461

1462
    auto stages = std::vector<int>{};
11✔
1463
    std::vector<std::pair<char, int>> groups;
11✔
1464
    std::vector<line_range> fqids;
11✔
1465
    std::optional<line_range> id_start;
11✔
1466
    bool saw_id_dot = false;
11✔
1467
    for (const auto& attr : sa) {
106✔
1468
        if (groups.empty() && attr.sa_type == &PRQL_PIPE_ATTR) {
95✔
1469
            stages.push_back(attr.sa_range.lr_start);
16✔
1470
        }
1471
        if (!id_start) {
95✔
1472
            if (attr.sa_type == &PRQL_IDENTIFIER_ATTR) {
59✔
1473
                id_start = attr.sa_range;
24✔
1474
                saw_id_dot = false;
24✔
1475
            }
1476
        } else if (!saw_id_dot) {
36✔
1477
            if (attr.sa_type == &PRQL_DOT_ATTR) {
29✔
1478
                saw_id_dot = true;
7✔
1479
            } else {
1480
                fqids.emplace_back(id_start.value());
22✔
1481
                id_start = std::nullopt;
22✔
1482
                saw_id_dot = false;
22✔
1483
            }
1484
        } else {
1485
            if (attr.sa_type == &PRQL_IDENTIFIER_ATTR) {
7✔
1486
                id_start = line_range{
7✔
1487
                    id_start.value().lr_start,
7✔
1488
                    attr.sa_range.lr_end,
7✔
1489
                };
7✔
1490
            } else {
1491
                id_start = std::nullopt;
×
1492
            }
1493
            saw_id_dot = false;
7✔
1494
        }
1495
        if (attr.sa_type != &PRQL_PAREN_ATTR) {
95✔
1496
            continue;
93✔
1497
        }
1498

1499
        auto ch = line[attr.sa_range.lr_start];
2✔
1500
        switch (ch) {
2✔
1501
            case '(':
1✔
1502
            case '{':
1503
            case '[':
1504
                groups.emplace_back(ch, attr.sa_range.lr_start);
1✔
1505
                break;
1✔
1506
            case ')':
×
1507
                if (!groups.empty() && groups.back().first == '(') {
×
1508
                    groups.pop_back();
×
1509
                }
1510
                break;
×
1511
            case '}':
1✔
1512
                if (!groups.empty() && groups.back().first == '{') {
1✔
1513
                    groups.pop_back();
1✔
1514
                }
1515
                break;
1✔
1516
            case ']':
×
1517
                if (!groups.empty() && groups.back().first == '[') {
×
1518
                    groups.pop_back();
×
1519
                }
1520
                break;
×
1521
        }
1522
    }
1523
    if (id_start) {
11✔
1524
        fqids.emplace_back(id_start.value());
2✔
1525
    }
1526
    int prev_stage_index = 0;
11✔
1527
    for (auto stage_index : stages) {
27✔
1528
        sa.emplace_back(line_range{prev_stage_index, stage_index},
16✔
1529
                        PRQL_STAGE_ATTR.value());
32✔
1530
        prev_stage_index = stage_index;
16✔
1531
    }
1532
    sa.emplace_back(
11✔
1533
        line_range{prev_stage_index, (int) al.get_string().length()},
11✔
1534
        PRQL_STAGE_ATTR.value());
22✔
1535
    for (const auto& group : groups) {
11✔
1536
        sa.emplace_back(line_range{group.second, group.second + 1},
×
1537
                        PRQL_UNTERMINATED_PAREN_ATTR.value());
×
1538
    }
1539
    for (const auto& fqid_range : fqids) {
35✔
1540
        sa.emplace_back(fqid_range, PRQL_FQID_ATTR.value());
24✔
1541
    }
1542

1543
    stable_sort(sa.begin(), sa.end());
11✔
1544
}
11✔
1545

1546
}  // namespace sql
1547

1548
namespace prql {
1549

1550
std::string
1551
quote_ident(std::string id)
×
1552
{
1553
    static const auto PLAIN_NAME
1554
        = pcre2pp::code::from_const("^[a-zA-Z_][a-zA-Z_0-9]*$");
×
1555

1556
    if (PLAIN_NAME.find_in(id).ignore_error()) {
×
1557
        return id;
×
1558
    }
1559

1560
    auto buf = auto_buffer::alloc(id.length() + 8);
×
1561
    quote_content(buf, id, '`');
×
1562

1563
    return fmt::format(FMT_STRING("`{}`"), buf.in());
×
1564
}
×
1565

×
1566
}  // namespace prql
1567

1568
}  // namespace lnav
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