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

tstack / lnav / 24959179949-2999

26 Apr 2026 02:37PM UTC coverage: 69.227% (+0.09%) from 69.141%
24959179949-2999

push

github

tstack
[tests] fix paths

53969 of 77959 relevant lines covered (69.23%)

568944.78 hits per line

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

80.66
/src/sql_util.cc
1
/**
2
 * Copyright (c) 2013, Timothy Stack
3
 *
4
 * All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions are met:
8
 *
9
 * * Redistributions of source code must retain the above copyright notice, this
10
 * list of conditions and the following disclaimer.
11
 * * Redistributions in binary form must reproduce the above copyright notice,
12
 * this list of conditions and the following disclaimer in the documentation
13
 * and/or other materials provided with the distribution.
14
 * * Neither the name of Timothy Stack nor the names of its contributors
15
 * may be used to endorse or promote products derived from this software
16
 * without specific prior written permission.
17
 *
18
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ''AS IS'' AND ANY
19
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21
 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
22
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28
 *
29
 * @file sql_util.cc
30
 */
31

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

36
#include "sql_util.hh"
37

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

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

57
using namespace lnav::roles::literals;
58

59
constexpr std::array<const char*, 564> pg_sql_keywords = {
60
    "ABORT",
61
    "ABS",
62
    "ACCESSIBLE",
63
    "ACTION",
64
    "ADD",
65
    "ADMIN",
66
    "AFTER",
67
    "AGGREGATE",
68
    "ALIAS",
69
    "ALL",
70
    "ALLOCATE",
71
    "ALTER",
72
    "ALWAYS",
73
    "ANALYSE",
74
    "ANALYZE",
75
    "AND",
76
    "ANY",
77
    "ARE",
78
    "ARRAY",
79
    "AS",
80
    "ASC",
81
    "ASENSITIVE",
82
    "ASSERTION",
83
    "ASSIGNMENT",
84
    "ASYMMETRIC",
85
    "AT",
86
    "ATOMIC",
87
    "ATTRIBUTE",
88
    "ATTRIBUTES",
89
    "AUDIT",
90
    "AUTHORIZATION",
91
    "AVG",
92
    "BEFORE",
93
    "BEGIN",
94
    "BERNOULLI",
95
    "BETWEEN",
96
    "BIGINT",
97
    "BINARY",
98
    "BIT",
99
    "BLOB",
100
    "BOOLEAN",
101
    "BOTH",
102
    "BREADTH",
103
    "BY",
104
    "CALL",
105
    "CALLED",
106
    "CARDINALITY",
107
    "CASCADE",
108
    "CASCADED",
109
    "CASE",
110
    "CAST",
111
    "CATALOG",
112
    "CATALOG_NAME",
113
    "CEIL",
114
    "CEILING",
115
    "CHAR",
116
    "CHARACTER",
117
    "CHARACTER_LENGTH",
118
    "CHARACTER_SET_CATALOG",
119
    "CHARACTER_SET_NAME",
120
    "CHARACTER_SET_SCHEMA",
121
    "CHAR_LENGTH",
122
    "CHECK",
123
    "CLASS",
124
    "CLASS_ORIGIN",
125
    "CLOB",
126
    "CLOSE",
127
    "COALESCE",
128
    "COLLATE",
129
    "COLLATION",
130
    "COLLATION_CATALOG",
131
    "COLLATION_NAME",
132
    "COLLATION_SCHEMA",
133
    "COLLECT",
134
    "COLUMN",
135
    "COLUMN_NAME",
136
    "COMMAND_FUNCTION",
137
    "COMMAND_FUNCTION_CODE",
138
    "COMMIT",
139
    "COMMITTED",
140
    "CONDITION",
141
    "CONDITION_NUMBER",
142
    "CONNECT",
143
    "CONNECTION",
144
    "CONNECTION_NAME",
145
    "CONSTRAINT",
146
    "CONSTRAINT_CATALOG",
147
    "CONSTRAINT_NAME",
148
    "CONSTRAINT_SCHEMA",
149
    "CONSTRUCTORS",
150
    "CONTAINS",
151
    "CONTINUE",
152
    "CONVERT",
153
    "CORR",
154
    "CORRESPONDING",
155
    "COUNT",
156
    "COVAR_POP",
157
    "COVAR_SAMP",
158
    "CREATE",
159
    "CROSS",
160
    "CUBE",
161
    "CUME_DIST",
162
    "CURRENT",
163
    "CURRENT_CATALOG",
164
    "CURRENT_DATE",
165
    "CURRENT_DEFAULT_TRANSFORM_GROUP",
166
    "CURRENT_PATH",
167
    "CURRENT_ROLE",
168
    "CURRENT_ROW",
169
    "CURRENT_SCHEMA",
170
    "CURRENT_TIME",
171
    "CURRENT_TIMESTAMP",
172
    "CURRENT_TRANSFORM_GROUP_FOR_TYPE",
173
    "CURRENT_USER",
174
    "CURSOR",
175
    "CURSOR_NAME",
176
    "CYCLE",
177
    "DATA",
178
    "DATABASE",
179
    "DATE",
180
    "DATETIME_INTERVAL_CODE",
181
    "DATETIME_INTERVAL_PRECISION",
182
    "DAY",
183
    "DEALLOCATE",
184
    "DEC",
185
    "DECIMAL",
186
    "DECLARE",
187
    "DEFAULT",
188
    "DEFAULTS",
189
    "DEFERRABLE",
190
    "DEFERRED",
191
    "DEFINED",
192
    "DEFINER",
193
    "DEGREE",
194
    "DELETE",
195
    "DENSE_RANK",
196
    "DEPTH",
197
    "DEREF",
198
    "DESC",
199
    "DESCRIBE",
200
    "DESCRIPTOR",
201
    "DETERMINISTIC",
202
    "DIAGNOSTICS",
203
    "DISCONNECT",
204
    "DISPATCH",
205
    "DISTINCT",
206
    "DO",
207
    "DOMAIN",
208
    "DOUBLE",
209
    "DROP",
210
    "DYNAMIC",
211
    "DYNAMIC_FUNCTION",
212
    "DYNAMIC_FUNCTION_CODE",
213
    "EACH",
214
    "ELEMENT",
215
    "ELSE",
216
    "END",
217
    "END_FRAME",
218
    "END_OF_CHAIN",
219
    "EQUALS",
220
    "ESCAPE",
221
    "EVERY",
222
    "EXCEPT",
223
    "EXCEPTION",
224
    "EXCLUDE",
225
    "EXCLUDING",
226
    "EXEC",
227
    "EXECUTE",
228
    "EXISTS",
229
    "EXP",
230
    "EXPLAIN",
231
    "EXTEND",
232
    "EXTERNAL",
233
    "EXTRACT",
234
    "FALSE",
235
    "FAMILY",
236
    "FETCH",
237
    "FILE",
238
    "FINAL",
239
    "FIRST",
240
    "FIRST_VALUE",
241
    "FLAG",
242
    "FLOAT",
243
    "FLOOR",
244
    "FOLLOWING",
245
    "FOR",
246
    "FOREIGN",
247
    "FORTRAN",
248
    "FOUND",
249
    "FRAME_ROW",
250
    "FREE",
251
    "FREEZE",
252
    "FROM",
253
    "FULL",
254
    "FUNCTION",
255
    "FUSION",
256
    "GENERAL",
257
    "GENERATED",
258
    "GET",
259
    "GLOBAL",
260
    "GO",
261
    "GOTO",
262
    "GRANT",
263
    "GRANTED",
264
    "GROUP",
265
    "GROUPING",
266
    "GROUPS",
267
    "HAVING",
268
    "HOLD",
269
    "HOUR",
270
    "IDENTITY",
271
    "IF",
272
    "IGNORE",
273
    "ILIKE",
274
    "IMMEDIATE",
275
    "IMMEDIATELY",
276
    "IMPLEMENTATION",
277
    "IMPLICIT",
278
    "IN",
279
    "INCLUDING",
280
    "INCREMENT",
281
    "INDICATOR",
282
    "INHERIT",
283
    "INITIALLY",
284
    "INNER",
285
    "INOUT",
286
    "INPUT",
287
    "INSENSITIVE",
288
    "INSERT",
289
    "INSTANCE",
290
    "INSTANTIABLE",
291
    "INT",
292
    "INTEGER",
293
    "INTEGRITY",
294
    "INTERSECT",
295
    "INTERVAL",
296
    "INTO",
297
    "INVOKER",
298
    "IS",
299
    "ISNULL",
300
    "ISOLATION",
301
    "JAVA",
302
    "JOIN",
303
    "KEY",
304
    "KEY_MEMBER",
305
    "KEY_TYPE",
306
    "LAG",
307
    "LANGUAGE",
308
    "LARGE",
309
    "LAST",
310
    "LAST_VALUE",
311
    "LATERAL",
312
    "LEAD",
313
    "LEADING",
314
    "LEFT",
315
    "LENGTH",
316
    "LEVEL",
317
    "LIKE",
318
    "LIKE_REGEX",
319
    "LIMIT",
320
    "LN",
321
    "LOCAL",
322
    "LOCALTIME",
323
    "LOCALTIMESTAMP",
324
    "LOCATOR",
325
    "LOWER",
326
    "MAP",
327
    "MATCH",
328
    "MATCHED",
329
    "MAX",
330
    "MAX_CARDINALITY",
331
    "MEMBER",
332
    "MERGE",
333
    "MESSAGE_LENGTH",
334
    "MESSAGE_OCTET_LENGTH",
335
    "MESSAGE_TEXT",
336
    "METHOD",
337
    "MIN",
338
    "MINUTE",
339
    "MOD",
340
    "MODIFIES",
341
    "MODULE",
342
    "MONTH",
343
    "MULTISET",
344
    "NAME",
345
    "NAMES",
346
    "NATIONAL",
347
    "NATURAL",
348
    "NCHAR",
349
    "NCLOB",
350
    "NEW",
351
    "NEXT",
352
    "NO",
353
    "NONE",
354
    "NORMALIZE",
355
    "NORMALIZED",
356
    "NOT",
357
    "NOTNULL",
358
    "NTH_VALUE",
359
    "NULL",
360
    "NULLABLE",
361
    "NULLIF",
362
    "NULLS",
363
    "NUMBER",
364
    "NUMERIC",
365
    "OBJECT",
366
    "OCTET_LENGTH",
367
    "OF",
368
    "OFF",
369
    "OFFSET",
370
    "OLD",
371
    "ON",
372
    "ONLY",
373
    "OPEN",
374
    "OPERATION",
375
    "OPTION",
376
    "ORDINALITY",
377
    "ORDER",
378
    "ORDERING",
379
    "ORIENTATION",
380
    "ORIGIN",
381
    "OUT",
382
    "OUTER",
383
    "OUTPUT",
384
    "OVER",
385
    "OVERLAPS",
386
    "OVERLAY",
387
    "PAD",
388
    "PARAMETER",
389
    "PARAMETER_MODE",
390
    "PARAMETER_NAME",
391
    "PARAMETER_ORDINAL_POSITION",
392
    "PARAMETER_SPECIFIC_CATALOG",
393
    "PARAMETER_SPECIFIC_NAME",
394
    "PARAMETER_SPECIFIC_SCHEMA",
395
    "PARTIAL",
396
    "PARTITION",
397
    "PASCAL",
398
    "PATH",
399
    "PERCENT_RANK",
400
    "PERCENTILE_CONT",
401
    "PERCENTILE_DISC",
402
    "PERIOD",
403
    "PG_CATALOG",
404
    "PG_CLIENT",
405
    "PG_EXTENSION",
406
    "PG_GET_KEYWORDS",
407
    "PG_IS_TEMP_SCHEMA",
408
    "PG_IS_TEMP_SCHE",
409
    "PG_LAST_WAL_RECEIVE_LOCATION",
410
    "PG_LAST_WAL_REPLAY_LOCATION",
411
    "PG_LISTEN",
412
    "PG_NOTIFY",
413
    "PG_RELATION_SIZE",
414
    "PG_SLEEP",
415
    "PG_TABLE_SIZE",
416
    "PG_TRY_ADVISORY_LOCK",
417
    "PG_TRY_ADVISORY_UNLOCK",
418
    "PG_TYPEOF",
419
    "PG_UNLISTEN",
420
    "PG_WAL_LSN_DIFF",
421
    "PG_WAL_REPLAY_PAUSED",
422
    "PG_XLOG_LOCATION_DIFF",
423
    "PG_XLOG_REPLAY_PAUSED",
424
    "PG_XLOG_REPLAY_WAIT",
425
    "PLACING",
426
    "PLI",
427
    "PORTION",
428
    "POSITION",
429
    "POWER",
430
    "PRECEDES",
431
    "PRECISION",
432
    "PREPARE",
433
    "PRESERVE",
434
    "PRIMARY",
435
    "PRIOR",
436
    "PRIVILEGES",
437
    "PROCEDURE",
438
    "PUBLIC",
439
    "RANGE",
440
    "RANK",
441
    "READ",
442
    "READS",
443
    "REAL",
444
    "RECURSIVE",
445
    "REF",
446
    "REFERENCES",
447
    "REFERENCING",
448
    "REGR_AVGX",
449
    "REGR_AVGY",
450
    "REGR_COUNT",
451
    "REGR_INTERCEPT",
452
    "REGR_R2",
453
    "REGR_SLOPE",
454
    "REGR_SXX",
455
    "REGR_SXY",
456
    "REGR_SYY",
457
    "RELATIVE",
458
    "RELEASE",
459
    "REPEATABLE",
460
    "REPLACE",
461
    "RESPECT",
462
    "RESTART",
463
    "RESTRICT",
464
    "RETURN",
465
    "RETURNED_CARDINALITY",
466
    "RETURNED_LENGTH",
467
    "RETURNED_OCTET_LENGTH",
468
    "RETURNED_SQLSTATE",
469
    "RETURNING",
470
    "REVOKE",
471
    "RIGHT",
472
    "ROLLBACK",
473
    "ROLLUP",
474
    "ROUTINE",
475
    "ROUTINE_CATALOG",
476
    "ROUTINE_NAME",
477
    "ROUTINE_SCHEMA",
478
    "ROW",
479
    "ROW_COUNT",
480
    "ROW_NUMBER",
481
    "ROWS",
482
    "SAVEPOINT",
483
    "SCHEMA",
484
    "SCHEMA_NAME",
485
    "SCOPE",
486
    "SCOPE_CATALOG",
487
    "SCOPE_NAME",
488
    "SCOPE_SCHEMA",
489
    "SCROLL",
490
    "SEARCH",
491
    "SECOND",
492
    "SECTION",
493
    "SECURITY",
494
    "SELECT",
495
    "SELF",
496
    "SENSITIVE",
497
    "SEQUENCE",
498
    "SERIALIZABLE",
499
    "SERVER_NAME",
500
    "SESSION",
501
    "SESSION_USER",
502
    "SET",
503
    "SETS",
504
    "SIMILAR",
505
    "SIZE",
506
    "SMALLINT",
507
    "SOME",
508
    "SPACE",
509
    "SPECIFIC",
510
    "SPECIFIC_NAME",
511
    "SPECIFIC_SCHEMA",
512
    "SPECIFICTYPE",
513
    "SQL",
514
    "SQLCODE",
515
    "SQLERROR",
516
    "SQLEXCEPTION",
517
    "SQLSTATE",
518
    "SQLWARNING",
519
    "SQRT",
520
    "START",
521
    "STATE",
522
    "STATEMENT",
523
    "STATIC",
524
    "STDDEV_POP",
525
    "STDDEV_SAMP",
526
    "STRUCTURE",
527
    "STYLE",
528
    "SUBCLASS_ORIGIN",
529
    "SUBMULTISET",
530
    "SUBSTRING",
531
    "SUM",
532
    "SYMMETRIC",
533
    "SYSTEM",
534
    "SYSTEM_USER",
535
    "TABLE",
536
    "TABLE_NAME",
537
    "TABLESAMPLE",
538
    "TEMPORARY",
539
    "THEN",
540
    "TIES",
541
    "TIME",
542
    "TIMESTAMP",
543
    "TIMESTAMPADD",
544
    "TIMESTAMPDIFF",
545
    "TIMEZONE_HOUR",
546
    "TIMEZONE_MINUTE",
547
    "TO",
548
    "TOP",
549
    "TRAILING",
550
    "TRANSACTION",
551
    "TRANSACTIONS_COMMITTED",
552
    "TRANSACTIONS_ROLLED_BACK",
553
    "TRANSACTION_ACTIVE",
554
    "TRANSFORM",
555
    "TRANSFORMS",
556
    "TRANSLATE",
557
    "TRANSLATION",
558
    "TREAT",
559
    "TRIGGER",
560
    "TRIGGER_CATALOG",
561
    "TRIGGER_NAME",
562
    "TRIGGER_SCHEMA",
563
    "TRIM",
564
    "TRUE",
565
    "TRUNCATE",
566
    "UNBOUNDED",
567
    "UNCOMMITTED",
568
    "UNDER",
569
    "UNION",
570
    "UNIQUE",
571
    "UNKNOWN",
572
    "UNLISTEN",
573
    "UNNAMED",
574
    "UNNEST",
575
    "UNTIL",
576
    "UPDATE",
577
    "UPPER",
578
    "USAGE",
579
    "USER",
580
    "USER_DEFINED_TYPE_CATALOG",
581
    "USER_DEFINED_TYPE_CODE",
582
    "USER_DEFINED_TYPE_NAME",
583
    "USER_DEFINED_TYPE_SCHEMA",
584
    "USING",
585
    "VACUUM",
586
    "VALID",
587
    "VALUE",
588
    "VALUES",
589
    "VAR_POP",
590
    "VAR_SAMP",
591
    "VARCHAR",
592
    "VARYING",
593
    "VERBOSE",
594
    "VERSION",
595
    "VIEW",
596
    "WHEN",
597
    "WHENEVER",
598
    "WHERE",
599
    "WIDTH_BUCKET",
600
    "WINDOW",
601
    "WITH",
602
    "WITHIN",
603
    "WITHOUT",
604
    "WORK",
605
    "WRITE",
606
    "XML",
607
    "XMLAGG",
608
    "XMLATTRIBUTES",
609
    "XMLCONCAT",
610
    "XMLELEMENT",
611
    "XMLEXISTS",
612
    "XMLFOREST",
613
    "XMLPARSE",
614
    "XMLPI",
615
    "XMLQUERY",
616
    "XMLROOT",
617
    "XMLSERIALIZE",
618
    "XMLTABLE",
619
    "XMLTEXT",
620
    "XMLVALIDATE",
621
    "YEAR",
622
    "YES",
623
    "ZONE",
624
};
625

626
/**
627
 * Copied from -- http://www.sqlite.org/lang_keywords.html
628
 */
629
constexpr std::array<const char*, 145> sqlite_keywords = {
630
    "ABORT",
631
    "ACTION",
632
    "ADD",
633
    "AFTER",
634
    "ALL",
635
    "ALTER",
636
    "ALWAYS",
637
    "ANALYZE",
638
    "AND",
639
    "AS",
640
    "ASC",
641
    "ATTACH",
642
    "AUTOINCREMENT",
643
    "BEFORE",
644
    "BEGIN",
645
    "BETWEEN",
646
    "BY",
647
    "CASCADE",
648
    "CASE",
649
    "CAST",
650
    "CHECK",
651
    "COLLATE",
652
    "COLUMN",
653
    "COMMIT",
654
    "CONFLICT",
655
    "CONSTRAINT",
656
    "CREATE",
657
    "CROSS",
658
    "CURRENT",
659
    "CURRENT_DATE",
660
    "CURRENT_TIME",
661
    "CURRENT_TIMESTAMP",
662
    "DATABASE",
663
    "DEFAULT",
664
    "DEFERRABLE",
665
    "DEFERRED",
666
    "DELETE",
667
    "DESC",
668
    "DETACH",
669
    "DISTINCT",
670
    "DO",
671
    "DROP",
672
    "EACH",
673
    "ELSE",
674
    "END",
675
    "ESCAPE",
676
    "EXCEPT",
677
    "EXCLUDE",
678
    "EXCLUSIVE",
679
    "EXISTS",
680
    "EXPLAIN",
681
    "FAIL",
682
    "FILTER",
683
    "FIRST",
684
    "FOLLOWING",
685
    "FOR",
686
    "FOREIGN",
687
    "FROM",
688
    "FULL",
689
    "GENERATED",
690
    "GLOB",
691
    "GROUP",
692
    "GROUPS",
693
    "HAVING",
694
    "IF",
695
    "IGNORE",
696
    "IMMEDIATE",
697
    "IN",
698
    "INDEX",
699
    "INDEXED",
700
    "INITIALLY",
701
    "INNER",
702
    "INSERT",
703
    "INSTEAD",
704
    "INTERSECT",
705
    "INTO",
706
    "IS",
707
    "ISNULL",
708
    "JOIN",
709
    "KEY",
710
    "LAST",
711
    "LEFT",
712
    "LIKE",
713
    "LIMIT",
714
    "MATCH",
715
    "NATURAL",
716
    "NO",
717
    "NOT",
718
    "NOTHING",
719
    "NOTNULL",
720
    "NULL",
721
    "NULLS",
722
    "OF",
723
    "OFFSET",
724
    "ON",
725
    "OR",
726
    "ORDER",
727
    "OTHERS",
728
    "OUTER",
729
    "OVER",
730
    "PARTITION",
731
    "PLAN",
732
    "PRAGMA",
733
    "PRECEDING",
734
    "PRIMARY",
735
    "QUERY",
736
    "RAISE",
737
    "RANGE",
738
    "RECURSIVE",
739
    "REFERENCES",
740
    "REGEXP",
741
    "REINDEX",
742
    "RELEASE",
743
    "RENAME",
744
    "REPLACE",
745
    "RESTRICT",
746
    "RIGHT",
747
    "ROLLBACK",
748
    "ROW",
749
    "ROWS",
750
    "SAVEPOINT",
751
    "SELECT",
752
    "SET",
753
    "TABLE",
754
    "TEMP",
755
    "TEMPORARY",
756
    "THEN",
757
    "TIES",
758
    "TO",
759
    "TRANSACTION",
760
    "TRIGGER",
761
    "UNBOUNDED",
762
    "UNION",
763
    "UNIQUE",
764
    "UPDATE",
765
    "USING",
766
    "VACUUM",
767
    "VALUES",
768
    "VIEW",
769
    "VIRTUAL",
770
    "WHEN",
771
    "WHERE",
772
    "WINDOW",
773
    "WITH",
774
    "WITHOUT",
775
};
776

777
const char* sql_function_names[] = {
778
    /* http://www.sqlite.org/lang_aggfunc.html */
779
    "avg(",
780
    "count(",
781
    "group_concat(",
782
    "max(",
783
    "min(",
784
    "sum(",
785
    "total(",
786

787
    /* http://www.sqlite.org/lang_corefunc.html */
788
    "abs(",
789
    "changes()",
790
    "char(",
791
    "coalesce(",
792
    "glob(",
793
    "ifnull(",
794
    "instr(",
795
    "hex(",
796
    "last_insert_rowid()",
797
    "length(",
798
    "like(",
799
    "load_extension(",
800
    "lower(",
801
    "ltrim(",
802
    "nullif(",
803
    "printf(",
804
    "quote(",
805
    "random()",
806
    "randomblob(",
807
    "replace(",
808
    "round(",
809
    "rtrim(",
810
    "soundex(",
811
    "sqlite_compileoption_get(",
812
    "sqlite_compileoption_used(",
813
    "sqlite_source_id()",
814
    "sqlite_version()",
815
    "substr(",
816
    "total_changes()",
817
    "trim(",
818
    "typeof(",
819
    "unicode(",
820
    "upper(",
821
    "zeroblob(",
822

823
    /* http://www.sqlite.org/lang_datefunc.html */
824
    "date(",
825
    "time(",
826
    "datetime(",
827
    "julianday(",
828
    "strftime(",
829

830
    nullptr,
831
};
832

833
const std::unordered_map<unsigned char, const char*> sql_constraint_names = {
834
    {SQLITE_INDEX_CONSTRAINT_EQ, "="},
835
    {SQLITE_INDEX_CONSTRAINT_GT, ">"},
836
    {SQLITE_INDEX_CONSTRAINT_LE, "<="},
837
    {SQLITE_INDEX_CONSTRAINT_LT, "<"},
838
    {SQLITE_INDEX_CONSTRAINT_GE, ">="},
839
    {SQLITE_INDEX_CONSTRAINT_MATCH, "MATCH"},
840
    {SQLITE_INDEX_CONSTRAINT_LIKE, "LIKE"},
841
    {SQLITE_INDEX_CONSTRAINT_GLOB, "GLOB"},
842
    {SQLITE_INDEX_CONSTRAINT_REGEXP, "REGEXP"},
843
    {SQLITE_INDEX_CONSTRAINT_NE, "!="},
844
    {SQLITE_INDEX_CONSTRAINT_ISNOT, "IS NOT"},
845
    {SQLITE_INDEX_CONSTRAINT_ISNOTNULL, "IS NOT NULL"},
846
    {SQLITE_INDEX_CONSTRAINT_ISNULL, "IS NULL"},
847
    {SQLITE_INDEX_CONSTRAINT_IS, "IS"},
848
#if defined(SQLITE_INDEX_CONSTRAINT_LIMIT)
849
    {SQLITE_INDEX_CONSTRAINT_LIMIT, "LIMIT"},
850
    {SQLITE_INDEX_CONSTRAINT_OFFSET, "OFFSET"},
851
#endif
852
#if defined(SQLITE_INDEX_CONSTRAINT_FUNCTION)
853
    {SQLITE_INDEX_CONSTRAINT_FUNCTION, "function"},
854
#endif
855
};
856

857
std::multimap<std::string, const help_text*> sqlite_function_help;
858

859
const char* const LNAV_ATTACH_DB
860
    = "ATTACH DATABASE 'file:lnav_db?mode=memory&cache=shared' AS lnav_db";
861

862
static int
863
handle_db_list(void* ptr, int ncols, char** colvalues, char** colnames)
5✔
864
{
865
    auto* smc = (struct sqlite_metadata_callbacks*) ptr;
5✔
866

867
    smc->smc_db_list[colvalues[1]] = std::vector<std::string>();
15✔
868
    if (!smc->smc_database_list) {
5✔
869
        return 0;
×
870
    }
871

872
    return smc->smc_database_list(ptr, ncols, colvalues, colnames);
5✔
873
}
874

875
struct table_list_data {
876
    sqlite_metadata_callbacks* tld_callbacks;
877
    db_table_map_t::iterator* tld_iter;
878
};
879

880
static int
881
handle_table_list(void* ptr, int ncols, char** colvalues, char** colnames)
238✔
882
{
883
    auto* tld = (struct table_list_data*) ptr;
238✔
884

885
    (*tld->tld_iter)->second.emplace_back(colvalues[0]);
238✔
886
    if (!tld->tld_callbacks->smc_table_list) {
238✔
887
        return 0;
×
888
    }
889

890
    return tld->tld_callbacks->smc_table_list(
476✔
891
        tld->tld_callbacks, ncols, colvalues, colnames);
238✔
892
}
893

894
int
895
walk_sqlite_metadata(sqlite3* db, sqlite_metadata_callbacks& smc)
2✔
896
{
897
    auto_mem<char, sqlite3_free> errmsg;
2✔
898
    int retval;
899

900
    if (smc.smc_collation_list) {
2✔
901
        retval = sqlite3_exec(db,
2✔
902
                              "pragma collation_list",
903
                              smc.smc_collation_list,
904
                              &smc,
905
                              errmsg.out());
906
        if (retval != SQLITE_OK) {
2✔
907
            log_error("could not get collation list -- %s", errmsg.in());
×
908
            return retval;
×
909
        }
910
    }
911

912
    retval = sqlite3_exec(
2✔
913
        db, "pragma database_list", handle_db_list, &smc, errmsg.out());
914
    if (retval != SQLITE_OK) {
2✔
915
        log_error("could not get DB list -- %s", errmsg.in());
×
916
        return retval;
×
917
    }
918

919
    for (auto iter = smc.smc_db_list.begin(); iter != smc.smc_db_list.end();
7✔
920
         ++iter)
5✔
921
    {
922
        table_list_data tld = {&smc, &iter};
5✔
923
        auto_mem<char, sqlite3_free> query;
5✔
924

925
        query = sqlite3_mprintf(
926
            "SELECT name,sql FROM %Q.sqlite_master "
927
            "WHERE type in ('table', 'view')",
928
            iter->first.c_str());
5✔
929

930
        retval = sqlite3_exec(db, query, handle_table_list, &tld, errmsg.out());
5✔
931
        if (retval != SQLITE_OK) {
5✔
932
            log_error("could not get table list -- %s", errmsg.in());
×
933
            return retval;
×
934
        }
935

936
        for (auto table_iter = iter->second.begin();
5✔
937
             table_iter != iter->second.end();
243✔
938
             ++table_iter)
238✔
939
        {
940
            auto_mem<char, sqlite3_free> table_query;
238✔
941
            smc.smc_table_name = *table_iter;
238✔
942

943
            table_query = sqlite3_mprintf("pragma %Q.table_xinfo(%Q)",
944
                                          iter->first.c_str(),
238✔
945
                                          smc.smc_table_name.c_str());
476✔
946
            if (table_query == nullptr) {
238✔
947
                return SQLITE_NOMEM;
×
948
            }
949

950
            if (smc.smc_table_info) {
238✔
951
                retval = sqlite3_exec(
238✔
952
                    db, table_query, smc.smc_table_info, &smc, errmsg.out());
953
                if (retval != SQLITE_OK) {
238✔
954
                    log_error("could not get table info -- %s", errmsg.in());
×
955
                    return retval;
×
956
                }
957
            }
958

959
            table_query = sqlite3_mprintf("pragma %Q.foreign_key_list(%Q)",
960
                                          iter->first.c_str(),
238✔
961
                                          smc.smc_table_name.c_str());
476✔
962
            if (table_query == nullptr) {
238✔
963
                return SQLITE_NOMEM;
×
964
            }
965

966
            if (smc.smc_foreign_key_list) {
238✔
967
                retval = sqlite3_exec(db,
238✔
968
                                      table_query,
969
                                      smc.smc_foreign_key_list,
970
                                      &smc,
971
                                      errmsg.out());
972
                if (retval != SQLITE_OK) {
238✔
973
                    log_error("could not get foreign key list -- %s",
×
974
                              errmsg.in());
975
                    return retval;
×
976
                }
977
            }
978
        }
238✔
979
    }
5✔
980

981
    return retval;
2✔
982
}
2✔
983

984
static int
985
schema_collation_list(void* ptr, int ncols, char** colvalues, char** colnames)
16✔
986
{
987
    return 0;
16✔
988
}
989

990
static int
991
schema_db_list(void* ptr, int ncols, char** colvalues, char** colnames)
5✔
992
{
993
    auto* smc = (sqlite_metadata_callbacks*) ptr;
5✔
994
    auto& schema_out = *((std::string*) smc->smc_userdata);
5✔
995
    auto_mem<char, sqlite3_free> attach_sql;
5✔
996

997
    attach_sql = sqlite3_mprintf(
998
        "ATTACH DATABASE %Q AS %Q;\n", colvalues[2], colvalues[1]);
5✔
999

1000
    schema_out += attach_sql;
5✔
1001

1002
    return 0;
5✔
1003
}
5✔
1004

1005
static int
1006
schema_table_list(void* ptr, int ncols, char** colvalues, char** colnames)
238✔
1007
{
1008
    auto smc = (sqlite_metadata_callbacks*) ptr;
238✔
1009
    auto& schema_out = *((std::string*) smc->smc_userdata);
238✔
1010
    auto_mem<char, sqlite3_free> create_sql;
238✔
1011

1012
    create_sql = sqlite3_mprintf("%s;\n", colvalues[1]);
238✔
1013

1014
    schema_out += create_sql;
238✔
1015

1016
    return 0;
238✔
1017
}
238✔
1018

1019
static int
1020
schema_table_info(void* ptr, int ncols, char** colvalues, char** colnames)
7,394✔
1021
{
1022
    return 0;
7,394✔
1023
}
1024

1025
static int
1026
schema_foreign_key_list(void* ptr, int ncols, char** colvalues, char** colnames)
×
1027
{
1028
    return 0;
×
1029
}
1030

1031
void
1032
dump_sqlite_schema(sqlite3* db, std::string& schema_out)
2✔
1033
{
1034
    sqlite_metadata_callbacks schema_sql_meta_callbacks = {
2✔
1035
        schema_collation_list,
1036
        schema_db_list,
1037
        schema_table_list,
1038
        schema_table_info,
1039
        schema_foreign_key_list,
1040
        &schema_out,
1041
        {},
1042
    };
2✔
1043

1044
    walk_sqlite_metadata(db, schema_sql_meta_callbacks);
2✔
1045
}
2✔
1046

1047
void
1048
attach_sqlite_db(sqlite3* db, const std::string& filename)
1✔
1049
{
1050
    static const std::regex db_name_converter("[^\\w]");
1✔
1051

1052
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
1✔
1053

1054
    if (sqlite3_prepare_v2(db, "ATTACH DATABASE ? as ?", -1, stmt.out(), NULL)
1✔
1055
        != SQLITE_OK)
1✔
1056
    {
1057
        log_error("could not prepare DB attach statement -- %s",
×
1058
                  sqlite3_errmsg(db));
1059
        return;
×
1060
    }
1061

1062
    if (sqlite3_bind_text(
1✔
1063
            stmt.in(), 1, filename.c_str(), filename.length(), SQLITE_TRANSIENT)
1✔
1064
        != SQLITE_OK)
1✔
1065
    {
1066
        log_error("could not bind DB attach statement -- %s",
×
1067
                  sqlite3_errmsg(db));
1068
        return;
×
1069
    }
1070

1071
    size_t base_start = filename.find_last_of("/\\");
1✔
1072
    std::string db_name;
1✔
1073

1074
    if (base_start == std::string::npos) {
1✔
1075
        db_name = filename;
×
1076
    } else {
1077
        db_name = filename.substr(base_start + 1);
1✔
1078
    }
1079

1080
    db_name = std::regex_replace(db_name, db_name_converter, "_");
1✔
1081

1082
    if (sqlite3_bind_text(
1✔
1083
            stmt.in(), 2, db_name.c_str(), db_name.length(), SQLITE_TRANSIENT)
1✔
1084
        != SQLITE_OK)
1✔
1085
    {
1086
        log_error("could not bind DB attach statement -- %s",
×
1087
                  sqlite3_errmsg(db));
1088
        return;
×
1089
    }
1090

1091
    if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
1✔
1092
        log_error("could not execute DB attach statement -- %s",
×
1093
                  sqlite3_errmsg(db));
1094
        return;
×
1095
    }
1096
}
1✔
1097

1098
static void
1099
sqlite_logger(void* dummy, int code, const char* msg)
238✔
1100
{
1101
    lnav_log_level_t level;
1102

1103
    switch (code) {
238✔
1104
        case SQLITE_OK:
×
1105
            level = lnav_log_level_t::DEBUG;
×
1106
            break;
×
1107
#ifdef SQLITE_NOTICE
1108
        case SQLITE_NOTICE:
×
1109
            level = lnav_log_level_t::INFO;
×
1110
            break;
×
1111
#endif
1112
#ifdef SQLITE_WARNING
1113
        case SQLITE_WARNING:
×
1114
            level = lnav_log_level_t::WARNING;
×
1115
            break;
×
1116
#endif
1117
        default:
238✔
1118
            level = lnav_log_level_t::ERROR;
238✔
1119
            break;
238✔
1120
    }
1121

1122
    log_msg(level, __FILE__, __LINE__, "(%d) %s", code, msg);
238✔
1123

1124
    ensure(code != 21);
238✔
1125
}
238✔
1126

1127
void
1128
sql_install_logger()
836✔
1129
{
1130
#ifdef SQLITE_CONFIG_LOG
1131
    sqlite3_config(SQLITE_CONFIG_LOG, sqlite_logger, NULL);
836✔
1132
#endif
1133
}
836✔
1134

1135
bool
1136
sql_ident_needs_quote(const char* ident)
2,988✔
1137
{
1138
    for (int lpc = 0; ident[lpc]; lpc++) {
27,396✔
1139
        if (!isalnum(ident[lpc]) && ident[lpc] != '_') {
25,615✔
1140
            return true;
1,207✔
1141
        }
1142
    }
1143

1144
    return false;
1,781✔
1145
}
1146

1147
std::string
1148
sql_quote_text(const std::string& str)
×
1149
{
1150
    auto quoted_token = lnav::sql::mprintf("%Q", str.c_str());
×
1151
    return {quoted_token};
×
1152
}
1153

1154
auto_mem<char, sqlite3_free>
1155
sql_quote_ident(const char* ident)
607,441✔
1156
{
1157
    bool needs_quote = false;
607,441✔
1158
    size_t quote_count = 0, alloc_size;
607,441✔
1159
    auto_mem<char, sqlite3_free> retval;
607,441✔
1160

1161
    for (int lpc = 0; ident[lpc]; lpc++) {
6,018,694✔
1162
        if ((lpc == 0 && isdigit(ident[lpc]))
5,411,253✔
1163
            || (!isalnum(ident[lpc]) && ident[lpc] != '_'))
5,411,253✔
1164
        {
1165
            needs_quote = true;
81,149✔
1166
        }
1167
        if (ident[lpc] == '"') {
5,411,253✔
1168
            quote_count += 1;
×
1169
        }
1170
    }
1171

1172
    alloc_size = strlen(ident) + quote_count * 2 + (needs_quote ? 2 : 0) + 1;
607,441✔
1173
    if ((retval = (char*) sqlite3_malloc(alloc_size)) == nullptr) {
607,441✔
1174
        retval = nullptr;
×
1175
    } else {
1176
        char* curr = retval;
607,441✔
1177

1178
        if (needs_quote) {
607,441✔
1179
            curr[0] = '"';
53,880✔
1180
            curr += 1;
53,880✔
1181
        }
1182
        for (size_t lpc = 0; ident[lpc] != '\0'; lpc++) {
6,018,694✔
1183
            switch (ident[lpc]) {
5,411,253✔
1184
                case '"':
×
1185
                    curr[0] = '"';
×
1186
                    curr += 1;
×
1187
                default:
5,411,253✔
1188
                    curr[0] = ident[lpc];
5,411,253✔
1189
                    break;
5,411,253✔
1190
            }
1191
            curr += 1;
5,411,253✔
1192
        }
1193
        if (needs_quote) {
607,441✔
1194
            curr[0] = '"';
53,880✔
1195
            curr += 1;
53,880✔
1196
        }
1197

1198
        *curr = '\0';
607,441✔
1199
    }
1200

1201
    return retval;
607,441✔
1202
}
×
1203

1204
std::string
1205
sql_safe_ident(const string_fragment& ident)
11,706✔
1206
{
1207
    std::string retval = std::to_string(ident);
11,706✔
1208

1209
    for (size_t lpc = 0; lpc < retval.size(); lpc++) {
117,766✔
1210
        char ch = retval[lpc];
106,060✔
1211

1212
        if (isalnum(ch) || ch == '_') {
106,060✔
1213
            retval[lpc] = tolower(ch);
93,580✔
1214
        } else {
1215
            retval[lpc] = '_';
12,480✔
1216
        }
1217
    }
1218

1219
    return retval;
11,706✔
1220
}
×
1221

1222
attr_line_t
1223
annotate_sql_with_error(sqlite3* db, const char* sql, const char* tail)
9✔
1224
{
1225
    const auto* errmsg = sqlite3_errmsg(db);
9✔
1226
    attr_line_t retval;
9✔
1227
    int erroff = -1;
9✔
1228

1229
#if defined(HAVE_SQLITE3_ERROR_OFFSET)
1230
    erroff = sqlite3_error_offset(db);
9✔
1231
#endif
1232
    if (tail != nullptr) {
9✔
1233
        const auto* tail_lf = strchr(tail, '\n');
8✔
1234
        if (tail_lf == nullptr) {
8✔
1235
            tail = tail + strlen(tail);
7✔
1236
        } else {
1237
            tail = tail_lf;
1✔
1238
        }
1239
        retval.append(string_fragment::from_bytes(sql, tail - sql));
8✔
1240
    } else {
1241
        retval.append(sql);
1✔
1242
    }
1243
    if (erroff >= retval.length()) {
9✔
1244
        erroff -= 1;
×
1245
    }
1246
    if (erroff != -1 && !endswith(retval.get_string(), "\n")) {
9✔
1247
        retval.append("\n");
1✔
1248
    }
1249
    retval.with_attr_for_all(VC_ROLE.value(role_t::VCR_QUOTED_CODE));
9✔
1250
    readline_sql_highlighter(
9✔
1251
        retval, lnav::sql::dialect::sqlite, retval.length());
9✔
1252

1253
    if (erroff != -1) {
9✔
1254
        auto line_with_error
1255
            = string_fragment::from_str(retval.get_string())
1✔
1256
                  .find_boundaries_around(erroff, string_fragment::tag1{'\n'});
1✔
1257
        auto erroff_in_line = erroff - line_with_error.sf_begin;
1✔
1258

1259
        attr_line_t pointer;
1✔
1260

1261
        pointer.append(erroff_in_line, ' ')
1✔
1262
            .append("^ "_snippet_border)
1✔
1263
            .append(lnav::roles::error(errmsg))
2✔
1264
            .append("\n");
1✔
1265

1266
        retval.insert(line_with_error.sf_end + 1, pointer).rtrim();
1✔
1267
    }
1✔
1268

1269
    return retval;
9✔
1270
}
×
1271

1272
static void
1273
sql_execute_script(sqlite3* db,
829✔
1274
                   const std::map<std::string, scoped_value_t>& global_vars,
1275
                   const char* src_name,
1276
                   sqlite3_stmt* stmt,
1277
                   std::vector<lnav::console::user_message>& errors)
1278
{
1279
    std::map<std::string, scoped_value_t> lvars;
829✔
1280
    bool done = false;
829✔
1281
    int param_count;
1282

1283
    sqlite3_clear_bindings(stmt);
829✔
1284

1285
    param_count = sqlite3_bind_parameter_count(stmt);
829✔
1286
    for (int lpc = 0; lpc < param_count; lpc++) {
829✔
1287
        const char* name;
1288

1289
        name = sqlite3_bind_parameter_name(stmt, lpc + 1);
×
1290
        if (name[0] == '$') {
×
1291
            const char* env_value;
1292
            auto iter = lvars.find(&name[1]);
×
1293
            if (iter != lvars.end()) {
×
1294
                mapbox::util::apply_visitor(
×
1295
                    sqlitepp::bind_visitor(stmt, lpc + 1), iter->second);
×
1296
            } else {
1297
                auto giter = global_vars.find(&name[1]);
×
1298
                if (giter != global_vars.end()) {
×
1299
                    mapbox::util::apply_visitor(
×
1300
                        sqlitepp::bind_visitor(stmt, lpc + 1), giter->second);
×
1301
                } else if ((env_value = getenv(&name[1])) != nullptr) {
×
1302
                    sqlite3_bind_text(
×
1303
                        stmt, lpc + 1, env_value, -1, SQLITE_TRANSIENT);
1304
                } else {
1305
                    sqlite3_bind_null(stmt, lpc + 1);
×
1306
                }
1307
            }
1308
        } else {
1309
            sqlite3_bind_null(stmt, lpc + 1);
×
1310
        }
1311
    }
1312
    while (!done) {
1,658✔
1313
        int retcode = sqlite3_step(stmt);
829✔
1314
        switch (retcode) {
829✔
1315
            case SQLITE_OK:
828✔
1316
            case SQLITE_DONE:
1317
                done = true;
828✔
1318
                break;
828✔
1319

1320
            case SQLITE_ROW: {
×
1321
                int ncols = sqlite3_column_count(stmt);
×
1322

1323
                for (int lpc = 0; lpc < ncols; lpc++) {
×
1324
                    const char* name = sqlite3_column_name(stmt, lpc);
×
1325
                    auto* raw_value = sqlite3_column_value(stmt, lpc);
×
1326
                    auto value_type = sqlite3_value_type(raw_value);
×
1327
                    scoped_value_t value;
×
1328

1329
                    switch (value_type) {
×
1330
                        case SQLITE_INTEGER:
×
1331
                            value = (int64_t) sqlite3_value_int64(raw_value);
×
1332
                            break;
×
1333
                        case SQLITE_FLOAT:
×
1334
                            value = sqlite3_value_double(raw_value);
×
1335
                            break;
×
1336
                        case SQLITE_NULL:
×
1337
                            value = null_value_t{};
×
1338
                            break;
×
1339
                        default:
×
1340
                            value = string_fragment::from_bytes(
×
1341
                                sqlite3_value_text(raw_value),
1342
                                sqlite3_value_bytes(raw_value));
×
1343
                            break;
×
1344
                    }
1345
                    lvars[name] = value;
×
1346
                }
1347
                break;
×
1348
            }
1349

1350
            default: {
1✔
1351
                const auto* sql_str = sqlite3_sql(stmt);
1✔
1352
                auto sql_content
1353
                    = annotate_sql_with_error(db, sql_str, nullptr);
1✔
1354

1355
                errors.emplace_back(
1✔
1356
                    lnav::console::user_message::error(
2✔
1357
                        "failed to execute SQL statement")
1358
                        .with_reason(sqlite3_errmsg_to_attr_line(db))
2✔
1359
                        .with_snippet(lnav::console::snippet::from(
3✔
1360
                            intern_string::lookup(src_name), sql_content)));
1361
                done = true;
1✔
1362
                break;
1✔
1363
            }
1✔
1364
        }
1365
    }
1366

1367
    sqlite3_reset(stmt);
829✔
1368
}
829✔
1369

1370
static void
1371
sql_compile_script(sqlite3* db,
770✔
1372
                   const std::map<std::string, scoped_value_t>& global_vars,
1373
                   const char* src_name,
1374
                   const char* script_orig,
1375
                   std::vector<lnav::console::user_message>& errors)
1376
{
1377
    const char* script = script_orig;
770✔
1378

1379
    while (script != nullptr && script[0]) {
1,599✔
1380
        auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
1,599✔
1381
        int line_number = 1;
1,599✔
1382
        const char* tail;
1383
        int retcode;
1384

1385
        while (isspace(*script) && script[0]) {
3,257✔
1386
            script += 1;
1,658✔
1387
        }
1388
        for (const char* ch = script_orig; ch < script && ch[0]; ch++) {
147,484✔
1389
            if (*ch == '\n') {
145,885✔
1390
                line_number += 1;
4,946✔
1391
            }
1392
        }
1393

1394
        retcode = sqlite3_prepare_v2(db, script, -1, stmt.out(), &tail);
1,599✔
1395
        log_debug("retcode %d  %p %p", retcode, script, tail);
1,599✔
1396
        if (retcode != SQLITE_OK) {
1,599✔
1397
            const auto errmsg = string_fragment::from_c_str(sqlite3_errmsg(db));
1✔
1398
            auto sql_content = annotate_sql_with_error(db, script, tail);
1✔
1399
            auto um = lnav::console::user_message::error(
2✔
1400
                          "failed to compile SQL statement")
1401
                          .with_reason(errmsg.to_string())
2✔
1402
                          .with_snippet(
1✔
1403
                              lnav::console::snippet::from(
3✔
1404
                                  intern_string::lookup(src_name), sql_content)
1405
                                  .with_line(line_number));
2✔
1406
            if (errmsg.startswith("no such table: main.lnav")) {
1✔
1407
                um.with_help(
×
1408
                    attr_line_t("The lnav tables have been moved to the ")
×
1409
                        .append_quoted("lnav_db"_symbol)
×
1410
                        .append(" database.  Try prefixing the name with ")
×
1411
                        .append("lnav_db."_quoted_code));
×
1412
            }
1413
            errors.emplace_back(um);
1✔
1414
            break;
1✔
1415
        }
1✔
1416
        if (script == tail) {
1,598✔
1417
            break;
769✔
1418
        }
1419
        if (stmt == nullptr) {
829✔
1420
        } else {
1421
            sql_execute_script(db, global_vars, src_name, stmt.in(), errors);
829✔
1422
        }
1423

1424
        script = tail;
829✔
1425
    }
1,599✔
1426
}
770✔
1427

1428
void
1429
sql_execute_script(sqlite3* db,
770✔
1430
                   const std::map<std::string, scoped_value_t>& global_vars,
1431
                   const char* src_name,
1432
                   const char* script,
1433
                   std::vector<lnav::console::user_message>& errors)
1434
{
1435
    sql_compile_script(db, global_vars, src_name, script, errors);
770✔
1436
}
770✔
1437

1438
static const struct {
1439
    int sqlite_type;
1440
    const char* collator;
1441
    const char* sample;
1442
} TYPE_TEST_VALUE[] = {
1443
    {SQLITE3_TEXT, "", "foobar"},
1444
    {SQLITE_FLOAT, "", "123.0"},
1445
    {SQLITE_INTEGER, "", "123"},
1446
    {SQLITE_TEXT, "ipaddress", "127.0.0.1"},
1447
    {SQLITE_TEXT, "measure_with_units", "123ms"},
1448
    {SQLITE_TEXT, "measure_with_units", "123 ms"},
1449
    {SQLITE_TEXT, "measure_with_units", "123KB"},
1450
    {SQLITE_TEXT, "measure_with_units", "123 KB"},
1451
    {SQLITE_TEXT, "measure_with_units", "123Kbps"},
1452
    {SQLITE_TEXT, "measure_with_units", "123.0 Kbps"},
1453
    {SQLITE_TEXT, "measure_with_units", "123.0KB"},
1454
    {SQLITE_TEXT, "measure_with_units", "123.0 KB"},
1455
    {SQLITE_TEXT, "measure_with_units", "123.0Kbps"},
1456
    {SQLITE_TEXT, "measure_with_units", "123.0 Kbps"},
1457
};
1458

1459
int
1460
guess_type_from_pcre(const std::string& pattern, std::string& collator)
56,968✔
1461
{
1462
    log_info("guessing SQL type from pattern: %s", pattern.c_str());
56,968✔
1463
    auto compile_res = lnav::pcre2pp::code::from(pattern);
56,968✔
1464
    if (compile_res.isErr()) {
56,968✔
1465
        return SQLITE3_TEXT;
×
1466
    }
1467

1468
    auto re = compile_res.unwrap();
56,968✔
1469
    std::vector<int> matches;
56,968✔
1470
    int retval = SQLITE3_TEXT;
56,968✔
1471
    int index = 0;
56,968✔
1472

1473
    collator.clear();
56,968✔
1474
    for (const auto& test_value : TYPE_TEST_VALUE) {
154,523✔
1475
        log_info("  testing sample: %s", test_value.sample);
150,251✔
1476
        const auto find_res
1477
            = re.find_in(string_fragment::from_c_str(test_value.sample),
300,502✔
1478
                         PCRE2_ANCHORED)
1479
                  .ignore_error();
150,251✔
1480
        if (find_res && find_res->f_all.sf_begin == 0
218,612✔
1481
            && find_res->f_remaining.empty())
218,612✔
1482
        {
1483
            log_info("    matched!");
52,696✔
1484
            matches.push_back(index);
52,696✔
1485
            break;
52,696✔
1486
        }
1487
        if (!find_res) {
97,555✔
1488
            log_info("    mismatch");
81,890✔
1489
        } else if (!find_res->f_remaining.empty()) {
15,665✔
1490
            log_info("    incomplete match");
15,665✔
1491
        }
1492

1493
        index += 1;
97,555✔
1494
    }
1495

1496
    if (matches.size() == 1) {
56,968✔
1497
        retval = TYPE_TEST_VALUE[matches.front()].sqlite_type;
52,696✔
1498
        collator = TYPE_TEST_VALUE[matches.front()].collator;
52,696✔
1499
    }
1500

1501
    return retval;
56,968✔
1502
}
56,968✔
1503

1504
const char*
1505
sqlite3_type_to_string(int type)
607,445✔
1506
{
1507
    switch (type) {
607,445✔
1508
        case SQLITE_FLOAT:
11,580✔
1509
            return "FLOAT";
11,580✔
1510
        case SQLITE_INTEGER:
147,974✔
1511
            return "INTEGER";
147,974✔
1512
        case SQLITE_TEXT:
447,890✔
1513
            return "TEXT";
447,890✔
1514
        case SQLITE_NULL:
1✔
1515
            return "NULL";
1✔
1516
        case SQLITE_BLOB:
×
1517
            return "BLOB";
×
1518
    }
1519

1520
    ensure(!!!"Invalid sqlite type");
×
1521

1522
    return nullptr;
1523
}
1524

1525
/* XXX figure out how to do this with the template */
1526
void
1527
sqlite_close_wrapper(void* mem)
1,728✔
1528
{
1529
    sqlite3_close_v2((sqlite3*) mem);
1,728✔
1530
}
1,728✔
1531

1532
static thread_local std::set<std::string>* tl_authorizer_table_capture
1533
    = nullptr;
1534

1535
int
1536
sqlite_authorizer(void* pUserData,
1,066,375✔
1537
                  int action_code,
1538
                  const char* detail1,
1539
                  const char* detail2,
1540
                  const char* detail3,
1541
                  const char* detail4)
