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

tstack / lnav / 11872214087-1756

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

push

github

tstack
[build] disable regex101

46266 of 65866 relevant lines covered (70.24%)

467515.63 hits per line

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

83.93
/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
#ifdef __CYGWIN__
11
#    include <alloca.h>
12
#endif
13

14
#include <unordered_map>
15

16
#include <sqlite3.h>
17
#include <stdlib.h>
18
#include <string.h>
19

20
#include "base/humanize.hh"
21
#include "base/lnav.gzip.hh"
22
#include "base/string_util.hh"
23
#include "column_namer.hh"
24
#include "config.h"
25
#include "data_parser.hh"
26
#include "data_scanner.hh"
27
#include "elem_to_json.hh"
28
#include "formats/logfmt/logfmt.parser.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/scn.h"
35
#include "spookyhash/SpookyV2.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
};
55

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

62
        if (strcasecmp(algo_name, "base64") == 0) {
37✔
63
            return encode_algo::base64;
11✔
64
        }
65
        if (strcasecmp(algo_name, "hex") == 0) {
26✔
66
            return encode_algo::hex;
9✔
67
        }
68
        if (strcasecmp(algo_name, "uri") == 0) {
17✔
69
            return encode_algo::uri;
16✔
70
        }
71

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

77
namespace {
78

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

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

92
    auto iter = cache.find(re);
1,034✔
93
    if (iter == cache.end()) {
1,034✔
94
        auto compile_res = lnav::pcre2pp::code::from(re);
1,033✔
95
        if (compile_res.isErr()) {
1,033✔
96
            const static intern_string_t SRC = intern_string::lookup("arg");
×
97

98
            throw lnav::console::to_user_message(SRC, compile_res.unwrapErr());
×
99
        }
100

101
        cache_entry c;
1,033✔
102

103
        c.re2 = compile_res.unwrap().to_shared();
1,033✔
104
        auto pair = cache.insert(
1,033✔
105
            std::make_pair(string_fragment::from_str(c.re2->get_pattern()), c));
2,066✔
106

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

112
        iter = pair.first;
1,033✔
113
    }
1,033✔
114

115
    return &iter->second;
1,034✔
116
}
117

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

123
    return reobj->re2->find_in(str).ignore_error().has_value();
978✔
124
}
125

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

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

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

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

149
    yajlpp_gen gen;
31✔
150
    yajl_gen_config(gen, yajl_gen_beautify, false);
31✔
151

152
    if (extractor.get_capture_count() == 1) {
31✔
153
        auto cap = md[1];
11✔
154

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

159
        auto scan_int_res = scn::scan_value<int64_t>(cap->to_string_view());
11✔
160
        if (scan_int_res && scan_int_res.empty()) {
11✔
161
            return scan_int_res.value();
9✔
162
        }
163

164
        auto scan_float_res = scn::scan_value<double>(cap->to_string_view());
2✔
165
        if (scan_float_res && scan_float_res.empty()) {
2✔
166
            return scan_float_res.value();
1✔
167
        }
168

169
        return cap.value();
1✔
170
    } else {
171
        yajlpp_map root_map(gen);
20✔
172

173
        for (size_t lpc = 0; lpc < extractor.get_capture_count(); lpc++) {
60✔
174
            const auto& colname = reobj->cn->cn_names[lpc];
40✔
175
            const auto cap = md[lpc + 1];
40✔
176

177
            yajl_gen_pstring(gen, colname.data(), colname.length());
40✔
178

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

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

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

215
    {
216
        yajlpp_map root(gen);
9✔
217
        bool done = false;
9✔
218

219
        while (!done) {
46✔
220
            auto pair = p.step();
37✔
221

222
            done = pair.match(
37✔
223
                [](const logfmt::parser::end_of_input& eoi) { return true; },
9✔
224
                [&root, &gen](const logfmt::parser::kvpair& kvp) {
56✔
225
                    root.gen(kvp.first);
28✔
226

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

242
                            jo.jo_ptr_callbacks = json_op::gen_callbacks;
8✔
243
                            jo.jo_ptr_data = gen;
8✔
244
                            parse_handle.reset(yajl_alloc(
8✔
245
                                &json_op::ptr_callbacks, nullptr, &jo));
246

247
                            const auto* json_in
248
                                = (const unsigned char*) qv.qv_value.data();
8✔
249
                            auto json_len = qv.qv_value.length();
8✔
250

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

263
                    return false;
28✔
264
                },
265
                [](const logfmt::parser::error& e) -> bool {
×
266
                    throw sqlite_func_error("Invalid logfmt: {}", e.e_msg);
×
267
                });
268
        }
37✔
269
    }
9✔
270

271
    return json_string(gen);
18✔
272
}
9✔
273

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

279
    return reobj->re2->replace(str, repl);
24✔
280
}
281

282
std::string
283
spooky_hash(const std::vector<const char*>& args)
32✔
284
{
285
    byte_array<2, uint64> hash;
32✔
286
    SpookyHash context;
287

288
    context.Init(0, 0);
32✔
289
    for (const auto* const arg : args) {
88✔
290
        int64_t len = arg != nullptr ? strlen(arg) : 0;
56✔
291

292
        context.Update(&len, sizeof(len));
56✔
293
        if (arg == nullptr) {
56✔
294
            continue;
8✔
295
        }
296
        context.Update(arg, len);
48✔
297
    }
298
    context.Final(hash.out(0), hash.out(1));
32✔
299

300
    return hash.to_string();
32✔
301
}
302

303
void
304
sql_spooky_hash_step(sqlite3_context* context, int argc, sqlite3_value** argv)
16✔
305
{
306
    auto* hasher
307
        = (SpookyHash*) sqlite3_aggregate_context(context, sizeof(SpookyHash));
16✔
308

309
    for (int lpc = 0; lpc < argc; lpc++) {
32✔
310
        const auto* value = sqlite3_value_text(argv[lpc]);
16✔
311
        int64_t len = value != nullptr ? strlen((const char*) value) : 0;
16✔
312

313
        hasher->Update(&len, sizeof(len));
16✔
314
        if (value == nullptr) {
16✔
315
            continue;
×
316
        }
317
        hasher->Update(value, len);
16✔
318
    }
319
}
16✔
320

