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

tstack / lnav / 25348852825-3024

04 May 2026 11:18PM UTC coverage: 69.963% (+0.7%) from 69.226%
25348852825-3024

push

github

tstack
[ui] horizontal scroll should work on columns

Related to #1685

7 of 141 new or added lines in 5 files covered. (4.96%)

7760 existing lines in 84 files now uncovered.

57014 of 81492 relevant lines covered (69.96%)

622491.44 hits per line

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

85.77
/src/string-extension-functions.cc
1
/*
2
 * Written by Alexey Tourbin <at@altlinux.org>.
3
 *
4
 * The author has dedicated the code to the public domain.  Anyone is free
5
 * to copy, modify, publish, use, compile, sell, or distribute the original
6
 * code, either in source code form or as a compiled binary, for any purpose,
7
 * commercial or non-commercial, and by any means.
8
 */
9

10
#include <unordered_map>
11

12
#include <sqlite3.h>
13
#include <stdlib.h>
14
#include <string.h>
15

16
#include "base/fts_fuzzy_match.hh"
17
#include "base/humanize.hh"
18
#include "base/is_utf8.hh"
19
#include "base/lnav.gzip.hh"
20
#include "base/string_util.hh"
21
#include "column_namer.hh"
22
#include "config.h"
23
#include "data_parser.hh"
24
#include "data_scanner.hh"
25
#include "elem_to_json.hh"
26
#include "fmt/format.h"
27
#include "formats/logfmt/logfmt.parser.hh"
28
#include "hasher.hh"
29
#include "libbase64.h"
30
#include "mapbox/variant.hpp"
31
#include "md4cpp.hh"
32
#include "pcrepp/pcre2pp.hh"
33
#include "pretty_printer.hh"
34
#include "safe/safe.h"
35
#include "scn/scan.h"
36
#include "sqlite-extension-func.hh"
37
#include "text_anonymizer.hh"
38
#include "view_curses.hh"
39
#include "vtab_module.hh"
40
#include "vtab_module_json.hh"
41
#include "yajl/api/yajl_gen.h"
42
#include "yajlpp/json_op.hh"
43
#include "yajlpp/yajlpp.hh"
44
#include "yajlpp/yajlpp_def.hh"
45

46
#if defined(HAVE_LIBCURL)
47
#    include <curl/curl.h>
48
#endif
49

50
enum class encode_algo {
51
    base64,
52
    hex,
53
    uri,
54
    html,
55
};
56

57
template<>
58
struct from_sqlite<encode_algo> {
59
    encode_algo operator()(int argc, sqlite3_value** val, int argi)
35✔
60
    {
61
        const char* algo_name = (const char*) sqlite3_value_text(val[argi]);
35✔
62

63
        if (strcasecmp(algo_name, "base64") == 0) {
35✔
64
            return encode_algo::base64;
9✔
65
        }
66
        if (strcasecmp(algo_name, "hex") == 0) {
26✔
67
            return encode_algo::hex;
11✔
68
        }
69
        if (strcasecmp(algo_name, "uri") == 0) {
15✔
70
            return encode_algo::uri;
12✔
71
        }
72
        if (strcasecmp(algo_name, "html") == 0) {
3✔
73
            return encode_algo::html;
2✔
74
        }
75

76
        throw from_sqlite_conversion_error(
77
            "value of 'base64', 'hex', 'uri', or 'html'", argi);
1✔
78
    }
79
};
80

81
namespace {
82

83
struct cache_entry {
84
    std::shared_ptr<lnav::pcre2pp::code> re2;
85
    std::shared_ptr<column_namer> cn{
86
        std::make_shared<column_namer>(column_namer::language::JSON)};
87
};
88

89
cache_entry*
90
find_re(string_fragment re)
4,334✔
91
{
92
    using re_cache_t
93
        = std::unordered_map<string_fragment, cache_entry, frag_hasher>;
94
    thread_local re_cache_t cache;
4,334✔
95

96
    auto iter = cache.find(re);
4,334✔
97
    if (iter == cache.end()) {
4,334✔
98
        auto compile_res = lnav::pcre2pp::code::from(re);
3,839✔
99
        if (compile_res.isErr()) {
3,839✔
100
            const static intern_string_t SRC = intern_string::lookup("arg");
3✔
101

102
            throw lnav::console::to_user_message(SRC, compile_res.unwrapErr());
1✔
103
        }
104

105
        cache_entry c;
3,838✔
106

107
        c.re2 = compile_res.unwrap().to_shared();
3,838✔
108
        auto pair = cache.insert(
3,838✔
109
            std::make_pair(string_fragment::from_str(c.re2->get_pattern()), c));
7,676✔
110

111
        for (size_t lpc = 0; lpc < c.re2->get_capture_count(); lpc++) {
3,901✔
112
            c.cn->add_column(string_fragment::from_c_str(
63✔
113
                c.re2->get_name_for_capture(lpc + 1)));
114
        }
115

116
        iter = pair.first;
3,838✔
117
    }
3,839✔
118

119
    return &iter->second;
8,666✔
120
}
121

122
bool
123
regexp(string_fragment re, string_fragment str)
3,786✔
124
{
125
    auto* reobj = find_re(re);
3,786✔
126

127
    return reobj->re2->find_in(str).ignore_error().has_value();
3,786✔
128
}
129

130
mapbox::util::
131
    variant<int64_t, double, const char*, string_fragment, json_string>
132
    regexp_match(string_fragment re, string_fragment str)
520✔
133
{
134
    auto* reobj = find_re(re);
520✔
135
    auto& extractor = *reobj->re2;
519✔
136

137
    if (extractor.get_capture_count() == 0) {
519✔
138
        throw std::runtime_error(
139
            "regular expression does not have any captures");
1✔
140
    }
141

142
    auto md = extractor.create_match_data();
518✔
143
    auto match_res = extractor.capture_from(str).into(md).matches();
518✔
144
    if (match_res.is<lnav::pcre2pp::matcher::not_found>()) {
518✔
145
        return static_cast<const char*>(nullptr);
1✔
146
    }
147
    if (match_res.is<lnav::pcre2pp::matcher::error>()) {
517✔
148
        auto err = match_res.get<lnav::pcre2pp::matcher::error>();
×
149

150
        throw std::runtime_error(err.get_message());
×
151
    }
152

153
    if (extractor.get_capture_count() == 1) {
517✔
154
        auto cap = md[1];
501✔
155

156
        if (!cap) {
501✔
157
            return static_cast<const char*>(nullptr);
×
158
        }
159

160
        auto scan_int_res = scn::scan_int<int64_t>(cap->to_string_view());
501✔
161
        if (scan_int_res) {
501✔
162
            if (scan_int_res->range().empty()) {
16✔
163
                return scan_int_res->value();
16✔
164
            }
165
            auto scan_float_res
166
                = scn::scan_value<double>(cap->to_string_view());
1✔
167
            if (scan_float_res && scan_float_res->range().empty()) {
1✔
168
                return scan_float_res->value();
1✔
169
            }
170
        }
171

172
        return cap.value();
485✔
173
    }
174

175
    yajlpp_gen gen;
16✔
176
    yajl_gen_config(gen, yajl_gen_beautify, false);
16✔
177
    {
178
        yajlpp_map root_map(gen);
16✔
179

180
        for (size_t lpc = 0; lpc < extractor.get_capture_count(); lpc++) {
48✔
181
            const auto& colname = reobj->cn->cn_names[lpc];
32✔
182
            const auto cap = md[lpc + 1];
32✔
183

184
            yajl_gen_pstring(gen, colname.data(), colname.length());
32✔
185

186
            if (!cap) {
32✔
187
                yajl_gen_null(gen);
×
188
            } else {
189
                auto scan_int_res
190
                    = scn::scan_value<int64_t>(cap->to_string_view());
32✔
191
                if (scan_int_res && scan_int_res->range().empty()) {
32✔
192
                    yajl_gen_integer(gen, scan_int_res->value());
13✔
193
                } else {
194
                    auto scan_float_res
195
                        = scn::scan_value<double>(cap->to_string_view());
19✔
196
                    if (scan_float_res && scan_float_res->range().empty()) {
19✔
197
                        yajl_gen_number(gen, cap->data(), cap->length());
1✔
198
                    } else {
199
                        yajl_gen_pstring(gen, cap->data(), cap->length());
18✔
200
                    }
201
                }
202
            }
203
        }
204
    }
16✔
205

206
    return json_string(gen);
16✔
207
#if 0
208
    sqlite3_result_text(ctx, (const char *) buf, len, SQLITE_TRANSIENT);
209
#    ifdef HAVE_SQLITE3_VALUE_SUBTYPE
210
    sqlite3_result_subtype(ctx, JSON_SUBTYPE);
211
#    endif
212
#endif
213
}
518✔
214

215
static json_string
216
logfmt2json(string_fragment line)
7✔
217
{
218
    std::vector<string_fragment> unbound;
7✔
219
    logfmt::parser p(line);
7✔
220
    yajlpp_gen gen;
7✔
221
    yajl_gen_config(gen, yajl_gen_beautify, false);
7✔
222
    {
223
        yajlpp_map root(gen);
7✔
224
        bool done = false;
7✔
225

226
        while (!done) {
36✔
227
            auto pair = p.step();
29✔
228

229
            done = pair.match(
58✔
230
                [](const logfmt::parser::end_of_input& eoi) { return true; },
7✔
231
                [&unbound](const string_fragment& sf) {
×
232
                    unbound.emplace_back(sf);
×
233
                    return false;
×
234
                },
235
                [&root, &gen](const logfmt::parser::kvpair& kvp) {
29✔
236
                    root.gen(kvp.first);
22✔
237

238
                    kvp.second.match(
22✔
239
                        [&root](const logfmt::parser::bool_value& bv) {
×
240
                            root.gen(bv.bv_value);
×
241
                        },
×
242
                        [&root](const logfmt::parser::int_value& iv) {
×
243
                            root.gen(iv.iv_value);
14✔
244
                        },
14✔
245
                        [&root](const logfmt::parser::float_value& fv) {
×
246
                            root.gen(fv.fv_value);
1✔
247
                        },
1✔
248
                        [&root, &gen](const logfmt::parser::quoted_value& qv) {
×
249
                            auto_mem<yajl_handle_t> parse_handle(yajl_free);
6✔
250
                            json_ptr jp("");
6✔
251
                            json_op jo(jp);
6✔
252

253
                            jo.jo_ptr_callbacks = json_op::gen_callbacks;
6✔
254
                            jo.jo_ptr_data = gen;
6✔
255
                            parse_handle.reset(yajl_alloc(
6✔
256
                                &json_op::ptr_callbacks, nullptr, &jo));
257

258
                            const auto* json_in
259
                                = (const unsigned char*) qv.qv_value.data();
6✔
260
                            auto json_len = qv.qv_value.length();
6✔
261

262
                            if (yajl_parse(parse_handle.in(), json_in, json_len)
6✔
263
                                    != yajl_status_ok
264
                                || yajl_complete_parse(parse_handle.in())
6✔
265
                                    != yajl_status_ok)
266
                            {
267
                                root.gen(qv.qv_value);
×
268
                            }
269
                        },
6✔
270
                        [&root](const logfmt::parser::unquoted_value& uv) {
22✔
271
                            root.gen(uv.uv_value);
1✔
272
                        });
1✔
273

274
                    return false;
22✔
275
                },
276
                [](const logfmt::parser::error& e) -> bool {
×
277
                    throw sqlite_func_error("Invalid logfmt: {}", e.e_msg);
×
278
                });
279
        }
29✔
280

281
        if (!unbound.empty()) {
7✔
282
            root.gen("__unbound__");
×
283
            yajlpp_array unbound_array(gen);
×
284
            for (const auto& sf : unbound) {
×
285
                unbound_array.gen(sf);
×
286
            }
287
        }
288
    }
7✔
289

290
    return json_string(gen);
14✔
291
}
7✔
292

293
std::string
294
regexp_replace(string_fragment str, string_fragment re, const char* repl)
28✔
295
{
296
    auto* reobj = find_re(re);
28✔
297

298
    return reobj->re2->replace(str, repl);
28✔
299
}
300

301
std::optional<int64_t>
302
sql_fuzzy_match(const char* pat, const char* str)
6✔
303
{
304
    if (pat == nullptr) {
6✔
305
        return std::nullopt;
×
306
    }
307

308
    if (pat[0] == '\0') {
6✔
309
        return 1;
×
310
    }
311

312
    int score = 0;
6✔
313

314
    if (!fts::fuzzy_match(pat, str, score)) {
6✔
315
        return std::nullopt;
×
316
    }
317

318
    return score;
6✔
319
}
320

321
string_fragment
322
spooky_hash(const std::vector<const char*>& args)
5,531✔
323
{
324
    thread_local char hash_str_buf[hasher::STRING_SIZE];
325

326
    hasher context;
5,531✔
327
    for (const auto* const arg : args) {
16,587✔
328
        int64_t len = arg != nullptr ? strlen(arg) : 0;
11,056✔
329

330
        context.update((const char*) &len, sizeof(len));
11,056✔
331
        if (arg == nullptr) {
11,056✔
332
            continue;
31✔
333
        }
334
        context.update(arg, len);
11,025✔
335
    }
336
    context.to_string(hash_str_buf);
5,531✔
337

338
    return string_fragment::from_bytes(hash_str_buf, sizeof(hash_str_buf) - 1);
11,062✔
339
}
340

341
void
342
sql_spooky_hash_step(sqlite3_context* context, int argc, sqlite3_value** argv)
12✔
343
{
344
    auto* hasher
345
        = (SpookyHash*) sqlite3_aggregate_context(context, sizeof(SpookyHash));
12✔
346

347
    for (int lpc = 0; lpc < argc; lpc++) {
24✔
348
        const auto* value = sqlite3_value_text(argv[lpc]);
12✔
349
        int64_t len = value != nullptr ? strlen((const char*) value) : 0;
12✔
350

351
        hasher->Update(&len, sizeof(len));
12✔
352
        if (value == nullptr) {
12✔
353
            continue;
×
354
        }
355
        hasher->Update(value, len);
12✔
356
    }
357
}
12✔
358

359
void
360
sql_spooky_hash_final(sqlite3_context* context)
6✔
361
{
362
    auto* hasher
363
        = (SpookyHash*) sqlite3_aggregate_context(context, sizeof(SpookyHash));
6✔
364

365
    if (hasher == nullptr) {
6✔
366
        sqlite3_result_null(context);
×
367
    } else {
368
        byte_array<2, uint64> hash;
6✔
369

370
        hasher->Final(hash.out(0), hash.out(1));
6✔
371

372
        auto hex = hash.to_string();
6✔
373
        sqlite3_result_text(
6✔
374
            context, hex.c_str(), hex.length(), SQLITE_TRANSIENT);
6✔
375
    }
6✔
376
}
6✔
377

378
struct sparkline_context {
379
    bool sc_initialized{true};
380
    double sc_max_value{0.0};
381
    std::optional<double> sc_lower;
382
    std::vector<double> sc_values;
383
};
384

385
void
386
sparkline_step(sqlite3_context* context, int argc, sqlite3_value** argv)
60✔
387
{
388
    auto* sc = (sparkline_context*) sqlite3_aggregate_context(
60✔
389
        context, sizeof(sparkline_context));
390

391
    if (!sc->sc_initialized) {
60✔
392
        new (sc) sparkline_context;
12✔
393
    }
394

395
    if (argc == 0) {
60✔
UNCOV
396
        return;
×
397
    }
398

399
    sc->sc_values.push_back(sqlite3_value_double(argv[0]));
60✔
400
    sc->sc_max_value = std::max(sc->sc_max_value, sc->sc_values.back());
60✔
401

402
    if (argc >= 2) {
60✔
403
        sc->sc_max_value
404
            = std::max(sc->sc_max_value, sqlite3_value_double(argv[1]));
6✔
405
    }
406
    if (argc >= 3) {
60✔
UNCOV
407
        sc->sc_lower = sqlite3_value_double(argv[2]);
×
408
    }
409
}
410

411
void
412
sparkline_final(sqlite3_context* context)
12✔
413
{
414
    auto* sc = (sparkline_context*) sqlite3_aggregate_context(
12✔
415
        context, sizeof(sparkline_context));
416

417
    if (!sc->sc_initialized) {
12✔
UNCOV
418
        sqlite3_result_text(context, "", 0, SQLITE_STATIC);
×
UNCOV
419
        return;
×
420
    }
421

422
    auto retval = auto_mem<char>::malloc(sc->sc_values.size() * 3 + 1);
12✔
423
    auto* start = retval.in();
12✔
424

425
    for (const auto& value : sc->sc_values) {
72✔
426
        auto bar = humanize::sparkline(value, sc->sc_max_value, sc->sc_lower);
60✔
427

428
        strcpy(start, bar.c_str());
60✔
429
        start += bar.length();
60✔
430
    }
60✔
431
    *start = '\0';
12✔
432

433
    to_sqlite(context, std::move(retval));
12✔
434

435
    sc->~sparkline_context();
12✔
436
}
12✔
437

438
std::optional<mapbox::util::variant<blob_auto_buffer, sqlite3_int64, double>>
439
sql_gunzip(sqlite3_value* val)
2✔
440
{
441
    switch (sqlite3_value_type(val)) {
2✔
442
        case SQLITE3_TEXT:
2✔
443
        case SQLITE_BLOB: {
444
            const auto* buffer = sqlite3_value_blob(val);
2✔
445
            auto len = sqlite3_value_bytes(val);
2✔
446

447
            if (!lnav::gzip::is_gzipped((const char*) buffer, len)) {
2✔
UNCOV
448
                return blob_auto_buffer{
×
UNCOV
449
                    auto_buffer::from((const char*) buffer, len)};
×
450
            }
451

452
            auto res = lnav::gzip::uncompress("", buffer, len);
4✔
453

454
            if (res.isErr()) {
2✔
455
                throw sqlite_func_error("unable to uncompress -- {}",
UNCOV
456
                                        res.unwrapErr());
×
457
            }
458

459
            return blob_auto_buffer{res.unwrap()};
2✔
460
        }
2✔
UNCOV
461
        case SQLITE_INTEGER:
×
UNCOV
462
            return sqlite3_value_int64(val);
×
463
        case SQLITE_FLOAT:
×
UNCOV
464
            return sqlite3_value_double(val);
×
465
    }
466

UNCOV
467
    return std::nullopt;
×
468
}
469

470
std::optional<blob_auto_buffer>
471
sql_gzip(sqlite3_value* val)
3✔
472
{
473
    switch (sqlite3_value_type(val)) {
3✔
474
        case SQLITE3_TEXT:
1✔
475
        case SQLITE_BLOB: {
476
            const auto* buffer = sqlite3_value_blob(val);
1✔
477
            auto len = sqlite3_value_bytes(val);
1✔
478
            auto res = lnav::gzip::compress(buffer, len);
1✔
479

480
            if (res.isErr()) {
1✔
481
                throw sqlite_func_error("unable to compress -- {}",
UNCOV
482
                                        res.unwrapErr());
×
483
            }
484

485
            return blob_auto_buffer{res.unwrap()};
1✔
486
        }
1✔
487
        case SQLITE_INTEGER:
2✔
488
        case SQLITE_FLOAT: {
489
            const auto* buffer = sqlite3_value_text(val);
2✔
490
            auto res
491
                = lnav::gzip::compress(buffer, strlen((const char*) buffer));
2✔
492

493
            if (res.isErr()) {
2✔
494
                throw sqlite_func_error("unable to compress -- {}",
UNCOV
495
                                        res.unwrapErr());
×
496
            }
497

498
            return blob_auto_buffer{res.unwrap()};
2✔
499
        }
2✔
500
    }
501

UNCOV
502
    return std::nullopt;
×
503
}
504

505
#if defined(HAVE_LIBCURL)
506
static CURL*
507
get_curl_easy()
49✔
508
{
509
    static struct curl_wrapper {
510
        curl_wrapper() { this->cw_value = curl_easy_init(); }
27✔
511

512
        auto_mem<CURL> cw_value{curl_easy_cleanup};
513
    } retval;
49✔
514

515
    return retval.cw_value.in();
49✔
516
}
517
#endif
518

519
mapbox::util::variant<text_auto_buffer, auto_mem<char>, null_value_t>
520
sql_encode(sqlite3_value* value, encode_algo algo)
23✔
521
{
522
    switch (sqlite3_value_type(value)) {
23✔
523
        case SQLITE_NULL: {
1✔
524
            return null_value_t{};
1✔
525
        }
526
        case SQLITE_BLOB: {
1✔
527
            const auto* blob
528
                = static_cast<const char*>(sqlite3_value_blob(value));
1✔
529
            auto blob_len = sqlite3_value_bytes(value);
1✔
530

531
            switch (algo) {
1✔
532
                case encode_algo::base64: {
1✔
533
                    auto buf = auto_buffer::alloc((blob_len * 5) / 3);
1✔
534
                    auto outlen = buf.capacity();
1✔
535

536
                    base64_encode(blob, blob_len, buf.in(), &outlen, 0);
1✔
537
                    buf.resize(outlen);
1✔
538
                    return text_auto_buffer{std::move(buf)};
1✔
539
                }
1✔
540
                case encode_algo::hex: {
×
541
                    auto buf = auto_buffer::alloc(blob_len * 2 + 1);
×
542

UNCOV
543
                    for (int lpc = 0; lpc < blob_len; lpc++) {
×
UNCOV
544
                        fmt::format_to(std::back_inserter(buf),
×
545
                                       FMT_STRING("{:02x}"),
×
UNCOV
546
                                       blob[lpc]);
×
547
                    }
548

549
                    return text_auto_buffer{std::move(buf)};
×
550
                }
551
#if defined(HAVE_LIBCURL)
552
                case encode_algo::uri: {
×
UNCOV
553
                    auto_mem<char> retval(curl_free);
×
554

555
                    retval = curl_easy_escape(get_curl_easy(), blob, blob_len);
×
556
                    return std::move(retval);
×
557
                }
558
#endif
UNCOV
559
                case encode_algo::html: {
×
UNCOV
560
                    return md4cpp::escape_html(
×
UNCOV
561
                        string_fragment::from_bytes(blob, blob_len));
×
562
                }
563
            }
564
        }
565
        default: {
566
            const auto* text = (const char*) sqlite3_value_text(value);
21✔
567
            auto text_len = sqlite3_value_bytes(value);
21✔
568

569
            switch (algo) {
21✔
570
                case encode_algo::base64: {
6✔
571
                    auto buf = auto_buffer::alloc((text_len * 5) / 3);
6✔
572
                    size_t outlen = buf.capacity();
6✔
573

574
                    base64_encode(text, text_len, buf.in(), &outlen, 0);
6✔
575
                    buf.resize(outlen);
6✔
576
                    return text_auto_buffer{std::move(buf)};
6✔
577
                }
6✔
578
                case encode_algo::hex: {
8✔
579
                    auto buf = auto_buffer::alloc(text_len * 2 + 1);
8✔
580

581
                    for (int lpc = 0; lpc < text_len; lpc++) {
102✔
582
                        fmt::format_to(std::back_inserter(buf),
94✔
583
                                       FMT_STRING("{:02x}"),
282✔
584
                                       text[lpc]);
94✔
585
                    }
586

587
                    return text_auto_buffer{std::move(buf)};
8✔
588
                }
8✔
589
#if defined(HAVE_LIBCURL)
590
                case encode_algo::uri: {
6✔
591
                    auto_mem<char> retval(curl_free);
6✔
592

593
                    retval = curl_easy_escape(get_curl_easy(), text, text_len);
6✔
594
                    return std::move(retval);
6✔
595
                }
6✔
596
#endif
597
                case encode_algo::html: {
1✔
598
                    return md4cpp::escape_html(
2✔
599
                        string_fragment::from_bytes(text, text_len));
1✔
600
                }
601
            }
602
        }
603
    }
UNCOV
604
    ensure(false);
×
605
}
606

607
mapbox::util::variant<blob_auto_buffer, text_auto_buffer, auto_mem<char>>
608
sql_decode(string_fragment str, encode_algo algo)
11✔
609
{
610
    switch (algo) {
11✔
611
        case encode_algo::base64: {
1✔
612
            auto buf = auto_buffer::alloc(str.length());
1✔
613
            auto outlen = buf.capacity();
1✔
614
            base64_decode(str.data(), str.length(), buf.in(), &outlen, 0);
1✔
615
            buf.resize(outlen);
1✔
616

617
            return blob_auto_buffer{std::move(buf)};
1✔
618
        }
1✔
619
        case encode_algo::hex: {
3✔
620
            auto buf = auto_buffer::alloc(str.length() / 2);
3✔
621
            auto sv = str.to_string_view();
3✔
622

623
            if (sv.size() % 2 != 0) {
3✔
624
                throw sqlite_func_error(
625
                    "hex input is not a multiple of two characters");
2✔
626
            }
627
            while (sv.size() >= 2) {
16✔
628
                auto scan_res = scn::scan<uint8_t>(sv.substr(0, 2), "{:2x}");
15✔
629
                if (!scan_res) {
15✔
630
                    throw sqlite_func_error(
631
                        "invalid hex input at: {}",
632
                        std::distance(str.begin(), sv.begin()));
3✔
633
                }
634
                auto value = scan_res->value();
14✔
635
                buf.push_back((char) (value & 0xff));
14✔
636
                sv = sv.substr(2);
14✔
637
            }
638

639
            return blob_auto_buffer{std::move(buf)};
1✔
640
        }
3✔
641
#if defined(HAVE_LIBCURL)
642
        case encode_algo::uri: {
6✔
643
            auto_mem<char> retval(curl_free);
6✔
644

645
            retval = curl_easy_unescape(
646
                get_curl_easy(), str.data(), str.length(), nullptr);
6✔
647

648
            return std::move(retval);
6✔
649
        }
6✔
650
#endif
651
        case encode_algo::html: {
1✔
652
            static const auto ENTITY_RE = lnav::pcre2pp::code::from_const(
653
                R"(&(?:([a-z0-9]+)|#([0-9]{1,6})|#x([0-9a-fA-F]{1,6}));)",
654
                PCRE2_CASELESS);
1✔
655
            static const auto ENTITIES = md4cpp::get_xml_entity_map();
1✔
656

657
            auto buf = auto_buffer::alloc(str.length());
1✔
658
            auto res = ENTITY_RE.capture_from(str).for_each(
1✔
659
                [&buf](const auto& match) {
3✔
660
                    buf.append(match.leading().to_string_view());
3✔
661
                    auto named = match[1];
3✔
662
                    if (named) {
3✔
663
                        auto iter
664
                            = ENTITIES.xem_entities.find(match[0]->to_string());
1✔
665
                        if (iter != ENTITIES.xem_entities.end()) {
1✔
666
                            buf.append(iter->second.xe_chars);
1✔
667
                        } else {
UNCOV
668
                            buf.append(match[0]->to_string_view());
×
669
                        }
670
                        return;
1✔
671
                    }
672
                    auto dec = match[2];
2✔
673
                    if (dec) {
2✔
674
                        auto scan_res
1✔
675
                            = scn::scan_int<uint32_t>(dec->to_string_view());
676
                        if (scan_res) {
1✔
677
                            ww898::utf::utf8::write(
1✔
678
                                scan_res.value().value(),
1✔
679
                                [&buf](uint8_t c) { buf.push_back(c); });
3✔
680
                        } else {
UNCOV
681
                            buf.append(match[0]->to_string_view());
×
682
                        }
683
                        return;
1✔
684
                    }
685
                    auto hex = match[3];
1✔
686
                    if (hex) {
1✔
687
                        auto scan_res = scn::scan_int<uint32_t>(
1✔
688
                            hex->to_string_view(), 16);
689
                        if (scan_res) {
1✔
690
                            ww898::utf::utf8::write(
1✔
691
                                scan_res.value().value(),
1✔
692
                                [&buf](uint8_t c) { buf.push_back(c); });
3✔
693
                        } else {
UNCOV
694
                            buf.append(match[0]->to_string_view());
×
695
                        }
696
                        return;
1✔
697
                    }
698
                });
1✔
699
            if (res.isOk()) {
1✔
700
                auto remaining = res.unwrap();
1✔
701
                buf.append(remaining.to_string_view());
1✔
702
            }
703
            return text_auto_buffer{std::move(buf)};
1✔
704
        }
1✔
705
    }
UNCOV
706
    ensure(false);
×
707
}
708

709
std::string
710
sql_humanize_file_size(file_ssize_t value)
280✔
711
{
712
    return humanize::file_size(value, humanize::alignment::columnar);
280✔
713
}
714

715
std::string
716
sql_anonymize(string_fragment frag)
203✔
717
{
718
    static safe::Safe<lnav::text_anonymizer> ta;
203✔
719

720
    return ta.writeAccess()->next(frag);
203✔
721
}
722

723
#if !CURL_AT_LEAST_VERSION(7, 80, 0)
724
extern "C"
725
{
726
const char* curl_url_strerror(CURLUcode error);
727
}
728
#endif
729

730
json_string
731
sql_parse_url(std::string url)
29✔
732
{
733
    static auto* CURL_HANDLE = get_curl_easy();
29✔
734

735
    auto_mem<CURLU> cu(curl_url_cleanup);
29✔
736
    cu = curl_url();
29✔
737

738
    auto rc = curl_url_set(
29✔
739
        cu, CURLUPART_URL, url.c_str(), CURLU_NON_SUPPORT_SCHEME);
740
    if (rc != CURLUE_OK) {
29✔
741
        auto_mem<char> url_part(curl_free);
1✔
742
        yajlpp_gen gen;
1✔
743
        yajl_gen_config(gen, yajl_gen_beautify, false);
1✔
744

745
        {
746
            yajlpp_map root(gen);
1✔
747
            root.gen("error");
1✔
748
            root.gen("invalid-url");
1✔
749
            root.gen("url");
1✔
750
            root.gen(url);
1✔
751
            root.gen("reason");
1✔
752
            root.gen(curl_url_strerror(rc));
1✔
753
        }
1✔
754

755
        return json_string(gen);
1✔
756
    }
1✔
757

758
    auto_mem<char> url_part(curl_free);
28✔
759
    yajlpp_gen gen;
28✔
760
    yajl_gen_config(gen, yajl_gen_beautify, false);
28✔
761

762
    {
763
        yajlpp_map root(gen);
28✔
764

765
        root.gen("scheme");
28✔
766
        rc = curl_url_get(cu, CURLUPART_SCHEME, url_part.out(), 0);
28✔
767
        if (rc == CURLUE_OK) {
28✔
768
            root.gen(string_fragment::from_c_str(url_part.in()));
28✔
769
        } else {
UNCOV
770
            root.gen();
×
771
        }
772
        root.gen("username");
28✔
773
        rc = curl_url_get(cu, CURLUPART_USER, url_part.out(), CURLU_URLDECODE);
28✔
774
        if (rc == CURLUE_OK) {
28✔
775
            root.gen(string_fragment::from_c_str(url_part.in()));
6✔
776
        } else {
777
            root.gen();
22✔
778
        }
779
        root.gen("password");
28✔
780
        rc = curl_url_get(
28✔
781
            cu, CURLUPART_PASSWORD, url_part.out(), CURLU_URLDECODE);
782
        if (rc == CURLUE_OK) {
28✔
UNCOV
783
            root.gen(string_fragment::from_c_str(url_part.in()));
×
784
        } else {
785
            root.gen();
28✔
786
        }
787
        root.gen("host");
28✔
788
        rc = curl_url_get(cu, CURLUPART_HOST, url_part.out(), CURLU_URLDECODE);
28✔
789
        if (rc == CURLUE_OK) {
28✔
790
            root.gen(string_fragment::from_c_str(url_part.in()));
28✔
791
        } else {
UNCOV
792
            root.gen();
×
793
        }
794
        root.gen("port");
28✔
795
        rc = curl_url_get(cu, CURLUPART_PORT, url_part.out(), 0);
28✔
796
        if (rc == CURLUE_OK) {
28✔
UNCOV
797
            root.gen(string_fragment::from_c_str(url_part.in()));
×
798
        } else {
799
            root.gen();
28✔
800
        }
801
        root.gen("path");
28✔
802
        rc = curl_url_get(cu, CURLUPART_PATH, url_part.out(), CURLU_URLDECODE);
28✔
803
        if (rc == CURLUE_OK) {
28✔
804
            auto path_frag = string_fragment::from_c_str(url_part.in());
28✔
805
            auto path_utf_res = is_utf8(path_frag);
28✔
806
            if (path_utf_res.is_valid()) {
28✔
807
                root.gen(path_frag);
28✔
808
            } else {
809
                rc = curl_url_get(cu, CURLUPART_PATH, url_part.out(), 0);
×
UNCOV
810
                if (rc == CURLUE_OK) {
×
UNCOV
811
                    root.gen(string_fragment::from_c_str(url_part.in()));
×
812
                } else {
813
                    root.gen();
×
814
                }
815
            }
816
        } else {
UNCOV
817
            root.gen();
×
818
        }
819
        rc = curl_url_get(cu, CURLUPART_QUERY, url_part.out(), 0);
28✔
820
        if (rc == CURLUE_OK) {
28✔
821
            root.gen("query");
15✔
822
            root.gen(string_fragment::from_c_str(url_part.in()));
15✔
823

824
            root.gen("parameters");
15✔
825
            robin_hood::unordered_set<std::string> seen_keys;
15✔
826
            yajlpp_map query_map(gen);
15✔
827

828
            for (size_t lpc = 0; url_part.in()[lpc]; lpc++) {
222✔
829
                if (url_part.in()[lpc] == '+') {
207✔
830
                    url_part.in()[lpc] = ' ';
1✔
831
                }
832
            }
833
            auto query_frag = string_fragment::from_c_str(url_part.in());
15✔
834
            auto remaining = query_frag;
15✔
835

836
            while (true) {
837
                auto split_res
838
                    = remaining.split_when(string_fragment::tag1{'&'});
28✔
839
                auto_mem<char> kv_pair(curl_free);
28✔
840
                auto kv_pair_encoded = split_res.first;
28✔
841
                int out_len = 0;
28✔
842

843
                kv_pair = curl_easy_unescape(CURL_HANDLE,
844
                                             kv_pair_encoded.data(),
845
                                             kv_pair_encoded.length(),
846
                                             &out_len);
28✔
847

848
                auto kv_pair_frag
849
                    = string_fragment::from_bytes(kv_pair.in(), out_len);
28✔
850
                auto eq_index_opt = kv_pair_frag.find('=');
28✔
851
                if (eq_index_opt) {
28✔
852
                    auto key = kv_pair_frag.sub_range(0, eq_index_opt.value());
12✔
853
                    auto val = kv_pair_frag.substr(eq_index_opt.value() + 1);
12✔
854

855
                    auto key_utf_res = is_utf8(key);
12✔
856
                    auto val_utf_res = is_utf8(val);
12✔
857
                    if (key_utf_res.is_valid()) {
12✔
858
                        auto key_str = key.to_string();
12✔
859

860
                        if (seen_keys.count(key_str) == 0) {
12✔
861
                            seen_keys.emplace(key_str);
12✔
862
                            query_map.gen(key);
12✔
863
                            if (val_utf_res.is_valid()) {
12✔
864
                                query_map.gen(val);
12✔
865
                            } else {
UNCOV
866
                                auto eq = strchr(kv_pair_encoded.data(), '=');
×
UNCOV
867
                                query_map.gen(
×
UNCOV
868
                                    string_fragment::from_c_str(eq + 1));
×
869
                            }
870
                        }
871
                    } else {
12✔
872
                    }
873
                } else {
874
                    auto val_str = split_res.first.to_string();
16✔
875

876
                    if (seen_keys.count(val_str) == 0) {
16✔
877
                        seen_keys.insert(val_str);
16✔
878
                        query_map.gen(split_res.first);
16✔
879
                        query_map.gen();
16✔
880
                    }
881
                }
16✔
882

883
                if (split_res.second.empty()) {
28✔
884
                    break;
15✔
885
                }
886

887
                remaining = split_res.second;
13✔
888
            }
41✔
889
        } else {
15✔
890
            root.gen("query");
13✔
891
            root.gen();
13✔
892
            root.gen("parameters");
13✔
893
            root.gen();
13✔
894
        }
895
        root.gen("fragment");
28✔
896
        rc = curl_url_get(
28✔
897
            cu, CURLUPART_FRAGMENT, url_part.out(), CURLU_URLDECODE);
898
        if (rc == CURLUE_OK) {
28✔
899
            root.gen(string_fragment::from_c_str(url_part.in()));
3✔
900
        } else {
901
            root.gen();
25✔
902
        }
903
    }
28✔
904

905
    return json_string(gen);
28✔
906
}
29✔
907

908
struct url_parts {
909
    std::optional<std::string> up_scheme;
910
    std::optional<std::string> up_username;
911
    std::optional<std::string> up_password;
912
    std::optional<std::string> up_host;
913
    std::optional<std::string> up_port;
914
    std::optional<std::string> up_path;
915
    std::optional<std::string> up_query;
916
    std::map<std::string, std::optional<std::string>> up_parameters;
917
    std::optional<std::string> up_fragment;
918
};
919

920
const typed_json_path_container<url_parts>&
921
get_url_parts_handlers()
14✔
922
{
923
    static const json_path_container url_params_handlers = {
924
        yajlpp::pattern_property_handler("(?<param>.*)")
14✔
925
            .for_field(&url_parts::up_parameters),
14✔
926
    };
56✔
927

928
    static const typed_json_path_container<url_parts> retval = {
929
        yajlpp::property_handler("scheme").for_field(&url_parts::up_scheme),
28✔
930
        yajlpp::property_handler("username").for_field(&url_parts::up_username),
28✔
931
        yajlpp::property_handler("password").for_field(&url_parts::up_password),
28✔
932
        yajlpp::property_handler("host").for_field(&url_parts::up_host),
28✔
933
        yajlpp::property_handler("port").for_field(&url_parts::up_port),
28✔
934
        yajlpp::property_handler("path").for_field(&url_parts::up_path),
28✔
935
        yajlpp::property_handler("query").for_field(&url_parts::up_query),
28✔
936
        yajlpp::property_handler("parameters")
28✔
937
            .with_children(url_params_handlers),
14✔
938
        yajlpp::property_handler("fragment").for_field(&url_parts::up_fragment),
28✔
939
    };
168✔
940

941
    return retval;
14✔
942
}
168✔
943

944
auto_mem<char>
945
sql_unparse_url(string_fragment in)
14✔
946
{
947
    static auto* CURL_HANDLE = get_curl_easy();
14✔
948
    static const intern_string_t SRC = intern_string::lookup("arg");
42✔
949

950
    auto parse_res = get_url_parts_handlers().parser_for(SRC).of(in);
14✔
951
    if (parse_res.isErr()) {
14✔
952
        throw parse_res.unwrapErr()[0];
3✔
953
    }
954

955
    auto up = parse_res.unwrap();
11✔
956
    auto_mem<CURLU> cu(curl_url_cleanup);
11✔
957
    cu = curl_url();
11✔
958

959
    if (up.up_scheme) {
11✔
960
        curl_url_set(
10✔
961
            cu, CURLUPART_SCHEME, up.up_scheme->c_str(), CURLU_URLENCODE);
962
    }
963
    if (up.up_username) {
11✔
964
        curl_url_set(
×
965
            cu, CURLUPART_USER, up.up_username->c_str(), CURLU_URLENCODE);
966
    }
967
    if (up.up_password) {
11✔
UNCOV
968
        curl_url_set(
×
969
            cu, CURLUPART_PASSWORD, up.up_password->c_str(), CURLU_URLENCODE);
970
    }
971
    if (up.up_host) {
11✔
972
        curl_url_set(cu, CURLUPART_HOST, up.up_host->c_str(), CURLU_URLENCODE);
10✔
973
    }
974
    if (up.up_port) {
11✔
UNCOV
975
        curl_url_set(cu, CURLUPART_PORT, up.up_port->c_str(), 0);
×
976
    }
977
    if (up.up_path) {
11✔
978
        curl_url_set(cu, CURLUPART_PATH, up.up_path->c_str(), CURLU_URLENCODE);
4✔
979
    }
980
    if (up.up_query) {
11✔
981
        curl_url_set(cu, CURLUPART_QUERY, up.up_query->c_str(), 0);
4✔
982
    } else if (!up.up_parameters.empty()) {
7✔
UNCOV
983
        for (const auto& pair : up.up_parameters) {
×
UNCOV
984
            auto_mem<char> key(curl_free);
×
985
            auto_mem<char> value(curl_free);
×
986
            std::string qparam;
×
987

988
            key = curl_easy_escape(
989
                CURL_HANDLE, pair.first.c_str(), pair.first.length());
×
UNCOV
990
            if (pair.second) {
×
991
                value = curl_easy_escape(
UNCOV
992
                    CURL_HANDLE, pair.second->c_str(), pair.second->length());
×
UNCOV
993
                qparam = fmt::format(FMT_STRING("{}={}"), key.in(), value.in());
×
994
            } else {
UNCOV
995
                qparam = key.in();
×
996
            }
997

UNCOV
998
            curl_url_set(
×
999
                cu, CURLUPART_QUERY, qparam.c_str(), CURLU_APPENDQUERY);
1000
        }
1001
    }
1002
    if (up.up_fragment) {
11✔
1003
        curl_url_set(
1✔
1004
            cu, CURLUPART_FRAGMENT, up.up_fragment->c_str(), CURLU_URLENCODE);
1005
    }
1006

1007
    auto_mem<char> retval(curl_free);
11✔
1008

1009
    curl_url_get(cu, CURLUPART_URL, retval.out(), 0);
11✔
1010
    return retval;
22✔
1011
}
14✔
1012

1013
}  // namespace
1014

1015
json_string
1016
extract(const char* str)
18✔
1017
{
1018
    data_scanner ds(str);
18✔
1019
    data_parser dp(&ds);
18✔
1020

1021
    dp.parse();
18✔
1022
    // dp.print(stderr, dp.dp_pairs);
1023

1024
    yajlpp_gen gen;
18✔
1025
    yajl_gen_config(gen, yajl_gen_beautify, false);
18✔
1026

1027
    elements_to_json(gen, dp, &dp.dp_pairs);
18✔
1028

1029
    return json_string(gen);
36✔
1030
}
18✔
1031

1032
static std::string
1033
sql_humanize_id(string_fragment id)
8✔
1034
{
1035
    auto& vc = view_colors::singleton();
8✔
1036
    auto attrs = vc.attrs_for_ident(id.data(), id.length());
8✔
1037

1038
    return fmt::format(FMT_STRING("\x1b[38;5;{}m{}\x1b[0m"),
16✔
1039
                       // XXX attrs.ta_fg_color.value_or(COLOR_CYAN),
1040
                       (int8_t) ansi_color::cyan,
8✔
1041
                       id);
8✔
1042
}
1043

1044
static std::string
1045
sql_pretty_print(string_fragment in)
7✔
1046
{
1047
    data_scanner ds(in);
7✔
1048
    pretty_printer pp(&ds, {});
7✔
1049
    attr_line_t retval;
7✔
1050

1051
    pp.append_to(retval);
7✔
1052

1053
    return std::move(retval.get_string());
14✔
1054
}
7✔
1055

1056
int
1057
string_extension_functions(struct FuncDef** basic_funcs,
2,069✔
1058
                           struct FuncDefAgg** agg_funcs)
1059
{
1060
    static struct FuncDef string_funcs[] = {
1061
        sqlite_func_adapter<decltype(&regexp), regexp>::builder(
UNCOV
1062
            help_text("regexp", "Test if a string matches a regular expression")
×
1063
                .sql_function()
1,272✔
1064
                .with_parameter({"re", "The regular expression to use"})
2,544✔
1065
                .with_parameter({
2,544✔
1066
                    "str",
1067
                    "The string to test against the regular expression",
1068
                })),
1069

1070
        sqlite_func_adapter<decltype(&regexp_match), regexp_match>::builder(
2,544✔
UNCOV
1071
            help_text("regexp_match",
×
1072
                      "Match a string against a regular expression and return "
1073
                      "the capture groups as JSON.")
1074
                .sql_function()
1,272✔
1075
                .with_prql_path({"text", "regexp_match"})
1,272✔
1076
                .with_parameter({"re", "The regular expression to use"})
2,544✔
1077
                .with_parameter({
2,544✔
1078
                    "str",
1079
                    "The string to test against the regular expression",
1080
                })
1081
                .with_tags({"string", "regex"})
1,272✔
1082
                .with_example({
1,272✔
1083
                    "To capture the digits from the string '123'",
1084
                    "SELECT regexp_match('(\\d+)', '123')",
1085
                })
1086
                .with_example({
1,272✔
1087
                    "To capture a number and word into a JSON object with the "
1088
                    "properties 'col_0' and 'col_1'",
1089
                    "SELECT regexp_match('(\\d+) (\\w+)', '123 four')",
1090
                })
1091
                .with_example({
2,544✔
1092
                    "To capture a number and word into a JSON object with the "
1093
                    "named properties 'num' and 'str'",
1094
                    "SELECT regexp_match('(?<num>\\d+) (?<str>\\w+)', '123 "
1095
                    "four')",
1096
                }))
1097
            .with_result_subtype(),
1098

1099
        sqlite_func_adapter<decltype(&regexp_replace), regexp_replace>::builder(
UNCOV
1100
            help_text("regexp_replace",
×
1101
                      "Replace the parts of a string that match a regular "
1102
                      "expression.")
1103
                .sql_function()
1,272✔
1104
                .with_prql_path({"text", "regexp_replace"})
1,272✔
1105
                .with_parameter(
2,544✔
1106
                    {"str", "The string to perform replacements on"})
1107
                .with_parameter({"re", "The regular expression to match"})
2,544✔
1108
                .with_parameter({
2,544✔
1109
                    "repl",
1110
                    "The replacement string.  "
1111
                    "You can reference capture groups with a "
1112
                    "backslash followed by the number of the "
1113
                    "group, starting with 1.",
1114
                })
1115
                .with_tags({"string", "regex"})
1,272✔
1116
                .with_example({
1,272✔
1117
                    "To replace the word at the start of the string "
1118
                    "'Hello, World!' with 'Goodbye'",
1119
                    "SELECT regexp_replace('Hello, World!', "
1120
                    "'^(\\w+)', 'Goodbye')",
1121
                })
1122
                .with_example({
2,544✔
1123
                    "To wrap alphanumeric words with angle brackets",
1124
                    "SELECT regexp_replace('123 abc', '(\\w+)', '<\\1>')",
1125
                })),
1126

1127
        sqlite_func_adapter<decltype(&sql_humanize_file_size),
1128
                            sql_humanize_file_size>::
UNCOV
1129
            builder(help_text(
×
1130
                        "humanize_file_size",
1131
                        "Format the given file size as a human-friendly string")
1132
                        .sql_function()
1,272✔
1133
                        .with_prql_path({"humanize", "file_size"})
1,272✔
1134
                        .with_parameter({"value", "The file size to format"})
2,544✔
1135
                        .with_tags({"string"})
1,272✔
1136
                        .with_example({
2,544✔
1137
                            "To format an amount",
1138
                            "SELECT humanize_file_size(10 * 1024 * 1024)",
1139
                        })),
1140

1141
        sqlite_func_adapter<decltype(&sql_humanize_id), sql_humanize_id>::
UNCOV
1142
            builder(help_text("humanize_id",
×
1143
                              "Colorize the given ID using ANSI escape codes.")
1144
                        .sql_function()
1,272✔
1145
                        .with_prql_path({"humanize", "id"})
1,272✔
1146
                        .with_parameter({"id", "The identifier to color"})
2,544✔
1147
                        .with_tags({"string"})
1,272✔
1148
                        .with_example({
2,544✔
1149
                            "To colorize the ID 'cluster1'",
1150
                            "SELECT humanize_id('cluster1')",
1151
                        })),
1152

1153
        sqlite_func_adapter<decltype(&humanize::sparkline),
1154
                            humanize::sparkline>::
1155
            builder(
UNCOV
1156
                help_text("sparkline",
×
1157
                          "Function used to generate a sparkline bar chart.  "
1158
                          "The non-aggregate version converts a single numeric "
1159
                          "value on a range to a bar chart character.  The "
1160
                          "aggregate version returns a string with a bar "
1161
                          "character for every numeric input")
1162
                    .sql_function()
1,272✔
1163
                    .with_prql_path({"text", "sparkline"})
1,272✔
1164
                    .with_parameter({"value", "The numeric value to convert"})
2,544✔
1165
                    .with_parameter(help_text("bound_a",
3,816✔
1166
                                              "One bound of the numeric "
1167
                                              "range.  Order does not matter: "
1168
                                              "the smaller of bound_a and "
1169
                                              "bound_b is the floor, the "
1170
                                              "larger is the ceiling.  "
1171
                                              "Defaults to 100; the aggregate "
1172
                                              "version uses the largest input "
1173
                                              "as the ceiling.")
1174
                                        .optional())
1,272✔
1175
                    .with_parameter(help_text("bound_b",
3,816✔
1176
                                              "The other bound of the "
1177
                                              "numeric range.  Defaults to 0.")
1178
                                        .optional())
1,272✔
1179
                    .with_tags({"string"})
1,272✔
1180
                    .with_example({
1,272✔
1181
                        "To get the unicode block element for the "
1182
                        "value 32 in the "
1183
                        "range of 0-128",
1184
                        "SELECT sparkline(32, 128)",
1185
                    })
1186
                    .with_example({
2,544✔
1187
                        "To chart the values in a JSON array",
1188
                        "SELECT sparkline(value) FROM json_each('[0, 1, 2, 3, "
1189
                        "4, 5, 6, 7, 8]')",
1190
                    })),
1191

1192
        sqlite_func_adapter<decltype(&sql_anonymize), sql_anonymize>::builder(
UNCOV
1193
            help_text("anonymize",
×
1194
                      "Replace identifying information with random values.")
1195
                .sql_function()
1,272✔
1196
                .with_prql_path({"text", "anonymize"})
1,272✔
1197
                .with_parameter({"value", "The text to anonymize"})
2,544✔
1198
                .with_tags({"string"})
1,272✔
1199
                .with_example({
2,544✔
1200
                    "To anonymize an IP address",
1201
                    "SELECT anonymize('Hello, 192.168.1.2')",
1202
                })),
1203

1204
        sqlite_func_adapter<decltype(&extract), extract>::builder(
2,544✔
UNCOV
1205
            help_text("extract",
×
1206
                      "Automatically Parse and extract data from a string")
1207
                .sql_function()
1,272✔
1208
                .with_prql_path({"text", "discover"})
1,272✔
1209
                .with_parameter({"str", "The string to parse"})
2,544✔
1210
                .with_tags({"string"})
1,272✔
1211
                .with_example({
1,272✔
1212
                    "To extract key/value pairs from a string",
1213
                    "SELECT extract('foo=1 bar=2 name=\"Rolo Tomassi\"')",
1214
                })
1215
                .with_example({
2,544✔
1216
                    "To extract columnar data from a string",
1217
                    "SELECT extract('1.0 abc 2.0')",
1218
                }))
1219
            .with_result_subtype(),
1220

1221
        sqlite_func_adapter<decltype(&logfmt2json), logfmt2json>::builder(
2,544✔
1222
            help_text("logfmt2json",
1,272✔
1223
                      "Convert a logfmt-encoded string into JSON")
1224
                .sql_function()
1,272✔
1225
                .with_prql_path({"logfmt", "to_json"})
1,272✔
1226
                .with_parameter({"str", "The logfmt message to parse"})
2,544✔
1227
                .with_tags({"string"})
1,272✔
1228
                .with_example({
2,544✔
1229
                    "To extract key/value pairs from a log message",
1230
                    "SELECT logfmt2json('foo=1 bar=2 name=\"Rolo Tomassi\"')",
1231
                }))
1232
            .with_result_subtype(),
1233

1234
        sqlite_func_adapter<
1235
            decltype(static_cast<bool (*)(const char*, const char*)>(
1236
                &startswith)),
1237
            startswith>::
UNCOV
1238
            builder(help_text("startswith",
×
1239
                              "Test if a string begins with the given prefix")
1240
                        .sql_function()
1,272✔
1241
                        .with_parameter({"str", "The string to test"})
2,544✔
1242
                        .with_parameter(
2,544✔
1243
                            {"prefix", "The prefix to check in the string"})
1244
                        .with_tags({"string"})
1,272✔
1245
                        .with_example({
1,272✔
1246
                            "To test if the string 'foobar' starts with 'foo'",
1247
                            "SELECT startswith('foobar', 'foo')",
1248
                        })
1249
                        .with_example({
2,544✔
1250
                            "To test if the string 'foobar' starts with 'bar'",
1251
                            "SELECT startswith('foobar', 'bar')",
1252
                        })),
1253

1254
        sqlite_func_adapter<decltype(static_cast<bool (*)(
1255
                                         const char*, const char*)>(&endswith)),
1256
                            endswith>::
1257
            builder(
UNCOV
1258
                help_text("endswith",
×
1259
                          "Test if a string ends with the given suffix")
1260
                    .sql_function()
1,272✔
1261
                    .with_parameter({"str", "The string to test"})
2,544✔
1262
                    .with_parameter(
2,544✔
1263
                        {"suffix", "The suffix to check in the string"})
1264
                    .with_tags({"string"})
1,272✔
1265
                    .with_example({
1,272✔
1266
                        "To test if the string 'notbad.jpg' ends with '.jpg'",
1267
                        "SELECT endswith('notbad.jpg', '.jpg')",
1268
                    })
1269
                    .with_example({
2,544✔
1270
                        "To test if the string 'notbad.png' starts with '.jpg'",
1271
                        "SELECT endswith('notbad.png', '.jpg')",
1272
                    })),
1273

1274
        sqlite_func_adapter<decltype(&sql_fuzzy_match), sql_fuzzy_match>::
UNCOV
1275
            builder(help_text(
×
1276
                        "fuzzy_match",
1277
                        "Perform a fuzzy match of a pattern against a "
1278
                        "string and return a score or NULL if the pattern was "
1279
                        "not matched")
1280
                        .sql_function()
1,272✔
1281
                        .with_parameter(help_text(
2,544✔
1282
                            "pattern", "The pattern to look for in the string"))
1283
                        .with_parameter(
1,272✔
1284
                            help_text("str", "The string to match against"))
2,544✔
1285
                        .with_tags({"string"})
1,272✔
1286
                        .with_example({
2,544✔
1287
                            "To match the pattern 'fo' against 'filter-out'",
1288
                            "SELECT fuzzy_match('fo', 'filter-out')",
1289
                        })),
1290

1291
        sqlite_func_adapter<decltype(&spooky_hash), spooky_hash>::builder(
UNCOV
1292
            help_text("spooky_hash",
×
1293
                      "Compute the hash value for the given arguments.")
1294
                .sql_function()
1,272✔
1295
                .with_parameter(
1,272✔
1296
                    help_text("str", "The string to hash").one_or_more())
2,544✔
1297
                .with_tags({"string"})
1,272✔
1298
                .with_example({
1,272✔
1299
                    "To produce a hash for the string 'Hello, World!'",
1300
                    "SELECT spooky_hash('Hello, World!')",
1301
                })
1302
                .with_example({
1,272✔
1303
                    "To produce a hash for the parameters where one is NULL",
1304
                    "SELECT spooky_hash('Hello, World!', NULL)",
1305
                })
1306
                .with_example({
1,272✔
1307
                    "To produce a hash for the parameters where one "
1308
                    "is an empty string",
1309
                    "SELECT spooky_hash('Hello, World!', '')",
1310
                })
1311
                .with_example({
2,544✔
1312
                    "To produce a hash for the parameters where one "
1313
                    "is a number",
1314
                    "SELECT spooky_hash('Hello, World!', 123)",
1315
                })),
1316

1317
        sqlite_func_adapter<decltype(&sql_gunzip), sql_gunzip>::builder(
UNCOV
1318
            help_text("gunzip", "Decompress a gzip file")
×
1319
                .sql_function()
1,272✔
1320
                .with_parameter(
1,272✔
1321
                    help_text("b", "The blob to decompress").one_or_more())
2,544✔
1322
                .with_tags({"string"})),
2,544✔
1323

1324
        sqlite_func_adapter<decltype(&sql_gzip), sql_gzip>::builder(
UNCOV
1325
            help_text("gzip", "Compress a string into a gzip file")
×
1326
                .sql_function()
1,272✔
1327
                .with_parameter(
1,272✔
1328
                    help_text("value", "The value to compress").one_or_more())
2,544✔
1329
                .with_tags({"string"})),
2,544✔
1330

1331
        sqlite_func_adapter<decltype(&sql_encode), sql_encode>::builder(
UNCOV
1332
            help_text("encode", "Encode the value using the given algorithm")
×
1333
                .sql_function()
1,272✔
1334
                .with_parameter(help_text("value", "The value to encode"))
2,544✔
1335
                .with_parameter(help_text("algorithm",
2,544✔
1336
                                          "One of the following encoding "
1337
                                          "algorithms: base64, hex, uri, html"))
1338
                .with_tags({"string"})
1,272✔
1339
                .with_example({
1,272✔
1340
                    "To base64-encode 'Hello, World!'",
1341
                    "SELECT encode('Hello, World!', 'base64')",
1342
                })
1343
                .with_example({
1,272✔
1344
                    "To hex-encode 'Hello, World!'",
1345
                    "SELECT encode('Hello, World!', 'hex')",
1346
                })
1347
                .with_example({
2,544✔
1348
                    "To URI-encode 'Hello, World!'",
1349
                    "SELECT encode('Hello, World!', 'uri')",
1350
                })),
1351

1352
        sqlite_func_adapter<decltype(&sql_decode), sql_decode>::builder(
UNCOV
1353
            help_text("decode", "Decode the value using the given algorithm")
×
1354
                .sql_function()
1,272✔
1355
                .with_parameter(help_text("value", "The value to decode"))
2,544✔
1356
                .with_parameter(help_text("algorithm",
2,544✔
1357
                                          "One of the following encoding "
1358
                                          "algorithms: base64, hex, uri"))
1359
                .with_tags({"string"})
1,272✔
1360
                .with_example({
2,544✔
1361
                    "To decode the URI-encoded string '%63%75%72%6c'",
1362
                    "SELECT decode('%63%75%72%6c', 'uri')",
1363
                })),
1364

1365
        sqlite_func_adapter<decltype(&sql_parse_url), sql_parse_url>::builder(
2,544✔
UNCOV
1366
            help_text("parse_url",
×
1367
                      "Parse a URL and return the components in a JSON object. "
1368
                      "Limitations: not all URL schemes are supported and "
1369
                      "repeated query parameters are not captured.")
1370
                .sql_function()
1,272✔
1371
                .with_parameter(help_text("url", "The URL to parse"))
2,544✔
1372
                .with_result({
2,544✔
1373
                    "scheme",
1374
                    "The URL's scheme",
1375
                })
1376
                .with_result({
2,544✔
1377
                    "username",
1378
                    "The name of the user specified in the URL",
1379
                })
1380
                .with_result({
2,544✔
1381
                    "password",
1382
                    "The password specified in the URL",
1383
                })
1384
                .with_result({
2,544✔
1385
                    "host",
1386
                    "The host name / IP specified in the URL",
1387
                })
1388
                .with_result({
2,544✔
1389
                    "port",
1390
                    "The port specified in the URL",
1391
                })
1392
                .with_result({
2,544✔
1393
                    "path",
1394
                    "The path specified in the URL",
1395
                })
1396
                .with_result({
2,544✔
1397
                    "query",
1398
                    "The query string in the URL",
1399
                })
1400
                .with_result({
2,544✔
1401
                    "parameters",
1402
                    "An object containing the query parameters",
1403
                })
1404
                .with_result({
2,544✔
1405
                    "fragment",
1406
                    "The fragment specified in the URL",
1407
                })
1408
                .with_tags({"string", "url"})
1,272✔
1409
                .with_example({
1,272✔
1410
                    "To parse the URL "
1411
                    "'https://example.com/search?q=hello%20world'",
1412
                    "SELECT "
1413
                    "parse_url('https://example.com/search?q=hello%20world')",
1414
                })
1415
                .with_example({
2,544✔
1416
                    "To parse the URL "
1417
                    "'https://alice@[fe80::14ff:4ee5:1215:2fb2]'",
1418
                    "SELECT "
1419
                    "parse_url('https://alice@[fe80::14ff:4ee5:1215:2fb2]')",
1420
                }))
1421
            .with_result_subtype(),
1422

1423
        sqlite_func_adapter<decltype(&sql_unparse_url), sql_unparse_url>::
1424
            builder(
UNCOV
1425
                help_text("unparse_url",
×
1426
                          "Convert a JSON object containing the parts of a "
1427
                          "URL into a URL string")
1428
                    .sql_function()
1,272✔
1429
                    .with_parameter(help_text(
2,544✔
1430
                        "obj", "The JSON object containing the URL parts"))
1431
                    .with_tags({"string", "url"})
1,272✔
1432
                    .with_example({
2,544✔
1433
                        "To unparse the object "
1434
                        "'{\"scheme\": \"https\", \"host\": \"example.com\"}'",
1435
                        "SELECT "
1436
                        "unparse_url('{\"scheme\": \"https\", \"host\": "
1437
                        "\"example.com\"}')",
1438
                    })),
1439

1440
        sqlite_func_adapter<decltype(&sql_pretty_print), sql_pretty_print>::
1441
            builder(
UNCOV
1442
                help_text("pretty_print", "Pretty-print the given string")
×
1443
                    .sql_function()
1,272✔
1444
                    .with_prql_path({"text", "pretty"})
1,272✔
1445
                    .with_parameter(help_text("str", "The string to format"))
2,544✔
1446
                    .with_tags({"string"})
1,272✔
1447
                    .with_example({
2,544✔
1448
                        "To pretty-print the string "
1449
                        "'{\"scheme\": \"https\", \"host\": \"example.com\"}'",
1450
                        "SELECT "
1451
                        "pretty_print('{\"scheme\": \"https\", \"host\": "
1452
                        "\"example.com\"}')",
1453
                    })),
1454

1455
        {nullptr},
1456
    };
51,677✔
1457

1458
    static struct FuncDefAgg str_agg_funcs[] = {
1459
        {
1460
            "group_spooky_hash",
1461
            -1,
1462
            SQLITE_UTF8,
1463
            0,
1464
            sql_spooky_hash_step,
1465
            sql_spooky_hash_final,
1466
            help_text("group_spooky_hash",
1,272✔
1467
                      "Compute the hash value for the given arguments")
1468
                .sql_agg_function()
1,272✔
1469
                .with_parameter(
1,272✔
1470
                    help_text("str", "The string to hash").one_or_more())
2,544✔
1471
                .with_tags({"string"})
1,272✔
1472
                .with_example({
2,544✔
1473
                    "To produce a hash of all of the values of 'column1'",
1474
                    "SELECT group_spooky_hash(column1) FROM (VALUES ('abc'), "
1475
                    "('123'))",
1476
                }),
1477
        },
1478

1479
        {
1480
            "sparkline",
1481
            -1,
1482
            SQLITE_UTF8,
1483
            0,
1484
            sparkline_step,
1485
            sparkline_final,
1486
        },
1487

1488
        {nullptr},
1489
    };
3,341✔
1490

1491
    *basic_funcs = string_funcs;
2,069✔
1492
    *agg_funcs = str_agg_funcs;
2,069✔
1493

1494
    return SQLITE_OK;
2,069✔
1495
}
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