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

tstack / lnav / 18230413998-2546

03 Oct 2025 06:20PM UTC coverage: 64.943% (+0.1%) from 64.848%
18230413998-2546

push

github

tstack
[log2src] plumb stack trace

... and a pile of minor fixes

41 of 52 new or added lines in 11 files covered. (78.85%)

2 existing lines in 2 files now uncovered.

46070 of 70939 relevant lines covered (64.94%)

406890.07 hits per line

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

86.64
/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 "pcrepp/pcre2pp.hh"
32
#include "pretty_printer.hh"
33
#include "safe/safe.h"
34
#include "scn/scan.h"
35
#include "sqlite-extension-func.hh"
36
#include "text_anonymizer.hh"
37
#include "view_curses.hh"
38
#include "vtab_module.hh"
39
#include "vtab_module_json.hh"
40
#include "yajl/api/yajl_gen.h"
41
#include "yajlpp/json_op.hh"
42
#include "yajlpp/yajlpp.hh"
43
#include "yajlpp/yajlpp_def.hh"
44

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

49
enum class encode_algo {
50
    base64,
51
    hex,
52
    uri,
53
};
54

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

61
        if (strcasecmp(algo_name, "base64") == 0) {
29✔
62
            return encode_algo::base64;
8✔
63
        }
64
        if (strcasecmp(algo_name, "hex") == 0) {
21✔
65
            return encode_algo::hex;
10✔
66
        }
67
        if (strcasecmp(algo_name, "uri") == 0) {
11✔
68
            return encode_algo::uri;
10✔
69
        }
70

71
        throw from_sqlite_conversion_error("value of 'base64', 'hex', or 'uri'",
72
                                           argi);
1✔
73
    }
74
};
75