321
static void
322
sql_spooky_hash_final(sqlite3_context* context)
8✔
323
{
324
    auto* hasher
325
        = (SpookyHash*) sqlite3_aggregate_context(context, sizeof(SpookyHash));
8✔
326

327
    if (hasher == nullptr) {
8✔
328
        sqlite3_result_null(context);
×
329
    } else {
330
        byte_array<2, uint64> hash;
8✔
331

332
        hasher->Final(hash.out(0), hash.out(1));
8✔
333

334
        auto hex = hash.to_string();
8✔
335
        sqlite3_result_text(
8✔
336
            context, hex.c_str(), hex.length(), SQLITE_TRANSIENT);
8✔
337
    }
8✔
338
}
8✔
339

340
struct sparkline_context {
341
    bool sc_initialized{true};
342
    double sc_max_value{0.0};
343
    std::vector<double> sc_values;
344
};
345

346
static void
347
sparkline_step(sqlite3_context* context, int argc, sqlite3_value** argv)
80✔
348
{
349
    auto* sc = (sparkline_context*) sqlite3_aggregate_context(
80✔
350
        context, sizeof(sparkline_context));
351

352
    if (!sc->sc_initialized) {
80✔
353
        new (sc) sparkline_context;
16✔
354
    }
355

356
    if (argc == 0) {
80✔
357
        return;
×
358
    }
359

360
    sc->sc_values.push_back(sqlite3_value_double(argv[0]));
80✔
361
    sc->sc_max_value = std::max(sc->sc_max_value, sc->sc_values.back());
80✔
362

363
    if (argc >= 2) {
80✔
364
        sc->sc_max_value
365
            = std::max(sc->sc_max_value, sqlite3_value_double(argv[1]));
8✔
366
    }
367
}
368

369
static void
370
sparkline_final(sqlite3_context* context)
16✔
371
{
372
    auto* sc = (sparkline_context*) sqlite3_aggregate_context(
16✔
373
        context, sizeof(sparkline_context));
374

375
    if (!sc->sc_initialized) {
16✔
376
        sqlite3_result_text(context, "", 0, SQLITE_STATIC);
×
377
        return;
×
378
    }
379

380
    auto retval = auto_mem<char>::malloc(sc->sc_values.size() * 3 + 1);
16✔
381
    auto* start = retval.in();
16✔
382

383
    for (const auto& value : sc->sc_values) {
96✔
384
        auto bar = humanize::sparkline(value, sc->sc_max_value);
80✔
385

386
        strcpy(start, bar.c_str());
80✔
387
        start += bar.length();
80✔
388
    }
80✔
389
    *start = '\0';
16✔
390

391
    to_sqlite(context, std::move(retval));
16✔
392

393
    sc->~sparkline_context();
16✔
394
}
16✔
395

396
std::optional<mapbox::util::variant<blob_auto_buffer, sqlite3_int64, double>>
397
sql_gunzip(sqlite3_value* val)
2✔
398
{
399
    switch (sqlite3_value_type(val)) {
2✔
400
        case SQLITE3_TEXT:
2✔
401
        case SQLITE_BLOB: {
402
            const auto* buffer = sqlite3_value_blob(val);
2✔
403
            auto len = sqlite3_value_bytes(val);
2✔
404

405
            if (!lnav::gzip::is_gzipped((const char*) buffer, len)) {
2✔
406
                return blob_auto_buffer{
×
407
                    auto_buffer::from((const char*) buffer, len)};
×
408
            }
409

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

412
            if (res.isErr()) {
2✔
413
                throw sqlite_func_error("unable to uncompress -- {}",
×
414
                                        res.unwrapErr());
×
415
            }
416

417
            return blob_auto_buffer{res.unwrap()};
2✔
418
        }
2✔
419
        case SQLITE_INTEGER:
×
420
            return sqlite3_value_int64(val);
×
421
        case SQLITE_FLOAT:
×
422
            return sqlite3_value_double(val);
×
423
    }
424

425
    return std::nullopt;
×
426
}
427

428
std::optional<blob_auto_buffer>
429
sql_gzip(sqlite3_value* val)
3✔
430
{
431
    switch (sqlite3_value_type(val)) {
3✔
432
        case SQLITE3_TEXT:
1✔
433
        case SQLITE_BLOB: {
434
            const auto* buffer = sqlite3_value_blob(val);
1✔
435
            auto len = sqlite3_value_bytes(val);
1✔
436
            auto res = lnav::gzip::compress(buffer, len);
1✔
437

438
            if (res.isErr()) {
1✔
439
                throw sqlite_func_error("unable to compress -- {}",
×
440
                                        res.unwrapErr());
×
441
            }
442

443
            return blob_auto_buffer{res.unwrap()};
1✔
444
        }
1✔
445
        case SQLITE_INTEGER:
2✔
446
        case SQLITE_FLOAT: {
447
            const auto* buffer = sqlite3_value_text(val);
2✔
448
            auto res
449
                = lnav::gzip::compress(buffer, strlen((const char*) buffer));
2✔
450

451
            if (res.isErr()) {
2✔
452
                throw sqlite_func_error("unable to compress -- {}",
×
453
                                        res.unwrapErr());
×
454
            }
455

456
            return blob_auto_buffer{res.unwrap()};
2✔
457
        }
2✔
458
    }
459

460
    return std::nullopt;
×
461
}
462

463
#if defined(HAVE_LIBCURL)
464
static CURL*
465
get_curl_easy()
55✔
466
{
467
    static struct curl_wrapper {
468
        curl_wrapper() { this->cw_value = curl_easy_init(); }
27✔
469

470
        auto_mem<CURL> cw_value{curl_easy_cleanup};
471
    } retval;
55✔
472

473
    return retval.cw_value.in();
55✔
474
}
475
#endif
476

477
static mapbox::util::variant<text_auto_buffer, auto_mem<char>, null_value_t>
478
sql_encode(sqlite3_value* value, encode_algo algo)
27✔
479
{
480
    switch (sqlite3_value_type(value)) {
27✔
481
        case SQLITE_NULL: {
1✔
482
            return null_value_t{};
1✔
483
        }
484
        case SQLITE_BLOB: {
1✔
485
            const auto* blob
486
                = static_cast<const char*>(sqlite3_value_blob(value));
1✔
487
            auto blob_len = sqlite3_value_bytes(value);
1✔
488

489
            switch (algo) {
490
                case encode_algo::base64: {
1✔
491
                    auto buf = auto_buffer::alloc((blob_len * 5) / 3);
1✔
492
                    auto outlen = buf.capacity();
1✔
493

494
                    base64_encode(blob, blob_len, buf.in(), &outlen, 0);
1✔
495
                    buf.resize(outlen);
1✔
496
                    return text_auto_buffer{std::move(buf)};
1✔
497
                }
1✔
498
                case encode_algo::hex: {
×
499
                    auto buf = auto_buffer::alloc(blob_len * 2 + 1);
×
500

501
                    for (int lpc = 0; lpc < blob_len; lpc++) {
×
502
                        fmt::format_to(std::back_inserter(buf),
×
503
                                       FMT_STRING("{:x}"),
×
504
                                       blob[lpc]);
×
505
                    }
×
506

×
507
                    return text_auto_buffer{std::move(buf)};
508
                }
509
#if defined(HAVE_LIBCURL)
×
510
                case encode_algo::uri: {
511
                    auto_mem<char> retval(curl_free);
512

×
513
                    retval = curl_easy_escape(get_curl_easy(), blob, blob_len);
×
514
                    return std::move(retval);
515
                }
×
516
#endif
×
517
            }
518
        }
519
        default: {
520
            const auto* text = (const char*) sqlite3_value_text(value);
521
            auto text_len = sqlite3_value_bytes(value);
522

25✔
523
            switch (algo) {
25✔
524
                case encode_algo::base64: {
525
                    auto buf = auto_buffer::alloc((text_len * 5) / 3);
526
                    size_t outlen = buf.capacity();
8✔
527

8✔
528
                    base64_encode(text, text_len, buf.in(), &outlen, 0);
8✔
529
                    buf.resize(outlen);
530
                    return text_auto_buffer{std::move(buf)};
8✔
531
                }
8✔
532
                case encode_algo::hex: {
8✔
533
                    auto buf = auto_buffer::alloc(text_len * 2 + 1);
8✔
534

9✔
535
                    for (int lpc = 0; lpc < text_len; lpc++) {
9✔
536
                        fmt::format_to(std::back_inserter(buf),
537
                                       FMT_STRING("{:02x}"),
116✔
538
                                       text[lpc]);
107✔
539
                    }
321✔
540

107✔
541
                    return text_auto_buffer{std::move(buf)};
107✔
542
                }
107✔
543
#if defined(HAVE_LIBCURL)
544
                case encode_algo::uri: {
545
                    auto_mem<char> retval(curl_free);
9✔
546

9✔
547
                    retval = curl_easy_escape(get_curl_easy(), text, text_len);
548
                    return std::move(retval);
8✔
549
                }
8✔
550
#endif
551
            }
8✔
552
        }
8✔
553
    }
8✔
554
    ensure(false);
555
}
556

557
static mapbox::util::variant<blob_auto_buffer, auto_mem<char>>
558
sql_decode(string_fragment str, encode_algo algo)
×
559
{
560
    switch (algo) {
561
        case encode_algo::base64: {
562
            auto buf = auto_buffer::alloc(str.length());
9✔
563
            auto outlen = buf.capacity();
564
            base64_decode(str.data(), str.length(), buf.in(), &outlen, 0);
9✔
565
            buf.resize(outlen);
1✔
566

1✔
567
            return blob_auto_buffer{std::move(buf)};
1✔
568
        }
1✔
569
        case encode_algo::hex: {
1✔
570
            auto buf = auto_buffer::alloc(str.length() / 2);
571
            auto sv = str.to_string_view();
1✔
572

1✔
573
            while (!sv.empty()) {
×
574
                int32_t value;
×
575
                auto scan_res = scn::scan(sv, "{:2x}", value);
×
576
                if (!scan_res) {
577
                    throw sqlite_func_error(
×
578
                        "invalid hex input at: {}",
579
                        std::distance(str.begin(), sv.begin()));
×
580
                }
×
581
                buf.push_back((char) (value & 0xff));
×
582
                sv = scan_res.range_as_string_view();
583
            }
×
584

585
            return blob_auto_buffer{std::move(buf)};
×
586
        }
×
587
#if defined(HAVE_LIBCURL)
588
        case encode_algo::uri: {
589
            auto_mem<char> retval(curl_free);
×
590

591
            retval = curl_easy_unescape(
592
                get_curl_easy(), str.data(), str.length(), nullptr);
8✔
593

8✔
594
            return std::move(retval);
595
        }
596
#endif
8✔
597
    }
598
    ensure(false);
8✔
599
}
8✔
600

601
std::string
602
sql_humanize_file_size(file_ssize_t value)
×
603
{
604
    return humanize::file_size(value, humanize::alignment::columnar);
605
}
606

9✔
607
static std::string
608
sql_anonymize(string_fragment frag)
9✔
609
{
610
    static safe::Safe<lnav::text_anonymizer> ta;
611

612
    return ta.writeAccess()->next(frag);
205✔
613
}
614

205✔
615
#if !CURL_AT_LEAST_VERSION(7, 80, 0)
616
extern "C"
410✔
617
{
618
const char* curl_url_strerror(CURLUcode error);
619
}
620
#endif
621

622
json_string
623
sql_parse_url(std::string url)
624
{
625
    static auto* CURL_HANDLE = get_curl_easy();
626

627
    auto_mem<CURLU> cu(curl_url_cleanup);
31✔
628
    cu = curl_url();
629

31✔
630
    auto rc = curl_url_set(
631
        cu, CURLUPART_URL, url.c_str(), CURLU_NON_SUPPORT_SCHEME);
31✔
632
    if (rc != CURLUE_OK) {
31✔
633
        throw lnav::console::user_message::error(
634
            attr_line_t("invalid URL: ").append(lnav::roles::file(url)))
31✔
635
            .with_reason(curl_url_strerror(rc));
636
    }
31✔
637

×
638
    auto_mem<char> url_part(curl_free);
2✔
639
    yajlpp_gen gen;
3✔
640
    yajl_gen_config(gen, yajl_gen_beautify, false);
641

642
    {
30✔
643
        yajlpp_map root(gen);
30✔
644

30✔
645
        root.gen("scheme");
646
        rc = curl_url_get(cu, CURLUPART_SCHEME, url_part.out(), 0);
647
        if (rc == CURLUE_OK) {
30✔
648
            root.gen(string_fragment::from_c_str(url_part.in()));
649
        } else {
30✔
650
            root.gen();
30✔
651
        }
30✔
652
        root.gen("username");
30✔
653
        rc = curl_url_get(cu, CURLUPART_USER, url_part.out(), CURLU_URLDECODE);
654
        if (rc == CURLUE_OK) {
×
655
            root.gen(string_fragment::from_c_str(url_part.in()));
656
        } else {
30✔
657
            root.gen();
30✔
658
        }
30✔
659
        root.gen("password");
8✔
660
        rc = curl_url_get(
661
            cu, CURLUPART_PASSWORD, url_part.out(), CURLU_URLDECODE);
22✔
662
        if (rc == CURLUE_OK) {
663
            root.gen(string_fragment::from_c_str(url_part.in()));
30✔
664
        } else {
30✔
665
            root.gen();
666
        }
30✔
667
        root.gen("host");
×
668
        rc = curl_url_get(cu, CURLUPART_HOST, url_part.out(), CURLU_URLDECODE);
669
        if (rc == CURLUE_OK) {
30✔
670
            root.gen(string_fragment::from_c_str(url_part.in()));
671
        } else {
30✔
672
            root.gen();
30✔
673
        }
30✔
674
        root.gen("port");
30✔
675
        rc = curl_url_get(cu, CURLUPART_PORT, url_part.out(), 0);
676
        if (rc == CURLUE_OK) {
×
677
            root.gen(string_fragment::from_c_str(url_part.in()));
678
        } else {
30✔
679
            root.gen();
30✔
680
        }
30✔
681
        root.gen("path");
×
682
        rc = curl_url_get(cu, CURLUPART_PATH, url_part.out(), CURLU_URLDECODE);
683
        if (rc == CURLUE_OK) {
30✔
684
            root.gen(string_fragment::from_c_str(url_part.in()));
685
        } else {
30✔
686
            root.gen();
30✔
687
        }
30✔
688
        rc = curl_url_get(cu, CURLUPART_QUERY, url_part.out(), 0);
30✔
689
        if (rc == CURLUE_OK) {
690
            root.gen("query");
×
691
            root.gen(string_fragment::from_c_str(url_part.in()));
692

30✔
693
            root.gen("parameters");
30✔
694
            robin_hood::unordered_set<std::string> seen_keys;
17✔
695
            yajlpp_map query_map(gen);
17✔
696

697
            for (size_t lpc = 0; url_part.in()[lpc]; lpc++) {
17✔
698
                if (url_part.in()[lpc] == '+') {
17✔
699
                    url_part.in()[lpc] = ' ';
17✔
700
                }
701
            }
254✔
702
            auto query_frag = string_fragment::from_c_str(url_part.in());
237✔
703
            auto remaining = query_frag;
1✔
704

705
            while (true) {
706
                auto split_res
17✔
707
                    = remaining.split_when(string_fragment::tag1{'&'});
17✔
708
                auto_mem<char> kv_pair(curl_free);
709
                auto kv_pair_encoded = split_res.first;
710
                int out_len = 0;
711

30✔
712
                kv_pair = curl_easy_unescape(CURL_HANDLE,
30✔
713
                                             kv_pair_encoded.data(),
30✔
714
                                             kv_pair_encoded.length(),
30✔
715
                                             &out_len);
716

717
                auto kv_pair_frag
718
                    = string_fragment::from_bytes(kv_pair.in(), out_len);
719
                auto eq_index_opt = kv_pair_frag.find('=');
30✔
720
                if (eq_index_opt) {
721
                    auto key = kv_pair_frag.sub_range(0, eq_index_opt.value());
722
                    auto val = kv_pair_frag.substr(eq_index_opt.value() + 1);
30✔
723
                    auto key_str = key.to_string();
30✔
724

30✔
725
                    if (seen_keys.count(key_str) == 0) {
14✔
726
                        seen_keys.emplace(key_str);
14✔
727
                        query_map.gen(key);
14✔
728
                        query_map.gen(val);
729
                    }
14✔
730
                } else {
14✔
731
                    auto val_str = split_res.first.to_string();
14✔
732

14✔
733
                    if (seen_keys.count(val_str) == 0) {
734
                        seen_keys.insert(val_str);
14✔
735
                        query_map.gen(split_res.first);
16✔
736
                        query_map.gen();
737
                    }
16✔
738
                }
16✔
739

16✔
740
                if (split_res.second.empty()) {
16✔
741
                    break;
742
                }
16✔
743

744
                remaining = split_res.second;
30✔
745
            }
17✔
746
        } else {
747
            root.gen("query");
748
            root.gen();
13✔
749
            root.gen("parameters");
43✔
750
            root.gen();
17✔
751
        }
13✔
752
        root.gen("fragment");
13✔
753
        rc = curl_url_get(
13✔
754
            cu, CURLUPART_FRAGMENT, url_part.out(), CURLU_URLDECODE);
13✔
755
        if (rc == CURLUE_OK) {
756
            root.gen(string_fragment::from_c_str(url_part.in()));
30✔
757
        } else {
30✔
758
            root.gen();
759
        }
30✔
760
    }
3✔
761

762
    return json_string(gen);
27✔
763
}
764

30✔
765
struct url_parts {
766
    std::optional<std::string> up_scheme;
60✔
767
    std::optional<std::string> up_username;
31✔
768
    std::optional<std::string> up_password;
769
    std::optional<std::string> up_host;
770
    std::optional<std::string> up_port;
771
    std::optional<std::string> up_path;
772
    std::optional<std::string> up_query;
773
    std::map<std::string, std::optional<std::string>> up_parameters;
774
    std::optional<std::string> up_fragment;
775
};
776

777
static const json_path_container url_params_handlers = {
778
    yajlpp::pattern_property_handler("(?<param>.*)")
779
        .for_field(&url_parts::up_parameters),
780
};
781

782
static const typed_json_path_container<url_parts> url_parts_handlers = {
783
    yajlpp::property_handler("scheme").for_field(&url_parts::up_scheme),
784
    yajlpp::property_handler("username").for_field(&url_parts::up_username),
785
    yajlpp::property_handler("password").for_field(&url_parts::up_password),
786
    yajlpp::property_handler("host").for_field(&url_parts::up_host),
787
    yajlpp::property_handler("port").for_field(&url_parts::up_port),
788
    yajlpp::property_handler("path").for_field(&url_parts::up_path),
789
    yajlpp::property_handler("query").for_field(&url_parts::up_query),
790
    yajlpp::property_handler("parameters").with_children(url_params_handlers),
791
    yajlpp::property_handler("fragment").for_field(&url_parts::up_fragment),
792
};
793

794
static auto_mem<char>
795
sql_unparse_url(string_fragment in)
796
{
797
    static auto* CURL_HANDLE = get_curl_easy();
798
    static intern_string_t SRC = intern_string::lookup("arg");
799

16✔
800
    auto parse_res = url_parts_handlers.parser_for(SRC).of(in);
801
    if (parse_res.isErr()) {
16✔
802
        throw parse_res.unwrapErr()[0];
16✔
803
    }
804

16✔
805
    auto up = parse_res.unwrap();
16✔
806
    auto_mem<CURLU> cu(curl_url_cleanup);
3✔
807
    cu = curl_url();
808

809
    if (up.up_scheme) {
13✔
810
        curl_url_set(
13✔
811
            cu, CURLUPART_SCHEME, up.up_scheme->c_str(), CURLU_URLENCODE);
13✔
812
    }
813
    if (up.up_username) {
13✔
814
        curl_url_set(
12✔
815
            cu, CURLUPART_USER, up.up_username->c_str(), CURLU_URLENCODE);
816
    }
817
    if (up.up_password) {
13✔
818
        curl_url_set(
×
819
            cu, CURLUPART_PASSWORD, up.up_password->c_str(), CURLU_URLENCODE);
820
    }
821
    if (up.up_host) {
13✔
822
        curl_url_set(cu, CURLUPART_HOST, up.up_host->c_str(), CURLU_URLENCODE);
×
823
    }
824
    if (up.up_port) {
825
        curl_url_set(cu, CURLUPART_PORT, up.up_port->c_str(), 0);
13✔
826
    }
12✔
827
    if (up.up_path) {
828
        curl_url_set(cu, CURLUPART_PATH, up.up_path->c_str(), CURLU_URLENCODE);
13✔
829
    }
×
830
    if (up.up_query) {
831
        curl_url_set(cu, CURLUPART_QUERY, up.up_query->c_str(), 0);
13✔
832
    } else if (!up.up_parameters.empty()) {
4✔
833
        for (const auto& pair : up.up_parameters) {
834
            auto_mem<char> key(curl_free);
13✔
835
            auto_mem<char> value(curl_free);
4✔
836
            std::string qparam;
9✔
837

×
838
            key = curl_easy_escape(
×
839
                CURL_HANDLE, pair.first.c_str(), pair.first.length());
×
840
            if (pair.second) {
×
841
                value = curl_easy_escape(
842
                    CURL_HANDLE, pair.second->c_str(), pair.second->length());
843
                qparam = fmt::format(FMT_STRING("{}={}"), key.in(), value.in());
×
844
            } else {
×
845
                qparam = key.in();
846
            }
×
847

×
848
            curl_url_set(
×
849
                cu, CURLUPART_QUERY, qparam.c_str(), CURLU_APPENDQUERY);
×
850
        }
851
    }
×
852
    if (up.up_fragment) {
853
        curl_url_set(
854
            cu, CURLUPART_FRAGMENT, up.up_fragment->c_str(), CURLU_URLENCODE);
×
855
    }
856

857
    auto_mem<char> retval(curl_free);
858

13✔
859
    curl_url_get(cu, CURLUPART_URL, retval.out(), 0);
1✔
860
    return retval;
861
}
862

863
}  // namespace
13✔
864

865
json_string
13✔
866
extract(const char* str)
26✔
867
{
16✔
868
    data_scanner ds(str);
869
    data_parser dp(&ds);
870

871
    dp.parse();
872
    // dp.print(stderr, dp.dp_pairs);
22✔
873

874
    yajlpp_gen gen;
44✔
875
    yajl_gen_config(gen, yajl_gen_beautify, false);
22✔
876

877
    elements_to_json(gen, dp, &dp.dp_pairs);
22✔
878

879
    return json_string(gen);
880
}
22✔
881

22✔
882
static std::string
883
sql_humanize_id(string_fragment id)
22✔
884
{
885
    auto& vc = view_colors::singleton();
44✔
886
    auto attrs = vc.attrs_for_ident(id.data(), id.length());
22✔
887

888
    return fmt::format(FMT_STRING("\x1b[38;5;{}m{}\x1b[0m"),
889
                       attrs.ta_fg_color.value_or(COLOR_CYAN),
10✔
890
                       id);
891
}
10✔
892

10✔
893
static std::string
894
sql_pretty_print(string_fragment in)
20✔
895
{
10✔
896
    data_scanner ds(in);
10✔
897
    pretty_printer pp(&ds, {});
10✔
898
    attr_line_t retval;
10✔
899

900
    pp.append_to(retval);
901

902
    return std::move(retval.get_string());
9✔
903
}
904

9✔
905
int
9✔
906
string_extension_functions(struct FuncDef** basic_funcs,
9✔
907
                           struct FuncDefAgg** agg_funcs)
908
{
9✔
909
    static struct FuncDef string_funcs[] = {
910
        sqlite_func_adapter<decltype(&regexp), regexp>::builder(
18✔
911
            help_text("regexp", "Test if a string matches a regular expression")
9✔
912
                .sql_function()
913
                .with_parameter({"re", "The regular expression to use"})
914
                .with_parameter({
1,466✔
915
                    "str",
916
                    "The string to test against the regular expression",
917
                })),
918

919
        sqlite_func_adapter<decltype(&regexp_match), regexp_match>::builder(
×
920
            help_text("regexp_match",
946✔
921
                      "Match a string against a regular expression and return "
1,892✔
922
                      "the capture groups as JSON.")
1,892✔
923
                .sql_function()
924
                .with_prql_path({"text", "regexp_match"})
925
                .with_parameter({"re", "The regular expression to use"})
926
                .with_parameter({
927
                    "str",
1,892✔
928
                    "The string to test against the regular expression",
×
929
                })
930
                .with_tags({"string", "regex"})
931
                .with_example({
946✔
932
                    "To capture the digits from the string '123'",
946✔
933
                    "SELECT regexp_match('(\\d+)', '123')",
1,892✔
934
                })
1,892✔
935
                .with_example({
936
                    "To capture a number and word into a JSON object with the "
937
                    "properties 'col_0' and 'col_1'",
938
                    "SELECT regexp_match('(\\d+) (\\w+)', '123 four')",
946✔
939
                })
946✔
940
                .with_example({
941
                    "To capture a number and word into a JSON object with the "
942
                    "named properties 'num' and 'str'",
943
                    "SELECT regexp_match('(?<num>\\d+) (?<str>\\w+)', '123 "
946✔
944
                    "four')",
945
                }))
946
            .with_result_subtype(),
947

948
        sqlite_func_adapter<decltype(&regexp_replace), regexp_replace>::builder(
1,892✔
949
            help_text("regexp_replace",
950
                      "Replace the parts of a string that match a regular "
951
                      "expression.")
952
                .sql_function()
953
                .with_prql_path({"text", "regexp_replace"})
954
                .with_parameter(
955
                    {"str", "The string to perform replacements on"})
956
                .with_parameter({"re", "The regular expression to match"})
957
                .with_parameter({
×
958
                    "repl",
959
                    "The replacement string.  "
960
                    "You can reference capture groups with a "
946✔
961
                    "backslash followed by the number of the "
946✔
962
                    "group, starting with 1.",
1,892✔
963
                })
964
                .with_tags({"string", "regex"})
1,892✔
965
                .with_example({
1,892✔
966
                    "To replace the word at the start of the string "
967
                    "'Hello, World!' with 'Goodbye'",
968
                    "SELECT regexp_replace('Hello, World!', "
969
                    "'^(\\w+)', 'Goodbye')",
970
                })
971
                .with_example({
972
                    "To wrap alphanumeric words with angle brackets",
946✔
973
                    "SELECT regexp_replace('123 abc', '(\\w+)', '<\\1>')",
946✔
974
                })),
975

976
        sqlite_func_adapter<decltype(&sql_humanize_file_size),
977
                            sql_humanize_file_size>::
978
            builder(help_text(
979
                        "humanize_file_size",
946✔
980
                        "Format the given file size as a human-friendly string")
981
                        .sql_function()
982
                        .with_prql_path({"humanize", "file_size"})
983
                        .with_parameter({"value", "The file size to format"})
984
                        .with_tags({"string"})
985
                        .with_example({
986
                            "To format an amount",
×
987
                            "SELECT humanize_file_size(10 * 1024 * 1024)",
988
                        })),
989

946✔
990
        sqlite_func_adapter<decltype(&sql_humanize_id), sql_humanize_id>::
946✔
991
            builder(help_text("humanize_id",
1,892✔
992
                              "Colorize the given ID using ANSI escape codes.")
946✔
993
                        .sql_function()
946✔
994
                        .with_prql_path({"humanize", "id"})
995
                        .with_parameter({"id", "The identifier to color"})
996
                        .with_tags({"string"})
997
                        .with_example({
998
                            "To colorize the ID 'cluster1'",
999
                            "SELECT humanize_id('cluster1')",
×
1000
                        })),
1001

946✔
1002
        sqlite_func_adapter<decltype(&humanize::sparkline),
946✔
1003
                            humanize::sparkline>::
1,892✔
1004
            builder(
946✔
1005
                help_text("sparkline",
946✔
1006
                          "Function used to generate a sparkline bar chart.  "
1007
                          "The non-aggregate version converts a single numeric "
1008
                          "value on a range to a bar chart character.  The "
1009
                          "aggregate version returns a string with a bar "
1010
                          "character for every numeric input")
1011
                    .sql_function()
1012
                    .with_prql_path({"text", "sparkline"})
1013
                    .with_parameter({"value", "The numeric value to convert"})
×
1014
                    .with_parameter(help_text("upper",
1015
                                              "The upper bound of the numeric "
1016
                                              "range.  The non-aggregate "
1017
                                              "version defaults to 100.  The "
1018
                                              "aggregate version uses the "
1019
                                              "largest value in the inputs.")
946✔
1020
                                        .optional())
946✔
1021
                    .with_tags({"string"})
1,892✔
1022
                    .with_example({
2,838✔
1023
                        "To get the unicode block element for the "
1024
                        "value 32 in the "
1025
                        "range of 0-128",
1026
                        "SELECT sparkline(32, 128)",
1027
                    })
1028
                    .with_example({
946✔
1029
                        "To chart the values in a JSON array",
946✔
1030
                        "SELECT sparkline(value) FROM json_each('[0, 1, 2, 3, "
946✔
1031
                        "4, 5, 6, 7, 8]')",
1032
                    })),
1033

1034
        sqlite_func_adapter<decltype(&sql_anonymize), sql_anonymize>::builder(
1035
            help_text("anonymize",
1036
                      "Replace identifying information with random values.")
946✔
1037
                .sql_function()
1038
                .with_prql_path({"text", "anonymize"})
1039
                .with_parameter({"value", "The text to anonymize"})
1040
                .with_tags({"string"})
1041
                .with_example({
1042
                    "To anonymize an IP address",
1043
                    "SELECT anonymize('Hello, 192.168.1.2')",
×
1044
                })),
1045

946✔
1046
        sqlite_func_adapter<decltype(&extract), extract>::builder(
946✔
1047
            help_text("extract",
1,892✔
1048
                      "Automatically Parse and extract data from a string")
946✔
1049
                .sql_function()
946✔
1050
                .with_prql_path({"text", "discover"})
1051
                .with_parameter({"str", "The string to parse"})
1052
                .with_tags({"string"})
1053
                .with_example({
1054
                    "To extract key/value pairs from a string",
1,892✔
1055
                    "SELECT extract('foo=1 bar=2 name=\"Rolo Tomassi\"')",
×
1056
                })
1057
                .with_example({
946✔
1058
                    "To extract columnar data from a string",
946✔
1059
                    "SELECT extract('1.0 abc 2.0')",
1,892✔
1060
                }))
946✔
1061
            .with_result_subtype(),
946✔
1062

1063
        sqlite_func_adapter<decltype(&logfmt2json), logfmt2json>::builder(
1064
            help_text("logfmt2json",
1065
                      "Convert a logfmt-encoded string into JSON")
1,892✔
1066
                .sql_function()
1067
                .with_prql_path({"logfmt", "to_json"})
1068
                .with_parameter({"str", "The logfmt message to parse"})
1069
                .with_tags({"string"})
1070
                .with_example({
1071
                    "To extract key/value pairs from a log message",
1,892✔
1072
                    "SELECT logfmt2json('foo=1 bar=2 name=\"Rolo Tomassi\"')",
×
1073
                }))
1074
            .with_result_subtype(),
946✔
1075

946✔
1076
        sqlite_func_adapter<
1,892✔
1077
            decltype(static_cast<bool (*)(const char*, const char*)>(
946✔
1078
                &startswith)),
1,892✔
1079
            startswith>::
1080
            builder(help_text("startswith",
1081
                              "Test if a string begins with the given prefix")
1082
                        .sql_function()
1083
                        .with_parameter({"str", "The string to test"})
1084
                        .with_parameter(
1085
                            {"prefix", "The prefix to check in the string"})
1086
                        .with_tags({"string"})
1087
                        .with_example({
1088
                            "To test if the string 'foobar' starts with 'foo'",
×
1089
                            "SELECT startswith('foobar', 'foo')",
1090
                        })
946✔
1091
                        .with_example({
1,892✔
1092
                            "To test if the string 'foobar' starts with 'bar'",
1,892✔
1093
                            "SELECT startswith('foobar', 'bar')",
1094
                        })),
946✔
1095

946✔
1096
        sqlite_func_adapter<decltype(static_cast<bool (*)(
1097
                                         const char*, const char*)>(&endswith)),
1098
                            endswith>::
1099
            builder(
946✔
1100
                help_text("endswith",
1101
                          "Test if a string ends with the given suffix")
1102
                    .sql_function()
1103
                    .with_parameter({"str", "The string to test"})
1104
                    .with_parameter(
1105
                        {"suffix", "The suffix to check in the string"})
1106
                    .with_tags({"string"})
1107
                    .with_example({
1108
                        "To test if the string 'notbad.jpg' ends with '.jpg'",
×
1109
                        "SELECT endswith('notbad.jpg', '.jpg')",
1110
                    })
946✔
1111
                    .with_example({
1,892✔
1112
                        "To test if the string 'notbad.png' starts with '.jpg'",
1,892✔
1113
                        "SELECT endswith('notbad.png', '.jpg')",
1114
                    })),
946✔
1115

946✔
1116
        sqlite_func_adapter<decltype(&spooky_hash), spooky_hash>::builder(
1117
            help_text("spooky_hash",
1118
                      "Compute the hash value for the given arguments.")
1119
                .sql_function()
946✔
1120
                .with_parameter(
1121
                    help_text("str", "The string to hash").one_or_more())
1122
                .with_tags({"string"})
1123
                .with_example({
1124
                    "To produce a hash for the string 'Hello, World!'",
1125
                    "SELECT spooky_hash('Hello, World!')",
×
1126
                })
1127
                .with_example({
946✔
1128
                    "To produce a hash for the parameters where one is NULL",
946✔
1129
                    "SELECT spooky_hash('Hello, World!', NULL)",
1,892✔
1130
                })
946✔
1131
                .with_example({
946✔
1132
                    "To produce a hash for the parameters where one "
1133
                    "is an empty string",
1134
                    "SELECT spooky_hash('Hello, World!', '')",
1135
                })
946✔
1136
                .with_example({
1137
                    "To produce a hash for the parameters where one "
1138
                    "is a number",
1139
                    "SELECT spooky_hash('Hello, World!', 123)",
946✔
1140
                })),
1141

1142
        sqlite_func_adapter<decltype(&sql_gunzip), sql_gunzip>::builder(
1143
            help_text("gunzip", "Decompress a gzip file")
1144
                .sql_function()
946✔
1145
                .with_parameter(
1146
                    help_text("b", "The blob to decompress").one_or_more())
1147
                .with_tags({"string"})),
1148

1149
        sqlite_func_adapter<decltype(&sql_gzip), sql_gzip>::builder(
1150
            help_text("gzip", "Compress a string into a gzip file")
1151
                .sql_function()
×
1152
                .with_parameter(
946✔
1153
                    help_text("value", "The value to compress").one_or_more())
946✔
1154
                .with_tags({"string"})),
1,892✔
1155

946✔
1156
        sqlite_func_adapter<decltype(&sql_encode), sql_encode>::builder(
1157
            help_text("encode", "Encode the value using the given algorithm")
1158
                .sql_function()
×
1159
                .with_parameter(help_text("value", "The value to encode"))
946✔
1160
                .with_parameter(help_text("algorithm",
946✔
1161
                                          "One of the following encoding "
1,892✔
1162
                                          "algorithms: base64, hex, uri"))
946✔
1163
                .with_tags({"string"})
1164
                .with_example({
1165
                    "To base64-encode 'Hello, World!'",
×
1166
                    "SELECT encode('Hello, World!', 'base64')",
946✔
1167
                })
1,892✔
1168
                .with_example({
1,892✔
1169
                    "To hex-encode 'Hello, World!'",
1170
                    "SELECT encode('Hello, World!', 'hex')",
1171
                })
946✔
1172
                .with_example({
946✔
1173
                    "To URI-encode 'Hello, World!'",
1174
                    "SELECT encode('Hello, World!', 'uri')",
1175
                })),
1176

946✔
1177
        sqlite_func_adapter<decltype(&sql_decode), sql_decode>::builder(
1178
            help_text("decode", "Decode the value using the given algorithm")
1179
                .sql_function()
1180
                .with_parameter(help_text("value", "The value to decode"))
946✔
1181
                .with_parameter(help_text("algorithm",
1182
                                          "One of the following encoding "
1183
                                          "algorithms: base64, hex, uri"))
1184
                .with_tags({"string"})
1185
                .with_example({
1186
                    "To decode the URI-encoded string '%63%75%72%6c'",
×
1187
                    "SELECT decode('%63%75%72%6c', 'uri')",
946✔
1188
                })),
1,892✔
1189

1,892✔
1190
        sqlite_func_adapter<decltype(&sql_parse_url), sql_parse_url>::builder(
1191
            help_text("parse_url",
1192
                      "Parse a URL and return the components in a JSON object. "
946✔
1193
                      "Limitations: not all URL schemes are supported and "
946✔
1194
                      "repeated query parameters are not captured.")
1195
                .sql_function()
1196
                .with_parameter(help_text("url", "The URL to parse"))
1197
                .with_result({
1198
                    "scheme",
1,892✔
1199
                    "The URL's scheme",
×
1200
                })
1201
                .with_result({
1202
                    "username",
1203
                    "The name of the user specified in the URL",
946✔
1204
                })
1,892✔
1205
                .with_result({
1,892✔
1206
                    "password",
1207
                    "The password specified in the URL",
1208
                })
1209
                .with_result({
1,892✔
1210
                    "host",
1211
                    "The host name / IP specified in the URL",
1212
                })
1213
                .with_result({
1,892✔
1214
                    "port",
1215
                    "The port specified in the URL",
1216
                })
1217
                .with_result({
1,892✔
1218
                    "path",
1219
                    "The path specified in the URL",
1220
                })
1221
                .with_result({
1,892✔
1222
                    "query",
1223
                    "The query string in the URL",
1224
                })
1225
                .with_result({
1,892✔
1226
                    "parameters",
1227
                    "An object containing the query parameters",
1228
                })
1229
                .with_result({
1,892✔
1230
                    "fragment",
1231
                    "The fragment specified in the URL",
1232
                })
1233
                .with_tags({"string", "url"})
1,892✔
1234
                .with_example({
1235
                    "To parse the URL "
1236
                    "'https://example.com/search?q=hello%20world'",
1237
                    "SELECT "
1,892✔
1238
                    "parse_url('https://example.com/search?q=hello%20world')",
1239
                })
1240
                .with_example({
1241
                    "To parse the URL "
946✔
1242
                    "'https://alice@[fe80::14ff:4ee5:1215:2fb2]'",
946✔
1243
                    "SELECT "
1244
                    "parse_url('https://alice@[fe80::14ff:4ee5:1215:2fb2]')",
1245
                }))
1246
            .with_result_subtype(),
1247

1248
        sqlite_func_adapter<decltype(&sql_unparse_url), sql_unparse_url>::
1,892✔
1249
            builder(
1250
                help_text("unparse_url",
1251
                          "Convert a JSON object containing the parts of a "
1252
                          "URL into a URL string")
1253
                    .sql_function()
1254
                    .with_parameter(help_text(
1255
                        "obj", "The JSON object containing the URL parts"))
1256
                    .with_tags({"string", "url"})
1257
                    .with_example({
1258
                        "To unparse the object "
×
1259
                        "'{\"scheme\": \"https\", \"host\": \"example.com\"}'",
1260
                        "SELECT "
1261
                        "unparse_url('{\"scheme\": \"https\", \"host\": "
946✔
1262
                        "\"example.com\"}')",
1,892✔
1263
                    })),
1264

946✔
1265
        sqlite_func_adapter<decltype(&sql_pretty_print), sql_pretty_print>::
946✔
1266
            builder(
1267
                help_text("pretty_print", "Pretty-print the given string")
1268
                    .sql_function()
1269
                    .with_prql_path({"text", "pretty"})
1270
                    .with_parameter(help_text("str", "The string to format"))
1271
                    .with_tags({"string"})
1272
                    .with_example({
1273
                        "To pretty-print the string "
1274
                        "'{\"scheme\": \"https\", \"host\": \"example.com\"}'",
1275
                        "SELECT "
×
1276
                        "pretty_print('{\"scheme\": \"https\", \"host\": "
946✔
1277
                        "\"example.com\"}')",
946✔
1278
                    })),
1,892✔
1279

946✔
1280
        {nullptr},
946✔
1281
    };
1282

1283
    static struct FuncDefAgg str_agg_funcs[] = {
1284
        {
1285
            "group_spooky_hash",
1286
            -1,
1287
            SQLITE_UTF8,
1288
            0,
1289
            sql_spooky_hash_step,
37,414✔
1290
            sql_spooky_hash_final,
1291
            help_text("group_spooky_hash",
1292
                      "Compute the hash value for the given arguments")
1293
                .sql_agg_function()
1294
                .with_parameter(
1295
                    help_text("str", "The string to hash").one_or_more())
1296
                .with_tags({"string"})
1297
                .with_example({
1298
                    "To produce a hash of all of the values of 'column1'",
1299
                    "SELECT group_spooky_hash(column1) FROM (VALUES ('abc'), "
×
1300
                    "('123'))",
1301
                }),
946✔
1302
        },
946✔
1303

1,892✔
1304
        {
946✔
1305
            "sparkline",
1,892✔
1306
            -1,
1307
            SQLITE_UTF8,
1308
            0,
1309
            sparkline_step,
1310
            sparkline_final,
1311
        },
1312

1313
        {nullptr},
1314
    };
1315

1316
    *basic_funcs = string_funcs;
1317
    *agg_funcs = str_agg_funcs;
1318

1319
    return SQLITE_OK;
1320
}
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