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

tstack / lnav / 25348852825-3024

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

push

github

tstack
[ui] horizontal scroll should work on columns

Related to #1685

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

7760 existing lines in 84 files now uncovered.

57014 of 81492 relevant lines covered (69.96%)

622491.44 hits per line

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

81.94
/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 "prql-modules.h"
52
#include "readline_context.hh"
53
#include "readline_highlighters.hh"
54
#include "sql_execute.hh"
55
#include "sql_help.hh"
56
#include "sqlitepp.hh"
57

58
#ifdef HAVE_RUST_DEPS
59
#    include "lnav_rs_ext.cxx.hh"
60
#endif
61

62
using namespace lnav::roles::literals;
63

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

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

782
const char* sql_function_names[] = {
783
    /* http://www.sqlite.org/lang_aggfunc.html */
784
    "avg(",
785
    "count(",
786
    "group_concat(",
787
    "max(",
788
    "min(",
789
    "sum(",
790
    "total(",
791

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

828
    /* http://www.sqlite.org/lang_datefunc.html */
829
    "date(",
830
    "time(",
831
    "datetime(",
832
    "julianday(",
833
    "strftime(",
834

835
    nullptr,
836
};
837

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

862
std::multimap<std::string, const help_text*> sqlite_function_help;
863

864
const char* const LNAV_ATTACH_DB
865
    = "ATTACH DATABASE 'file:lnav_db?mode=memory&cache=shared' AS lnav_db";
866

867
static int
868
handle_db_list(void* ptr, int ncols, char** colvalues, char** colnames)
5✔
869
{
870
    auto* smc = (struct sqlite_metadata_callbacks*) ptr;
5✔
871

872
    smc->smc_db_list[colvalues[1]] = std::vector<std::string>();
15✔
873
    if (!smc->smc_database_list) {
5✔
UNCOV
874
        return 0;
×
875
    }
876

877
    return smc->smc_database_list(ptr, ncols, colvalues, colnames);
5✔
878
}
879

880
struct table_list_data {
881
    sqlite_metadata_callbacks* tld_callbacks;
882
    db_table_map_t::iterator* tld_iter;
883
};
884

885
static int
886
handle_table_list(void* ptr, int ncols, char** colvalues, char** colnames)
240✔
887
{
888
    auto* tld = (struct table_list_data*) ptr;
240✔
889

890
    (*tld->tld_iter)->second.emplace_back(colvalues[0]);
240✔
891
    if (!tld->tld_callbacks->smc_table_list) {
240✔
UNCOV
892
        return 0;
×
893
    }
894

895
    return tld->tld_callbacks->smc_table_list(
480✔
896
        tld->tld_callbacks, ncols, colvalues, colnames);
240✔
897
}
898

899
int
900
walk_sqlite_metadata(sqlite3* db, sqlite_metadata_callbacks& smc)
2✔
901
{
902
    auto_mem<char, sqlite3_free> errmsg;
2✔
903
    int retval;
904

905
    if (smc.smc_collation_list) {
2✔
906
        retval = sqlite3_exec(db,
2✔
907
                              "pragma collation_list",
908
                              smc.smc_collation_list,
909
                              &smc,
910
                              errmsg.out());
911
        if (retval != SQLITE_OK) {
2✔
UNCOV
912
            log_error("could not get collation list -- %s", errmsg.in());
×
UNCOV
913
            return retval;
×
914
        }
915
    }
916

917
    retval = sqlite3_exec(
2✔
918
        db, "pragma database_list", handle_db_list, &smc, errmsg.out());
919
    if (retval != SQLITE_OK) {
2✔
UNCOV
920
        log_error("could not get DB list -- %s", errmsg.in());
×
UNCOV
921
        return retval;
×
922
    }
923

924
    for (auto iter = smc.smc_db_list.begin(); iter != smc.smc_db_list.end();
7✔
925
         ++iter)
5✔
926
    {
927
        table_list_data tld = {&smc, &iter};
5✔
928
        auto_mem<char, sqlite3_free> query;
5✔
929

930
        query = sqlite3_mprintf(
931
            "SELECT name,sql FROM %Q.sqlite_master "
932
            "WHERE type in ('table', 'view')",
933
            iter->first.c_str());
5✔
934

935
        retval = sqlite3_exec(db, query, handle_table_list, &tld, errmsg.out());
5✔
936
        if (retval != SQLITE_OK) {
5✔
UNCOV
937
            log_error("could not get table list -- %s", errmsg.in());
×
UNCOV
938
            return retval;
×
939
        }
940

941
        for (auto table_iter = iter->second.begin();
5✔
942
             table_iter != iter->second.end();
245✔
943
             ++table_iter)
240✔
944
        {
945
            auto_mem<char, sqlite3_free> table_query;
240✔
946
            smc.smc_table_name = *table_iter;
240✔
947

948
            table_query = sqlite3_mprintf("pragma %Q.table_xinfo(%Q)",
949
                                          iter->first.c_str(),
240✔
950
                                          smc.smc_table_name.c_str());
480✔
951
            if (table_query == nullptr) {
240✔
UNCOV
952
                return SQLITE_NOMEM;
×
953
            }
954

955
            if (smc.smc_table_info) {
240✔
956
                retval = sqlite3_exec(
240✔
957
                    db, table_query, smc.smc_table_info, &smc, errmsg.out());
958
                if (retval != SQLITE_OK) {
240✔
UNCOV
959
                    log_error("could not get table info -- %s", errmsg.in());
×
UNCOV
960
                    return retval;
×
961
                }
962
            }
963

964
            table_query = sqlite3_mprintf("pragma %Q.foreign_key_list(%Q)",
965
                                          iter->first.c_str(),
240✔
966
                                          smc.smc_table_name.c_str());
480✔
967
            if (table_query == nullptr) {
240✔
UNCOV
968
                return SQLITE_NOMEM;
×
969
            }
970

971
            if (smc.smc_foreign_key_list) {
240✔
972
                retval = sqlite3_exec(db,
240✔
973
                                      table_query,
974
                                      smc.smc_foreign_key_list,
975
                                      &smc,
976
                                      errmsg.out());
977
                if (retval != SQLITE_OK) {
240✔
UNCOV
978
                    log_error("could not get foreign key list -- %s",
×
979
                              errmsg.in());
UNCOV
980
                    return retval;
×
981
                }
982
            }
983
        }
240✔
984
    }
5✔
985

986
    return retval;
2✔
987
}
2✔
988

989
static int
990
schema_collation_list(void* ptr, int ncols, char** colvalues, char** colnames)
16✔
991
{
992
    return 0;
16✔
993
}
994

995
static int
996
schema_db_list(void* ptr, int ncols, char** colvalues, char** colnames)
5✔
997
{
998
    auto* smc = (sqlite_metadata_callbacks*) ptr;
5✔
999
    auto& schema_out = *((std::string*) smc->smc_userdata);
5✔
1000
    auto_mem<char, sqlite3_free> attach_sql;
5✔
1001

1002
    attach_sql = sqlite3_mprintf(
1003
        "ATTACH DATABASE %Q AS %Q;\n", colvalues[2], colvalues[1]);
5✔
1004

1005
    schema_out += attach_sql;
5✔
1006

1007
    return 0;
5✔
1008
}
5✔
1009

1010
static int
1011
schema_table_list(void* ptr, int ncols, char** colvalues, char** colnames)
240✔
1012
{
1013
    auto smc = (sqlite_metadata_callbacks*) ptr;
240✔
1014
    auto& schema_out = *((std::string*) smc->smc_userdata);
240✔
1015
    auto_mem<char, sqlite3_free> create_sql;
240✔
1016

1017
    create_sql = sqlite3_mprintf("%s;\n", colvalues[1]);
240✔
1018

1019
    schema_out += create_sql;
240✔
1020

1021
    return 0;
240✔
1022
}
240✔
1023

1024
static int
1025
schema_table_info(void* ptr, int ncols, char** colvalues, char** colnames)
7,414✔
1026
{
1027
    return 0;
7,414✔
1028
}
1029

1030
static int
UNCOV
1031
schema_foreign_key_list(void* ptr, int ncols, char** colvalues, char** colnames)
×
1032
{
UNCOV
1033
    return 0;
×
1034
}
1035

1036
void
1037
dump_sqlite_schema(sqlite3* db, std::string& schema_out)
2✔
1038
{
1039
    sqlite_metadata_callbacks schema_sql_meta_callbacks = {
2✔
1040
        schema_collation_list,
1041
        schema_db_list,
1042
        schema_table_list,
1043
        schema_table_info,
1044
        schema_foreign_key_list,
1045
        &schema_out,
1046
        {},
1047
    };
2✔
1048

1049
    walk_sqlite_metadata(db, schema_sql_meta_callbacks);
2✔
1050
}
2✔
1051

1052
void
1053
attach_sqlite_db(sqlite3* db, const std::string& filename)
1✔
1054
{
1055
    static const std::regex db_name_converter("[^\\w]");
1✔
1056

1057
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
1✔
1058

1059
    if (sqlite3_prepare_v2(db, "ATTACH DATABASE ? as ?", -1, stmt.out(), NULL)
1✔
1060
        != SQLITE_OK)
1✔
1061
    {
UNCOV
1062
        log_error("could not prepare DB attach statement -- %s",
×
1063
                  sqlite3_errmsg(db));
UNCOV
1064
        return;
×
1065
    }
1066

1067
    if (sqlite3_bind_text(
1✔
1068
            stmt.in(), 1, filename.c_str(), filename.length(), SQLITE_TRANSIENT)
1✔
1069
        != SQLITE_OK)
1✔
1070
    {
UNCOV
1071
        log_error("could not bind DB attach statement -- %s",
×
1072
                  sqlite3_errmsg(db));
UNCOV
1073
        return;
×
1074
    }
1075

1076
    size_t base_start = filename.find_last_of("/\\");
1✔
1077
    std::string db_name;
1✔
1078

1079
    if (base_start == std::string::npos) {
1✔
UNCOV
1080
        db_name = filename;
×
1081
    } else {
1082
        db_name = filename.substr(base_start + 1);
1✔
1083
    }
1084

1085
    db_name = std::regex_replace(db_name, db_name_converter, "_");
1✔
1086

1087
    if (sqlite3_bind_text(
1✔
1088
            stmt.in(), 2, db_name.c_str(), db_name.length(), SQLITE_TRANSIENT)
1✔
1089
        != SQLITE_OK)
1✔
1090
    {
UNCOV
1091
        log_error("could not bind DB attach statement -- %s",
×
1092
                  sqlite3_errmsg(db));
UNCOV
1093
        return;
×
1094
    }
1095

1096
    if (sqlite3_step(stmt.in()) != SQLITE_DONE) {
1✔
UNCOV
1097
        log_error("could not execute DB attach statement -- %s",
×
1098
                  sqlite3_errmsg(db));
UNCOV
1099
        return;
×
1100
    }
1101
}
1✔
1102

1103
static void
1104
sqlite_logger(void* dummy, int code, const char* msg)
376✔
1105
{
1106
    lnav_log_level_t level;
1107

1108
    switch (code) {
376✔
1109
        case SQLITE_OK:
×
1110
            level = lnav_log_level_t::DEBUG;
×
UNCOV
1111
            break;
×
1112
#ifdef SQLITE_NOTICE
1113
        case SQLITE_NOTICE:
×
1114
            level = lnav_log_level_t::INFO;
×
1115
            break;
×
1116
#endif
1117
#ifdef SQLITE_WARNING
1118
        case SQLITE_WARNING:
8✔
1119
            level = lnav_log_level_t::WARNING;
8✔
1120
            break;
8✔
1121
#endif
1122
        default:
368✔
1123
            level = lnav_log_level_t::ERROR;
368✔
1124
            break;
368✔
1125
    }
1126

1127
    log_msg(level, __FILE__, __LINE__, "(%d) %s", code, msg);
376✔
1128

1129
    ensure(code != 21);
376✔
1130
}
376✔
1131

1132
void
1133
sql_install_logger()
924✔
1134
{
1135
#ifdef SQLITE_CONFIG_LOG
1136
    sqlite3_config(SQLITE_CONFIG_LOG, sqlite_logger, NULL);
924✔
1137
#endif
1138
}
924✔
1139

1140
bool
1141
sql_ident_needs_quote(const char* ident)
3,336✔
1142
{
1143
    for (int lpc = 0; ident[lpc]; lpc++) {
30,398✔
1144
        if (!isalnum(ident[lpc]) && ident[lpc] != '_') {
28,478✔
1145
            return true;
1,416✔
1146
        }
1147
    }
1148

1149
    return false;
1,920✔
1150
}
1151

1152
std::string
UNCOV
1153
sql_quote_text(const std::string& str)
×
1154
{
UNCOV
1155
    auto quoted_token = lnav::sql::mprintf("%Q", str.c_str());
×
UNCOV
1156
    return {quoted_token};
×
1157
}
1158

1159
auto_mem<char, sqlite3_free>
1160
sql_quote_ident(const char* ident)
680,952✔
1161
{
1162
    bool needs_quote = false;
680,952✔
1163
    size_t quote_count = 0, alloc_size;
680,952✔
1164
    auto_mem<char, sqlite3_free> retval;
680,952✔
1165

1166
    for (int lpc = 0; ident[lpc]; lpc++) {
6,738,184✔
1167
        if ((lpc == 0 && isdigit(ident[lpc]))
6,057,232✔
1168
            || (!isalnum(ident[lpc]) && ident[lpc] != '_'))
6,057,232✔
1169
        {
1170
            needs_quote = true;
90,704✔
1171
        }
1172
        if (ident[lpc] == '"') {
6,057,232✔
UNCOV
1173
            quote_count += 1;
×
1174
        }
1175
    }
1176

1177
    alloc_size = strlen(ident) + quote_count * 2 + (needs_quote ? 2 : 0) + 1;
680,952✔
1178
    if ((retval = (char*) sqlite3_malloc(alloc_size)) == nullptr) {
680,952✔
UNCOV
1179
        retval = nullptr;
×
1180
    } else {
1181
        char* curr = retval;
680,952✔
1182

1183
        if (needs_quote) {
680,952✔
1184
            curr[0] = '"';
60,223✔
1185
            curr += 1;
60,223✔
1186
        }
1187
        for (size_t lpc = 0; ident[lpc] != '\0'; lpc++) {
6,738,184✔
1188
            switch (ident[lpc]) {
6,057,232✔
UNCOV
1189
                case '"':
×
UNCOV
1190
                    curr[0] = '"';
×
UNCOV
1191
                    curr += 1;
×
1192
                default:
6,057,232✔
1193
                    curr[0] = ident[lpc];
6,057,232✔
1194
                    break;
6,057,232✔
1195
            }
1196
            curr += 1;
6,057,232✔
1197
        }
1198
        if (needs_quote) {
680,952✔
1199
            curr[0] = '"';
60,223✔
1200
            curr += 1;
60,223✔
1201
        }
1202

1203
        *curr = '\0';
680,952✔
1204
    }
1205

1206
    return retval;
680,952✔
UNCOV
1207
}
×
1208

1209
std::string
1210
sql_safe_ident(const string_fragment& ident)
12,971✔
1211
{
1212
    std::string retval = std::to_string(ident);
12,971✔
1213

1214
    for (size_t lpc = 0; lpc < retval.size(); lpc++) {
130,478✔
1215
        char ch = retval[lpc];
117,507✔
1216

1217
        if (isalnum(ch) || ch == '_') {
117,507✔
1218
            retval[lpc] = tolower(ch);
103,637✔
1219
        } else {
1220
            retval[lpc] = '_';
13,870✔
1221
        }
1222
    }
1223

1224
    return retval;
12,971✔
UNCOV
1225
}
×
1226

1227
attr_line_t
1228
annotate_sql_with_error(sqlite3* db, const char* sql, const char* tail)
9✔
1229
{
1230
    const auto* errmsg = sqlite3_errmsg(db);
9✔
1231
    attr_line_t retval;
9✔
1232
    int erroff = -1;
9✔
1233

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

1258
    if (erroff != -1) {
9✔
1259
        auto line_with_error
1260
            = string_fragment::from_str(retval.get_string())
1✔
1261
                  .find_boundaries_around(erroff, string_fragment::tag1{'\n'});
1✔
1262
        auto erroff_in_line = erroff - line_with_error.sf_begin;
1✔
1263

1264
        attr_line_t pointer;
1✔
1265

1266
        pointer.append(erroff_in_line, ' ')
1✔
1267
            .append("^ "_snippet_border)
1✔
1268
            .append(lnav::roles::error(errmsg))
2✔
1269
            .append("\n");
1✔
1270

1271
        retval.insert(line_with_error.sf_end + 1, pointer).rtrim();
1✔
1272
    }
1✔
1273

1274
    return retval;
9✔
UNCOV
1275
}
×
1276

1277
static void
1278
sql_execute_script(sqlite3* db,
925✔
1279
                   const std::map<std::string, scoped_value_t>& global_vars,
1280
                   const char* src_name,
1281
                   sqlite3_stmt* stmt,
1282
                   std::vector<lnav::console::user_message>& errors)
1283
{
1284
    std::map<std::string, scoped_value_t> lvars;
925✔
1285
    bool done = false;
925✔
1286
    int param_count;
1287

1288
    sqlite3_clear_bindings(stmt);
925✔
1289

1290
    param_count = sqlite3_bind_parameter_count(stmt);
925✔
1291
    for (int lpc = 0; lpc < param_count; lpc++) {
925✔
1292
        const char* name;
1293

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

1325
            case SQLITE_ROW: {
×
1326
                int ncols = sqlite3_column_count(stmt);
×
1327

UNCOV
1328
                for (int lpc = 0; lpc < ncols; lpc++) {
×
1329
                    const char* name = sqlite3_column_name(stmt, lpc);
×
1330
                    auto* raw_value = sqlite3_column_value(stmt, lpc);
×
1331
                    auto value_type = sqlite3_value_type(raw_value);
×
1332
                    scoped_value_t value;
×
1333

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

1355
            default: {
1✔
1356
                const auto* sql_str = sqlite3_sql(stmt);
1✔
1357
                auto sql_content
1358
                    = annotate_sql_with_error(db, sql_str, nullptr);
1✔
1359

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

1372
    sqlite3_reset(stmt);
925✔
1373
}
925✔
1374

1375
static void
1376
sql_compile_script(sqlite3* db,
860✔
1377
                   const std::map<std::string, scoped_value_t>& global_vars,
1378
                   const char* src_name,
1379
                   const char* script_orig,
1380
                   std::vector<lnav::console::user_message>& errors)
1381
{
1382
    const char* script = script_orig;
860✔
1383

1384
    while (script != nullptr && script[0]) {
1,785✔
1385
        auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
1,785✔
1386
        int line_number = 1;
1,785✔
1387
        const char* tail;
1388
        int retcode;
1389

1390
        while (isspace(*script) && script[0]) {
3,635✔
1391
            script += 1;
1,850✔
1392
        }
1393
        for (const char* ch = script_orig; ch < script && ch[0]; ch++) {
164,332✔
1394
            if (*ch == '\n') {
162,547✔
1395
                line_number += 1;
5,510✔
1396
            }
1397
        }
1398

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

1429
        script = tail;
925✔
1430
    }
1,785✔
1431
}
860✔
1432

1433
void
1434
sql_execute_script(sqlite3* db,
860✔
1435
                   const std::map<std::string, scoped_value_t>& global_vars,
1436
                   const char* src_name,
1437
                   const char* script,
1438
                   std::vector<lnav::console::user_message>& errors)
1439
{
1440
    sql_compile_script(db, global_vars, src_name, script, errors);
860✔
1441
}
860✔
1442

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

1464
int
1465
guess_type_from_pcre(const std::string& pattern, std::string& collator)
63,688✔
1466
{
1467
    log_info("guessing SQL type from pattern: %s", pattern.c_str());
63,688✔
1468
    auto compile_res = lnav::pcre2pp::code::from(pattern);
63,688✔
1469
    if (compile_res.isErr()) {
63,688✔
UNCOV
1470
        return SQLITE3_TEXT;
×
1471
    }
1472

1473
    auto re = compile_res.unwrap();
63,688✔
1474
    std::vector<int> matches;
63,688✔
1475
    int retval = SQLITE3_TEXT;
63,688✔
1476
    int index = 0;
63,688✔
1477

1478
    collator.clear();
63,688✔
1479
    for (const auto& test_value : TYPE_TEST_VALUE) {
172,751✔
1480
        log_info("  testing sample: %s", test_value.sample);
167,975✔
1481
        const auto find_res
1482
            = re.find_in(string_fragment::from_c_str(test_value.sample),
335,950✔
1483
                         PCRE2_ANCHORED)
1484
                  .ignore_error();
167,975✔
1485
        if (find_res && find_res->f_all.sf_begin == 0
244,400✔
1486
            && find_res->f_remaining.empty())
244,400✔
1487
        {
1488
            log_info("    matched!");
58,912✔
1489
            matches.push_back(index);
58,912✔
1490
            break;
58,912✔
1491
        }
1492
        if (!find_res) {
109,063✔
1493
            log_info("    mismatch");
91,550✔
1494
        } else if (!find_res->f_remaining.empty()) {
17,513✔
1495
            log_info("    incomplete match");
17,513✔
1496
        }
1497

1498
        index += 1;
109,063✔
1499
    }
1500

1501
    if (matches.size() == 1) {
63,688✔
1502
        retval = TYPE_TEST_VALUE[matches.front()].sqlite_type;
58,912✔
1503
        collator = TYPE_TEST_VALUE[matches.front()].collator;
58,912✔
1504
    }
1505

1506
    return retval;
63,688✔
1507
}
63,688✔
1508

1509
const char*
1510
sqlite3_type_to_string(int type)
680,956✔
1511
{
1512
    switch (type) {
680,956✔
1513
        case SQLITE_FLOAT:
13,144✔
1514
            return "FLOAT";
13,144✔
1515
        case SQLITE_INTEGER:
165,464✔
1516
            return "INTEGER";
165,464✔
1517
        case SQLITE_TEXT:
502,347✔
1518
            return "TEXT";
502,347✔
1519
        case SQLITE_NULL:
1✔
1520
            return "NULL";
1✔
UNCOV
1521
        case SQLITE_BLOB:
×
UNCOV
1522
            return "BLOB";
×
1523
    }
1524

UNCOV
1525
    ensure(!!!"Invalid sqlite type");
×
1526

1527
    return nullptr;
1528
}
1529

1530
/* XXX figure out how to do this with the template */
1531
void
1532
sqlite_close_wrapper(void* mem)
1,921✔
1533
{
1534
    sqlite3_close_v2((sqlite3*) mem);
1,921✔
1535
}
1,921✔
1536

1537
static thread_local std::set<std::string>* tl_authorizer_table_capture
1538
    = nullptr;
1539

1540
int
1541
sqlite_authorizer(void* pUserData,
1,193,947✔
1542
                  int action_code,
1543
                  const char* detail1,
1544
                  const char* detail2,
1545
                  const char* detail3,
1546
                  const char* detail4)
1547
{
1548
    static auto& lnflags = injector::get<lnav_flags_storage&>();
1,193,947✔
1549

1550
    if (lnflags.is_set<lnav_flags::secure_mode>()
1,193,947✔
1551
        && action_code == SQLITE_ATTACH)
1,193,947✔
1552
    {
1553
        return SQLITE_DENY;
4✔
1554
    }
1555
    if (action_code == SQLITE_READ && tl_authorizer_table_capture != nullptr
1,193,943✔
1556
        && detail1 != nullptr)
3,520✔
1557
    {
1558
        tl_authorizer_table_capture->emplace(detail1);
3,520✔
1559
    }
1560
    return SQLITE_OK;
1,193,943✔
1561
}
1562

1563
sql_table_capture_guard::sql_table_capture_guard(std::set<std::string>& into)
3,901✔
1564
    : stcg_prev(tl_authorizer_table_capture)
3,901✔
1565
{
1566
    tl_authorizer_table_capture = &into;
3,901✔
1567
}
3,901✔
1568

1569
sql_table_capture_guard::~sql_table_capture_guard()
3,901✔
1570
{
1571
    tl_authorizer_table_capture = this->stcg_prev;
3,901✔
1572
}
3,901✔
1573

1574
#if HAVE_RUST_DEPS
1575
extern rust::Vec<lnav_rs_ext::SourceTreeElement> sqlite_extension_prql;
1576
#endif
1577

1578
Result<std::string, lnav::console::user_message>
1579
lnav::prql::compile(const std::string& src)
50✔
1580
{
1581
#if HAVE_RUST_DEPS
1582
    auto opts = lnav_rs_ext::Options{true, "sql.sqlite", true};
50✔
1583

1584
    auto tree = sqlite_extension_prql;
50✔
1585
    for (const auto& mod : lnav_prql_modules) {
150✔
1586
        auto name = mod.get_name().to_string();
100✔
1587
        log_debug("lnav_rs_ext adding mod %s", name.c_str());
100✔
1588
        tree.emplace_back(lnav_rs_ext::SourceTreeElement{
200✔
1589
            name.c_str(),
1590
            mod.to_string_fragment_producer()->to_string(),
200✔
1591
        });
1592
    }
100✔
1593
    tree.emplace_back(lnav_rs_ext::SourceTreeElement{"", src});
50✔
1594
    log_debug("BEGIN compiling tree");
50✔
1595
    auto cr = lnav_rs_ext::compile_tree(tree, opts);
50✔
1596
    log_debug("END compiling tree");
50✔
1597

1598
    for (const auto& msg : cr.messages) {
50✔
1599
        if (msg.kind != lnav_rs_ext::MessageKind::Error) {
3✔
UNCOV
1600
            continue;
×
1601
        }
1602

1603
        auto stmt_al = attr_line_t(src);
3✔
1604
        readline_sql_highlighter(stmt_al, lnav::sql::dialect::prql, 0);
3✔
UNCOV
1605
        auto um = lnav::console::user_message::error(
×
1606
                      attr_line_t("unable to compile PRQL: ").append(stmt_al))
6✔
1607
                      .with_reason(
3✔
1608
                          attr_line_t::from_ansi_str((std::string) msg.reason));
6✔
1609
        if (!msg.display.empty()) {
3✔
1610
            um.with_note(attr_line_t::from_ansi_str((std::string) msg.display));
1✔
1611
        }
1612
        for (const auto& hint : msg.hints) {
3✔
1613
            um.with_help(attr_line_t::from_ansi_str((std::string) hint));
2✔
1614
            break;
2✔
1615
        }
1616
        return Err(um);
3✔
1617
    }
3✔
1618
    log_debug("done!");
47✔
1619
    return Ok((std::string) cr.output);
47✔
1620
#else
1621
    auto um = lnav::console::user_message::error(
1622
        attr_line_t("PRQL is not supported in this build"));
1623
    return Err(um);
1624
#endif
1625
}
50✔
1626

1627
attr_line_t
1628
sqlite3_errmsg_to_attr_line(sqlite3* db)
1✔
1629
{
1630
    const auto* errmsg = sqlite3_errmsg(db);
1✔
1631
    if (startswith(errmsg, sqlitepp::ERROR_PREFIX)) {
1✔
1632
        auto from_res = lnav::from_json<lnav::console::user_message>(
1633
            &errmsg[strlen(sqlitepp::ERROR_PREFIX)]);
1✔
1634

1635
        if (from_res.isOk()) {
1✔
1636
            return from_res.unwrap().to_attr_line();
1✔
1637
        }
1638

UNCOV
1639
        return from_res.unwrapErr()[0].um_message.get_string();
×
1640
    }
1✔
1641

UNCOV
1642
    return attr_line_t(errmsg);
×
1643
}
1644

1645
static void
1646
append_kw(std::string& retval, bool& first, const char* kw)
333,230✔
1647
{
1648
    if (!first) {
333,230✔
1649
        retval.append("|");
332,290✔
1650
    } else {
1651
        first = false;
940✔
1652
    }
1653
    retval.append("\\b");
333,230✔
1654
    retval.append(kw);
333,230✔
1655
    retval.append("\\b");
333,230✔
1656
}
333,230✔
1657

1658
static std::string
1659
sql_keyword_re(lnav::sql::dialect dia)
940✔
1660
{
1661
    std::string retval = "(?:";
940✔
1662
    auto first = true;
940✔
1663

1664
    switch (dia) {
940✔
1665
        case lnav::sql::dialect::sqlite:
470✔
1666
            for (const char* kw : sqlite_keywords) {
68,620✔
1667
                append_kw(retval, first, kw);
68,150✔
1668
            }
1669
            break;
470✔
1670
        default:
470✔
1671
            for (const char* kw : pg_sql_keywords) {
265,550✔
1672
                append_kw(retval, first, kw);
265,080✔
1673
            }
1674
            break;
470✔
1675
    }
1676

1677
    retval += ")";
940✔
1678

1679
    return retval;
1,880✔
UNCOV
1680
}
×
1681

1682
constexpr string_attr_type<void> SQL_COMMAND_ATTR("sql_command");
1683
constexpr string_attr_type<void> SQL_KEYWORD_ATTR("sql_keyword");
1684
constexpr string_attr_type<void> SQL_IDENTIFIER_ATTR("sql_ident");
1685
constexpr string_attr_type<std::string> SQL_FUNCTION_ATTR("sql_func");
1686
constexpr string_attr_type<void> SQL_STRING_ATTR("sql_string");
1687
constexpr string_attr_type<void> SQL_HEX_LIT_ATTR("sql_hex_lit");
1688
constexpr string_attr_type<void> SQL_NUMBER_ATTR("sql_number");
1689
constexpr string_attr_type<void> SQL_OPERATOR_ATTR("sql_oper");
1690
constexpr string_attr_type<void> SQL_PAREN_ATTR("sql_paren");
1691
constexpr string_attr_type<void> SQL_COMMA_ATTR("sql_comma");
1692
constexpr string_attr_type<void> SQL_GARBAGE_ATTR("sql_garbage");
1693
constexpr string_attr_type<void> SQL_COMMENT_ATTR("sql_comment");
1694

1695
void
1696
annotate_sql_statement(attr_line_t& al, lnav::sql::dialect dia)
2,266✔
1697
{
1698
    static const std::string sqlite_keyword_re_str
1699
        = R"(\A)" + sql_keyword_re(lnav::sql::dialect::sqlite);
2,266✔
1700
    static const auto sqlite_keyword_re
1701
        = lnav::pcre2pp::code::from(sqlite_keyword_re_str, PCRE2_CASELESS)
940✔
1702
              .unwrap();
2,736✔
1703
    static const std::string sql_keyword_re_str
1704
        = R"(\A)" + sql_keyword_re(lnav::sql::dialect::sql);
2,266✔
1705
    static const auto sql_keyword_re
1706
        = lnav::pcre2pp::code::from(sql_keyword_re_str, PCRE2_CASELESS)
940✔
1707
              .unwrap();
2,736✔
1708

1709
    static const struct {
1710
        lnav::pcre2pp::code re;
1711
        const string_attr_type<void>* type;
1712
    } PATTERNS[] = {
1713
        {
1714
            lnav::pcre2pp::code::from_const(R"(\A,)"),
1715
            &SQL_COMMA_ATTR,
1716
        },
1717
        {
1718
            lnav::pcre2pp::code::from_const(R"(\A\(|\A\))"),
1719
            &SQL_PAREN_ATTR,
1720
        },
1721
        {
1722
            lnav::pcre2pp::code::from_const(
1723
                R"(\A(?:x|X)'(?:(?:[0-9a-fA-F]{2})+'|[0-9a-fA-F]*$))"),
1724
            &SQL_HEX_LIT_ATTR,
1725
        },
1726
        {
1727
            lnav::pcre2pp::code::from_const(R"(\A'[^']*('(?:'[^']*')*|$))"),
1728
            &SQL_STRING_ATTR,
1729
        },
1730
        {
1731
            lnav::pcre2pp::code::from_const(R"(\A0x[0-9a-fA-F]+)"),
1732
            &SQL_NUMBER_ATTR,
1733
        },
1734
        {
1735
            lnav::pcre2pp::code::from_const(
1736
                R"(\A-?\d+(?:\.\d+)?(?:[eE][\-\+]?\d+)?\b)"),
1737
            &SQL_NUMBER_ATTR,
1738
        },
1739
        {
1740
            lnav::pcre2pp::code::from_const(
1741
                R"(\A(((\$|:|@)?\b[a-z_]\w*)|\"([^\"]+)\"|\[([^\]]+)]))",
1742
                PCRE2_CASELESS),
1743
            &SQL_IDENTIFIER_ATTR,
1744
        },
1745
        {
1746
            lnav::pcre2pp::code::from_const(R"(\A--.*)"),
1747
            &SQL_COMMENT_ATTR,
1748
        },
1749
        {
1750
            lnav::pcre2pp::code::from_const(
1751
                R"(\A(~|%|\*|\->{1,2}|<=|>=|<<|>>|<>|<|>|={1,2}|!=?|\-|\+|\|\|{1,2}|&|::))"),
1752
            &SQL_OPERATOR_ATTR,
1753
        },
1754
        {
1755
            lnav::pcre2pp::code::from_const(R"(\A[0-9][a-zA-Z0-9\-\._]+)"),
1756
            &SQL_GARBAGE_ATTR,
1757
        },
1758
        {
1759
            lnav::pcre2pp::code::from_const(R"(\A.)"),
1760
            &SQL_GARBAGE_ATTR,
1761
        },
1762
    };
2,266✔
1763

1764
    static const auto cmd_pattern
1765
        = lnav::pcre2pp::code::from_const(R"(^;?(\.\w+))");
2,266✔
1766
    static const auto ws_pattern = lnav::pcre2pp::code::from_const(R"(\A\s+)");
2,266✔
1767

1768
    const auto& line = al.get_string();
2,266✔
1769
    auto& sa = al.get_attrs();
2,266✔
1770

1771
    if (lnav::sql::is_prql(line)) {
2,266✔
1772
        lnav::sql::annotate_prql_statement(al);
19✔
1773
        return;
27✔
1774
    }
1775

1776
    auto cmd_find_res
1777
        = cmd_pattern.find_in(line, PCRE2_ANCHORED).ignore_error();
2,247✔
1778
    if (cmd_find_res) {
2,247✔
1779
        auto cap = cmd_find_res->f_all;
8✔
1780
        sa.emplace_back(line_range(cap.sf_begin, cap.sf_end),
8✔
1781
                        SQL_COMMAND_ATTR.value());
16✔
1782
        return;
8✔
1783
    }
1784

1785
    auto remaining = string_fragment::from_str(line);
2,239✔
1786
    while (!remaining.empty()) {
35,511✔
1787
        auto ws_find_res = ws_pattern.find_in(remaining).ignore_error();
33,272✔
1788
        if (ws_find_res) {
33,272✔
1789
            remaining = ws_find_res->f_remaining;
11,526✔
1790
            continue;
17,192✔
1791
        }
1792
        const auto& kw_pat = dia == lnav::sql::dialect::sqlite
21,746✔
1793
            ? sqlite_keyword_re
1794
            : sql_keyword_re;
1795
        auto kw_pat_find_res = kw_pat.find_in(remaining).ignore_error();
21,746✔
1796
        if (kw_pat_find_res) {
21,746✔
1797
            sa.emplace_back(to_line_range(kw_pat_find_res->f_all),
5,666✔
1798
                            SQL_KEYWORD_ATTR.value());
11,332✔
1799
            remaining = kw_pat_find_res->f_remaining;
5,666✔
1800
            continue;
5,666✔
1801
        }
1802
        for (const auto& pat : PATTERNS) {
79,863✔
1803
            auto pat_find_res = pat.re.find_in(remaining).ignore_error();
79,863✔
1804
            if (pat_find_res) {
79,863✔
1805
                sa.emplace_back(to_line_range(pat_find_res->f_all),
16,080✔
1806
                                pat.type->value());
32,160✔
1807
                remaining = pat_find_res->f_remaining;
16,080✔
1808
                break;
16,080✔
1809
            }
1810
        }
1811
    }
1812

1813
    string_attrs_t::const_iterator iter;
2,239✔
1814
    int start = 0;
2,239✔
1815

1816
    while ((iter = find_string_attr(sa, &SQL_IDENTIFIER_ATTR, start))
9,947✔
1817
           != sa.end())
15,416✔
1818
    {
1819
        string_attrs_t::const_iterator piter;
5,469✔
1820

1821
        start = iter->sa_range.lr_end;
5,469✔
1822
        if (start < (ssize_t) line.length() && line[start] == '(') {
5,469✔
1823
            ssize_t pstart = start + 1;
1,628✔
1824
            int depth = 1;
1,628✔
1825

1826
            while (depth > 0
1,628✔
1827
                   && (piter = find_string_attr(sa, &SQL_PAREN_ATTR, pstart))
7,997✔
1828
                       != sa.end())
7,997✔
1829
            {
1830
                if (line[piter->sa_range.lr_start] == '(') {
2,115✔
1831
                    depth += 1;
250✔
1832
                } else {
1833
                    depth -= 1;
1,865✔
1834
                }
1835
                pstart = piter->sa_range.lr_end;
2,115✔
1836
            }
1837

1838
            line_range func_range{iter->sa_range.lr_start};
1,628✔
1839
            if (piter == sa.end()) {
1,628✔
1840
                func_range.lr_end = line.length();
12✔
1841
            } else {
1842
                func_range.lr_end = piter->sa_range.lr_end;
1,616✔
1843
            }
1844
            auto func_name = al.to_string_fragment(iter);
1,628✔
1845
            sa.emplace_back(
1,628✔
1846
                func_range,
1847
                SQL_FUNCTION_ATTR.value(tolower(func_name.to_string())));
3,256✔
1848
        }
1849
    }
1850

1851
    // remove_string_attr(sa, &SQL_PAREN_ATTR);
1852
    stable_sort(sa.begin(), sa.end());
2,239✔
1853
}
1854

1855
std::vector<const help_text*>
1856
find_sql_help_for_line(const attr_line_t& al, size_t x)
10✔
1857
{
1858
    static const auto* sql_cmd_map
1859
        = injector::get<readline_context::command_map_t*, sql_cmd_map_tag>();
10✔
1860

1861
    std::vector<const help_text*> retval;
10✔
1862
    const auto& sa = al.get_attrs();
10✔
1863
    std::string name;
10✔
1864

1865
    x = al.nearest_text(x);
10✔
1866

1867
    {
1868
        auto sa_opt = get_string_attr(al.get_attrs(), &SQL_COMMAND_ATTR);
10✔
1869
        if (sa_opt) {
10✔
1870
            auto cmd_name = al.get_substring((*sa_opt)->sa_range);
×
1871
            auto cmd_iter = sql_cmd_map->find(cmd_name);
×
1872

1873
            if (cmd_iter != sql_cmd_map->end()) {
×
UNCOV
1874
                return {&cmd_iter->second->c_help};
×
1875
            }
1876
        }
1877

1878
        auto prql_trans_iter = find_string_attr_containing(
10✔
1879
            al.get_attrs(), &lnav::sql::PRQL_TRANSFORM_ATTR, x);
1880
        if (prql_trans_iter != al.get_attrs().end()) {
10✔
1881
            auto cmd_name = al.get_substring(prql_trans_iter->sa_range);
1✔
1882
            auto cmd_iter = sql_cmd_map->find(cmd_name);
1✔
1883

1884
            if (cmd_iter != sql_cmd_map->end()) {
1✔
1885
                return {&cmd_iter->second->c_help};
3✔
1886
            }
1887
        }
1✔
1888
    }
1889

1890
    auto prql_fqid_iter = find_string_attr_containing(
9✔
1891
        al.get_attrs(), &lnav::sql ::PRQL_FQID_ATTR, x);
1892
    if (prql_fqid_iter != al.get_attrs().end()) {
9✔
1893
        auto fqid = al.get_substring(prql_fqid_iter->sa_range);
1✔
1894
        auto cmd_iter = sql_cmd_map->find(fqid);
1✔
1895
        if (cmd_iter != sql_cmd_map->end()) {
1✔
1896
            return {&cmd_iter->second->c_help};
3✔
1897
        }
1898

UNCOV
1899
        auto func_pair = lnav::sql::prql_functions.equal_range(fqid);
×
1900

UNCOV
1901
        for (auto func_iter = func_pair.first; func_iter != func_pair.second;
×
1902
             ++func_iter)
1903
        {
1904
            retval.emplace_back(func_iter->second);
×
UNCOV
1905
            return retval;
×
1906
        }
1907
    }
1✔
1908

1909
    std::vector<std::string> kw;
8✔
1910
    auto iter = rfind_string_attr_if(sa, x, [&al, &name, &kw, x](auto sa) {
8✔
1911
        if (sa.sa_type != &SQL_FUNCTION_ATTR && sa.sa_type != &SQL_KEYWORD_ATTR)
33✔
1912
        {
1913
            return false;
18✔
1914
        }
1915

1916
        const auto& str = al.get_string();
15✔
1917
        const auto& lr = sa.sa_range;
15✔
1918

1919
        if (sa.sa_type == &SQL_FUNCTION_ATTR) {
15✔
1920
            if (!sa.sa_range.contains(x)) {
7✔
1921
                return false;
1✔
1922
            }
1923
        }
1924

1925
        auto lpc = lr.lr_start;
14✔
1926
        for (; lpc < lr.lr_end; lpc++) {
92✔
1927
            if (!isalnum(str[lpc]) && str[lpc] != '_') {
84✔
1928
                break;
6✔
1929
            }
1930
        }
1931

1932
        auto tmp_name = str.substr(lr.lr_start, lpc - lr.lr_start);
14✔
1933
        if (sa.sa_type == &SQL_KEYWORD_ATTR) {
14✔
1934
            tmp_name = toupper(tmp_name);
8✔
1935
        }
1936
        bool retval = sqlite_function_help.count(tmp_name) > 0;
14✔
1937

1938
        if (retval) {
14✔
1939
            kw.push_back(tmp_name);
14✔
1940
            name = tmp_name;
14✔
1941
        }
1942
        return retval;
14✔
1943
    });
14✔
1944

1945
    if (iter != sa.end()) {
8✔
1946
        auto func_pair = sqlite_function_help.equal_range(name);
8✔
1947
        size_t help_count = std::distance(func_pair.first, func_pair.second);
8✔
1948

1949
        if (help_count > 1 && name != func_pair.first->second->ht_name) {
8✔
UNCOV
1950
            while (func_pair.first != func_pair.second) {
×
UNCOV
1951
                if (find(kw.begin(), kw.end(), func_pair.first->second->ht_name)
×
UNCOV
1952
                    == kw.end())
×
1953
                {
UNCOV
1954
                    ++func_pair.first;
×
1955
                } else {
UNCOV
1956
                    func_pair.second = next(func_pair.first);
×
UNCOV
1957
                    break;
×
1958
                }
1959
            }
1960
        }
1961
        for (auto func_iter = func_pair.first; func_iter != func_pair.second;
17✔
1962
             ++func_iter)
9✔
1963
        {
1964
            retval.emplace_back(func_iter->second);
9✔
1965
        }
1966
    }
1967

1968
    return retval;
8✔
1969
}
10✔
1970

1971
template<>
1972
Result<lnav::sql::dialect, std::string>
1973
from(string_fragment sf)
4✔
1974
{
1975
    if (sf == "sql"_frag) {
4✔
UNCOV
1976
        return Ok(lnav::sql::dialect::sql);
×
1977
    }
1978
    if (sf == "sqlite"_frag) {
4✔
UNCOV
1979
        return Ok(lnav::sql::dialect::sqlite);
×
1980
    }
1981
    if (sf == "plpgsql"_frag) {
4✔
1982
        return Ok(lnav::sql::dialect::plpgsql);
8✔
1983
    }
UNCOV
1984
    if (sf == "prql"_frag) {
×
UNCOV
1985
        return Ok(lnav::sql::dialect::prql);
×
1986
    }
UNCOV
1987
    return Err(fmt::format(FMT_STRING("unknown SQL dialect: {}"), sf));
×
1988
}
1989

1990
namespace lnav {
1991
namespace sql {
1992

1993
auto_mem<char, sqlite3_free>
1994
mprintf(const char* fmt, ...)
680,956✔
1995
{
1996
    auto_mem<char, sqlite3_free> retval;
680,956✔
1997
    va_list args;
1998

1999
    va_start(args, fmt);
680,956✔
2000
    retval = sqlite3_vmprintf(fmt, args);
680,956✔
2001
    va_end(args);
680,956✔
2002

2003
    return retval;
1,361,912✔
2004
}
×
2005

2006
bool
2007
is_prql(const string_fragment& sf)
4,308✔
2008
{
2009
    auto trimmed = sf.trim().skip(string_fragment::tag1{';'});
4,308✔
2010

2011
    return trimmed == "let"_frag || trimmed.startswith("let ")
12,924✔
2012
        || trimmed.startswith("from");
12,924✔
2013
}
2014

2015
static const char* const prql_transforms[] = {
2016
    "aggregate",
2017
    "append",
2018
    "derive",
2019
    "filter",
2020
    "from",
2021
    "group",
2022
    "join",
2023
    "loop",
2024
    "select",
2025
    "sort",
2026
    "take",
2027
    "window",
2028

2029
    nullptr,
2030
};
2031

2032
const char* const prql_keywords[] = {
2033
    "average", "avg", "case", "count", "count_distinct", "false", "func",
2034
    "into",    "let", "max",  "min",   "module",         "null",  "prql",
2035
    "stddev",  "sum", "true", "type",
2036

2037
    nullptr,
2038
};
2039

2040
std::string
2041
prql_keyword_re()
15✔
2042
{
2043
    std::string retval = "(?:";
15✔
2044
    bool first = true;
15✔
2045

2046
    for (const char* kw : prql_keywords) {
285✔
2047
        if (kw == nullptr) {
285✔
2048
            break;
15✔
2049
        }
2050
        if (!first) {
270✔
2051
            retval.append("|");
255✔
2052
        } else {
2053
            first = false;
15✔
2054
        }
2055
        retval.append("\\b");
270✔
2056
        retval.append(kw);
270✔
2057
        retval.append("\\b");
270✔
2058
    }
2059
    retval += ")";
15✔
2060

2061
    return retval;
15✔
UNCOV
2062
}
×
2063

2064
std::string
2065
prql_transform_re()
15✔
2066
{
2067
    std::string retval = "(?:";
15✔
2068
    bool first = true;
15✔
2069

2070
    for (const char* kw : prql_transforms) {
195✔
2071
        if (kw == nullptr) {
195✔
2072
            break;
15✔
2073
        }
2074
        if (!first) {
180✔
2075
            retval.append("|");
165✔
2076
        } else {
2077
            first = false;
15✔
2078
        }
2079
        retval.append("\\b");
180✔
2080
        retval.append(kw);
180✔
2081
        retval.append("\\b");
180✔
2082
    }
2083
    retval += ")";
15✔
2084

2085
    return retval;
15✔
UNCOV
2086
}
×
2087

2088
constexpr string_attr_type<void> PRQL_STAGE_ATTR("prql_stage");
2089
constexpr string_attr_type<void> PRQL_TRANSFORM_ATTR("prql_transform");
2090
constexpr string_attr_type<void> PRQL_KEYWORD_ATTR("prql_keyword");
2091
constexpr string_attr_type<void> PRQL_IDENTIFIER_ATTR("prql_ident");
2092
constexpr string_attr_type<void> PRQL_FQID_ATTR("prql_fqid");
2093
constexpr string_attr_type<void> PRQL_DOT_ATTR("prql_dot");
2094
constexpr string_attr_type<void> PRQL_PIPE_ATTR("prql_pipe");
2095
constexpr string_attr_type<void> PRQL_STRING_ATTR("prql_string");
2096
constexpr string_attr_type<void> PRQL_NUMBER_ATTR("prql_number");
2097
constexpr string_attr_type<void> PRQL_OPERATOR_ATTR("prql_oper");
2098
constexpr string_attr_type<void> PRQL_PAREN_ATTR("prql_paren");
2099
constexpr string_attr_type<void> PRQL_UNTERMINATED_PAREN_ATTR(
2100
    "prql_unterminated_paren");
2101
constexpr string_attr_type<void> PRQL_GARBAGE_ATTR("prql_garbage");
2102
constexpr string_attr_type<void> PRQL_COMMENT_ATTR("prql_comment");
2103

2104
void
2105
annotate_prql_statement(attr_line_t& al)
19✔
2106
{
2107
    static const std::string keyword_re_str = R"(\A)" + prql_keyword_re();
19✔
2108
    static const std::string transform_re_str = R"(\A)" + prql_transform_re();
19✔
2109

2110
    static const struct {
2111
        lnav::pcre2pp::code re;
2112
        const string_attr_type<void>* type;
2113
    } PATTERNS[] = {
2114
        {
2115
            lnav::pcre2pp::code::from_const(R"(\A(?:\[|\]|\{|\}|\(|\)))"),
2116
            &PRQL_PAREN_ATTR,
2117
        },
2118
        {
2119
            lnav::pcre2pp::code::from(transform_re_str).unwrap(),
30✔
2120
            &PRQL_TRANSFORM_ATTR,
2121
        },
2122
        {
2123
            lnav::pcre2pp::code::from(keyword_re_str).unwrap(),
30✔
2124
            &PRQL_KEYWORD_ATTR,
2125
        },
2126
        {
2127
            lnav::pcre2pp::code::from_const(R"(\A(?:f|r|s)?'([^']|\\.)*')"),
2128
            &PRQL_STRING_ATTR,
2129
        },
2130
        {
2131
            lnav::pcre2pp::code::from_const(R"(\A(?:f|r|s)?"([^\"]|\\.)*")"),
2132
            &PRQL_STRING_ATTR,
2133
        },
2134
        {
2135
            lnav::pcre2pp::code::from_const(R"(\A0x[0-9a-fA-F]+)"),
2136
            &PRQL_NUMBER_ATTR,
2137
        },
2138
        {
2139
            lnav::pcre2pp::code::from_const(
2140
                R"(\A-?\d+(?:\.\d+)?(?:[eE][\-\+]?\d+)?)"),
2141
            &PRQL_NUMBER_ATTR,
2142
        },
2143
        {
2144
            lnav::pcre2pp::code::from_const(
2145
                R"(\A(?:(?:(?:\$)?\b[a-z_]\w*)|`([^`]+)`))", PCRE2_CASELESS),
2146
            &PRQL_IDENTIFIER_ATTR,
2147
        },
2148
        {
2149
            lnav::pcre2pp::code::from_const(R"(\A#.*)"),
2150
            &PRQL_COMMENT_ATTR,
2151
        },
2152
        {
2153
            lnav::pcre2pp::code::from_const(
2154
                R"(\A(\*|\->{1,2}|<|>|=>|={1,2}|\|\||&&|!|\-|\+|~=|\.\.|,|\?\?))"),
2155
            &PRQL_OPERATOR_ATTR,
2156
        },
2157
        {
2158
            lnav::pcre2pp::code::from_const(R"(\A(?:\||\n))"),
2159
            &PRQL_PIPE_ATTR,
2160
        },
2161
        {
2162
            lnav::pcre2pp::code::from_const(R"(\A\.)"),
2163
            &PRQL_DOT_ATTR,
2164
        },
2165
        {
2166
            lnav::pcre2pp::code::from_const(R"(\A.)"),
2167
            &PRQL_GARBAGE_ATTR,
2168
        },
2169
    };
49✔
2170

2171
    static const auto ws_pattern
2172
        = lnav::pcre2pp::code::from_const(R"(\A[ \t\r]+)");
19✔
2173

2174
    const auto& line = al.get_string();
19✔
2175
    auto& sa = al.get_attrs();
19✔
2176
    auto remaining = string_fragment::from_str(line);
19✔
2177
    while (!remaining.empty()) {
344✔
2178
        auto ws_find_res = ws_pattern.find_in(remaining).ignore_error();
325✔
2179
        if (ws_find_res) {
325✔
2180
            remaining = ws_find_res->f_remaining;
128✔
2181
            continue;
128✔
2182
        }
2183
        for (const auto& pat : PATTERNS) {
1,464✔
2184
            auto pat_find_res = pat.re.find_in(remaining).ignore_error();
1,464✔
2185
            if (pat_find_res) {
1,464✔
2186
                sa.emplace_back(to_line_range(pat_find_res->f_all),
197✔
2187
                                pat.type->value());
394✔
2188
                if (sa.back().sa_type == &PRQL_PIPE_ATTR
197✔
2189
                    && pat_find_res->f_all == "\n"_frag)
197✔
2190
                {
2191
                    sa.back().sa_range.lr_start += 1;
14✔
2192
                }
2193
                remaining = pat_find_res->f_remaining;
197✔
2194
                break;
197✔
2195
            }
2196
        }
2197
    }
2198

2199
    auto stages = std::vector<int>{};
19✔
2200
    std::vector<std::pair<char, int>> groups;
19✔
2201
    std::vector<line_range> fqids;
19✔
2202
    std::optional<line_range> id_start;
19✔
2203
    const string_attr_type_base* last_attr_type = nullptr;
19✔
2204
    bool saw_id_dot = false;
19✔
2205
    for (const auto& attr : sa) {
216✔
2206
        if (attr.sa_type == &PRQL_PIPE_ATTR && groups.size() == 1
34✔
2207
            && groups.front().first == 'l')
231✔
2208
        {
2209
            groups.pop_back();
3✔
2210
            last_attr_type = nullptr;
3✔
2211
            continue;
191✔
2212
        }
2213
        if (groups.empty() && attr.sa_type == &PRQL_PIPE_ATTR
351✔
2214
            && last_attr_type != &PRQL_PIPE_ATTR)
351✔
2215
        {
2216
            stages.push_back(attr.sa_range.lr_start);
31✔
2217
        }
2218
        last_attr_type = attr.sa_type;
194✔
2219
        if (!id_start) {
194✔
2220
            if (attr.sa_type == &PRQL_IDENTIFIER_ATTR) {
118✔
2221
                id_start = attr.sa_range;
45✔
2222
                saw_id_dot = false;
45✔
2223
            }
2224
        } else if (!saw_id_dot) {
76✔
2225
            if (attr.sa_type == &PRQL_DOT_ATTR) {
66✔
2226
                saw_id_dot = true;
10✔
2227
            } else {
2228
                fqids.emplace_back(id_start.value());
56✔
2229
                if (attr.sa_type == &PRQL_IDENTIFIER_ATTR) {
56✔
2230
                    id_start = attr.sa_range;
13✔
2231
                } else {
2232
                    id_start = std::nullopt;
43✔
2233
                }
2234
                saw_id_dot = false;
56✔
2235
            }
2236
        } else {
2237
            if (attr.sa_type == &PRQL_IDENTIFIER_ATTR) {
10✔
2238
                id_start = line_range{
10✔
2239
                    id_start.value().lr_start,
10✔
2240
                    attr.sa_range.lr_end,
10✔
2241
                };
10✔
2242
            } else {
UNCOV
2243
                id_start = std::nullopt;
×
2244
            }
2245
            saw_id_dot = false;
10✔
2246
        }
2247
        if (attr.sa_type == &PRQL_KEYWORD_ATTR
388✔
2248
            && al.to_string_fragment(attr) == "let"_frag)
194✔
2249
        {
2250
            groups.emplace_back('l', attr.sa_range.lr_start);
5✔
2251
        }
2252
        if (attr.sa_type != &PRQL_PAREN_ATTR) {
194✔
2253
            continue;
188✔
2254
        }
2255

2256
        auto ch = line[attr.sa_range.lr_start];
6✔
2257
        switch (ch) {
6✔
2258
            case '(':
3✔
2259
            case '{':
2260
            case '[':
2261
                groups.emplace_back(ch, attr.sa_range.lr_start);
3✔
2262
                break;
3✔
UNCOV
2263
            case ')':
×
UNCOV
2264
                if (!groups.empty() && groups.back().first == '(') {
×
UNCOV
2265
                    groups.pop_back();
×
2266
                }
UNCOV
2267
                break;
×
2268
            case '}':
3✔
2269
                if (!groups.empty() && groups.back().first == '{') {
3✔
2270
                    groups.pop_back();
3✔
2271
                }
2272
                break;
3✔
UNCOV
2273
            case ']':
×
UNCOV
2274
                if (!groups.empty() && groups.back().first == '[') {
×
UNCOV
2275
                    groups.pop_back();
×
2276
                }
UNCOV
2277
                break;
×
2278
        }
2279
    }
2280
    if (id_start) {
19✔
2281
        fqids.emplace_back(id_start.value());
2✔
2282
    }
2283
    int prev_stage_index = 0;
19✔
2284
    for (auto stage_index : stages) {
50✔
2285
        sa.emplace_back(line_range{prev_stage_index, stage_index},
31✔
2286
                        PRQL_STAGE_ATTR.value());
62✔
2287
        prev_stage_index = stage_index;
31✔
2288
    }
2289
    sa.emplace_back(
19✔
2290
        line_range{prev_stage_index, (int) al.get_string().length()},
19✔
2291
        PRQL_STAGE_ATTR.value());
38✔
2292
    for (const auto& group : groups) {
21✔
2293
        sa.emplace_back(line_range{group.second, group.second + 1},
2✔
2294
                        PRQL_UNTERMINATED_PAREN_ATTR.value());
4✔
2295
    }
2296
    for (const auto& fqid_range : fqids) {
77✔
2297
        sa.emplace_back(fqid_range, PRQL_FQID_ATTR.value());
58✔
2298
    }
2299
    remove_string_attr(sa, &PRQL_IDENTIFIER_ATTR);
19✔
2300
    remove_string_attr(sa, &PRQL_DOT_ATTR);
19✔
2301

2302
    stable_sort(sa.begin(), sa.end());
19✔
2303
}
19✔
2304

2305
}  // namespace sql
2306

2307
namespace prql {
2308

2309
std::string
UNCOV
2310
quote_ident(std::string id)
×
2311
{
2312
    static const auto PLAIN_NAME
UNCOV
2313
        = pcre2pp::code::from_const("^[a-zA-Z_][a-zA-Z_0-9]*$");
×
2314

UNCOV
2315
    if (PLAIN_NAME.find_in(id).ignore_error()) {
×
UNCOV
2316
        return id;
×
2317
    }
2318

UNCOV
2319
    auto buf = auto_buffer::alloc(id.length() + 8);
×
UNCOV
2320
    quote_content(buf, id, '`');
×
2321

UNCOV
2322
    return fmt::format(FMT_STRING("`{}`"), buf.in());
×
2323
}
2324

2325
}  // namespace prql
2326

2327
}  // 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