76
namespace {
77

78
struct cache_entry {
79
    std::shared_ptr<lnav::pcre2pp::code> re2;
80
    std::shared_ptr<column_namer> cn{
81
        std::make_shared<column_namer>(column_namer::language::JSON)};
82
};
83

84
cache_entry*
85
find_re(string_fragment re)
2,763✔
86
{
87
    using re_cache_t
88
        = std::unordered_map<string_fragment, cache_entry, frag_hasher>;
89
    thread_local re_cache_t cache;
2,763✔
90

91
    auto iter = cache.find(re);
2,763✔
92
    if (iter == cache.end()) {
2,763✔
93
        auto compile_res = lnav::pcre2pp::code::from(re);
2,268✔
94
        if (compile_res.isErr()) {
2,268✔
95
            const static intern_string_t SRC = intern_string::lookup("arg");
3✔
96

97
            throw lnav::console::to_user_message(SRC, compile_res.unwrapErr());
1✔
98
        }
99

100
        cache_entry c;
2,267✔
101

102
        c.re2 = compile_res.unwrap().to_shared();
2,267✔
103
        auto pair = cache.insert(
2,267✔
104
            std::make_pair(string_fragment::from_str(c.re2->get_pattern()), c));
4,534✔
105

106
        for (size_t lpc = 0; lpc < c.re2->get_capture_count(); lpc++) {
2,323✔
107
            c.cn->add_column(string_fragment::from_c_str(
56✔
108
                c.re2->get_name_for_capture(lpc + 1)));
109
        }
110

111
        iter = pair.first;
2,267✔
112
    }
2,268✔
113

114
    return &iter->second;
5,524✔
115
}
116

117
bool
118
regexp(string_fragment re, string_fragment str)
2,220✔
119
{
120
    auto* reobj = find_re(re);
2,220✔
121

122
    return reobj->re2->find_in(str).ignore_error().has_value();
2,220✔
123
}
124

125
mapbox::util::
126
    variant<int64_t, double, const char*, string_fragment, json_string>
127
    regexp_match(string_fragment re, string_fragment str)
517✔
128
{
129
    auto* reobj = find_re(re);
517✔
130
    auto& extractor = *reobj->re2;
516✔
131

132
    if (extractor.get_capture_count() == 0) {
516✔
133
        throw std::runtime_error(
134
            "regular expression does not have any captures");
1✔
135
    }
136

137
    auto md = extractor.create_match_data();
515✔
138
    auto match_res = extractor.capture_from(str).into(md).matches();
515✔
139
    if (match_res.is<lnav::pcre2pp::matcher::not_found>()) {
515✔
140
        return static_cast<const char*>(nullptr);
1✔
141
    }
142
    if (match_res.is<lnav::pcre2pp::matcher::error>()) {
514✔
143
        auto err = match_res.get<lnav::pcre2pp::matcher::error>();
×
144

145
        throw std::runtime_error(err.get_message());
×
146
    }
147

148
    if (extractor.get_capture_count() == 1) {
514✔
149
        auto cap = md[1];
500✔
150

151
        if (!cap) {
500✔
152
            return static_cast<const char*>(nullptr);
×
153
        }
154

155
        auto scan_int_res = scn::scan_int<int64_t>(cap->to_string_view());
500✔
156
        if (scan_int_res) {
500✔
157
            if (scan_int_res->range().empty()) {
15✔
158
                return scan_int_res->value();
15✔
159
            }
160
            auto scan_float_res
161
                = scn::scan_value<double>(cap->to_string_view());
1✔
162
            if (scan_float_res && scan_float_res->range().empty()) {
1✔
163
                return scan_float_res->value();
1✔
164
            }
165
        }
166

167
        return cap.value();
485✔
168
    }
169

170
    yajlpp_gen gen;
14✔
171
    yajl_gen_config(gen, yajl_gen_beautify, false);
14✔
172
    {
173
        yajlpp_map root_map(gen);
14✔
174

175
        for (size_t lpc = 0; lpc < extractor.get_capture_count(); lpc++) {
42✔
176
            const auto& colname = reobj->cn->cn_names[lpc];
28✔
177
            const auto cap = md[lpc + 1];
28✔
178

179
            yajl_gen_pstring(gen, colname.data(), colname.length());
28✔
180

181
            if (!cap) {
28✔
182
                yajl_gen_null(gen);
×
183
            } else {
184
                auto scan_int_res
185
                    = scn::scan_value<int64_t>(cap->to_string_view());
28✔
186
                if (scan_int_res && scan_int_res->range().empty()) {
28✔
187
                    yajl_gen_integer(gen, scan_int_res->value());
11✔
188
                } else {
189
                    auto scan_float_res
190
                        = scn::scan_value<double>(cap->to_string_view());
17✔
191
                    if (scan_float_res && scan_float_res->range().empty()) {
17✔
192
                        yajl_gen_number(gen, cap->data(), cap->length());
1✔
193
                    } else {
194
                        yajl_gen_pstring(gen, cap->data(), cap->length());
16✔
195
                    }
196
                }
197
            }
198
        }
199
    }
14✔
200

201
    return json_string(gen);
14✔
202
#if 0
203
    sqlite3_result_text(ctx, (const char *) buf, len, SQLITE_TRANSIENT);
204
#    ifdef HAVE_SQLITE3_VALUE_SUBTYPE
205
    sqlite3_result_subtype(ctx, JSON_SUBTYPE);
206
#    endif
207
#endif
208
}
515✔
209

210
static json_string
211
logfmt2json(string_fragment line)
6✔
212
{
213
    logfmt::parser p(line);
6✔
214
    yajlpp_gen gen;
6✔
215
    yajl_gen_config(gen, yajl_gen_beautify, false);
6✔
216

217
    {
218
        yajlpp_map root(gen);
6✔
219
        bool done = false;
6✔
220

221
        while (!done) {
31✔
222
            auto pair = p.step();
25✔
223

224
            done = pair.match(
50✔
225
                [](const logfmt::parser::end_of_input& eoi) { return true; },
6✔
226
                [&root, &gen](const logfmt::parser::kvpair& kvp) {
25✔
227
                    root.gen(kvp.first);
19✔
228

229
                    kvp.second.match(
19✔
230
                        [&root](const logfmt::parser::bool_value& bv) {
×
231
                            root.gen(bv.bv_value);
×
232
                        },
×
233
                        [&root](const logfmt::parser::int_value& iv) {
×
234
                            root.gen(iv.iv_value);
12✔
235
                        },
12✔
236
                        [&root](const logfmt::parser::float_value& fv) {
×
237
                            root.gen(fv.fv_value);
1✔
238
                        },
1✔
239
                        [&root, &gen](const logfmt::parser::quoted_value& qv) {
×
240
                            auto_mem<yajl_handle_t> parse_handle(yajl_free);
5✔
241
                            json_ptr jp("");
5✔
242
                            json_op jo(jp);
5✔
243

244
                            jo.jo_ptr_callbacks = json_op::gen_callbacks;
5✔
245
                            jo.jo_ptr_data = gen;
5✔
246
                            parse_handle.reset(yajl_alloc(
5✔
247
                                &json_op::ptr_callbacks, nullptr, &jo));
248

249
                            const auto* json_in
250
                                = (const unsigned char*) qv.qv_value.data();
5✔
251
                            auto json_len = qv.qv_value.length();
5✔
252

253
                            if (yajl_parse(parse_handle.in(), json_in, json_len)
5✔
254
                                    != yajl_status_ok
255
                                || yajl_complete_parse(parse_handle.in())
5✔
256
                                    != yajl_status_ok)
257
                            {
258
                                root.gen(qv.qv_value);
×
259
                            }
260
                        },
5✔
261
                        [&root](const logfmt::parser::unquoted_value& uv) {
19✔
262
                            root.gen(uv.uv_value);
1✔
263
                        });
1✔
264

265
                    return false;
19✔
266
                },
267
                [](const logfmt::parser::error& e) -> bool {
×
268
                    throw sqlite_func_error("Invalid logfmt: {}", e.e_msg);
×
269
                });
270
        }
25✔
271
    }
6✔
272

273
    return json_string(gen);
12✔
274
}
6✔
275

276
std::string
277
regexp_replace(string_fragment str, string_fragment re, const char* repl)
26✔
278
{
279
    auto* reobj = find_re(re);
26✔
280

281
    return reobj->re2->replace(str, repl);
26✔
282
}
283

284
std::optional<int64_t>
285
sql_fuzzy_match(const char* pat, const char* str)
5✔
286
{
287
    if (pat == nullptr) {
5✔
288
        return std::nullopt;
×
289
    }
290

291
    if (pat[0] == '\0') {
5✔
292
        return 1;
×
293
    }
294

295
    int score = 0;
5✔
296

297
    if (!fts::fuzzy_match(pat, str, score)) {
5✔
298
        return std::nullopt;
×
299
    }
300

301
    return score;
5✔
302
}
303

304
string_fragment
305
spooky_hash(const std::vector<const char*>& args)
5,527✔
306
{
307
    thread_local char hash_str_buf[hasher::STRING_SIZE];
308

309
    hasher context;
5,527✔
310
    for (const auto* const arg : args) {
16,576✔
311
        int64_t len = arg != nullptr ? strlen(arg) : 0;
11,049✔
312

313
        context.update((const char*) &len, sizeof(len));
11,049✔
314
        if (arg == nullptr) {
11,049✔
315
            continue;
30✔
316
        }
317
        context.update(arg, len);
11,019✔
318
    }
319
    context.to_string(hash_str_buf);
5,527✔
320

321
    return string_fragment::from_bytes(hash_str_buf, sizeof(hash_str_buf) - 1);
11,054✔
322
}
323

324
void
325
sql_spooky_hash_step(sqlite3_context* context, int argc, sqlite3_value** argv)
10✔
326
{
327
    auto* hasher
328
        = (SpookyHash*) sqlite3_aggregate_context(context, sizeof(SpookyHash));
10✔
329

330
    for (int lpc = 0; lpc < argc; lpc++) {
20✔
331
        const auto* value = sqlite3_value_text(argv[lpc]);
10✔
332
        int64_t len = value != nullptr ? strlen((const char*) value) : 0;
10✔
333

334
        hasher->Update(&len, sizeof(len));
10✔
335
        if (value == nullptr) {
10✔
336
            continue;
×
337
        }
338
        hasher->Update(value, len);
10✔
339
    }
340
}
10✔
341

342
void
343
sql_spooky_hash_final(sqlite3_context* context)
5✔
344
{
345
    auto* hasher
346
        = (SpookyHash*) sqlite3_aggregate_context(context, sizeof(SpookyHash));
5✔
347

348
    if (hasher == nullptr) {
5✔
349
        sqlite3_result_null(context);
×
350
    } else {
351
        byte_array<2, uint64> hash;
5✔
352

353
        hasher->Final(hash.out(0), hash.out(1));
5✔
354

355
        auto hex = hash.to_string();
5✔
356
        sqlite3_result_text(
5✔
357
            context, hex.c_str(), hex.length(), SQLITE_TRANSIENT);
5✔
358
    }
5✔
359
}
5✔
360

361
struct sparkline_context {
362
    bool sc_initialized{true};
363
    double sc_max_value{0.0};
364
    std::vector<double> sc_values;
365
};
366

367
void
368
sparkline_step(sqlite3_context* context, int argc, sqlite3_value** argv)
50✔
369
{
370
    auto* sc = (sparkline_context*) sqlite3_aggregate_context(
50✔
371
        context, sizeof(sparkline_context));
372

373
    if (!sc->sc_initialized) {
50✔
374
        new (sc) sparkline_context;
10✔
375
    }
376

377
    if (argc == 0) {
50✔
378
        return;
×
379
    }
380

381
    sc->sc_values.push_back(sqlite3_value_double(argv[0]));
50✔
382
    sc->sc_max_value = std::max(sc->sc_max_value, sc->sc_values.back());
50✔
383

384
    if (argc >= 2) {
50✔
385
        sc->sc_max_value
386
            = std::max(sc->sc_max_value, sqlite3_value_double(argv[1]));
5✔
387
    }
388
}
389

390
void
391
sparkline_final(sqlite3_context* context)
10✔
392
{
393
    auto* sc = (sparkline_context*) sqlite3_aggregate_context(
10✔
394
        context, sizeof(sparkline_context));
395

396
    if (!sc->sc_initialized) {
10✔
397
        sqlite3_result_text(context, "", 0, SQLITE_STATIC);
×
398
        return;
×
399
    }
400

401
    auto retval = auto_mem<char>::malloc(sc->sc_values.size() * 3 + 1);
10✔
402
    auto* start = retval.in();
10✔
403

404
    for (const auto& value : sc->sc_values) {
60✔
405
        auto bar = humanize::sparkline(value, sc->sc_max_value);
50✔
406

407
        strcpy(start, bar.c_str());
50✔
408
        start += bar.length();
50✔
409
    }
50✔
410
    *start = '\0';
10✔
411

412
    to_sqlite(context, std::move(retval));
10✔
413

414
    sc->~sparkline_context();
10✔
415
}
10✔
416

417
std::optional<mapbox::util::variant<blob_auto_buffer, sqlite3_int64, double>>
418
sql_gunzip(sqlite3_value* val)
2✔
419
{
420
    switch (sqlite3_value_type(val)) {
2✔
421
        case SQLITE3_TEXT:
2✔
422
        case SQLITE_BLOB: {
423
            const auto* buffer = sqlite3_value_blob(val);
2✔
424
            auto len = sqlite3_value_bytes(val);
2✔
425

426
            if (!lnav::gzip::is_gzipped((const char*) buffer, len)) {
2✔
427
                return blob_auto_buffer{
×
428
                    auto_buffer::from((const char*) buffer, len)};
×
429
            }
430

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

433
            if (res.isErr()) {
2✔
434
                throw sqlite_func_error("unable to uncompress -- {}",
435
                                        res.unwrapErr());
×
436
            }
437

438
            return blob_auto_buffer{res.unwrap()};
2✔
439
        }
2✔
440
        case SQLITE_INTEGER:
×
441
            return sqlite3_value_int64(val);
×
442
        case SQLITE_FLOAT:
×
443
            return sqlite3_value_double(val);
×
444
    }
445

446
    return std::nullopt;
×
447
}
448

449
std::optional<blob_auto_buffer>
450
sql_gzip(sqlite3_value* val)
3✔
451
{
452
    switch (sqlite3_value_type(val)) {
3✔
453
        case SQLITE3_TEXT:
1✔
454
        case SQLITE_BLOB: {
455
            const auto* buffer = sqlite3_value_blob(val);
1✔
456
            auto len = sqlite3_value_bytes(val);
1✔
457
            auto res = lnav::gzip::compress(buffer, len);
1✔
458

459
            if (res.isErr()) {
1✔
460
                throw sqlite_func_error("unable to compress -- {}",
461
                                        res.unwrapErr());
×
462
            }
463

464
            return blob_auto_buffer{res.unwrap()};
1✔
465
        }
1✔
466
        case SQLITE_INTEGER:
2✔
467
        case SQLITE_FLOAT: {
468
            const auto* buffer = sqlite3_value_text(val);
2✔
469
            auto res
470
                = lnav::gzip::compress(buffer, strlen((const char*) buffer));
2✔
471

472
            if (res.isErr()) {
2✔
473
                throw sqlite_func_error("unable to compress -- {}",
474
                                        res.unwrapErr());
×
475
            }
476

477
            return blob_auto_buffer{res.unwrap()};
2✔
478
        }
2✔
479
    }
480

481
    return std::nullopt;
×
482
}
483

484
#if defined(HAVE_LIBCURL)
485
static CURL*
486
get_curl_easy()
45✔
487
{
488
    static struct curl_wrapper {
489
        curl_wrapper() { this->cw_value = curl_easy_init(); }
26✔
490

491
        auto_mem<CURL> cw_value{curl_easy_cleanup};
492
    } retval;
45✔
493

494
    return retval.cw_value.in();
45✔
495
}
496
#endif
497

498
mapbox::util::variant<text_auto_buffer, auto_mem<char>, null_value_t>
499
sql_encode(sqlite3_value* value, encode_algo algo)
19✔
500
{
501
    switch (sqlite3_value_type(value)) {
19✔
502
        case SQLITE_NULL: {
1✔
503
            return null_value_t{};
1✔
504
        }
505
        case SQLITE_BLOB: {
1✔
506
            const auto* blob
507
                = static_cast<const char*>(sqlite3_value_blob(value));
1✔
508
            auto blob_len = sqlite3_value_bytes(value);
1✔
509

510
            switch (algo) {
1✔
511
                case encode_algo::base64: {
1✔
512
                    auto buf = auto_buffer::alloc((blob_len * 5) / 3);
1✔
513
                    auto outlen = buf.capacity();
1✔
514

515
                    base64_encode(blob, blob_len, buf.in(), &outlen, 0);
1✔
516
                    buf.resize(outlen);
1✔
517
                    return text_auto_buffer{std::move(buf)};
1✔
518
                }
1✔
519
                case encode_algo::hex: {
×
520
                    auto buf = auto_buffer::alloc(blob_len * 2 + 1);
×
521

522
                    for (int lpc = 0; lpc < blob_len; lpc++) {
×
523
                        fmt::format_to(std::back_inserter(buf),
×
NEW
524
                                       FMT_STRING("{:02x}"),
×
525
                                       blob[lpc]);
×
526
                    }
527

528
                    return text_auto_buffer{std::move(buf)};
×
529
                }
530
#if defined(HAVE_LIBCURL)
531
                case encode_algo::uri: {
×
532
                    auto_mem<char> retval(curl_free);
×
533

534
                    retval = curl_easy_escape(get_curl_easy(), blob, blob_len);
×
535
                    return std::move(retval);
×
536
                }
537
#endif
538
            }
539
        }
540
        default: {
541
            const auto* text = (const char*) sqlite3_value_text(value);
17✔
542
            auto text_len = sqlite3_value_bytes(value);
17✔
543

544
            switch (algo) {
17✔
545
                case encode_algo::base64: {
5✔
546
                    auto buf = auto_buffer::alloc((text_len * 5) / 3);
5✔
547
                    size_t outlen = buf.capacity();
5✔
548

549
                    base64_encode(text, text_len, buf.in(), &outlen, 0);
5✔
550
                    buf.resize(outlen);
5✔
551
                    return text_auto_buffer{std::move(buf)};
5✔
552
                }
5✔
553
                case encode_algo::hex: {
7✔
554
                    auto buf = auto_buffer::alloc(text_len * 2 + 1);
7✔
555

556
                    for (int lpc = 0; lpc < text_len; lpc++) {
88✔
557
                        fmt::format_to(std::back_inserter(buf),
81✔
558
                                       FMT_STRING("{:02x}"),
243✔
559
                                       text[lpc]);
81✔
560
                    }
561

562
                    return text_auto_buffer{std::move(buf)};
7✔
563
                }
7✔
564
#if defined(HAVE_LIBCURL)
565
                case encode_algo::uri: {
5✔
566
                    auto_mem<char> retval(curl_free);
5✔
567

568
                    retval = curl_easy_escape(get_curl_easy(), text, text_len);
5✔
569
                    return std::move(retval);
5✔
570
                }
5✔
571
#endif
572
            }
573
        }
574
    }
575
    ensure(false);
×
576
}
577

578
mapbox::util::variant<blob_auto_buffer, auto_mem<char>>
579
sql_decode(string_fragment str, encode_algo algo)
9✔
580
{
581
    switch (algo) {
9✔
582
        case encode_algo::base64: {
1✔
583
            auto buf = auto_buffer::alloc(str.length());
1✔
584
            auto outlen = buf.capacity();
1✔
585
            base64_decode(str.data(), str.length(), buf.in(), &outlen, 0);
1✔
586
            buf.resize(outlen);
1✔
587

588
            return blob_auto_buffer{std::move(buf)};
1✔
589
        }
1✔
590
        case encode_algo::hex: {
3✔
591
            auto buf = auto_buffer::alloc(str.length() / 2);
3✔
592
            auto sv = str.to_string_view();
3✔
593

594
            if (sv.size() % 2 != 0) {
3✔
595
                throw sqlite_func_error(
596
                    "hex input is not a multiple of two characters");
2✔
597
            }
598
            while (sv.size() >= 2) {
16✔
599
                auto scan_res = scn::scan<uint8_t>(sv.substr(0, 2), "{:2x}");
15✔
600
                if (!scan_res) {
15✔
601
                    throw sqlite_func_error(
602
                        "invalid hex input at: {}",
603
                        std::distance(str.begin(), sv.begin()));
3✔
604
                }
605
                auto value = scan_res->value();
14✔
606
                buf.push_back((char) (value & 0xff));
14✔
607
                sv = sv.substr(2);
14✔
608
            }
609

610
            return blob_auto_buffer{std::move(buf)};
1✔
611
        }
3✔
612
#if defined(HAVE_LIBCURL)
613
        case encode_algo::uri: {
5✔
614
            auto_mem<char> retval(curl_free);
5✔
615

616
            retval = curl_easy_unescape(
617
                get_curl_easy(), str.data(), str.length(), nullptr);
5✔
618

619
            return std::move(retval);
5✔
620
        }
5✔
621
#endif
622
    }
623
    ensure(false);
×
624
}
625

626
std::string
627
sql_humanize_file_size(file_ssize_t value)
279✔
628
{
629
    return humanize::file_size(value, humanize::alignment::columnar);
279✔
630
}
631

632
std::string
633
sql_anonymize(string_fragment frag)
202✔
634
{
635
    static safe::Safe<lnav::text_anonymizer> ta;
202✔
636

637
    return ta.writeAccess()->next(frag);
202✔
638
}
639

640
#if !CURL_AT_LEAST_VERSION(7, 80, 0)
641
extern "C"
642
{
643
const char* curl_url_strerror(CURLUcode error);
644
}
645
#endif
646

647
json_string
648
sql_parse_url(std::string url)
27✔
649
{
650
    static auto* CURL_HANDLE = get_curl_easy();
27✔
651

652
    auto_mem<CURLU> cu(curl_url_cleanup);
27✔
653
    cu = curl_url();
27✔
654

655
    auto rc = curl_url_set(
27✔
656
        cu, CURLUPART_URL, url.c_str(), CURLU_NON_SUPPORT_SCHEME);
657
    if (rc != CURLUE_OK) {
27✔
658
        auto_mem<char> url_part(curl_free);
1✔
659
        yajlpp_gen gen;
1✔
660
        yajl_gen_config(gen, yajl_gen_beautify, false);
1✔
661

662
        {
663
            yajlpp_map root(gen);
1✔
664
            root.gen("error");
1✔
665
            root.gen("invalid-url");
1✔
666
            root.gen("url");
1✔
667
            root.gen(url);
1✔
668
            root.gen("reason");
1✔
669
            root.gen(curl_url_strerror(rc));
1✔
670
        }
1✔
671

672
        return json_string(gen);
1✔
673
    }
1✔
674

675
    auto_mem<char> url_part(curl_free);
26✔
676
    yajlpp_gen gen;
26✔
677
    yajl_gen_config(gen, yajl_gen_beautify, false);
26✔
678

679
    {
680
        yajlpp_map root(gen);
26✔
681

682
        root.gen("scheme");
26✔
683
        rc = curl_url_get(cu, CURLUPART_SCHEME, url_part.out(), 0);
26✔
684
        if (rc == CURLUE_OK) {
26✔
685
            root.gen(string_fragment::from_c_str(url_part.in()));
26✔
686
        } else {
687
            root.gen();
×
688
        }
689
        root.gen("username");
26✔
690
        rc = curl_url_get(cu, CURLUPART_USER, url_part.out(), CURLU_URLDECODE);
26✔
691
        if (rc == CURLUE_OK) {
26✔
692
            root.gen(string_fragment::from_c_str(url_part.in()));
5✔
693
        } else {
694
            root.gen();
21✔
695
        }
696
        root.gen("password");
26✔
697
        rc = curl_url_get(
26✔
698
            cu, CURLUPART_PASSWORD, url_part.out(), CURLU_URLDECODE);
699
        if (rc == CURLUE_OK) {
26✔
700
            root.gen(string_fragment::from_c_str(url_part.in()));
×
701
        } else {
702
            root.gen();
26✔
703
        }
704
        root.gen("host");
26✔
705
        rc = curl_url_get(cu, CURLUPART_HOST, url_part.out(), CURLU_URLDECODE);
26✔
706
        if (rc == CURLUE_OK) {
26✔
707
            root.gen(string_fragment::from_c_str(url_part.in()));
26✔
708
        } else {
709
            root.gen();
×
710
        }
711
        root.gen("port");
26✔
712
        rc = curl_url_get(cu, CURLUPART_PORT, url_part.out(), 0);
26✔
713
        if (rc == CURLUE_OK) {
26✔
714
            root.gen(string_fragment::from_c_str(url_part.in()));
×
715
        } else {
716
            root.gen();
26✔
717
        }
718
        root.gen("path");
26✔
719
        rc = curl_url_get(cu, CURLUPART_PATH, url_part.out(), CURLU_URLDECODE);
26✔
720
        if (rc == CURLUE_OK) {
26✔
721
            auto path_frag = string_fragment::from_c_str(url_part.in());
26✔
722
            auto path_utf_res = is_utf8(path_frag);
26✔
723
            if (path_utf_res.is_valid()) {
26✔
724
                root.gen(path_frag);
26✔
725
            } else {
726
                rc = curl_url_get(cu, CURLUPART_PATH, url_part.out(), 0);
×
727
                if (rc == CURLUE_OK) {
×
728
                    root.gen(string_fragment::from_c_str(url_part.in()));
×
729
                } else {
730
                    root.gen();
×
731
                }
732
            }
733
        } else {
734
            root.gen();
×
735
        }
736
        rc = curl_url_get(cu, CURLUPART_QUERY, url_part.out(), 0);
26✔
737
        if (rc == CURLUE_OK) {
26✔
738
            root.gen("query");
14✔
739
            root.gen(string_fragment::from_c_str(url_part.in()));
14✔
740

741
            root.gen("parameters");
14✔
742
            robin_hood::unordered_set<std::string> seen_keys;
14✔
743
            yajlpp_map query_map(gen);
14✔
744

745
            for (size_t lpc = 0; url_part.in()[lpc]; lpc++) {
206✔
746
                if (url_part.in()[lpc] == '+') {
192✔
747
                    url_part.in()[lpc] = ' ';
1✔
748
                }
749
            }
750
            auto query_frag = string_fragment::from_c_str(url_part.in());
14✔
751
            auto remaining = query_frag;
14✔
752

753
            while (true) {
754
                auto split_res
755
                    = remaining.split_when(string_fragment::tag1{'&'});
27✔
756
                auto_mem<char> kv_pair(curl_free);
27✔
757
                auto kv_pair_encoded = split_res.first;
27✔
758
                int out_len = 0;
27✔
759

760
                kv_pair = curl_easy_unescape(CURL_HANDLE,
761
                                             kv_pair_encoded.data(),
762
                                             kv_pair_encoded.length(),
763
                                             &out_len);
27✔
764

765
                auto kv_pair_frag
766
                    = string_fragment::from_bytes(kv_pair.in(), out_len);
27✔
767
                auto eq_index_opt = kv_pair_frag.find('=');
27✔
768
                if (eq_index_opt) {
27✔
769
                    auto key = kv_pair_frag.sub_range(0, eq_index_opt.value());
11✔
770
                    auto val = kv_pair_frag.substr(eq_index_opt.value() + 1);
11✔
771

772
                    auto key_utf_res = is_utf8(key);
11✔
773
                    auto val_utf_res = is_utf8(val);
11✔
774
                    if (key_utf_res.is_valid()) {
11✔
775
                        auto key_str = key.to_string();
11✔
776

777
                        if (seen_keys.count(key_str) == 0) {
11✔
778
                            seen_keys.emplace(key_str);
11✔
779
                            query_map.gen(key);
11✔
780
                            if (val_utf_res.is_valid()) {
11✔
781
                                query_map.gen(val);
11✔
782
                            } else {
783
                                auto eq = strchr(kv_pair_encoded.data(), '=');
×
784
                                query_map.gen(
×
785
                                    string_fragment::from_c_str(eq + 1));
×
786
                            }
787
                        }
788
                    } else {
11✔
789
                    }
790
                } else {
791
                    auto val_str = split_res.first.to_string();
16✔
792

793
                    if (seen_keys.count(val_str) == 0) {
16✔
794
                        seen_keys.insert(val_str);
16✔
795
                        query_map.gen(split_res.first);
16✔
796
                        query_map.gen();
16✔
797
                    }
798
                }
16✔
799

800
                if (split_res.second.empty()) {
27✔
801
                    break;
14✔
802
                }
803

804
                remaining = split_res.second;
13✔
805
            }
40✔
806
        } else {
14✔
807
            root.gen("query");
12✔
808
            root.gen();
12✔
809
            root.gen("parameters");
12✔
810
            root.gen();
12✔
811
        }
812
        root.gen("fragment");
26✔
813
        rc = curl_url_get(
26✔
814
            cu, CURLUPART_FRAGMENT, url_part.out(), CURLU_URLDECODE);
815
        if (rc == CURLUE_OK) {
26✔
816
            root.gen(string_fragment::from_c_str(url_part.in()));
3✔
817
        } else {
818
            root.gen();
23✔
819
        }
820
    }
26✔
821

822
    return json_string(gen);
26✔
823
}
27✔
824

825
struct url_parts {
826
    std::optional<std::string> up_scheme;
827
    std::optional<std::string> up_username;
828
    std::optional<std::string> up_password;
829
    std::optional<std::string> up_host;
830
    std::optional<std::string> up_port;
831
    std::optional<std::string> up_path;
832
    std::optional<std::string> up_query;
833
    std::map<std::string, std::optional<std::string>> up_parameters;
834
    std::optional<std::string> up_fragment;
835
};
836

837
const typed_json_path_container<url_parts>&
838
get_url_parts_handlers()
13✔
839
{
840
    static const json_path_container url_params_handlers = {
841
        yajlpp::pattern_property_handler("(?<param>.*)")
13✔
842
            .for_field(&url_parts::up_parameters),
13✔
843
    };
52✔
844

845
    static const typed_json_path_container<url_parts> retval = {
846
        yajlpp::property_handler("scheme").for_field(&url_parts::up_scheme),
26✔
847
        yajlpp::property_handler("username").for_field(&url_parts::up_username),
26✔
848
        yajlpp::property_handler("password").for_field(&url_parts::up_password),
26✔
849
        yajlpp::property_handler("host").for_field(&url_parts::up_host),
26✔
850
        yajlpp::property_handler("port").for_field(&url_parts::up_port),
26✔
851
        yajlpp::property_handler("path").for_field(&url_parts::up_path),
26✔
852
        yajlpp::property_handler("query").for_field(&url_parts::up_query),
26✔
853
        yajlpp::property_handler("parameters")
26✔
854
            .with_children(url_params_handlers),
13✔
855
        yajlpp::property_handler("fragment").for_field(&url_parts::up_fragment),
26✔
856
    };
156✔
857

858
    return retval;
13✔
859
}
156✔
860

861
auto_mem<char>
862
sql_unparse_url(string_fragment in)
13✔
863
{
864
    static auto* CURL_HANDLE = get_curl_easy();
13✔
865
    static const intern_string_t SRC = intern_string::lookup("arg");
39✔
866

867
    auto parse_res = get_url_parts_handlers().parser_for(SRC).of(in);
13✔
868
    if (parse_res.isErr()) {
13✔
869
        throw parse_res.unwrapErr()[0];
3✔
870
    }
871

872
    auto up = parse_res.unwrap();
10✔
873
    auto_mem<CURLU> cu(curl_url_cleanup);
10✔
874
    cu = curl_url();
10✔
875

876
    if (up.up_scheme) {
10✔
877
        curl_url_set(
9✔
878
            cu, CURLUPART_SCHEME, up.up_scheme->c_str(), CURLU_URLENCODE);
879
    }
880
    if (up.up_username) {
10✔
881
        curl_url_set(
×
882
            cu, CURLUPART_USER, up.up_username->c_str(), CURLU_URLENCODE);
883
    }
884
    if (up.up_password) {
10✔
885
        curl_url_set(
×
886
            cu, CURLUPART_PASSWORD, up.up_password->c_str(), CURLU_URLENCODE);
887
    }
888
    if (up.up_host) {
10✔
889
        curl_url_set(cu, CURLUPART_HOST, up.up_host->c_str(), CURLU_URLENCODE);
9✔
890
    }
891
    if (up.up_port) {
10✔
892
        curl_url_set(cu, CURLUPART_PORT, up.up_port->c_str(), 0);
×
893
    }
894
    if (up.up_path) {
10✔
895
        curl_url_set(cu, CURLUPART_PATH, up.up_path->c_str(), CURLU_URLENCODE);
4✔
896
    }
897
    if (up.up_query) {
10✔
898
        curl_url_set(cu, CURLUPART_QUERY, up.up_query->c_str(), 0);
4✔
899
    } else if (!up.up_parameters.empty()) {
6✔
900
        for (const auto& pair : up.up_parameters) {
×
901
            auto_mem<char> key(curl_free);
×
902
            auto_mem<char> value(curl_free);
×
903
            std::string qparam;
×
904

905
            key = curl_easy_escape(
906
                CURL_HANDLE, pair.first.c_str(), pair.first.length());
×
907
            if (pair.second) {
×
908
                value = curl_easy_escape(
909
                    CURL_HANDLE, pair.second->c_str(), pair.second->length());
×
910
                qparam = fmt::format(FMT_STRING("{}={}"), key.in(), value.in());
×
911
            } else {
912
                qparam = key.in();
×
913
            }
914

915
            curl_url_set(
×
916
                cu, CURLUPART_QUERY, qparam.c_str(), CURLU_APPENDQUERY);
917
        }
918
    }
919
    if (up.up_fragment) {
10✔
920
        curl_url_set(
1✔
921
            cu, CURLUPART_FRAGMENT, up.up_fragment->c_str(), CURLU_URLENCODE);
922
    }
923

924
    auto_mem<char> retval(curl_free);
10✔
925

926
    curl_url_get(cu, CURLUPART_URL, retval.out(), 0);
10✔
927
    return retval;
20✔
928
}
13✔
929

930
}  // namespace
931

932
json_string
933
extract(const char* str)
16✔
934
{
935
    data_scanner ds(str);
16✔
936
    data_parser dp(&ds);
16✔
937

938
    dp.parse();
16✔
939
    // dp.print(stderr, dp.dp_pairs);
940

941
    yajlpp_gen gen;
16✔
942
    yajl_gen_config(gen, yajl_gen_beautify, false);
16✔
943

944
    elements_to_json(gen, dp, &dp.dp_pairs);
16✔
945

946
    return json_string(gen);
32✔
947
}
16✔
948

949
static std::string
950
sql_humanize_id(string_fragment id)
7✔
951
{
952
    auto& vc = view_colors::singleton();
7✔
953
    auto attrs = vc.attrs_for_ident(id.data(), id.length());
7✔
954

955
    return fmt::format(FMT_STRING("\x1b[38;5;{}m{}\x1b[0m"),
14✔
956
                       // XXX attrs.ta_fg_color.value_or(COLOR_CYAN),
957
                       (int8_t) ansi_color::cyan,
7✔
958
                       id);
14✔
959
}
7✔
960

961
static std::string
962
sql_pretty_print(string_fragment in)
6✔
963
{
964
    data_scanner ds(in);
6✔
965
    pretty_printer pp(&ds, {});
6✔
966
    attr_line_t retval;
6✔
967

968
    pp.append_to(retval);
6✔
969

970
    return std::move(retval.get_string());
12✔
971
}
6✔
972

973
int
974
string_extension_functions(struct FuncDef** basic_funcs,
1,633✔
975
                           struct FuncDefAgg** agg_funcs)
976
{
977
    static struct FuncDef string_funcs[] = {
978
        sqlite_func_adapter<decltype(&regexp), regexp>::builder(
979
            help_text("regexp", "Test if a string matches a regular expression")
×
980
                .sql_function()
1,047✔
981
                .with_parameter({"re", "The regular expression to use"})
2,094✔
982
                .with_parameter({
2,094✔
983
                    "str",
984
                    "The string to test against the regular expression",
985
                })),
986

987
        sqlite_func_adapter<decltype(&regexp_match), regexp_match>::builder(
2,094✔
988
            help_text("regexp_match",
×
989
                      "Match a string against a regular expression and return "
990
                      "the capture groups as JSON.")
991
                .sql_function()
1,047✔
992
                .with_prql_path({"text", "regexp_match"})
1,047✔
993
                .with_parameter({"re", "The regular expression to use"})
2,094✔
994
                .with_parameter({
2,094✔
995
                    "str",
996
                    "The string to test against the regular expression",
997
                })
998
                .with_tags({"string", "regex"})
1,047✔
999
                .with_example({
1,047✔
1000
                    "To capture the digits from the string '123'",
1001
                    "SELECT regexp_match('(\\d+)', '123')",
1002
                })
1003
                .with_example({
1,047✔
1004
                    "To capture a number and word into a JSON object with the "
1005
                    "properties 'col_0' and 'col_1'",
1006
                    "SELECT regexp_match('(\\d+) (\\w+)', '123 four')",
1007
                })
1008
                .with_example({
2,094✔
1009
                    "To capture a number and word into a JSON object with the "
1010
                    "named properties 'num' and 'str'",
1011
                    "SELECT regexp_match('(?<num>\\d+) (?<str>\\w+)', '123 "
1012
                    "four')",
1013
                }))
1014
            .with_result_subtype(),
1015

1016
        sqlite_func_adapter<decltype(&regexp_replace), regexp_replace>::builder(
1017
            help_text("regexp_replace",
×
1018
                      "Replace the parts of a string that match a regular "
1019
                      "expression.")
1020
                .sql_function()
1,047✔
1021
                .with_prql_path({"text", "regexp_replace"})
1,047✔
1022
                .with_parameter(
2,094✔
1023
                    {"str", "The string to perform replacements on"})
1024
                .with_parameter({"re", "The regular expression to match"})
2,094✔
1025
                .with_parameter({
2,094✔
1026
                    "repl",
1027
                    "The replacement string.  "
1028
                    "You can reference capture groups with a "
1029
                    "backslash followed by the number of the "
1030
                    "group, starting with 1.",
1031
                })
1032
                .with_tags({"string", "regex"})
1,047✔
1033
                .with_example({
1,047✔
1034
                    "To replace the word at the start of the string "
1035
                    "'Hello, World!' with 'Goodbye'",
1036
                    "SELECT regexp_replace('Hello, World!', "
1037
                    "'^(\\w+)', 'Goodbye')",
1038
                })
1039
                .with_example({
2,094✔
1040
                    "To wrap alphanumeric words with angle brackets",
1041
                    "SELECT regexp_replace('123 abc', '(\\w+)', '<\\1>')",
1042
                })),
1043

1044
        sqlite_func_adapter<decltype(&sql_humanize_file_size),
1045
                            sql_humanize_file_size>::
1046
            builder(help_text(
×
1047
                        "humanize_file_size",
1048
                        "Format the given file size as a human-friendly string")
1049
                        .sql_function()
1,047✔
1050
                        .with_prql_path({"humanize", "file_size"})
1,047✔
1051
                        .with_parameter({"value", "The file size to format"})
2,094✔
1052
                        .with_tags({"string"})
1,047✔
1053
                        .with_example({
2,094✔
1054
                            "To format an amount",
1055
                            "SELECT humanize_file_size(10 * 1024 * 1024)",
1056
                        })),
1057

1058
        sqlite_func_adapter<decltype(&sql_humanize_id), sql_humanize_id>::
1059
            builder(help_text("humanize_id",
×
1060
                              "Colorize the given ID using ANSI escape codes.")
1061
                        .sql_function()
1,047✔
1062
                        .with_prql_path({"humanize", "id"})
1,047✔
1063
                        .with_parameter({"id", "The identifier to color"})
2,094✔
1064
                        .with_tags({"string"})
1,047✔
1065
                        .with_example({
2,094✔
1066
                            "To colorize the ID 'cluster1'",
1067
                            "SELECT humanize_id('cluster1')",
1068
                        })),
1069

1070
        sqlite_func_adapter<decltype(&humanize::sparkline),
1071
                            humanize::sparkline>::
1072
            builder(
1073
                help_text("sparkline",
×
1074
                          "Function used to generate a sparkline bar chart.  "
1075
                          "The non-aggregate version converts a single numeric "
1076
                          "value on a range to a bar chart character.  The "
1077
                          "aggregate version returns a string with a bar "
1078
                          "character for every numeric input")
1079
                    .sql_function()
1,047✔
1080
                    .with_prql_path({"text", "sparkline"})
1,047✔
1081
                    .with_parameter({"value", "The numeric value to convert"})
2,094✔
1082
                    .with_parameter(help_text("upper",
3,141✔
1083
                                              "The upper bound of the numeric "
1084
                                              "range.  The non-aggregate "
1085
                                              "version defaults to 100.  The "
1086
                                              "aggregate version uses the "
1087
                                              "largest value in the inputs.")
1088
                                        .optional())
1,047✔
1089
                    .with_tags({"string"})
1,047✔
1090
                    .with_example({
1,047✔
1091
                        "To get the unicode block element for the "
1092
                        "value 32 in the "
1093
                        "range of 0-128",
1094
                        "SELECT sparkline(32, 128)",
1095
                    })
1096
                    .with_example({
2,094✔
1097
                        "To chart the values in a JSON array",
1098
                        "SELECT sparkline(value) FROM json_each('[0, 1, 2, 3, "
1099
                        "4, 5, 6, 7, 8]')",
1100
                    })),
1101

1102
        sqlite_func_adapter<decltype(&sql_anonymize), sql_anonymize>::builder(
1103
            help_text("anonymize",
×
1104
                      "Replace identifying information with random values.")
1105
                .sql_function()
1,047✔
1106
                .with_prql_path({"text", "anonymize"})
1,047✔
1107
                .with_parameter({"value", "The text to anonymize"})
2,094✔
1108
                .with_tags({"string"})
1,047✔
1109
                .with_example({
2,094✔
1110
                    "To anonymize an IP address",
1111
                    "SELECT anonymize('Hello, 192.168.1.2')",
1112
                })),
1113

1114
        sqlite_func_adapter<decltype(&extract), extract>::builder(
2,094✔
1115
            help_text("extract",
×
1116
                      "Automatically Parse and extract data from a string")
1117
                .sql_function()
1,047✔
1118
                .with_prql_path({"text", "discover"})
1,047✔
1119
                .with_parameter({"str", "The string to parse"})
2,094✔
1120
                .with_tags({"string"})
1,047✔
1121
                .with_example({
1,047✔
1122
                    "To extract key/value pairs from a string",
1123
                    "SELECT extract('foo=1 bar=2 name=\"Rolo Tomassi\"')",
1124
                })
1125
                .with_example({
2,094✔
1126
                    "To extract columnar data from a string",
1127
                    "SELECT extract('1.0 abc 2.0')",
1128
                }))
1129
            .with_result_subtype(),
1130

1131
        sqlite_func_adapter<decltype(&logfmt2json), logfmt2json>::builder(
2,094✔
1132
            help_text("logfmt2json",
1,047✔
1133
                      "Convert a logfmt-encoded string into JSON")
1134
                .sql_function()
1,047✔
1135
                .with_prql_path({"logfmt", "to_json"})
1,047✔
1136
                .with_parameter({"str", "The logfmt message to parse"})
2,094✔
1137
                .with_tags({"string"})
1,047✔
1138
                .with_example({
2,094✔
1139
                    "To extract key/value pairs from a log message",
1140
                    "SELECT logfmt2json('foo=1 bar=2 name=\"Rolo Tomassi\"')",
1141
                }))
1142
            .with_result_subtype(),
1143

1144
        sqlite_func_adapter<
1145
            decltype(static_cast<bool (*)(const char*, const char*)>(
1146
                &startswith)),
1147
            startswith>::
1148
            builder(help_text("startswith",
×
1149
                              "Test if a string begins with the given prefix")
1150
                        .sql_function()
1,047✔
1151
                        .with_parameter({"str", "The string to test"})
2,094✔
1152
                        .with_parameter(
2,094✔
1153
                            {"prefix", "The prefix to check in the string"})
1154
                        .with_tags({"string"})
1,047✔
1155
                        .with_example({
1,047✔
1156
                            "To test if the string 'foobar' starts with 'foo'",
1157
                            "SELECT startswith('foobar', 'foo')",
1158
                        })
1159
                        .with_example({
2,094✔
1160
                            "To test if the string 'foobar' starts with 'bar'",
1161
                            "SELECT startswith('foobar', 'bar')",
1162
                        })),
1163

1164
        sqlite_func_adapter<decltype(static_cast<bool (*)(
1165
                                         const char*, const char*)>(&endswith)),
1166
                            endswith>::
1167
            builder(
1168
                help_text("endswith",
×
1169
                          "Test if a string ends with the given suffix")
1170
                    .sql_function()
1,047✔
1171
                    .with_parameter({"str", "The string to test"})
2,094✔
1172
                    .with_parameter(
2,094✔
1173
                        {"suffix", "The suffix to check in the string"})
1174
                    .with_tags({"string"})
1,047✔
1175
                    .with_example({
1,047✔
1176
                        "To test if the string 'notbad.jpg' ends with '.jpg'",
1177
                        "SELECT endswith('notbad.jpg', '.jpg')",
1178
                    })
1179
                    .with_example({
2,094✔
1180
                        "To test if the string 'notbad.png' starts with '.jpg'",
1181
                        "SELECT endswith('notbad.png', '.jpg')",
1182
                    })),
1183

1184
        sqlite_func_adapter<decltype(&sql_fuzzy_match), sql_fuzzy_match>::
1185
            builder(help_text(
×
1186
                        "fuzzy_match",
1187
                        "Perform a fuzzy match of a pattern against a "
1188
                        "string and return a score or NULL if the pattern was "
1189
                        "not matched")
1190
                        .sql_function()
1,047✔
1191
                        .with_parameter(help_text(
2,094✔
1192
                            "pattern", "The pattern to look for in the string"))
1193
                        .with_parameter(
1,047✔
1194
                            help_text("str", "The string to match against"))
2,094✔
1195
                        .with_tags({"string"})
1,047✔
1196
                        .with_example({
2,094✔
1197
                            "To match the pattern 'fo' against 'filter-out'",
1198
                            "SELECT fuzzy_match('fo', 'filter-out')",
1199
                        })),
1200

1201
        sqlite_func_adapter<decltype(&spooky_hash), spooky_hash>::builder(
1202
            help_text("spooky_hash",
×
1203
                      "Compute the hash value for the given arguments.")
1204
                .sql_function()
1,047✔
1205
                .with_parameter(
1,047✔
1206
                    help_text("str", "The string to hash").one_or_more())
2,094✔
1207
                .with_tags({"string"})
1,047✔
1208
                .with_example({
1,047✔
1209
                    "To produce a hash for the string 'Hello, World!'",
1210
                    "SELECT spooky_hash('Hello, World!')",
1211
                })
1212
                .with_example({
1,047✔
1213
                    "To produce a hash for the parameters where one is NULL",
1214
                    "SELECT spooky_hash('Hello, World!', NULL)",
1215
                })
1216
                .with_example({
1,047✔
1217
                    "To produce a hash for the parameters where one "
1218
                    "is an empty string",
1219
                    "SELECT spooky_hash('Hello, World!', '')",
1220
                })
1221
                .with_example({
2,094✔
1222
                    "To produce a hash for the parameters where one "
1223
                    "is a number",
1224
                    "SELECT spooky_hash('Hello, World!', 123)",
1225
                })),
1226

1227
        sqlite_func_adapter<decltype(&sql_gunzip), sql_gunzip>::builder(
1228
            help_text("gunzip", "Decompress a gzip file")
×
1229
                .sql_function()
1,047✔
1230
                .with_parameter(
1,047✔
1231
                    help_text("b", "The blob to decompress").one_or_more())
2,094✔
1232
                .with_tags({"string"})),
2,094✔
1233

1234
        sqlite_func_adapter<decltype(&sql_gzip), sql_gzip>::builder(
1235
            help_text("gzip", "Compress a string into a gzip file")
×
1236
                .sql_function()
1,047✔
1237
                .with_parameter(
1,047✔
1238
                    help_text("value", "The value to compress").one_or_more())
2,094✔
1239
                .with_tags({"string"})),
2,094✔
1240

1241
        sqlite_func_adapter<decltype(&sql_encode), sql_encode>::builder(
1242
            help_text("encode", "Encode the value using the given algorithm")
×
1243
                .sql_function()
1,047✔
1244
                .with_parameter(help_text("value", "The value to encode"))
2,094✔
1245
                .with_parameter(help_text("algorithm",
2,094✔
1246
                                          "One of the following encoding "
1247
                                          "algorithms: base64, hex, uri"))
1248
                .with_tags({"string"})
1,047✔
1249
                .with_example({
1,047✔
1250
                    "To base64-encode 'Hello, World!'",
1251
                    "SELECT encode('Hello, World!', 'base64')",
1252
                })
1253
                .with_example({
1,047✔
1254
                    "To hex-encode 'Hello, World!'",
1255
                    "SELECT encode('Hello, World!', 'hex')",
1256
                })
1257
                .with_example({
2,094✔
1258
                    "To URI-encode 'Hello, World!'",
1259
                    "SELECT encode('Hello, World!', 'uri')",
1260
                })),
1261

1262
        sqlite_func_adapter<decltype(&sql_decode), sql_decode>::builder(
1263
            help_text("decode", "Decode the value using the given algorithm")
×
1264
                .sql_function()
1,047✔
1265
                .with_parameter(help_text("value", "The value to decode"))
2,094✔
1266
                .with_parameter(help_text("algorithm",
2,094✔
1267
                                          "One of the following encoding "
1268
                                          "algorithms: base64, hex, uri"))
1269
                .with_tags({"string"})
1,047✔
1270
                .with_example({
2,094✔
1271
                    "To decode the URI-encoded string '%63%75%72%6c'",
1272
                    "SELECT decode('%63%75%72%6c', 'uri')",
1273
                })),
1274

1275
        sqlite_func_adapter<decltype(&sql_parse_url), sql_parse_url>::builder(
2,094✔
1276
            help_text("parse_url",
×
1277
                      "Parse a URL and return the components in a JSON object. "
1278
                      "Limitations: not all URL schemes are supported and "
1279
                      "repeated query parameters are not captured.")
1280
                .sql_function()
1,047✔
1281
                .with_parameter(help_text("url", "The URL to parse"))
2,094✔
1282
                .with_result({
2,094✔
1283
                    "scheme",
1284
                    "The URL's scheme",
1285
                })
1286
                .with_result({
2,094✔
1287
                    "username",
1288
                    "The name of the user specified in the URL",
1289
                })
1290
                .with_result({
2,094✔
1291
                    "password",
1292
                    "The password specified in the URL",
1293
                })
1294
                .with_result({
2,094✔
1295
                    "host",
1296
                    "The host name / IP specified in the URL",
1297
                })
1298
                .with_result({
2,094✔
1299
                    "port",
1300
                    "The port specified in the URL",
1301
                })
1302
                .with_result({
2,094✔
1303
                    "path",
1304
                    "The path specified in the URL",
1305
                })
1306
                .with_result({
2,094✔
1307
                    "query",
1308
                    "The query string in the URL",
1309
                })
1310
                .with_result({
2,094✔
1311
                    "parameters",
1312
                    "An object containing the query parameters",
1313
                })
1314
                .with_result({
2,094✔
1315
                    "fragment",
1316
                    "The fragment specified in the URL",
1317
                })
1318
                .with_tags({"string", "url"})
1,047✔
1319
                .with_example({
1,047✔
1320
                    "To parse the URL "
1321
                    "'https://example.com/search?q=hello%20world'",
1322
                    "SELECT "
1323
                    "parse_url('https://example.com/search?q=hello%20world')",
1324
                })
1325
                .with_example({
2,094✔
1326
                    "To parse the URL "
1327
                    "'https://alice@[fe80::14ff:4ee5:1215:2fb2]'",
1328
                    "SELECT "
1329
                    "parse_url('https://alice@[fe80::14ff:4ee5:1215:2fb2]')",
1330
                }))
1331
            .with_result_subtype(),
1332

1333
        sqlite_func_adapter<decltype(&sql_unparse_url), sql_unparse_url>::
1334
            builder(
1335
                help_text("unparse_url",
×
1336
                          "Convert a JSON object containing the parts of a "
1337
                          "URL into a URL string")
1338
                    .sql_function()
1,047✔
1339
                    .with_parameter(help_text(
2,094✔
1340
                        "obj", "The JSON object containing the URL parts"))
1341
                    .with_tags({"string", "url"})
1,047✔
1342
                    .with_example({
2,094✔
1343
                        "To unparse the object "
1344
                        "'{\"scheme\": \"https\", \"host\": \"example.com\"}'",
1345
                        "SELECT "
1346
                        "unparse_url('{\"scheme\": \"https\", \"host\": "
1347
                        "\"example.com\"}')",
1348
                    })),
1349

1350
        sqlite_func_adapter<decltype(&sql_pretty_print), sql_pretty_print>::
1351
            builder(
1352
                help_text("pretty_print", "Pretty-print the given string")
×
1353
                    .sql_function()
1,047✔
1354
                    .with_prql_path({"text", "pretty"})
1,047✔
1355
                    .with_parameter(help_text("str", "The string to format"))
2,094✔
1356
                    .with_tags({"string"})
1,047✔
1357
                    .with_example({
2,094✔
1358
                        "To pretty-print the string "
1359
                        "'{\"scheme\": \"https\", \"host\": \"example.com\"}'",
1360
                        "SELECT "
1361
                        "pretty_print('{\"scheme\": \"https\", \"host\": "
1362
                        "\"example.com\"}')",
1363
                    })),
1364

1365
        {nullptr},
1366
    };
42,466✔
1367

1368
    static struct FuncDefAgg str_agg_funcs[] = {
1369
        {
1370
            "group_spooky_hash",
1371
            -1,
1372
            SQLITE_UTF8,
1373
            0,
1374
            sql_spooky_hash_step,
1375
            sql_spooky_hash_final,
1376
            help_text("group_spooky_hash",
1,047✔
1377
                      "Compute the hash value for the given arguments")
1378
                .sql_agg_function()
1,047✔
1379
                .with_parameter(
1,047✔
1380
                    help_text("str", "The string to hash").one_or_more())
2,094✔
1381
                .with_tags({"string"})
1,047✔
1382
                .with_example({
2,094✔
1383
                    "To produce a hash of all of the values of 'column1'",
1384
                    "SELECT group_spooky_hash(column1) FROM (VALUES ('abc'), "
1385
                    "('123'))",
1386
                }),
1387
        },
1388

1389
        {
1390
            "sparkline",
1391
            -1,
1392
            SQLITE_UTF8,
1393
            0,
1394
            sparkline_step,
1395
            sparkline_final,
1396
        },
1397

1398
        {nullptr},
1399
    };
2,680✔
1400

1401
    *basic_funcs = string_funcs;
1,633✔
1402
    *agg_funcs = str_agg_funcs;
1,633✔
1403

1404
    return SQLITE_OK;
1,633✔
1405
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc