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

tstack / lnav / 18378041721-2564

09 Oct 2025 01:27PM UTC coverage: 70.133% (-0.08%) from 70.213%
18378041721-2564

push

github

tstack
[db] move lnav tables to a separate DB

Related to #1525

156 of 292 new or added lines in 11 files covered. (53.42%)

43 existing lines in 6 files now uncovered.

50274 of 71684 relevant lines covered (70.13%)

412914.39 hits per line

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

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

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

36
#include "sql_util.hh"
37

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

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

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

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

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

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

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

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

830
    nullptr,
831
};
832

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

981
    return retval;
3✔
982
}
3✔
983

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

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

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

1000
    schema_out += attach_sql;
2✔
1001

1002
    return 0;
2✔
1003
}
2✔
1004

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

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

1014
    schema_out += create_sql;
110✔
1015

1016
    return 0;
110✔
1017
}
110✔
1018

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1135
bool
1136
sql_ident_needs_quote(const char* ident)
2,855✔
1137
{
1138
    for (int lpc = 0; ident[lpc]; lpc++) {
26,284✔
1139
        if (!isalnum(ident[lpc]) && ident[lpc] != '_') {
24,600✔
1140
            return true;
1,171✔
1141
        }
1142
    }
1143

1144
    return false;
1,684✔
1145
}
1146

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

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

1161
    for (int lpc = 0; ident[lpc]; lpc++) {
4,373,424✔
1162
        if ((lpc == 0 && isdigit(ident[lpc]))
3,918,505✔
1163
            || (!isalnum(ident[lpc]) && ident[lpc] != '_'))
3,918,505✔
1164
        {
1165
            needs_quote = true;
37,669✔
1166
        }
1167
        if (ident[lpc] == '"') {
3,918,505✔
1168
            quote_count += 1;
×
1169
        }
1170
    }
1171

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

1178
        if (needs_quote) {
454,919✔
1179
            curr[0] = '"';
24,938✔
1180
            curr += 1;
24,938✔
1181
        }
1182
        for (size_t lpc = 0; ident[lpc] != '\0'; lpc++) {
4,373,424✔
1183
            switch (ident[lpc]) {
3,918,505✔
1184
                case '"':
×
1185
                    curr[0] = '"';
×
1186
                    curr += 1;
×
1187
                default:
3,918,505✔
1188
                    curr[0] = ident[lpc];
3,918,505✔
1189
                    break;
3,918,505✔
1190
            }
1191
            curr += 1;
3,918,505✔
1192
        }
1193
        if (needs_quote) {
454,919✔
1194
            curr[0] = '"';
24,938✔
1195
            curr += 1;
24,938✔
1196
        }
1197

1198
        *curr = '\0';
454,919✔
1199
    }
1200

1201
    return retval;
454,919✔
1202
}
×
1203

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

1209
    for (size_t lpc = 0; lpc < retval.size(); lpc++) {
100,734✔
1210
        char ch = retval[lpc];
90,722✔
1211

1212
        if (isalnum(ch) || ch == '_') {
90,722✔
1213
            retval[lpc] = tolower(ch);
80,086✔
1214
        } else {
1215
            retval[lpc] = '_';
10,636✔
1216
        }
1217
    }
1218

1219
    return retval;
10,012✔
1220
}
×
1221

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

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

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

1259
        attr_line_t pointer;
1✔
1260

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

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

1269
    return retval;
9✔
1270
}
×
1271

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

1283
    sqlite3_clear_bindings(stmt);
712✔
1284

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

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

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

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

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

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

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

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

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

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

1385
        while (isspace(*script) && script[0]) {
2,787✔
1386
            script += 1;
1,424✔
1387
        }
1388
        for (const char* ch = script_orig; ch < script && ch[0]; ch++) {
131,540✔
1389
            if (*ch == '\n') {
130,177✔
1390
                line_number += 1;
4,434✔
1391
            }
1392
        }
1393

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

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

1418
        script = tail;
712✔
1419
    }
1,363✔
1420
}
651✔
1421

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

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

1453
int
1454
guess_type_from_pcre(const std::string& pattern, std::string& collator)
42,393✔
1455
{
1456
    log_info("guessing SQL type from pattern: %s", pattern.c_str());
42,393✔
1457
    auto compile_res = lnav::pcre2pp::code::from(pattern);
42,393✔
1458
    if (compile_res.isErr()) {
42,393✔
1459
        return SQLITE3_TEXT;
×
1460
    }
1461

1462
    auto re = compile_res.unwrap();
42,393✔
1463
    std::vector<int> matches;
42,393✔
1464
    int retval = SQLITE3_TEXT;
42,393✔
1465
    int index = 0;
42,393✔
1466

1467
    collator.clear();
42,393✔
1468
    for (const auto& test_value : TYPE_TEST_VALUE) {
121,208✔
1469
        log_info("  testing sample: %s", test_value.sample);
117,626✔
1470
        const auto find_res
1471
            = re.find_in(string_fragment::from_c_str(test_value.sample),
235,252✔
1472
                         PCRE2_ANCHORED)
1473
                  .ignore_error();
117,626✔
1474
        if (find_res && find_res->f_all.sf_begin == 0
169,572✔
1475
            && find_res->f_remaining.empty())
169,572✔
1476
        {
1477
            log_info("    matched!");
38,811✔
1478
            matches.push_back(index);
38,811✔
1479
            break;
38,811✔
1480
        }
1481
        if (!find_res) {
78,815✔
1482
            log_info("    mismatch");
65,680✔
1483
        } else if (!find_res->f_remaining.empty()) {
13,135✔
1484
            log_info("    incomplete match");
13,135✔
1485
        }
1486

1487
        index += 1;
78,815✔
1488
    }
1489

1490
    if (matches.size() == 1) {
42,393✔
1491
        retval = TYPE_TEST_VALUE[matches.front()].sqlite_type;
38,811✔
1492
        collator = TYPE_TEST_VALUE[matches.front()].collator;
38,811✔
1493
    }
1494

1495
    return retval;
42,393✔
1496
}
42,393✔
1497

1498
const char*
1499
sqlite3_type_to_string(int type)
448,887✔
1500
{
1501
    switch (type) {
448,887✔
1502
        case SQLITE_FLOAT:
7,883✔
1503
            return "FLOAT";
7,883✔
1504
        case SQLITE_INTEGER:
109,847✔
1505
            return "INTEGER";
109,847✔
1506
        case SQLITE_TEXT:
331,156✔
1507
            return "TEXT";
331,156✔
1508
        case SQLITE_NULL:
1✔
1509
            return "NULL";
1✔
1510
        case SQLITE_BLOB:
×
1511
            return "BLOB";
×
1512
    }
1513

1514
    ensure(!!!"Invalid sqlite type");
×
1515

1516
    return nullptr;
1517
}
1518

1519
/* XXX figure out how to do this with the template */
1520
void
1521
sqlite_close_wrapper(void* mem)
1,483✔
1522
{
1523
    sqlite3_close_v2((sqlite3*) mem);
1,483✔
1524
}
1,483✔
1525

1526
int
1527
sqlite_authorizer(void* pUserData,
6,818✔
1528
                  int action_code,
1529
                  const char* detail1,
1530
                  const char* detail2,
1531
                  const char* detail3,
1532
                  const char* detail4)
1533
{
1534
    if (action_code == SQLITE_ATTACH) {
6,818✔
1535
        return SQLITE_DENY;
4✔
1536
    }
1537
    return SQLITE_OK;
6,814✔
1538
}
1539

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

1548
        if (from_res.isOk()) {
1✔
1549
            return from_res.unwrap().to_attr_line();
3✔
1550
        }
1551

1552
        return from_res.unwrapErr()[0].um_message.get_string();
×
1553
    }
1✔
1554

1555
    return attr_line_t(errmsg);
×
1556
}
1557

1558
static void
1559
append_kw(std::string& retval, bool& first, const char* kw)
262,330✔
1560
{
1561
    if (!first) {
262,330✔
1562
        retval.append("|");
261,590✔
1563
    } else {
1564
        first = false;
740✔
1565
    }
1566
    retval.append("\\b");
262,330✔
1567
    retval.append(kw);
262,330✔
1568
    retval.append("\\b");
262,330✔
1569
}
262,330✔
1570

1571
static std::string
1572
sql_keyword_re(lnav::sql::dialect dia)
740✔
1573
{
1574
    std::string retval = "(?:";
740✔
1575
    auto first = true;
740✔
1576

1577
    switch (dia) {
740✔
1578
        case lnav::sql::dialect::sqlite:
370✔
1579
            for (const char* kw : sqlite_keywords) {
54,020✔
1580
                append_kw(retval, first, kw);
53,650✔
1581
            }
1582
            break;
370✔
1583
        default:
370✔
1584
            for (const char* kw : pg_sql_keywords) {
209,050✔
1585
                append_kw(retval, first, kw);
208,680✔
1586
            }
1587
            break;
370✔
1588
    }
1589

1590
    retval += ")";
740✔
1591

1592
    return retval;
1,480✔
1593
}
×
1594

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

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

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

1677
    static const auto cmd_pattern
1678
        = lnav::pcre2pp::code::from_const(R"(^;?(\.\w+))");
1,550✔
1679
    static const auto ws_pattern = lnav::pcre2pp::code::from_const(R"(\A\s+)");
1,550✔
1680

1681
    const auto& line = al.get_string();
1,550✔
1682
    auto& sa = al.get_attrs();
1,550✔
1683

1684
    if (lnav::sql::is_prql(line)) {
1,550✔
1685
        lnav::sql::annotate_prql_statement(al);
10✔
1686
        return;
18✔
1687
    }
1688

1689
    auto cmd_find_res
1690
        = cmd_pattern.find_in(line, PCRE2_ANCHORED).ignore_error();
1,540✔
1691
    if (cmd_find_res) {
1,540✔
1692
        auto cap = cmd_find_res->f_all;
8✔
1693
        sa.emplace_back(line_range(cap.sf_begin, cap.sf_end),
8✔
1694
                        SQL_COMMAND_ATTR.value());
16✔
1695
        return;
8✔
1696
    }
1697

1698
    auto remaining = string_fragment::from_str(line);
1,532✔
1699
    while (!remaining.empty()) {
23,465✔
1700
        auto ws_find_res = ws_pattern.find_in(remaining).ignore_error();
21,933✔
1701
        if (ws_find_res) {
21,933✔
1702
            remaining = ws_find_res->f_remaining;
7,542✔
1703
            continue;
11,215✔
1704
        }
1705
        const auto& kw_pat = dia == lnav::sql::dialect::sqlite
14,391✔
1706
            ? sqlite_keyword_re
1707
            : sql_keyword_re;
1708
        auto kw_pat_find_res = kw_pat.find_in(remaining).ignore_error();
14,391✔
1709
        if (kw_pat_find_res) {
14,391✔
1710
            sa.emplace_back(to_line_range(kw_pat_find_res->f_all),
3,673✔
1711
                            SQL_KEYWORD_ATTR.value());
7,346✔
1712
            remaining = kw_pat_find_res->f_remaining;
3,673✔
1713
            continue;
3,673✔
1714
        }
1715
        for (const auto& pat : PATTERNS) {
51,489✔
1716
            auto pat_find_res = pat.re.find_in(remaining).ignore_error();
51,489✔
1717
            if (pat_find_res) {
51,489✔
1718
                sa.emplace_back(to_line_range(pat_find_res->f_all),
10,718✔
1719
                                pat.type->value());
21,436✔
1720
                remaining = pat_find_res->f_remaining;
10,718✔
1721
                break;
10,718✔
1722
            }
1723
        }
1724
    }
1725

1726
    string_attrs_t::const_iterator iter;
1,532✔
1727
    int start = 0;
1,532✔
1728

1729
    while ((iter = find_string_attr(sa, &SQL_IDENTIFIER_ATTR, start))
6,642✔
1730
           != sa.end())
10,220✔
1731
    {
1732
        string_attrs_t::const_iterator piter;
3,578✔
1733

1734
        start = iter->sa_range.lr_end;
3,578✔
1735
        if (start < (ssize_t) line.length() && line[start] == '(') {
3,578✔
1736
            ssize_t pstart = start + 1;
1,139✔
1737
            int depth = 1;
1,139✔
1738

1739
            while (depth > 0
1,139✔
1740
                   && (piter = find_string_attr(sa, &SQL_PAREN_ATTR, pstart))
5,483✔
1741
                       != sa.end())
5,483✔
1742
            {
1743
                if (line[piter->sa_range.lr_start] == '(') {
1,440✔
1744
                    depth += 1;
157✔
1745
                } else {
1746
                    depth -= 1;
1,283✔
1747
                }
1748
                pstart = piter->sa_range.lr_end;
1,440✔
1749
            }
1750

1751
            line_range func_range{iter->sa_range.lr_start};
1,139✔
1752
            if (piter == sa.end()) {
1,139✔
1753
                func_range.lr_end = line.length();
12✔
1754
            } else {
1755
                func_range.lr_end = piter->sa_range.lr_end;
1,127✔
1756
            }
1757
            auto func_name = al.to_string_fragment(iter);
1,139✔
1758
            sa.emplace_back(
1,139✔
1759
                func_range,
1760
                SQL_FUNCTION_ATTR.value(tolower(func_name.to_string())));
2,278✔
1761
        }
1762
    }
1763

1764
    // remove_string_attr(sa, &SQL_PAREN_ATTR);
1765
    stable_sort(sa.begin(), sa.end());
1,532✔
1766
}
1767

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

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

1778
    x = al.nearest_text(x);
10✔
1779

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

1786
            if (cmd_iter != sql_cmd_map->end()) {
×
1787
                return {&cmd_iter->second->c_help};
×
1788
            }
1789
        }
1790

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

1797
            if (cmd_iter != sql_cmd_map->end()) {
1✔
1798
                return {&cmd_iter->second->c_help};
3✔
1799
            }
1800
        }
1✔
1801
    }
1802

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

1812
        auto func_pair = lnav::sql::prql_functions.equal_range(fqid);
×
1813

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

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

1829
        const auto& str = al.get_string();
15✔
1830
        const auto& lr = sa.sa_range;
15✔
1831

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

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

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

1851
        if (retval) {
14✔
1852
            kw.push_back(tmp_name);
14✔
1853
            name = tmp_name;
14✔
1854
        }
1855
        return retval;
14✔
1856
    });
14✔
1857

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

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

1881
    return retval;
8✔
1882
}
10✔
1883

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

1903
namespace lnav {
1904
namespace sql {
1905

1906
auto_mem<char, sqlite3_free>
1907
mprintf(const char* fmt, ...)
448,927✔
1908
{
1909
    auto_mem<char, sqlite3_free> retval;
448,927✔
1910
    va_list args;
1911

1912
    va_start(args, fmt);
448,927✔
1913
    retval = sqlite3_vmprintf(fmt, args);
448,927✔
1914
    va_end(args);
448,927✔
1915

1916
    return retval;
897,854✔
1917
}
×
1918

1919
bool
1920
is_prql(const string_fragment& sf)
3,273✔
1921
{
1922
    auto trimmed = sf.trim().skip(string_fragment::tag1{';'});
3,273✔
1923

1924
    return (trimmed.startswith("let ") || trimmed.startswith("from"));
6,546✔
1925
}
1926

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

1941
    nullptr,
1942
};
1943

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

1949
    nullptr,
1950
};
1951

1952
std::string
1953
prql_keyword_re()
9✔
1954
{
1955
    std::string retval = "(?:";
9✔
1956
    bool first = true;
9✔
1957

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

1973
    return retval;
9✔
1974
}
×
1975

1976
std::string
1977
prql_transform_re()
9✔
1978
{
1979
    std::string retval = "(?:";
9✔
1980
    bool first = true;
9✔
1981

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

1997
    return retval;
9✔
1998
}
×
1999

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

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

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

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

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

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

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

2198
    stable_sort(sa.begin(), sa.end());
10✔
2199
}
10✔
2200

2201
}  // namespace sql
2202

2203
namespace prql {
2204

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

2211
    if (PLAIN_NAME.find_in(id).ignore_error()) {
×
2212
        return id;
×
2213
    }
2214

2215
    auto buf = auto_buffer::alloc(id.length() + 8);
×
2216
    quote_content(buf, id, '`');
×
2217

2218
    return fmt::format(FMT_STRING("`{}`"), buf.in());
×
2219
}
2220

2221
}  // namespace prql
2222

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

© 2026 Coveralls, Inc