1542
{
1543
    static auto& lnflags = injector::get<lnav_flags_storage&>();
1,066,375✔
1544

1545
    if (lnflags.is_set<lnav_flags::secure_mode>()
1,066,375✔
1546
        && action_code == SQLITE_ATTACH)
1,066,375✔
1547
    {
1548
        return SQLITE_DENY;
4✔
1549
    }
1550
    if (action_code == SQLITE_READ && tl_authorizer_table_capture != nullptr
1,066,371✔
1551
        && detail1 != nullptr)
3,249✔
1552
    {
1553
        tl_authorizer_table_capture->emplace(detail1);
3,249✔
1554
    }
1555
    return SQLITE_OK;
1,066,371✔
1556
}
1557

1558
sql_table_capture_guard::sql_table_capture_guard(std::set<std::string>& into)
3,347✔
1559
    : stcg_prev(tl_authorizer_table_capture)
3,347✔
1560
{
1561
    tl_authorizer_table_capture = &into;
3,347✔
1562
}
3,347✔
1563

1564
sql_table_capture_guard::~sql_table_capture_guard()
3,347✔
1565
{
1566
    tl_authorizer_table_capture = this->stcg_prev;
3,347✔
1567
}
3,347✔
1568

1569
attr_line_t
1570
sqlite3_errmsg_to_attr_line(sqlite3* db)
1✔
1571
{
1572
    const auto* errmsg = sqlite3_errmsg(db);
1✔
1573
    if (startswith(errmsg, sqlitepp::ERROR_PREFIX)) {
1✔
1574
        auto from_res = lnav::from_json<lnav::console::user_message>(
1575
            &errmsg[strlen(sqlitepp::ERROR_PREFIX)]);
1✔
1576

1577
        if (from_res.isOk()) {
1✔
1578
            return from_res.unwrap().to_attr_line();
1✔
1579
        }
1580

1581
        return from_res.unwrapErr()[0].um_message.get_string();
×
1582
    }
1✔
1583

1584
    return attr_line_t(errmsg);
×
1585
}
1586

1587
static void
1588
append_kw(std::string& retval, bool& first, const char* kw)
292,817✔
1589
{
1590
    if (!first) {
292,817✔
1591
        retval.append("|");
291,991✔
1592
    } else {
1593
        first = false;
826✔
1594
    }
1595
    retval.append("\\b");
292,817✔
1596
    retval.append(kw);
292,817✔
1597
    retval.append("\\b");
292,817✔
1598
}
292,817✔
1599

1600
static std::string
1601
sql_keyword_re(lnav::sql::dialect dia)
826✔
1602
{
1603
    std::string retval = "(?:";
826✔
1604
    auto first = true;
826✔
1605

1606
    switch (dia) {
826✔
1607
        case lnav::sql::dialect::sqlite:
413✔
1608
            for (const char* kw : sqlite_keywords) {
60,298✔
1609
                append_kw(retval, first, kw);
59,885✔
1610
            }
1611
            break;
413✔
1612
        default:
413✔
1613
            for (const char* kw : pg_sql_keywords) {
233,345✔
1614
                append_kw(retval, first, kw);
232,932✔
1615
            }
1616
            break;
413✔
1617
    }
1618

1619
    retval += ")";
826✔
1620

1621
    return retval;
1,652✔
1622
}
×
1623

1624
constexpr string_attr_type<void> SQL_COMMAND_ATTR("sql_command");
1625
constexpr string_attr_type<void> SQL_KEYWORD_ATTR("sql_keyword");
1626
constexpr string_attr_type<void> SQL_IDENTIFIER_ATTR("sql_ident");
1627
constexpr string_attr_type<std::string> SQL_FUNCTION_ATTR("sql_func");
1628
constexpr string_attr_type<void> SQL_STRING_ATTR("sql_string");
1629
constexpr string_attr_type<void> SQL_HEX_LIT_ATTR("sql_hex_lit");
1630
constexpr string_attr_type<void> SQL_NUMBER_ATTR("sql_number");
1631
constexpr string_attr_type<void> SQL_OPERATOR_ATTR("sql_oper");
1632
constexpr string_attr_type<void> SQL_PAREN_ATTR("sql_paren");
1633
constexpr string_attr_type<void> SQL_COMMA_ATTR("sql_comma");
1634
constexpr string_attr_type<void> SQL_GARBAGE_ATTR("sql_garbage");
1635
constexpr string_attr_type<void> SQL_COMMENT_ATTR("sql_comment");
1636

1637
void
1638
annotate_sql_statement(attr_line_t& al, lnav::sql::dialect dia)
1,907✔
1639
{
1640
    static const std::string sqlite_keyword_re_str
1641
        = R"(\A)" + sql_keyword_re(lnav::sql::dialect::sqlite);
1,907✔
1642
    static const auto sqlite_keyword_re
1643
        = lnav::pcre2pp::code::from(sqlite_keyword_re_str, PCRE2_CASELESS)
826✔
1644
              .unwrap();
2,320✔
1645
    static const std::string sql_keyword_re_str
1646
        = R"(\A)" + sql_keyword_re(lnav::sql::dialect::sql);
1,907✔
1647
    static const auto sql_keyword_re
1648
        = lnav::pcre2pp::code::from(sql_keyword_re_str, PCRE2_CASELESS)
826✔
1649
              .unwrap();
2,320✔
1650

1651
    static const struct {
1652
        lnav::pcre2pp::code re;
1653
        const string_attr_type<void>* type;
1654
    } PATTERNS[] = {
1655
        {
1656
            lnav::pcre2pp::code::from_const(R"(\A,)"),
1657
            &SQL_COMMA_ATTR,
1658
        },
1659
        {
1660
            lnav::pcre2pp::code::from_const(R"(\A\(|\A\))"),
1661
            &SQL_PAREN_ATTR,
1662
        },
1663
        {
1664
            lnav::pcre2pp::code::from_const(
1665
                R"(\A(?:x|X)'(?:(?:[0-9a-fA-F]{2})+'|[0-9a-fA-F]*$))"),
1666
            &SQL_HEX_LIT_ATTR,
1667
        },
1668
        {
1669
            lnav::pcre2pp::code::from_const(R"(\A'[^']*('(?:'[^']*')*|$))"),
1670
            &SQL_STRING_ATTR,
1671
        },
1672
        {
1673
            lnav::pcre2pp::code::from_const(R"(\A0x[0-9a-fA-F]+)"),
1674
            &SQL_NUMBER_ATTR,
1675
        },
1676
        {
1677
            lnav::pcre2pp::code::from_const(
1678
                R"(\A-?\d+(?:\.\d+)?(?:[eE][\-\+]?\d+)?\b)"),
1679
            &SQL_NUMBER_ATTR,
1680
        },
1681
        {
1682
            lnav::pcre2pp::code::from_const(
1683
                R"(\A(((\$|:|@)?\b[a-z_]\w*)|\"([^\"]+)\"|\[([^\]]+)]))",
1684
                PCRE2_CASELESS),
1685
            &SQL_IDENTIFIER_ATTR,
1686
        },
1687
        {
1688
            lnav::pcre2pp::code::from_const(R"(\A--.*)"),
1689
            &SQL_COMMENT_ATTR,
1690
        },
1691
        {
1692
            lnav::pcre2pp::code::from_const(
1693
                R"(\A(~|%|\*|\->{1,2}|<=|>=|<<|>>|<>|<|>|={1,2}|!=?|\-|\+|\|\|{1,2}|&|::))"),
1694
            &SQL_OPERATOR_ATTR,
1695
        },
1696
        {
1697
            lnav::pcre2pp::code::from_const(R"(\A[0-9][a-zA-Z0-9\-\._]+)"),
1698
            &SQL_GARBAGE_ATTR,
1699
        },
1700
        {
1701
            lnav::pcre2pp::code::from_const(R"(\A.)"),
1702
            &SQL_GARBAGE_ATTR,
1703
        },
1704
    };
1,907✔
1705

1706
    static const auto cmd_pattern
1707
        = lnav::pcre2pp::code::from_const(R"(^;?(\.\w+))");
1,907✔
1708
    static const auto ws_pattern = lnav::pcre2pp::code::from_const(R"(\A\s+)");
1,907✔
1709

1710
    const auto& line = al.get_string();
1,907✔
1711
    auto& sa = al.get_attrs();
1,907✔
1712

1713
    if (lnav::sql::is_prql(line)) {
1,907✔
1714
        lnav::sql::annotate_prql_statement(al);
16✔
1715
        return;
24✔
1716
    }
1717

1718
    auto cmd_find_res
1719
        = cmd_pattern.find_in(line, PCRE2_ANCHORED).ignore_error();
1,891✔
1720
    if (cmd_find_res) {
1,891✔
1721
        auto cap = cmd_find_res->f_all;
8✔
1722
        sa.emplace_back(line_range(cap.sf_begin, cap.sf_end),
8✔
1723
                        SQL_COMMAND_ATTR.value());
16✔
1724
        return;
8✔
1725
    }
1726

1727
    auto remaining = string_fragment::from_str(line);
1,883✔
1728
    while (!remaining.empty()) {
30,619✔
1729
        auto ws_find_res = ws_pattern.find_in(remaining).ignore_error();
28,736✔
1730
        if (ws_find_res) {
28,736✔
1731
            remaining = ws_find_res->f_remaining;
10,062✔
1732
            continue;
15,006✔
1733
        }
1734
        const auto& kw_pat = dia == lnav::sql::dialect::sqlite
18,674✔
1735
            ? sqlite_keyword_re
1736
            : sql_keyword_re;
1737
        auto kw_pat_find_res = kw_pat.find_in(remaining).ignore_error();
18,674✔
1738
        if (kw_pat_find_res) {
18,674✔
1739
            sa.emplace_back(to_line_range(kw_pat_find_res->f_all),
4,944✔
1740
                            SQL_KEYWORD_ATTR.value());
9,888✔
1741
            remaining = kw_pat_find_res->f_remaining;
4,944✔
1742
            continue;
4,944✔
1743
        }
1744
        for (const auto& pat : PATTERNS) {
69,130✔
1745
            auto pat_find_res = pat.re.find_in(remaining).ignore_error();
69,130✔
1746
            if (pat_find_res) {
69,130✔
1747
                sa.emplace_back(to_line_range(pat_find_res->f_all),
13,730✔
1748
                                pat.type->value());
27,460✔
1749
                remaining = pat_find_res->f_remaining;
13,730✔
1750
                break;
13,730✔
1751
            }
1752
        }
1753
    }
1754

1755
    string_attrs_t::const_iterator iter;
1,883✔
1756
    int start = 0;
1,883✔
1757

1758
    while ((iter = find_string_attr(sa, &SQL_IDENTIFIER_ATTR, start))
8,448✔
1759
           != sa.end())
13,130✔
1760
    {
1761
        string_attrs_t::const_iterator piter;
4,682✔
1762

1763
        start = iter->sa_range.lr_end;
4,682✔
1764
        if (start < (ssize_t) line.length() && line[start] == '(') {
4,682✔
1765
            ssize_t pstart = start + 1;
1,361✔
1766
            int depth = 1;
1,361✔
1767

1768
            while (depth > 0
1,361✔
1769
                   && (piter = find_string_attr(sa, &SQL_PAREN_ATTR, pstart))
6,845✔
1770
                       != sa.end())
6,845✔
1771
            {
1772
                if (line[piter->sa_range.lr_start] == '(') {
1,820✔
1773
                    depth += 1;
236✔
1774
                } else {
1775
                    depth -= 1;
1,584✔
1776
                }
1777
                pstart = piter->sa_range.lr_end;
1,820✔
1778
            }
1779

1780
            line_range func_range{iter->sa_range.lr_start};
1,361✔
1781
            if (piter == sa.end()) {
1,361✔
1782
                func_range.lr_end = line.length();
12✔
1783
            } else {
1784
                func_range.lr_end = piter->sa_range.lr_end;
1,349✔
1785
            }
1786
            auto func_name = al.to_string_fragment(iter);
1,361✔
1787
            sa.emplace_back(
1,361✔
1788
                func_range,
1789
                SQL_FUNCTION_ATTR.value(tolower(func_name.to_string())));
2,722✔
1790
        }
1791
    }
1792

1793
    // remove_string_attr(sa, &SQL_PAREN_ATTR);
1794
    stable_sort(sa.begin(), sa.end());
1,883✔
1795
}
1796

1797
std::vector<const help_text*>
1798
find_sql_help_for_line(const attr_line_t& al, size_t x)
10✔
1799
{
1800
    static const auto* sql_cmd_map
1801
        = injector::get<readline_context::command_map_t*, sql_cmd_map_tag>();
10✔
1802

1803
    std::vector<const help_text*> retval;
10✔
1804
    const auto& sa = al.get_attrs();
10✔
1805
    std::string name;
10✔
1806

1807
    x = al.nearest_text(x);
10✔
1808

1809
    {
1810
        auto sa_opt = get_string_attr(al.get_attrs(), &SQL_COMMAND_ATTR);
10✔
1811
        if (sa_opt) {
10✔
1812
            auto cmd_name = al.get_substring((*sa_opt)->sa_range);
×
1813
            auto cmd_iter = sql_cmd_map->find(cmd_name);
×
1814

1815
            if (cmd_iter != sql_cmd_map->end()) {
×
1816
                return {&cmd_iter->second->c_help};
×
1817
            }
1818
        }
1819

1820
        auto prql_trans_iter = find_string_attr_containing(
10✔
1821
            al.get_attrs(), &lnav::sql::PRQL_TRANSFORM_ATTR, x);
1822
        if (prql_trans_iter != al.get_attrs().end()) {
10✔
1823
            auto cmd_name = al.get_substring(prql_trans_iter->sa_range);
1✔
1824
            auto cmd_iter = sql_cmd_map->find(cmd_name);
1✔
1825

1826
            if (cmd_iter != sql_cmd_map->end()) {
1✔
1827
                return {&cmd_iter->second->c_help};
3✔
1828
            }
1829
        }
1✔
1830
    }
1831

1832
    auto prql_fqid_iter = find_string_attr_containing(
9✔
1833
        al.get_attrs(), &lnav::sql ::PRQL_FQID_ATTR, x);
1834
    if (prql_fqid_iter != al.get_attrs().end()) {
9✔
1835
        auto fqid = al.get_substring(prql_fqid_iter->sa_range);
1✔
1836
        auto cmd_iter = sql_cmd_map->find(fqid);
1✔
1837
        if (cmd_iter != sql_cmd_map->end()) {
1✔
1838
            return {&cmd_iter->second->c_help};
3✔
1839
        }
1840

1841
        auto func_pair = lnav::sql::prql_functions.equal_range(fqid);
×
1842

1843
        for (auto func_iter = func_pair.first; func_iter != func_pair.second;
×
1844
             ++func_iter)
1845
        {
1846
            retval.emplace_back(func_iter->second);
×
1847
            return retval;
×
1848
        }
1849
    }
1✔
1850

1851
    std::vector<std::string> kw;
8✔
1852
    auto iter = rfind_string_attr_if(sa, x, [&al, &name, &kw, x](auto sa) {
8✔
1853
        if (sa.sa_type != &SQL_FUNCTION_ATTR && sa.sa_type != &SQL_KEYWORD_ATTR)
33✔
1854
        {
1855
            return false;
18✔
1856
        }
1857

1858
        const auto& str = al.get_string();
15✔
1859
        const auto& lr = sa.sa_range;
15✔
1860

1861
        if (sa.sa_type == &SQL_FUNCTION_ATTR) {
15✔
1862
            if (!sa.sa_range.contains(x)) {
7✔
1863
                return false;
1✔
1864
            }
1865
        }
1866

1867
        auto lpc = lr.lr_start;
14✔
1868
        for (; lpc < lr.lr_end; lpc++) {
92✔
1869
            if (!isalnum(str[lpc]) && str[lpc] != '_') {
84✔
1870
                break;
6✔
1871
            }
1872
        }
1873

1874
        auto tmp_name = str.substr(lr.lr_start, lpc - lr.lr_start);
14✔
1875
        if (sa.sa_type == &SQL_KEYWORD_ATTR) {
14✔
1876
            tmp_name = toupper(tmp_name);
8✔
1877
        }
1878
        bool retval = sqlite_function_help.count(tmp_name) > 0;
14✔
1879

1880
        if (retval) {
14✔
1881
            kw.push_back(tmp_name);
14✔
1882
            name = tmp_name;
14✔
1883
        }
1884
        return retval;
14✔
1885
    });
14✔
1886

1887
    if (iter != sa.end()) {
8✔
1888
        auto func_pair = sqlite_function_help.equal_range(name);
8✔
1889
        size_t help_count = std::distance(func_pair.first, func_pair.second);
8✔
1890

1891
        if (help_count > 1 && name != func_pair.first->second->ht_name) {
8✔
1892
            while (func_pair.first != func_pair.second) {
×
1893
                if (find(kw.begin(), kw.end(), func_pair.first->second->ht_name)
×
1894
                    == kw.end())
×
1895
                {
1896
                    ++func_pair.first;
×
1897
                } else {
1898
                    func_pair.second = next(func_pair.first);
×
1899
                    break;
×
1900
                }
1901
            }
1902
        }
1903
        for (auto func_iter = func_pair.first; func_iter != func_pair.second;
17✔
1904
             ++func_iter)
9✔
1905
        {
1906
            retval.emplace_back(func_iter->second);
9✔
1907
        }
1908
    }
1909

1910
    return retval;
8✔
1911
}
10✔
1912

1913
template<>
1914
Result<lnav::sql::dialect, std::string>
1915
from(string_fragment sf)
4✔
1916
{
1917
    if (sf == "sql"_frag) {
4✔
1918
        return Ok(lnav::sql::dialect::sql);
×
1919
    }
1920
    if (sf == "sqlite"_frag) {
4✔
1921
        return Ok(lnav::sql::dialect::sqlite);
×
1922
    }
1923
    if (sf == "plpgsql"_frag) {
4✔
1924
        return Ok(lnav::sql::dialect::plpgsql);
8✔
1925
    }
1926
    if (sf == "prql"_frag) {
×
1927
        return Ok(lnav::sql::dialect::prql);
×
1928
    }
1929
    return Err(fmt::format(FMT_STRING("unknown SQL dialect: {}"), sf));
×
1930
}
1931

