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

tstack / lnav / 17589970077-2502

09 Sep 2025 05:00PM UTC coverage: 65.196% (-5.0%) from 70.225%
17589970077-2502

push

github

tstack
[format] add fields for source file/line

Knowing the source file/line context in a log
message can help find log messages when using
log2src.

56 of 70 new or added lines in 2 files covered. (80.0%)

13954 existing lines in 210 files now uncovered.

45516 of 69814 relevant lines covered (65.2%)

404154.37 hits per line

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

83.58
/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
static int
860
handle_db_list(void* ptr, int ncols, char** colvalues, char** colnames)
1✔
861
{
862
    auto* smc = (struct sqlite_metadata_callbacks*) ptr;
1✔
863

864
    smc->smc_db_list[colvalues[1]] = std::vector<std::string>();
3✔
865
    if (!smc->smc_database_list) {
1✔
UNCOV
866
        return 0;
×
867
    }
868

869
    return smc->smc_database_list(ptr, ncols, colvalues, colnames);
1✔
870
}
871

872
struct table_list_data {
873
    sqlite_metadata_callbacks* tld_callbacks;
874
    db_table_map_t::iterator* tld_iter;
875
};
876

877
static int
878
handle_table_list(void* ptr, int ncols, char** colvalues, char** colnames)
106✔
879
{
880
    auto* tld = (struct table_list_data*) ptr;
106✔
881

882
    (*tld->tld_iter)->second.emplace_back(colvalues[0]);
106✔
883
    if (!tld->tld_callbacks->smc_table_list) {
106✔
UNCOV
884
        return 0;
×
885
    }
886

887
    return tld->tld_callbacks->smc_table_list(
212✔
888
        tld->tld_callbacks, ncols, colvalues, colnames);
106✔
889
}
890

891
int
892
walk_sqlite_metadata(sqlite3* db, sqlite_metadata_callbacks& smc)
1✔
893
{
894
    auto_mem<char, sqlite3_free> errmsg;
1✔
895
    int retval;
896

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

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

916
    for (auto iter = smc.smc_db_list.begin(); iter != smc.smc_db_list.end();
2✔
917
         ++iter)
1✔
918
    {
919
        table_list_data tld = {&smc, &iter};
1✔
920
        auto_mem<char, sqlite3_free> query;
1✔
921

922
        query = sqlite3_mprintf(
923
            "SELECT name,sql FROM %Q.sqlite_master "
924
            "WHERE type in ('table', 'view')",
925
            iter->first.c_str());
1✔
926

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

933
        for (auto table_iter = iter->second.begin();
1✔
934
             table_iter != iter->second.end();
107✔
935
             ++table_iter)
106✔
936
        {
937
            auto_mem<char, sqlite3_free> table_query;
106✔
938
            smc.smc_table_name = *table_iter;
106✔
939

940
            table_query = sqlite3_mprintf("pragma %Q.table_xinfo(%Q)",
941
                                          iter->first.c_str(),
106✔
942
                                          smc.smc_table_name.c_str());
212✔
943
            if (table_query == nullptr) {
106✔
UNCOV
944
                return SQLITE_NOMEM;
×
945
            }
946

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

956
            table_query = sqlite3_mprintf("pragma %Q.foreign_key_list(%Q)",
957
                                          iter->first.c_str(),
106✔
958
                                          smc.smc_table_name.c_str());
212✔
959
            if (table_query == nullptr) {
106✔
960
                return SQLITE_NOMEM;
×
961
            }
962

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

978
    return retval;
1✔
979
}
1✔
980

981
static int
982
schema_collation_list(void* ptr, int ncols, char** colvalues, char** colnames)
8✔
983
{
984
    return 0;
8✔
985
}
986

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

994
    attach_sql = sqlite3_mprintf(
995
        "ATTACH DATABASE %Q AS %Q;\n", colvalues[2], colvalues[1]);
1✔
996

997
    schema_out += attach_sql;
1✔
998

999
    return 0;
1✔
1000
}
1✔
1001

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

1009
    create_sql = sqlite3_mprintf("%s;\n", colvalues[1]);
106✔
1010

1011
    schema_out += create_sql;
106✔
1012

1013
    return 0;
106✔
1014
}
106✔
1015

1016
static int
1017
schema_table_info(void* ptr, int ncols, char** colvalues, char** colnames)
3,003✔
1018
{
1019
    return 0;
3,003✔
1020
}
1021

1022
static int
UNCOV
1023
schema_foreign_key_list(void* ptr, int ncols, char** colvalues, char** colnames)
×
1024
{
UNCOV
1025
    return 0;
×
1026
}
1027

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

1041
    walk_sqlite_metadata(db, schema_sql_meta_callbacks);
1✔
1042
}
1✔
1043

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

1049
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
1✔
1050

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

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

1068
    size_t base_start = filename.find_last_of("/\\");
1✔
1069
    std::string db_name;
1✔
1070

1071
    if (base_start == std::string::npos) {
1✔
UNCOV
1072
        db_name = filename;
×
1073
    } else {
1074
        db_name = filename.substr(base_start + 1);
1✔
1075
    }
1076

1077
    db_name = std::regex_replace(db_name, db_name_converter, "_");
1✔
1078

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

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

1095
static void
1096
sqlite_logger(void* dummy, int code, const char* msg)
162✔
1097
{
1098
    lnav_log_level_t level;
1099

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

1119
    log_msg(level, __FILE__, __LINE__, "(%d) %s", code, msg);
162✔
1120

1121
    ensure(code != 21);
162✔
1122
}
162✔
1123

1124
void
1125
sql_install_logger()
693✔
1126
{
1127
#ifdef SQLITE_CONFIG_LOG
1128
    sqlite3_config(SQLITE_CONFIG_LOG, sqlite_logger, NULL);
693✔
1129
#endif
1130
}
693✔
1131

1132
bool
1133
sql_ident_needs_quote(const char* ident)
2,778✔
1134
{
1135
    for (int lpc = 0; ident[lpc]; lpc++) {
25,419✔
1136
        if (!isalnum(ident[lpc]) && ident[lpc] != '_') {
23,811✔
1137
            return true;
1,170✔
1138
        }
1139
    }
1140

1141
    return false;
1,608✔
1142
}
1143

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

1151
auto_mem<char, sqlite3_free>
1152
sql_quote_ident(const char* ident)
431,858✔
1153
{
1154
    bool needs_quote = false;
431,858✔
1155
    size_t quote_count = 0, alloc_size;
431,858✔
1156
    auto_mem<char, sqlite3_free> retval;
431,858✔
1157

1158
    for (int lpc = 0; ident[lpc]; lpc++) {
4,140,682✔
1159
        if ((lpc == 0 && isdigit(ident[lpc]))
3,708,824✔
1160
            || (!isalnum(ident[lpc]) && ident[lpc] != '_'))
3,708,824✔
1161
        {
1162
            needs_quote = true;
36,405✔
1163
        }
1164
        if (ident[lpc] == '"') {
3,708,824✔
UNCOV
1165
            quote_count += 1;
×
1166
        }
1167
    }
1168

1169
    alloc_size = strlen(ident) + quote_count * 2 + (needs_quote ? 2 : 0) + 1;
431,858✔
1170
    if ((retval = (char*) sqlite3_malloc(alloc_size)) == nullptr) {
431,858✔
UNCOV
1171
        retval = nullptr;
×
1172
    } else {
1173
        char* curr = retval;
431,858✔
1174

1175
        if (needs_quote) {
431,858✔
1176
            curr[0] = '"';
24,097✔
1177
            curr += 1;
24,097✔
1178
        }
1179
        for (size_t lpc = 0; ident[lpc] != '\0'; lpc++) {
4,140,682✔
1180
            switch (ident[lpc]) {
3,708,824✔
UNCOV
1181
                case '"':
×
UNCOV
1182
                    curr[0] = '"';
×
UNCOV
1183
                    curr += 1;
×
1184
                default:
3,708,824✔
1185
                    curr[0] = ident[lpc];
3,708,824✔
1186
                    break;
3,708,824✔
1187
            }
1188
            curr += 1;
3,708,824✔
1189
        }
1190
        if (needs_quote) {
431,858✔
1191
            curr[0] = '"';
24,097✔
1192
            curr += 1;
24,097✔
1193
        }
1194

1195
        *curr = '\0';
431,858✔
1196
    }
1197

1198
    return retval;
431,858✔
UNCOV
1199
}
×
1200

1201
std::string
1202
sql_safe_ident(const string_fragment& ident)
9,820✔
1203
{
1204
    std::string retval = std::to_string(ident);
9,820✔
1205

1206
    for (size_t lpc = 0; lpc < retval.size(); lpc++) {
98,802✔
1207
        char ch = retval[lpc];
88,982✔
1208

1209
        if (isalnum(ch) || ch == '_') {
88,982✔
1210
            retval[lpc] = tolower(ch);
78,562✔
1211
        } else {
1212
            retval[lpc] = '_';
10,420✔
1213
        }
1214
    }
1215

1216
    return retval;
9,820✔
UNCOV
1217
}
×
1218

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

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

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

1256
        attr_line_t pointer;
1✔
1257

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

1263
        retval.insert(line_with_error.sf_end + 1, pointer).rtrim();
1✔
1264
    }
1✔
1265

1266
    return retval;
9✔
UNCOV
1267
}
×
1268

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

1280
    sqlite3_clear_bindings(stmt);
677✔
1281

1282
    param_count = sqlite3_bind_parameter_count(stmt);
677✔
1283
    for (int lpc = 0; lpc < param_count; lpc++) {
677✔
1284
        const char* name;
1285

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

1317
            case SQLITE_ROW: {
105✔
1318
                int ncols = sqlite3_column_count(stmt);
105✔
1319

1320
                for (int lpc = 0; lpc < ncols; lpc++) {
630✔
1321
                    const char* name = sqlite3_column_name(stmt, lpc);
525✔
1322
                    auto* raw_value = sqlite3_column_value(stmt, lpc);
525✔
1323
                    auto value_type = sqlite3_value_type(raw_value);
525✔
1324
                    scoped_value_t value;
525✔
1325

1326
                    switch (value_type) {
525✔
1327
                        case SQLITE_INTEGER:
105✔
1328
                            value = (int64_t) sqlite3_value_int64(raw_value);
105✔
1329
                            break;
105✔
UNCOV
1330
                        case SQLITE_FLOAT:
×
UNCOV
1331
                            value = sqlite3_value_double(raw_value);
×
1332
                            break;
×
1333
                        case SQLITE_NULL:
1✔
1334
                            value = null_value_t{};
1✔
1335
                            break;
1✔
1336
                        default:
419✔
1337
                            value = string_fragment::from_bytes(
419✔
1338
                                sqlite3_value_text(raw_value),
1339
                                sqlite3_value_bytes(raw_value));
838✔
1340
                            break;
419✔
1341
                    }
1342
                    lvars[name] = value;
525✔
1343
                }
525✔
1344
                break;
105✔
1345
            }
1346

1347
            default: {
1✔
1348
                const auto* sql_str = sqlite3_sql(stmt);
1✔
1349
                auto sql_content
1350
                    = annotate_sql_with_error(db, sql_str, nullptr);
1✔
1351

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

1364
    sqlite3_reset(stmt);
677✔
1365
}
677✔
1366

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

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

1382
        while (isspace(*script) && script[0]) {
2,660✔
1383
            script += 1;
1,354✔
1384
        }
1385
        for (const char* ch = script_orig; ch < script && ch[0]; ch++) {
112,339✔
1386
            if (*ch == '\n') {
111,033✔
1387
                line_number += 1;
3,960✔
1388
            }
1389
        }
1390

1391
        retcode = sqlite3_prepare_v2(db, script, -1, stmt.out(), &tail);
1,306✔
1392
        log_debug("retcode %d  %p %p", retcode, script, tail);
1,306✔
1393
        if (retcode != SQLITE_OK) {
1,306✔
1394
            const auto* errmsg = sqlite3_errmsg(db);
1✔
1395
            auto sql_content = annotate_sql_with_error(db, script, tail);
1✔
1396

1397
            errors.emplace_back(
1✔
1398
                lnav::console::user_message::error(
2✔
1399
                    "failed to compile SQL statement")
1400
                    .with_reason(errmsg)
2✔
1401
                    .with_snippet(
1402
                        lnav::console::snippet::from(
3✔
1403
                            intern_string::lookup(src_name), sql_content)
1404
                            .with_line(line_number)));
1✔
1405
            break;
1✔
1406
        }
1✔
1407
        if (script == tail) {
1,305✔
1408
            break;
628✔
1409
        }
1410
        if (stmt == nullptr) {
677✔
1411
        } else {
1412
            sql_execute_script(db, global_vars, src_name, stmt.in(), errors);
677✔
1413
        }
1414

1415
        script = tail;
677✔
1416
    }
1,306✔
1417
}
629✔
1418

1419
void
1420
sql_execute_script(sqlite3* db,
629✔
1421
                   const std::map<std::string, scoped_value_t>& global_vars,
1422
                   const char* src_name,
1423
                   const char* script,
1424
                   std::vector<lnav::console::user_message>& errors)
1425
{
1426
    sql_compile_script(db, global_vars, src_name, script, errors);
629✔
1427
}
629✔
1428

1429
static const struct {
1430
    int sqlite_type;
1431
    const char* collator;
1432
    const char* sample;
1433
} TYPE_TEST_VALUE[] = {
1434
    {SQLITE3_TEXT, "", "foobar"},
1435
    {SQLITE_FLOAT, "", "123.0"},
1436
    {SQLITE_INTEGER, "", "123"},
1437
    {SQLITE_TEXT, "ipaddress", "127.0.0.1"},
1438
    {SQLITE_TEXT, "measure_with_units", "123ms"},
1439
    {SQLITE_TEXT, "measure_with_units", "123 ms"},
1440
    {SQLITE_TEXT, "measure_with_units", "123KB"},
1441
    {SQLITE_TEXT, "measure_with_units", "123 KB"},
1442
    {SQLITE_TEXT, "measure_with_units", "123Kbps"},
1443
    {SQLITE_TEXT, "measure_with_units", "123.0 Kbps"},
1444
    {SQLITE_TEXT, "measure_with_units", "123.0KB"},
1445
    {SQLITE_TEXT, "measure_with_units", "123.0 KB"},
1446
    {SQLITE_TEXT, "measure_with_units", "123.0Kbps"},
1447
    {SQLITE_TEXT, "measure_with_units", "123.0 Kbps"},
1448
};
1449

1450
int
1451
guess_type_from_pcre(const std::string& pattern, std::string& collator)
41,115✔
1452
{
1453
    log_info("guessing SQL type from pattern: %s", pattern.c_str());
41,115✔
1454
    auto compile_res = lnav::pcre2pp::code::from(pattern);
41,115✔
1455
    if (compile_res.isErr()) {
41,115✔
UNCOV
1456
        return SQLITE3_TEXT;
×
1457
    }
1458

1459
    auto re = compile_res.unwrap();
41,115✔
1460
    std::vector<int> matches;
41,115✔
1461
    int retval = SQLITE3_TEXT;
41,115✔
1462
    int index = 0;
41,115✔
1463

1464
    collator.clear();
41,115✔
1465
    for (const auto& test_value : TYPE_TEST_VALUE) {
117,554✔
1466
        log_info("  testing sample: %s", test_value.sample);
114,080✔
1467
        const auto find_res
1468
            = re.find_in(string_fragment::from_c_str(test_value.sample),
228,160✔
1469
                         PCRE2_ANCHORED)
1470
                  .ignore_error();
114,080✔
1471
        if (find_res && find_res->f_all.sf_begin == 0
164,460✔
1472
            && find_res->f_remaining.empty())
164,460✔
1473
        {
1474
            log_info("    matched!");
37,641✔
1475
            matches.push_back(index);
37,641✔
1476
            break;
37,641✔
1477
        }
1478
        if (!find_res) {
76,439✔
1479
            log_info("    mismatch");
63,700✔
1480
        } else if (!find_res->f_remaining.empty()) {
12,739✔
1481
            log_info("    incomplete match");
12,739✔
1482
        }
1483

1484
        index += 1;
76,439✔
1485
    }
1486

1487
    if (matches.size() == 1) {
41,115✔
1488
        retval = TYPE_TEST_VALUE[matches.front()].sqlite_type;
37,641✔
1489
        collator = TYPE_TEST_VALUE[matches.front()].collator;
37,641✔
1490
    }
1491

1492
    return retval;
41,115✔
1493
}
41,115✔
1494

1495
const char*
1496
sqlite3_type_to_string(int type)
431,862✔
1497
{
1498
    switch (type) {
431,862✔
1499
        case SQLITE_FLOAT:
7,621✔
1500
            return "FLOAT";
7,621✔
1501
        case SQLITE_INTEGER:
109,436✔
1502
            return "INTEGER";
109,436✔
1503
        case SQLITE_TEXT:
314,804✔
1504
            return "TEXT";
314,804✔
1505
        case SQLITE_NULL:
1✔
1506
            return "NULL";
1✔
1507
        case SQLITE_BLOB:
×
1508
            return "BLOB";
×
1509
    }
1510

UNCOV
1511
    ensure(!!!"Invalid sqlite type");
×
1512

1513
    return nullptr;
1514
}
1515

1516
/* XXX figure out how to do this with the template */
1517
void
1518
sqlite_close_wrapper(void* mem)
1,427✔
1519
{
1520
    sqlite3_close_v2((sqlite3*) mem);
1,427✔
1521
}
1,427✔
1522

1523
int
1524
sqlite_authorizer(void* pUserData,
7,116✔
1525
                  int action_code,
1526
                  const char* detail1,
1527
                  const char* detail2,
1528
                  const char* detail3,
1529
                  const char* detail4)
1530
{
1531
    if (action_code == SQLITE_ATTACH) {
7,116✔
1532
        return SQLITE_DENY;
4✔
1533
    }
1534
    return SQLITE_OK;
7,112✔
1535
}
1536

1537
attr_line_t
1538
sqlite3_errmsg_to_attr_line(sqlite3* db)
1✔
1539
{
1540
    const auto* errmsg = sqlite3_errmsg(db);
1✔
1541
    if (startswith(errmsg, sqlitepp::ERROR_PREFIX)) {
1✔
1542
        auto from_res = lnav::from_json<lnav::console::user_message>(
1543
            &errmsg[strlen(sqlitepp::ERROR_PREFIX)]);
1✔
1544

1545
        if (from_res.isOk()) {
1✔
1546
            return from_res.unwrap().to_attr_line();
3✔
1547
        }
1548

UNCOV
1549
        return from_res.unwrapErr()[0].um_message.get_string();
×
1550
    }
1✔
1551

UNCOV
1552
    return attr_line_t(errmsg);
×
1553
}
1554

1555
static void
1556
append_kw(std::string& retval, bool& first, const char* kw)
252,404✔
1557
{
1558
    if (!first) {
252,404✔
1559
        retval.append("|");
251,692✔
1560
    } else {
1561
        first = false;
712✔
1562
    }
1563
    retval.append("\\b");
252,404✔
1564
    retval.append(kw);
252,404✔
1565
    retval.append("\\b");
252,404✔
1566
}
252,404✔
1567

1568
static std::string
1569
sql_keyword_re(lnav::sql::dialect dia)
712✔
1570
{
1571
    std::string retval = "(?:";
712✔
1572
    auto first = true;
712✔
1573

1574
    switch (dia) {
712✔
1575
        case lnav::sql::dialect::sqlite:
356✔
1576
            for (const char* kw : sqlite_keywords) {
51,976✔
1577
                append_kw(retval, first, kw);
51,620✔
1578
            }
1579
            break;
356✔
1580
        default:
356✔
1581
            for (const char* kw : pg_sql_keywords) {
201,140✔
1582
                append_kw(retval, first, kw);
200,784✔
1583
            }
1584
            break;
356✔
1585
    }
1586

1587
    retval += ")";
712✔
1588

1589
    return retval;
1,424✔
UNCOV
1590
}
×
1591

1592
constexpr string_attr_type<void> SQL_COMMAND_ATTR("sql_command");
1593
constexpr string_attr_type<void> SQL_KEYWORD_ATTR("sql_keyword");
1594
constexpr string_attr_type<void> SQL_IDENTIFIER_ATTR("sql_ident");
1595
constexpr string_attr_type<std::string> SQL_FUNCTION_ATTR("sql_func");
1596
constexpr string_attr_type<void> SQL_STRING_ATTR("sql_string");
1597
constexpr string_attr_type<void> SQL_HEX_LIT_ATTR("sql_hex_lit");
1598
constexpr string_attr_type<void> SQL_NUMBER_ATTR("sql_number");
1599
constexpr string_attr_type<void> SQL_OPERATOR_ATTR("sql_oper");
1600
constexpr string_attr_type<void> SQL_PAREN_ATTR("sql_paren");
1601
constexpr string_attr_type<void> SQL_COMMA_ATTR("sql_comma");
1602
constexpr string_attr_type<void> SQL_GARBAGE_ATTR("sql_garbage");
1603
constexpr string_attr_type<void> SQL_COMMENT_ATTR("sql_comment");
1604

1605
void
1606
annotate_sql_statement(attr_line_t& al, lnav::sql::dialect dia)
1,405✔
1607
{
1608
    static const std::string sqlite_keyword_re_str
1609
        = R"(\A)" + sql_keyword_re(lnav::sql::dialect::sqlite);
1,405✔
1610
    static const auto sqlite_keyword_re
1611
        = lnav::pcre2pp::code::from(sqlite_keyword_re_str, PCRE2_CASELESS)
712✔
1612
              .unwrap();
1,761✔
1613
    static const std::string sql_keyword_re_str
1614
        = R"(\A)" + sql_keyword_re(lnav::sql::dialect::sql);
1,405✔
1615
    static const auto sql_keyword_re
1616
        = lnav::pcre2pp::code::from(sql_keyword_re_str, PCRE2_CASELESS)
712✔
1617
              .unwrap();
1,761✔
1618

1619
    static const struct {
1620
        lnav::pcre2pp::code re;
1621
        const string_attr_type<void>* type;
1622
    } PATTERNS[] = {
1623
        {
1624
            lnav::pcre2pp::code::from_const(R"(\A,)"),
1625
            &SQL_COMMA_ATTR,
1626
        },
1627
        {
1628
            lnav::pcre2pp::code::from_const(R"(\A\(|\A\))"),
1629
            &SQL_PAREN_ATTR,
1630
        },
1631
        {
1632
            lnav::pcre2pp::code::from_const(
1633
                R"(\A(?:x|X)'(?:(?:[0-9a-fA-F]{2})+'|[0-9a-fA-F]*$))"),
1634
            &SQL_HEX_LIT_ATTR,
1635
        },
1636
        {
1637
            lnav::pcre2pp::code::from_const(R"(\A'[^']*('(?:'[^']*')*|$))"),
1638
            &SQL_STRING_ATTR,
1639
        },
1640
        {
1641
            lnav::pcre2pp::code::from_const(R"(\A0x[0-9a-fA-F]+)"),
1642
            &SQL_NUMBER_ATTR,
1643
        },
1644
        {
1645
            lnav::pcre2pp::code::from_const(
1646
                R"(\A-?\d+(?:\.\d+)?(?:[eE][\-\+]?\d+)?\b)"),
1647
            &SQL_NUMBER_ATTR,
1648
        },
1649
        {
1650
            lnav::pcre2pp::code::from_const(
1651
                R"(\A(((\$|:|@)?\b[a-z_]\w*)|\"([^\"]+)\"|\[([^\]]+)]))",
1652
                PCRE2_CASELESS),
1653
            &SQL_IDENTIFIER_ATTR,
1654
        },
1655
        {
1656
            lnav::pcre2pp::code::from_const(R"(\A--.*)"),
1657
            &SQL_COMMENT_ATTR,
1658
        },
1659
        {
1660
            lnav::pcre2pp::code::from_const(
1661
                R"(\A(~|%|\*|\->{1,2}|<=|>=|<<|>>|<>|<|>|={1,2}|!=?|\-|\+|\|\|{1,2}|&|::))"),
1662
            &SQL_OPERATOR_ATTR,
1663
        },
1664
        {
1665
            lnav::pcre2pp::code::from_const(R"(\A[0-9][a-zA-Z0-9\-\._]+)"),
1666
            &SQL_GARBAGE_ATTR,
1667
        },
1668
        {
1669
            lnav::pcre2pp::code::from_const(R"(\A.)"),
1670
            &SQL_GARBAGE_ATTR,
1671
        },
1672
    };
1,405✔
1673

1674
    static const auto cmd_pattern
1675
        = lnav::pcre2pp::code::from_const(R"(^;?(\.\w+))");
1,405✔
1676
    static const auto ws_pattern = lnav::pcre2pp::code::from_const(R"(\A\s+)");
1,405✔
1677

1678
    const auto& line = al.get_string();
1,405✔
1679
    auto& sa = al.get_attrs();
1,405✔
1680

1681
    if (lnav::sql::is_prql(line)) {
1,405✔
1682
        lnav::sql::annotate_prql_statement(al);
10✔
1683
        return;
16✔
1684
    }
1685

1686
    auto cmd_find_res
1687
        = cmd_pattern.find_in(line, PCRE2_ANCHORED).ignore_error();
1,395✔
1688
    if (cmd_find_res) {
1,395✔
1689
        auto cap = cmd_find_res->f_all;
6✔
1690
        sa.emplace_back(line_range(cap.sf_begin, cap.sf_end),
6✔
1691
                        SQL_COMMAND_ATTR.value());
12✔
1692
        return;
6✔
1693
    }
1694

1695
    auto remaining = string_fragment::from_str(line);
1,389✔
1696
    while (!remaining.empty()) {
21,187✔
1697
        auto ws_find_res = ws_pattern.find_in(remaining).ignore_error();
19,798✔
1698
        if (ws_find_res) {
19,798✔
1699
            remaining = ws_find_res->f_remaining;
6,698✔
1700
            continue;
9,982✔
1701
        }
1702
        const auto& kw_pat = dia == lnav::sql::dialect::sqlite
13,100✔
1703
            ? sqlite_keyword_re
1704
            : sql_keyword_re;
1705
        auto kw_pat_find_res = kw_pat.find_in(remaining).ignore_error();
13,100✔
1706
        if (kw_pat_find_res) {
13,100✔
1707
            sa.emplace_back(to_line_range(kw_pat_find_res->f_all),
3,284✔
1708
                            SQL_KEYWORD_ATTR.value());
6,568✔
1709
            remaining = kw_pat_find_res->f_remaining;
3,284✔
1710
            continue;
3,284✔
1711
        }
1712
        for (const auto& pat : PATTERNS) {
45,931✔
1713
            auto pat_find_res = pat.re.find_in(remaining).ignore_error();
45,931✔
1714
            if (pat_find_res) {
45,931✔
1715
                sa.emplace_back(to_line_range(pat_find_res->f_all),
9,816✔
1716
                                pat.type->value());
19,632✔
1717
                remaining = pat_find_res->f_remaining;
9,816✔
1718
                break;
9,816✔
1719
            }
1720
        }
1721
    }
1722

1723
    string_attrs_t::const_iterator iter;
1,389✔
1724
    int start = 0;
1,389✔
1725

1726
    while ((iter = find_string_attr(sa, &SQL_IDENTIFIER_ATTR, start))
5,898✔
1727
           != sa.end())
9,018✔
1728
    {
1729
        string_attrs_t::const_iterator piter;
3,120✔
1730

1731
        start = iter->sa_range.lr_end;
3,120✔
1732
        if (start < (ssize_t) line.length() && line[start] == '(') {
3,120✔
1733
            ssize_t pstart = start + 1;
1,088✔
1734
            int depth = 1;
1,088✔
1735

1736
            while (depth > 0
1,088✔
1737
                   && (piter = find_string_attr(sa, &SQL_PAREN_ATTR, pstart))
5,273✔
1738
                       != sa.end())
5,273✔
1739
            {
1740
                if (line[piter->sa_range.lr_start] == '(') {
1,387✔
1741
                    depth += 1;
156✔
1742
                } else {
1743
                    depth -= 1;
1,231✔
1744
                }
1745
                pstart = piter->sa_range.lr_end;
1,387✔
1746
            }
1747

1748
            line_range func_range{iter->sa_range.lr_start};
1,088✔
1749
            if (piter == sa.end()) {
1,088✔
1750
                func_range.lr_end = line.length();
12✔
1751
            } else {
1752
                func_range.lr_end = piter->sa_range.lr_end;
1,076✔
1753
            }
1754
            auto func_name = al.to_string_fragment(iter);
1,088✔
1755
            sa.emplace_back(
1,088✔
1756
                func_range,
1757
                SQL_FUNCTION_ATTR.value(tolower(func_name.to_string())));
2,176✔
1758
        }
1759
    }
1760

1761
    // remove_string_attr(sa, &SQL_PAREN_ATTR);
1762
    stable_sort(sa.begin(), sa.end());
1,389✔
1763
}
1764

1765
std::vector<const help_text*>
1766
find_sql_help_for_line(const attr_line_t& al, size_t x)
10✔
1767
{
1768
    static const auto* sql_cmd_map
1769
        = injector::get<readline_context::command_map_t*, sql_cmd_map_tag>();
10✔
1770

1771
    std::vector<const help_text*> retval;
10✔
1772
    const auto& sa = al.get_attrs();
10✔
1773
    std::string name;
10✔
1774

1775
    x = al.nearest_text(x);
10✔
1776

1777
    {
1778
        auto sa_opt = get_string_attr(al.get_attrs(), &SQL_COMMAND_ATTR);
10✔
1779
        if (sa_opt) {
10✔
UNCOV
1780
            auto cmd_name = al.get_substring((*sa_opt)->sa_range);
×
UNCOV
1781
            auto cmd_iter = sql_cmd_map->find(cmd_name);
×
1782

UNCOV
1783
            if (cmd_iter != sql_cmd_map->end()) {
×
UNCOV
1784
                return {&cmd_iter->second->c_help};
×
1785
            }
1786
        }
1787

1788
        auto prql_trans_iter = find_string_attr_containing(
10✔
1789
            al.get_attrs(), &lnav::sql::PRQL_TRANSFORM_ATTR, x);
1790
        if (prql_trans_iter != al.get_attrs().end()) {
10✔
1791
            auto cmd_name = al.get_substring(prql_trans_iter->sa_range);
1✔
1792
            auto cmd_iter = sql_cmd_map->find(cmd_name);
1✔
1793

1794
            if (cmd_iter != sql_cmd_map->end()) {
1✔
1795
                return {&cmd_iter->second->c_help};
3✔
1796
            }
1797
        }
1✔
1798
    }
1799

1800
    auto prql_fqid_iter = find_string_attr_containing(
9✔
1801
        al.get_attrs(), &lnav::sql ::PRQL_FQID_ATTR, x);
1802
    if (prql_fqid_iter != al.get_attrs().end()) {
9✔
1803
        auto fqid = al.get_substring(prql_fqid_iter->sa_range);
1✔
1804
        auto cmd_iter = sql_cmd_map->find(fqid);
1✔
1805
        if (cmd_iter != sql_cmd_map->end()) {
1✔
1806
            return {&cmd_iter->second->c_help};
3✔
1807
        }
1808

UNCOV
1809
        auto func_pair = lnav::sql::prql_functions.equal_range(fqid);
×
1810

UNCOV
1811
        for (auto func_iter = func_pair.first; func_iter != func_pair.second;
×
1812
             ++func_iter)
1813
        {
UNCOV
1814
            retval.emplace_back(func_iter->second);
×
UNCOV
1815
            return retval;
×
1816
        }
1817
    }
1✔
1818

1819
    std::vector<std::string> kw;
8✔
1820
    auto iter = rfind_string_attr_if(sa, x, [&al, &name, &kw, x](auto sa) {
8✔
1821
        if (sa.sa_type != &SQL_FUNCTION_ATTR && sa.sa_type != &SQL_KEYWORD_ATTR)
33✔
1822
        {
1823
            return false;
18✔
1824
        }
1825

1826
        const auto& str = al.get_string();
15✔
1827
        const auto& lr = sa.sa_range;
15✔
1828

1829
        if (sa.sa_type == &SQL_FUNCTION_ATTR) {
15✔
1830
            if (!sa.sa_range.contains(x)) {
7✔
1831
                return false;
1✔
1832
            }
1833
        }
1834

1835
        auto lpc = lr.lr_start;
14✔
1836
        for (; lpc < lr.lr_end; lpc++) {
92✔
1837
            if (!isalnum(str[lpc]) && str[lpc] != '_') {
84✔
1838
                break;
6✔
1839
            }
1840
        }
1841

1842
        auto tmp_name = str.substr(lr.lr_start, lpc - lr.lr_start);
14✔
1843
        if (sa.sa_type == &SQL_KEYWORD_ATTR) {
14✔
1844
            tmp_name = toupper(tmp_name);
8✔
1845
        }
1846
        bool retval = sqlite_function_help.count(tmp_name) > 0;
14✔
1847

1848
        if (retval) {
14✔
1849
            kw.push_back(tmp_name);
14✔
1850
            name = tmp_name;
14✔
1851
        }
1852
        return retval;
14✔
1853
    });
14✔
1854

1855
    if (iter != sa.end()) {
8✔
1856
        auto func_pair = sqlite_function_help.equal_range(name);
8✔
1857
        size_t help_count = std::distance(func_pair.first, func_pair.second);
8✔
1858

1859
        if (help_count > 1 && name != func_pair.first->second->ht_name) {
8✔
UNCOV
1860
            while (func_pair.first != func_pair.second) {
×
UNCOV
1861
                if (find(kw.begin(), kw.end(), func_pair.first->second->ht_name)
×
UNCOV
1862
                    == kw.end())
×
1863
                {
UNCOV
1864
                    ++func_pair.first;
×
1865
                } else {
UNCOV
1866
                    func_pair.second = next(func_pair.first);
×
UNCOV
1867
                    break;
×
1868
                }
1869
            }
1870
        }
1871
        for (auto func_iter = func_pair.first; func_iter != func_pair.second;
17✔
1872
             ++func_iter)
9✔
1873
        {
1874
            retval.emplace_back(func_iter->second);
9✔
1875
        }
1876
    }
1877

1878
    return retval;
8✔
1879
}
10✔
1880

1881
template<>
1882
Result<lnav::sql::dialect, std::string>
1883
from(string_fragment sf)
5✔
1884
{
1885
    if (sf == "sql"_frag) {
5✔
UNCOV
1886
        return Ok(lnav::sql::dialect::sql);
×
1887
    }
1888
    if (sf == "sqlite"_frag) {
5✔
UNCOV
1889
        return Ok(lnav::sql::dialect::sqlite);
×
1890
    }
1891
    if (sf == "plpgsql"_frag) {
5✔
1892
        return Ok(lnav::sql::dialect::plpgsql);
10✔
1893
    }
UNCOV
1894
    if (sf == "prql"_frag) {
×
UNCOV
1895
        return Ok(lnav::sql::dialect::prql);
×
1896
    }
UNCOV
1897
    return Err(fmt::format(FMT_STRING("unknown SQL dialect: {}"), sf));
×
1898
}
1899

1900
namespace lnav {
1901
namespace sql {
1902

1903
auto_mem<char, sqlite3_free>
1904
mprintf(const char* fmt, ...)
431,862✔
1905
{
1906
    auto_mem<char, sqlite3_free> retval;
431,862✔
1907
    va_list args;
1908

1909
    va_start(args, fmt);
431,862✔
1910
    retval = sqlite3_vmprintf(fmt, args);
431,862✔
1911
    va_end(args);
431,862✔
1912

1913
    return retval;
863,724✔
UNCOV
1914
}
×
1915

1916
bool
1917
is_prql(const string_fragment& sf)
3,081✔
1918
{
1919
    auto trimmed = sf.trim().skip(string_fragment::tag1{';'});
3,081✔
1920

1921
    return (trimmed.startswith("let ") || trimmed.startswith("from"));
6,162✔
1922
}
1923

1924
static const char* const prql_transforms[] = {
1925
    "aggregate",
1926
    "append",
1927
    "derive",
1928
    "filter",
1929
    "from",
1930
    "group",
1931
    "join",
1932
    "loop",
1933
    "select",
1934
    "sort",
1935
    "take",
1936
    "window",
1937

1938
    nullptr,
1939
};
1940

1941
const char* const prql_keywords[] = {
1942
    "average", "avg", "case", "count", "count_distinct", "false", "func",
1943
    "into",    "let", "max",  "min",   "module",         "null",  "prql",
1944
    "stddev",  "sum", "true", "type",
1945

1946
    nullptr,
1947
};
1948

1949
std::string
1950
prql_keyword_re()
9✔
1951
{
1952
    std::string retval = "(?:";
9✔
1953
    bool first = true;
9✔
1954

1955
    for (const char* kw : prql_keywords) {
171✔
1956
        if (kw == nullptr) {
171✔
1957
            break;
9✔
1958
        }
1959
        if (!first) {
162✔
1960
            retval.append("|");
153✔
1961
        } else {
1962
            first = false;
9✔
1963
        }
1964
        retval.append("\\b");
162✔
1965
        retval.append(kw);
162✔
1966
        retval.append("\\b");
162✔
1967
    }
1968
    retval += ")";
9✔
1969

1970
    return retval;
9✔
UNCOV
1971
}
×
1972

1973
std::string
1974
prql_transform_re()
9✔
1975
{
1976
    std::string retval = "(?:";
9✔
1977
    bool first = true;
9✔
1978

1979
    for (const char* kw : prql_transforms) {
117✔
1980
        if (kw == nullptr) {
117✔
1981
            break;
9✔
1982
        }
1983
        if (!first) {
108✔
1984
            retval.append("|");
99✔
1985
        } else {
1986
            first = false;
9✔
1987
        }
1988
        retval.append("\\b");
108✔
1989
        retval.append(kw);
108✔
1990
        retval.append("\\b");
108✔
1991
    }
1992
    retval += ")";
9✔
1993

1994
    return retval;
9✔
UNCOV
1995
}
×
1996

1997
constexpr string_attr_type<void> PRQL_STAGE_ATTR("prql_stage");
1998
constexpr string_attr_type<void> PRQL_TRANSFORM_ATTR("prql_transform");
1999
constexpr string_attr_type<void> PRQL_KEYWORD_ATTR("prql_keyword");
2000
constexpr string_attr_type<void> PRQL_IDENTIFIER_ATTR("prql_ident");
2001
constexpr string_attr_type<void> PRQL_FQID_ATTR("prql_fqid");
2002
constexpr string_attr_type<void> PRQL_DOT_ATTR("prql_dot");
2003
constexpr string_attr_type<void> PRQL_PIPE_ATTR("prql_pipe");
2004
constexpr string_attr_type<void> PRQL_STRING_ATTR("prql_string");
2005
constexpr string_attr_type<void> PRQL_NUMBER_ATTR("prql_number");
2006
constexpr string_attr_type<void> PRQL_OPERATOR_ATTR("prql_oper");
2007
constexpr string_attr_type<void> PRQL_PAREN_ATTR("prql_paren");
2008
constexpr string_attr_type<void> PRQL_UNTERMINATED_PAREN_ATTR(
2009
    "prql_unterminated_paren");
2010
constexpr string_attr_type<void> PRQL_GARBAGE_ATTR("prql_garbage");
2011
constexpr string_attr_type<void> PRQL_COMMENT_ATTR("prql_comment");
2012

2013
void
2014
annotate_prql_statement(attr_line_t& al)
10✔
2015
{
2016
    static const std::string keyword_re_str = R"(\A)" + prql_keyword_re();
10✔
2017
    static const std::string transform_re_str = R"(\A)" + prql_transform_re();
10✔
2018

2019
    static const struct {
2020
        lnav::pcre2pp::code re;
2021
        const string_attr_type<void>* type;
2022
    } PATTERNS[] = {
2023
        {
2024
            lnav::pcre2pp::code::from_const(R"(\A(?:\[|\]|\{|\}|\(|\)))"),
2025
            &PRQL_PAREN_ATTR,
2026
        },
2027
        {
2028
            lnav::pcre2pp::code::from(transform_re_str).unwrap(),
18✔
2029
            &PRQL_TRANSFORM_ATTR,
2030
        },
2031
        {
2032
            lnav::pcre2pp::code::from(keyword_re_str).unwrap(),
18✔
2033
            &PRQL_KEYWORD_ATTR,
2034
        },
2035
        {
2036
            lnav::pcre2pp::code::from_const(R"(\A(?:f|r|s)?'([^']|\\.)*')"),
2037
            &PRQL_STRING_ATTR,
2038
        },
2039
        {
2040
            lnav::pcre2pp::code::from_const(R"(\A(?:f|r|s)?"([^\"]|\\.)*")"),
2041
            &PRQL_STRING_ATTR,
2042
        },
2043
        {
2044
            lnav::pcre2pp::code::from_const(R"(\A0x[0-9a-fA-F]+)"),
2045
            &PRQL_NUMBER_ATTR,
2046
        },
2047
        {
2048
            lnav::pcre2pp::code::from_const(
2049
                R"(\A-?\d+(?:\.\d+)?(?:[eE][\-\+]?\d+)?)"),
2050
            &PRQL_NUMBER_ATTR,
2051
        },
2052
        {
2053
            lnav::pcre2pp::code::from_const(
2054
                R"(\A(?:(?:(?:\$)?\b[a-z_]\w*)|`([^`]+)`))", PCRE2_CASELESS),
2055
            &PRQL_IDENTIFIER_ATTR,
2056
        },
2057
        {
2058
            lnav::pcre2pp::code::from_const(R"(\A#.*)"),
2059
            &PRQL_COMMENT_ATTR,
2060
        },
2061
        {
2062
            lnav::pcre2pp::code::from_const(
2063
                R"(\A(\*|\->{1,2}|<|>|=>|={1,2}|\|\||&&|!|\-|\+|~=|\.\.|,|\?\?))"),
2064
            &PRQL_OPERATOR_ATTR,
2065
        },
2066
        {
2067
            lnav::pcre2pp::code::from_const(R"(\A(?:\||\n))"),
2068
            &PRQL_PIPE_ATTR,
2069
        },
2070
        {
2071
            lnav::pcre2pp::code::from_const(R"(\A\.)"),
2072
            &PRQL_DOT_ATTR,
2073
        },
2074
        {
2075
            lnav::pcre2pp::code::from_const(R"(\A.)"),
2076
            &PRQL_GARBAGE_ATTR,
2077
        },
2078
    };
28✔
2079

2080
    static const auto ws_pattern
2081
        = lnav::pcre2pp::code::from_const(R"(\A[ \t\r]+)");
10✔
2082

2083
    const auto& line = al.get_string();
10✔
2084
    auto& sa = al.get_attrs();
10✔
2085
    auto remaining = string_fragment::from_str(line);
10✔
2086
    while (!remaining.empty()) {
159✔
2087
        auto ws_find_res = ws_pattern.find_in(remaining).ignore_error();
149✔
2088
        if (ws_find_res) {
149✔
2089
            remaining = ws_find_res->f_remaining;
61✔
2090
            continue;
61✔
2091
        }
2092
        for (const auto& pat : PATTERNS) {
651✔
2093
            auto pat_find_res = pat.re.find_in(remaining).ignore_error();
651✔
2094
            if (pat_find_res) {
651✔
2095
                sa.emplace_back(to_line_range(pat_find_res->f_all),
88✔
2096
                                pat.type->value());
176✔
2097
                if (sa.back().sa_type == &PRQL_PIPE_ATTR
88✔
2098
                    && pat_find_res->f_all == "\n"_frag)
88✔
2099
                {
2100
                    sa.back().sa_range.lr_start += 1;
3✔
2101
                }
2102
                remaining = pat_find_res->f_remaining;
88✔
2103
                break;
88✔
2104
            }
2105
        }
2106
    }
2107

2108
    auto stages = std::vector<int>{};
10✔
2109
    std::vector<std::pair<char, int>> groups;
10✔
2110
    std::vector<line_range> fqids;
10✔
2111
    std::optional<line_range> id_start;
10✔
2112
    const string_attr_type_base* last_attr_type = nullptr;
10✔
2113
    bool saw_id_dot = false;
10✔
2114
    for (const auto& attr : sa) {
98✔
2115
        if (groups.empty() && attr.sa_type == &PRQL_PIPE_ATTR
174✔
2116
            && last_attr_type != &PRQL_PIPE_ATTR)
174✔
2117
        {
2118
            stages.push_back(attr.sa_range.lr_start);
17✔
2119
        }
2120
        last_attr_type = attr.sa_type;
88✔
2121
        if (!id_start) {
88✔
2122
            if (attr.sa_type == &PRQL_IDENTIFIER_ATTR) {
56✔
2123
                id_start = attr.sa_range;
22✔
2124
                saw_id_dot = false;
22✔
2125
            }
2126
        } else if (!saw_id_dot) {
32✔
2127
            if (attr.sa_type == &PRQL_DOT_ATTR) {
26✔
2128
                saw_id_dot = true;
6✔
2129
            } else {
2130
                fqids.emplace_back(id_start.value());
20✔
2131
                id_start = std::nullopt;
20✔
2132
                saw_id_dot = false;
20✔
2133
            }
2134
        } else {
2135
            if (attr.sa_type == &PRQL_IDENTIFIER_ATTR) {
6✔
2136
                id_start = line_range{
6✔
2137
                    id_start.value().lr_start,
6✔
2138
                    attr.sa_range.lr_end,
6✔
2139
                };
6✔
2140
            } else {
UNCOV
2141
                id_start = std::nullopt;
×
2142
            }
2143
            saw_id_dot = false;
6✔
2144
        }
2145
        if (attr.sa_type != &PRQL_PAREN_ATTR) {
88✔
2146
            continue;
86✔
2147
        }
2148

2149
        auto ch = line[attr.sa_range.lr_start];
2✔
2150
        switch (ch) {
2✔
2151
            case '(':
1✔
2152
            case '{':
2153
            case '[':
2154
                groups.emplace_back(ch, attr.sa_range.lr_start);
1✔
2155
                break;
1✔
UNCOV
2156
            case ')':
×
UNCOV
2157
                if (!groups.empty() && groups.back().first == '(') {
×
UNCOV
2158
                    groups.pop_back();
×
2159
                }
UNCOV
2160
                break;
×
2161
            case '}':
1✔
2162
                if (!groups.empty() && groups.back().first == '{') {
1✔
2163
                    groups.pop_back();
1✔
2164
                }
2165
                break;
1✔
UNCOV
2166
            case ']':
×
UNCOV
2167
                if (!groups.empty() && groups.back().first == '[') {
×
UNCOV
2168
                    groups.pop_back();
×
2169
                }
UNCOV
2170
                break;
×
2171
        }
2172
    }
2173
    if (id_start) {
10✔
2174
        fqids.emplace_back(id_start.value());
2✔
2175
    }
2176
    int prev_stage_index = 0;
10✔
2177
    for (auto stage_index : stages) {
27✔
2178
        sa.emplace_back(line_range{prev_stage_index, stage_index},
17✔
2179
                        PRQL_STAGE_ATTR.value());
34✔
2180
        prev_stage_index = stage_index;
17✔
2181
    }
2182
    sa.emplace_back(
10✔
2183
        line_range{prev_stage_index, (int) al.get_string().length()},
10✔
2184
        PRQL_STAGE_ATTR.value());
20✔
2185
    for (const auto& group : groups) {
10✔
UNCOV
2186
        sa.emplace_back(line_range{group.second, group.second + 1},
×
UNCOV
2187
                        PRQL_UNTERMINATED_PAREN_ATTR.value());
×
2188
    }
2189
    for (const auto& fqid_range : fqids) {
32✔
2190
        sa.emplace_back(fqid_range, PRQL_FQID_ATTR.value());
22✔
2191
    }
2192
    remove_string_attr(sa, &PRQL_IDENTIFIER_ATTR);
10✔
2193
    remove_string_attr(sa, &PRQL_DOT_ATTR);
10✔
2194

2195
    stable_sort(sa.begin(), sa.end());
10✔
2196
}
10✔
2197

2198
}  // namespace sql
2199

2200
namespace prql {
2201

2202
std::string
UNCOV
2203
quote_ident(std::string id)
×
2204
{
2205
    static const auto PLAIN_NAME
UNCOV
2206
        = pcre2pp::code::from_const("^[a-zA-Z_][a-zA-Z_0-9]*$");
×
2207

UNCOV
2208
    if (PLAIN_NAME.find_in(id).ignore_error()) {
×
UNCOV
2209
        return id;
×
2210
    }
2211

UNCOV
2212
    auto buf = auto_buffer::alloc(id.length() + 8);
×
UNCOV
2213
    quote_content(buf, id, '`');
×
2214

UNCOV
2215
    return fmt::format(FMT_STRING("`{}`"), buf.in());
×
2216
}
2217

2218
}  // namespace prql
2219

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