1932
namespace lnav {
1933
namespace sql {
1934

1935
auto_mem<char, sqlite3_free>
1936
mprintf(const char* fmt, ...)
607,445✔
1937
{
1938
    auto_mem<char, sqlite3_free> retval;
607,445✔
1939
    va_list args;
1940

1941
    va_start(args, fmt);
607,445✔
1942
    retval = sqlite3_vmprintf(fmt, args);
607,445✔
1943
    va_end(args);
607,445✔
1944

1945
    return retval;
1,214,890✔
1946
}
×
1947

1948
bool
1949
is_prql(const string_fragment& sf)
3,669✔
1950
{
1951
    auto trimmed = sf.trim().skip(string_fragment::tag1{';'});
3,669✔
1952

1953
    return (trimmed.startswith("let ") || trimmed.startswith("from"));
7,338✔
1954
}
1955

1956
static const char* const prql_transforms[] = {
1957
    "aggregate",
1958
    "append",
1959
    "derive",
1960
    "filter",
1961
    "from",
1962
    "group",
1963
    "join",
1964
    "loop",
1965
    "select",
1966
    "sort",
1967
    "take",
1968
    "window",
1969

1970
    nullptr,
1971
};
1972

1973
const char* const prql_keywords[] = {
1974
    "average", "avg", "case", "count", "count_distinct", "false", "func",
1975
    "into",    "let", "max",  "min",   "module",         "null",  "prql",
1976
    "stddev",  "sum", "true", "type",
1977

1978
    nullptr,
1979
};
1980

1981
std::string
1982
prql_keyword_re()
12✔
1983
{
1984
    std::string retval = "(?:";
12✔
1985
    bool first = true;
12✔
1986

1987
    for (const char* kw : prql_keywords) {
228✔
1988
        if (kw == nullptr) {
228✔
1989
            break;
12✔
1990
        }
1991
        if (!first) {
216✔
1992
            retval.append("|");
204✔
1993
        } else {
1994
            first = false;
12✔
1995
        }
1996
        retval.append("\\b");
216✔
1997
        retval.append(kw);
216✔
1998
        retval.append("\\b");
216✔
1999
    }
2000
    retval += ")";
12✔
2001

2002
    return retval;
12✔
2003
}
×
2004

2005
std::string
2006
prql_transform_re()
12✔
2007
{
2008
    std::string retval = "(?:";
12✔
2009
    bool first = true;
12✔
2010

2011
    for (const char* kw : prql_transforms) {
156✔
2012
        if (kw == nullptr) {
156✔
2013
            break;
12✔
2014
        }
2015
        if (!first) {
144✔
2016
            retval.append("|");
132✔
2017
        } else {
2018
            first = false;
12✔
2019
        }
2020
        retval.append("\\b");
144✔
2021
        retval.append(kw);
144✔
2022
        retval.append("\\b");
144✔
2023
    }
2024
    retval += ")";
12✔
2025

2026
    return retval;
12✔
2027
}
×
2028

2029
constexpr string_attr_type<void> PRQL_STAGE_ATTR("prql_stage");
2030
constexpr string_attr_type<void> PRQL_TRANSFORM_ATTR("prql_transform");
2031
constexpr string_attr_type<void> PRQL_KEYWORD_ATTR("prql_keyword");
2032
constexpr string_attr_type<void> PRQL_IDENTIFIER_ATTR("prql_ident");
2033
constexpr string_attr_type<void> PRQL_FQID_ATTR("prql_fqid");
2034
constexpr string_attr_type<void> PRQL_DOT_ATTR("prql_dot");
2035
constexpr string_attr_type<void> PRQL_PIPE_ATTR("prql_pipe");
2036
constexpr string_attr_type<void> PRQL_STRING_ATTR("prql_string");
2037
constexpr string_attr_type<void> PRQL_NUMBER_ATTR("prql_number");
2038
constexpr string_attr_type<void> PRQL_OPERATOR_ATTR("prql_oper");
2039
constexpr string_attr_type<void> PRQL_PAREN_ATTR("prql_paren");
2040
constexpr string_attr_type<void> PRQL_UNTERMINATED_PAREN_ATTR(
2041
    "prql_unterminated_paren");
2042
constexpr string_attr_type<void> PRQL_GARBAGE_ATTR("prql_garbage");
2043
constexpr string_attr_type<void> PRQL_COMMENT_ATTR("prql_comment");
2044

2045
void
2046
annotate_prql_statement(attr_line_t& al)
16✔
2047
{
2048
    static const std::string keyword_re_str = R"(\A)" + prql_keyword_re();
16✔
2049
    static const std::string transform_re_str = R"(\A)" + prql_transform_re();
16✔
2050

2051
    static const struct {
2052
        lnav::pcre2pp::code re;
2053
        const string_attr_type<void>* type;
2054
    } PATTERNS[] = {
2055
        {
2056
            lnav::pcre2pp::code::from_const(R"(\A(?:\[|\]|\{|\}|\(|\)))"),
2057
            &PRQL_PAREN_ATTR,
2058
        },
2059
        {
2060
            lnav::pcre2pp::code::from(transform_re_str).unwrap(),
24✔
2061
            &PRQL_TRANSFORM_ATTR,
2062
        },
2063
        {
2064
            lnav::pcre2pp::code::from(keyword_re_str).unwrap(),
24✔
2065
            &PRQL_KEYWORD_ATTR,
2066
        },
2067
        {
2068
            lnav::pcre2pp::code::from_const(R"(\A(?:f|r|s)?'([^']|\\.)*')"),
2069
            &PRQL_STRING_ATTR,
2070
        },
2071
        {
2072
            lnav::pcre2pp::code::from_const(R"(\A(?:f|r|s)?"([^\"]|\\.)*")"),
2073
            &PRQL_STRING_ATTR,
2074
        },
2075
        {
2076
            lnav::pcre2pp::code::from_const(R"(\A0x[0-9a-fA-F]+)"),
2077
            &PRQL_NUMBER_ATTR,
2078
        },
2079
        {
2080
            lnav::pcre2pp::code::from_const(
2081
                R"(\A-?\d+(?:\.\d+)?(?:[eE][\-\+]?\d+)?)"),
2082
            &PRQL_NUMBER_ATTR,
2083
        },
2084
        {
2085
            lnav::pcre2pp::code::from_const(
2086
                R"(\A(?:(?:(?:\$)?\b[a-z_]\w*)|`([^`]+)`))", PCRE2_CASELESS),
2087
            &PRQL_IDENTIFIER_ATTR,
2088
        },
2089
        {
2090
            lnav::pcre2pp::code::from_const(R"(\A#.*)"),
2091
            &PRQL_COMMENT_ATTR,
2092
        },
2093
        {
2094
            lnav::pcre2pp::code::from_const(
2095
                R"(\A(\*|\->{1,2}|<|>|=>|={1,2}|\|\||&&|!|\-|\+|~=|\.\.|,|\?\?))"),
2096
            &PRQL_OPERATOR_ATTR,
2097
        },
2098
        {
2099
            lnav::pcre2pp::code::from_const(R"(\A(?:\||\n))"),
2100
            &PRQL_PIPE_ATTR,
2101
        },
2102
        {
2103
            lnav::pcre2pp::code::from_const(R"(\A\.)"),
2104
            &PRQL_DOT_ATTR,
2105
        },
2106
        {
2107
            lnav::pcre2pp::code::from_const(R"(\A.)"),
2108
            &PRQL_GARBAGE_ATTR,
2109
        },
2110
    };
40✔
2111

2112
    static const auto ws_pattern
2113
        = lnav::pcre2pp::code::from_const(R"(\A[ \t\r]+)");
16✔
2114

2115
    const auto& line = al.get_string();
16✔
2116
    auto& sa = al.get_attrs();
16✔
2117
    auto remaining = string_fragment::from_str(line);
16✔
2118
    while (!remaining.empty()) {
309✔
2119
        auto ws_find_res = ws_pattern.find_in(remaining).ignore_error();
293✔
2120
        if (ws_find_res) {
293✔
2121
            remaining = ws_find_res->f_remaining;
115✔
2122
            continue;
115✔
2123
        }
2124
        for (const auto& pat : PATTERNS) {
1,320✔
2125
            auto pat_find_res = pat.re.find_in(remaining).ignore_error();
1,320✔
2126
            if (pat_find_res) {
1,320✔
2127
                sa.emplace_back(to_line_range(pat_find_res->f_all),
178✔
2128
                                pat.type->value());
356✔
2129
                if (sa.back().sa_type == &PRQL_PIPE_ATTR
178✔
2130
                    && pat_find_res->f_all == "\n"_frag)
178✔
2131
                {
2132
                    sa.back().sa_range.lr_start += 1;
13✔
2133
                }
2134
                remaining = pat_find_res->f_remaining;
178✔
2135
                break;
178✔
2136
            }
2137
        }
2138
    }
2139

2140
    auto stages = std::vector<int>{};
16✔
2141
    std::vector<std::pair<char, int>> groups;
16✔
2142
    std::vector<line_range> fqids;
16✔
2143
    std::optional<line_range> id_start;
16✔
2144
    const string_attr_type_base* last_attr_type = nullptr;
16✔
2145
    bool saw_id_dot = false;
16✔
2146
    for (const auto& attr : sa) {
194✔
2147
        if (attr.sa_type == &PRQL_PIPE_ATTR &&
31✔
2148
            groups.size() == 1 && groups.front().first == 'l') {
209✔
2149
            groups.pop_back();
3✔
2150
            last_attr_type = nullptr;
3✔
2151
            continue;
172✔
2152
        }
2153
        if (groups.empty() && attr.sa_type == &PRQL_PIPE_ATTR
319✔
2154
            && last_attr_type != &PRQL_PIPE_ATTR)
319✔
2155
        {
2156
            stages.push_back(attr.sa_range.lr_start);
28✔
2157
        }
2158
        last_attr_type = attr.sa_type;
175✔
2159
        if (!id_start) {
175✔
2160
            if (attr.sa_type == &PRQL_IDENTIFIER_ATTR) {
106✔
2161
                id_start = attr.sa_range;
41✔
2162
                saw_id_dot = false;
41✔
2163
            }
2164
        } else if (!saw_id_dot) {
69✔
2165
            if (attr.sa_type == &PRQL_DOT_ATTR) {
60✔
2166
                saw_id_dot = true;
9✔
2167
            } else {
2168
                fqids.emplace_back(id_start.value());
51✔
2169
                if (attr.sa_type == &PRQL_IDENTIFIER_ATTR) {
51✔
2170
                    id_start = attr.sa_range;
12✔
2171
                } else {
2172
                    id_start = std::nullopt;
39✔
2173
                }
2174
                saw_id_dot = false;
51✔
2175
            }
2176
        } else {
2177
            if (attr.sa_type == &PRQL_IDENTIFIER_ATTR) {
9✔
2178
                id_start = line_range{
9✔
2179
                    id_start.value().lr_start,
9✔
2180
                    attr.sa_range.lr_end,
9✔
2181
                };
9✔
2182
            } else {
2183
                id_start = std::nullopt;
×
2184
            }
2185
            saw_id_dot = false;
9✔
2186
        }
2187
        if (attr.sa_type == &PRQL_KEYWORD_ATTR &&
180✔
2188
            al.to_string_fragment(attr) == "let"_frag) {
180✔
2189
            groups.emplace_back('l', attr.sa_range.lr_start);
3✔
2190
        }
2191
        if (attr.sa_type != &PRQL_PAREN_ATTR) {
175✔
2192
            continue;
169✔
2193
        }
2194

2195
        auto ch = line[attr.sa_range.lr_start];
6✔
2196
        switch (ch) {
6✔
2197
            case '(':
3✔
2198
            case '{':
2199
            case '[':
2200
                groups.emplace_back(ch, attr.sa_range.lr_start);
3✔
2201
                break;
3✔
2202
            case ')':
×
2203
                if (!groups.empty() && groups.back().first == '(') {
×
2204
                    groups.pop_back();
×
2205
                }
2206
                break;
×
2207
            case '}':
3✔
2208
                if (!groups.empty() && groups.back().first == '{') {
3✔
2209
                    groups.pop_back();
3✔
2210
                }
2211
                break;
3✔
2212
            case ']':
×
2213
                if (!groups.empty() && groups.back().first == '[') {
×
2214
                    groups.pop_back();
×
2215
                }
2216
                break;
×
2217
        }
2218
    }
2219
    if (id_start) {
16✔
2220
        fqids.emplace_back(id_start.value());
2✔
2221
    }
2222
    int prev_stage_index = 0;
16✔
2223
    for (auto stage_index : stages) {
44✔
2224
        sa.emplace_back(line_range{prev_stage_index, stage_index},
28✔
2225
                        PRQL_STAGE_ATTR.value());
56✔
2226
        prev_stage_index = stage_index;
28✔
2227
    }
2228
    sa.emplace_back(
16✔
2229
        line_range{prev_stage_index, (int) al.get_string().length()},
16✔
2230
        PRQL_STAGE_ATTR.value());
32✔
2231
    for (const auto& group : groups) {
16✔
2232
        sa.emplace_back(line_range{group.second, group.second + 1},
×
2233
                        PRQL_UNTERMINATED_PAREN_ATTR.value());
×
2234
    }
2235
    for (const auto& fqid_range : fqids) {
69✔
2236
        sa.emplace_back(fqid_range, PRQL_FQID_ATTR.value());
53✔
2237
    }
2238
    remove_string_attr(sa, &PRQL_IDENTIFIER_ATTR);
16✔
2239
    remove_string_attr(sa, &PRQL_DOT_ATTR);
16✔
2240

2241
    stable_sort(sa.begin(), sa.end());
16✔
2242
}
16✔
2243

2244
}  // namespace sql
2245

2246
namespace prql {
2247

2248
std::string
2249
quote_ident(std::string id)
×
2250
{
2251
    static const auto PLAIN_NAME
2252
        = pcre2pp::code::from_const("^[a-zA-Z_][a-zA-Z_0-9]*$");
×
2253

2254
    if (PLAIN_NAME.find_in(id).ignore_error()) {
×
2255
        return id;
×
2256
    }
2257

2258
    auto buf = auto_buffer::alloc(id.length() + 8);
×
2259
    quote_content(buf, id, '`');
×
2260

2261
    return fmt::format(FMT_STRING("`{}`"), buf.in());
×
2262
}
2263

2264
}  // namespace prql
2265

2266
}  // namespace lnav
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc