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

tstack / lnav / 19546678120-2692

20 Nov 2025 06:03PM UTC coverage: 68.87% (-0.002%) from 68.872%
19546678120-2692

push

github

tstack
[vtab] prefix table names in vtab CREATE TABLE statements with lnav_db

Related to #1590

14 of 19 new or added lines in 2 files covered. (73.68%)

51078 of 74166 relevant lines covered (68.87%)

431904.52 hits per line

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

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

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

36
#include "sql_util.hh"
37

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

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

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

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

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

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

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

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

830
    nullptr,
831
};
832

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

950
            if (smc.smc_table_info) {
224✔
951
                retval = sqlite3_exec(
224✔
952
                    db, table_query, smc.smc_table_info, &smc, errmsg.out());
953
                if (retval != SQLITE_OK) {
224✔
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(),
224✔
961
                                          smc.smc_table_name.c_str());
448✔
962
            if (table_query == nullptr) {
224✔
963
                return SQLITE_NOMEM;
×
964
            }
965

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

981
    return retval;
2✔
982
}
2✔
983

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

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

997
    attach_sql = sqlite3_mprintf("-- BEGIN %s\nATTACH DATABASE %Q AS %Q;\n",
998
                                 colvalues[1],
5✔
999
                                 colvalues[2],
5✔
1000
                                 colvalues[1]);
5✔
1001

1002
    schema_out += attach_sql;
5✔
1003

1004
    return 0;
5✔
1005
}
5✔
1006

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

1014
    create_sql = sqlite3_mprintf("%s;\n", colvalues[1]);
224✔
1015

1016
    schema_out += create_sql;
224✔
1017

1018
    return 0;
224✔
1019
}
224✔
1020

1021
static int
1022
schema_table_info(void* ptr, int ncols, char** colvalues, char** colnames)
6,132✔
1023
{
1024
    return 0;
6,132✔
1025
}
1026

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

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

1046
    walk_sqlite_metadata(db, schema_sql_meta_callbacks);
2✔
1047
}
2✔
1048

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

1054
    auto_mem<sqlite3_stmt> stmt(sqlite3_finalize);
1✔
1055

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

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

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

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

1082
    db_name = std::regex_replace(db_name, db_name_converter, "_");
1✔
1083

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

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

1100
static void
1101
sqlite_logger(void* dummy, int code, const char* msg)
188✔
1102
{
1103
    lnav_log_level_t level;
1104

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

1124
    log_msg(level, __FILE__, __LINE__, "(%d) %s", code, msg);
188✔
1125

1126
    ensure(code != 21);
188✔
1127
}
188✔
1128

1129
void
1130
sql_install_logger()
756✔
1131
{
1132
#ifdef SQLITE_CONFIG_LOG
1133
    sqlite3_config(SQLITE_CONFIG_LOG, sqlite_logger, NULL);
756✔
1134
#endif
1135
}
756✔
1136

1137
bool
1138
sql_ident_needs_quote(const char* ident)
2,884✔
1139
{
1140
    for (int lpc = 0; ident[lpc]; lpc++) {
26,530✔
1141
        if (!isalnum(ident[lpc]) && ident[lpc] != '_') {
24,825✔
1142
            return true;
1,179✔
1143
        }
1144
    }
1145

1146
    return false;
1,705✔
1147
}
1148

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

1156
auto_mem<char, sqlite3_free>
1157
sql_quote_ident(const char* ident)
485,774✔
1158
{
1159
    bool needs_quote = false;
485,774✔
1160
    size_t quote_count = 0, alloc_size;
485,774✔
1161
    auto_mem<char, sqlite3_free> retval;
485,774✔
1162

1163
    for (int lpc = 0; ident[lpc]; lpc++) {
4,676,516✔
1164
        if ((lpc == 0 && isdigit(ident[lpc]))
4,190,742✔
1165
            || (!isalnum(ident[lpc]) && ident[lpc] != '_'))
4,190,742✔
1166
        {
1167
            needs_quote = true;
42,263✔
1168
        }
1169
        if (ident[lpc] == '"') {
4,190,742✔
1170
            quote_count += 1;
×
1171
        }
1172
    }
1173

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

1180
        if (needs_quote) {
485,774✔
1181
            curr[0] = '"';
26,931✔
1182
            curr += 1;
26,931✔
1183
        }
1184
        for (size_t lpc = 0; ident[lpc] != '\0'; lpc++) {
4,676,516✔
1185
            switch (ident[lpc]) {
4,190,742✔
1186
                case '"':
×
1187
                    curr[0] = '"';
×
1188
                    curr += 1;
×
1189
                default:
4,190,742✔
1190
                    curr[0] = ident[lpc];
4,190,742✔
1191
                    break;
4,190,742✔
1192
            }
1193
            curr += 1;
4,190,742✔
1194
        }
1195
        if (needs_quote) {
485,774✔
1196
            curr[0] = '"';
26,931✔
1197
            curr += 1;
26,931✔
1198
        }
1199

1200
        *curr = '\0';
485,774✔
1201
    }
1202

1203
    return retval;
485,774✔
1204
}
×
1205

1206
std::string
1207
sql_safe_ident(const string_fragment& ident)
10,494✔
1208
{
1209
    std::string retval = std::to_string(ident);
10,494✔
1210

1211
    for (size_t lpc = 0; lpc < retval.size(); lpc++) {
105,550✔
1212
        char ch = retval[lpc];
95,056✔
1213

1214
        if (isalnum(ch) || ch == '_') {
95,056✔
1215
            retval[lpc] = tolower(ch);
83,944✔
1216
        } else {
1217
            retval[lpc] = '_';
11,112✔
1218
        }
1219
    }
1220

1221
    return retval;
10,494✔
1222
}
×
1223

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

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

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

1261
        attr_line_t pointer;
1✔
1262

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

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

1271
    return retval;
9✔
1272
}
×
1273

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

1285
    sqlite3_clear_bindings(stmt);
739✔
1286

1287
    param_count = sqlite3_bind_parameter_count(stmt);
739✔
1288
    for (int lpc = 0; lpc < param_count; lpc++) {
739✔
1289
        const char* name;
1290

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

1322
            case SQLITE_ROW: {
×
1323
                int ncols = sqlite3_column_count(stmt);
×
1324

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

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

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

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

1369
    sqlite3_reset(stmt);
739✔
1370
}
739✔
1371

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

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

1387
        while (isspace(*script) && script[0]) {
2,902✔
1388
            script += 1;
1,478✔
1389
        }
1390
        for (const char* ch = script_orig; ch < script && ch[0]; ch++) {
131,684✔
1391
            if (*ch == '\n') {
130,260✔
1392
                line_number += 1;
4,416✔
1393
            }
1394
        }
1395

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

1426
        script = tail;
739✔
1427
    }
1,424✔
1428
}
685✔
1429

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

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

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

1470
    auto re = compile_res.unwrap();
48,670✔
1471
    std::vector<int> matches;
48,670✔
1472
    int retval = SQLITE3_TEXT;
48,670✔
1473
    int index = 0;
48,670✔
1474

1475
    collator.clear();
48,670✔
1476
    for (const auto& test_value : TYPE_TEST_VALUE) {
132,105✔
1477
        log_info("  testing sample: %s", test_value.sample);
128,313✔
1478
        const auto find_res
1479
            = re.find_in(string_fragment::from_c_str(test_value.sample),
256,626✔
1480
                         PCRE2_ANCHORED)
1481
                  .ignore_error();
128,313✔
1482
        if (find_res && find_res->f_all.sf_begin == 0
187,096✔
1483
            && find_res->f_remaining.empty())
187,096✔
1484
        {
1485
            log_info("    matched!");
44,878✔
1486
            matches.push_back(index);
44,878✔
1487
            break;
44,878✔
1488
        }
1489
        if (!find_res) {
83,435✔
1490
            log_info("    mismatch");
69,530✔
1491
        } else if (!find_res->f_remaining.empty()) {
13,905✔
1492
            log_info("    incomplete match");
13,905✔
1493
        }
1494

1495
        index += 1;
83,435✔
1496
    }
1497

1498
    if (matches.size() == 1) {
48,670✔
1499
        retval = TYPE_TEST_VALUE[matches.front()].sqlite_type;
44,878✔
1500
        collator = TYPE_TEST_VALUE[matches.front()].collator;
44,878✔
1501
    }
1502

1503
    return retval;
48,670✔
1504
}
48,670✔
1505

1506
const char*
1507
sqlite3_type_to_string(int type)
485,778✔
1508
{
1509
    switch (type) {
485,778✔
1510
        case SQLITE_FLOAT:
9,611✔
1511
            return "FLOAT";
9,611✔
1512
        case SQLITE_INTEGER:
118,151✔
1513
            return "INTEGER";
118,151✔
1514
        case SQLITE_TEXT:
358,015✔
1515
            return "TEXT";
358,015✔
1516
        case SQLITE_NULL:
1✔
1517
            return "NULL";
1✔
1518
        case SQLITE_BLOB:
×
1519
            return "BLOB";
×
1520
    }
1521

1522
    ensure(!!!"Invalid sqlite type");
×
1523

1524
    return nullptr;
1525
}
1526

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

1534
int
1535
sqlite_authorizer(void* pUserData,
6,924✔
1536
                  int action_code,
1537
                  const char* detail1,
1538
                  const char* detail2,
1539
                  const char* detail3,
1540
                  const char* detail4)
1541
{
1542
    if (action_code == SQLITE_ATTACH) {
6,924✔
1543
        return SQLITE_DENY;
4✔
1544
    }
1545
    return SQLITE_OK;
6,920✔
1546
}
1547

1548
attr_line_t
1549
sqlite3_errmsg_to_attr_line(sqlite3* db)
1✔
1550
{
1551
    const auto* errmsg = sqlite3_errmsg(db);
1✔
1552
    if (startswith(errmsg, sqlitepp::ERROR_PREFIX)) {
1✔
1553
        auto from_res = lnav::from_json<lnav::console::user_message>(
1554
            &errmsg[strlen(sqlitepp::ERROR_PREFIX)]);
1✔
1555

1556
        if (from_res.isOk()) {
1✔
1557
            return from_res.unwrap().to_attr_line();
1✔
1558
        }
1559

1560
        return from_res.unwrapErr()[0].um_message.get_string();
×
1561
    }
1✔
1562

1563
    return attr_line_t(errmsg);
×
1564
}
1565

1566
static void
1567
append_kw(std::string& retval, bool& first, const char* kw)
270,129✔
1568
{
1569
    if (!first) {
270,129✔
1570
        retval.append("|");
269,367✔
1571
    } else {
1572
        first = false;
762✔
1573
    }
1574
    retval.append("\\b");
270,129✔
1575
    retval.append(kw);
270,129✔
1576
    retval.append("\\b");
270,129✔
1577
}
270,129✔
1578

1579
static std::string
1580
sql_keyword_re(lnav::sql::dialect dia)
762✔
1581
{
1582
    std::string retval = "(?:";
762✔
1583
    auto first = true;
762✔
1584

1585
    switch (dia) {
762✔
1586
        case lnav::sql::dialect::sqlite:
381✔
1587
            for (const char* kw : sqlite_keywords) {
55,626✔
1588
                append_kw(retval, first, kw);
55,245✔
1589
            }
1590
            break;
381✔
1591
        default:
381✔
1592
            for (const char* kw : pg_sql_keywords) {
215,265✔
1593
                append_kw(retval, first, kw);
214,884✔
1594
            }
1595
            break;
381✔
1596
    }
1597

1598
    retval += ")";
762✔
1599

1600
    return retval;
1,524✔
1601
}
×
1602

1603
constexpr string_attr_type<void> SQL_COMMAND_ATTR("sql_command");
1604
constexpr string_attr_type<void> SQL_KEYWORD_ATTR("sql_keyword");
1605
constexpr string_attr_type<void> SQL_IDENTIFIER_ATTR("sql_ident");
1606
constexpr string_attr_type<std::string> SQL_FUNCTION_ATTR("sql_func");
1607
constexpr string_attr_type<void> SQL_STRING_ATTR("sql_string");
1608
constexpr string_attr_type<void> SQL_HEX_LIT_ATTR("sql_hex_lit");
1609
constexpr string_attr_type<void> SQL_NUMBER_ATTR("sql_number");
1610
constexpr string_attr_type<void> SQL_OPERATOR_ATTR("sql_oper");
1611
constexpr string_attr_type<void> SQL_PAREN_ATTR("sql_paren");
1612
constexpr string_attr_type<void> SQL_COMMA_ATTR("sql_comma");
1613
constexpr string_attr_type<void> SQL_GARBAGE_ATTR("sql_garbage");
1614
constexpr string_attr_type<void> SQL_COMMENT_ATTR("sql_comment");
1615

1616
void
1617
annotate_sql_statement(attr_line_t& al, lnav::sql::dialect dia)
1,498✔
1618
{
1619
    static const std::string sqlite_keyword_re_str
1620
        = R"(\A)" + sql_keyword_re(lnav::sql::dialect::sqlite);
1,498✔
1621
    static const auto sqlite_keyword_re
1622
        = lnav::pcre2pp::code::from(sqlite_keyword_re_str, PCRE2_CASELESS)
762✔
1623
              .unwrap();
1,879✔
1624
    static const std::string sql_keyword_re_str
1625
        = R"(\A)" + sql_keyword_re(lnav::sql::dialect::sql);
1,498✔
1626
    static const auto sql_keyword_re
1627
        = lnav::pcre2pp::code::from(sql_keyword_re_str, PCRE2_CASELESS)
762✔
1628
              .unwrap();
1,879✔
1629

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

1685
    static const auto cmd_pattern
1686
        = lnav::pcre2pp::code::from_const(R"(^;?(\.\w+))");
1,498✔
1687
    static const auto ws_pattern = lnav::pcre2pp::code::from_const(R"(\A\s+)");
1,498✔
1688

1689
    const auto& line = al.get_string();
1,498✔
1690
    auto& sa = al.get_attrs();
1,498✔
1691

1692
    if (lnav::sql::is_prql(line)) {
1,498✔
1693
        lnav::sql::annotate_prql_statement(al);
10✔
1694
        return;
18✔
1695
    }
1696

1697
    auto cmd_find_res
1698
        = cmd_pattern.find_in(line, PCRE2_ANCHORED).ignore_error();
1,488✔
1699
    if (cmd_find_res) {
1,488✔
1700
        auto cap = cmd_find_res->f_all;
8✔
1701
        sa.emplace_back(line_range(cap.sf_begin, cap.sf_end),
8✔
1702
                        SQL_COMMAND_ATTR.value());
16✔
1703
        return;
8✔
1704
    }
1705

1706
    auto remaining = string_fragment::from_str(line);
1,480✔
1707
    while (!remaining.empty()) {
22,486✔
1708
        auto ws_find_res = ws_pattern.find_in(remaining).ignore_error();
21,006✔
1709
        if (ws_find_res) {
21,006✔
1710
            remaining = ws_find_res->f_remaining;
7,212✔
1711
            continue;
10,755✔
1712
        }
1713
        const auto& kw_pat = dia == lnav::sql::dialect::sqlite
13,794✔
1714
            ? sqlite_keyword_re
1715
            : sql_keyword_re;
1716
        auto kw_pat_find_res = kw_pat.find_in(remaining).ignore_error();
13,794✔
1717
        if (kw_pat_find_res) {
13,794✔
1718
            sa.emplace_back(to_line_range(kw_pat_find_res->f_all),
3,543✔
1719
                            SQL_KEYWORD_ATTR.value());
7,086✔
1720
            remaining = kw_pat_find_res->f_remaining;
3,543✔
1721
            continue;
3,543✔
1722
        }
1723
        for (const auto& pat : PATTERNS) {
48,660✔
1724
            auto pat_find_res = pat.re.find_in(remaining).ignore_error();
48,660✔
1725
            if (pat_find_res) {
48,660✔
1726
                sa.emplace_back(to_line_range(pat_find_res->f_all),
10,251✔
1727
                                pat.type->value());
20,502✔
1728
                remaining = pat_find_res->f_remaining;
10,251✔
1729
                break;
10,251✔
1730
            }
1731
        }
1732
    }
1733

1734
    string_attrs_t::const_iterator iter;
1,480✔
1735
    int start = 0;
1,480✔
1736

1737
    while ((iter = find_string_attr(sa, &SQL_IDENTIFIER_ATTR, start))
6,321✔
1738
           != sa.end())
9,682✔
1739
    {
1740
        string_attrs_t::const_iterator piter;
3,361✔
1741

1742
        start = iter->sa_range.lr_end;
3,361✔
1743
        if (start < (ssize_t) line.length() && line[start] == '(') {
3,361✔
1744
            ssize_t pstart = start + 1;
1,104✔
1745
            int depth = 1;
1,104✔
1746

1747
            while (depth > 0
1,104✔
1748
                   && (piter = find_string_attr(sa, &SQL_PAREN_ATTR, pstart))
5,343✔
1749
                       != sa.end())
5,343✔
1750
            {
1751
                if (line[piter->sa_range.lr_start] == '(') {
1,405✔
1752
                    depth += 1;
157✔
1753
                } else {
1754
                    depth -= 1;
1,248✔
1755
                }
1756
                pstart = piter->sa_range.lr_end;
1,405✔
1757
            }
1758

1759
            line_range func_range{iter->sa_range.lr_start};
1,104✔
1760
            if (piter == sa.end()) {
1,104✔
1761
                func_range.lr_end = line.length();
12✔
1762
            } else {
1763
                func_range.lr_end = piter->sa_range.lr_end;
1,092✔
1764
            }
1765
            auto func_name = al.to_string_fragment(iter);
1,104✔
1766
            sa.emplace_back(
1,104✔
1767
                func_range,
1768
                SQL_FUNCTION_ATTR.value(tolower(func_name.to_string())));
2,208✔
1769
        }
1770
    }
1771

1772
    // remove_string_attr(sa, &SQL_PAREN_ATTR);
1773
    stable_sort(sa.begin(), sa.end());
1,480✔
1774
}
1775

1776
std::vector<const help_text*>
1777
find_sql_help_for_line(const attr_line_t& al, size_t x)
10✔
1778
{
1779
    static const auto* sql_cmd_map
1780
        = injector::get<readline_context::command_map_t*, sql_cmd_map_tag>();
10✔
1781

1782
    std::vector<const help_text*> retval;
10✔
1783
    const auto& sa = al.get_attrs();
10✔
1784
    std::string name;
10✔
1785

1786
    x = al.nearest_text(x);
10✔
1787

1788
    {
1789
        auto sa_opt = get_string_attr(al.get_attrs(), &SQL_COMMAND_ATTR);
10✔
1790
        if (sa_opt) {
10✔
1791
            auto cmd_name = al.get_substring((*sa_opt)->sa_range);
×
1792
            auto cmd_iter = sql_cmd_map->find(cmd_name);
×
1793

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

1799
        auto prql_trans_iter = find_string_attr_containing(
10✔
1800
            al.get_attrs(), &lnav::sql::PRQL_TRANSFORM_ATTR, x);
1801
        if (prql_trans_iter != al.get_attrs().end()) {
10✔
1802
            auto cmd_name = al.get_substring(prql_trans_iter->sa_range);
1✔
1803
            auto cmd_iter = sql_cmd_map->find(cmd_name);
1✔
1804

1805
            if (cmd_iter != sql_cmd_map->end()) {
1✔
1806
                return {&cmd_iter->second->c_help};
3✔
1807
            }
1808
        }
1✔
1809
    }
1810

1811
    auto prql_fqid_iter = find_string_attr_containing(
9✔
1812
        al.get_attrs(), &lnav::sql ::PRQL_FQID_ATTR, x);
1813
    if (prql_fqid_iter != al.get_attrs().end()) {
9✔
1814
        auto fqid = al.get_substring(prql_fqid_iter->sa_range);
1✔
1815
        auto cmd_iter = sql_cmd_map->find(fqid);
1✔
1816
        if (cmd_iter != sql_cmd_map->end()) {
1✔
1817
            return {&cmd_iter->second->c_help};
3✔
1818
        }
1819

1820
        auto func_pair = lnav::sql::prql_functions.equal_range(fqid);
×
1821

1822
        for (auto func_iter = func_pair.first; func_iter != func_pair.second;
×
1823
             ++func_iter)
1824
        {
1825
            retval.emplace_back(func_iter->second);
×
1826
            return retval;
×
1827
        }
1828
    }
1✔
1829

1830
    std::vector<std::string> kw;
8✔
1831
    auto iter = rfind_string_attr_if(sa, x, [&al, &name, &kw, x](auto sa) {
8✔
1832
        if (sa.sa_type != &SQL_FUNCTION_ATTR && sa.sa_type != &SQL_KEYWORD_ATTR)
33✔
1833
        {
1834
            return false;
18✔
1835
        }
1836

1837
        const auto& str = al.get_string();
15✔
1838
        const auto& lr = sa.sa_range;
15✔
1839

1840
        if (sa.sa_type == &SQL_FUNCTION_ATTR) {
15✔
1841
            if (!sa.sa_range.contains(x)) {
7✔
1842
                return false;
1✔
1843
            }
1844
        }
1845

1846
        auto lpc = lr.lr_start;
14✔
1847
        for (; lpc < lr.lr_end; lpc++) {
92✔
1848
            if (!isalnum(str[lpc]) && str[lpc] != '_') {
84✔
1849
                break;
6✔
1850
            }
1851
        }
1852

1853
        auto tmp_name = str.substr(lr.lr_start, lpc - lr.lr_start);
14✔
1854
        if (sa.sa_type == &SQL_KEYWORD_ATTR) {
14✔
1855
            tmp_name = toupper(tmp_name);
8✔
1856
        }
1857
        bool retval = sqlite_function_help.count(tmp_name) > 0;
14✔
1858

1859
        if (retval) {
14✔
1860
            kw.push_back(tmp_name);
14✔
1861
            name = tmp_name;
14✔
1862
        }
1863
        return retval;
14✔
1864
    });
14✔
1865

1866
    if (iter != sa.end()) {
8✔
1867
        auto func_pair = sqlite_function_help.equal_range(name);
8✔
1868
        size_t help_count = std::distance(func_pair.first, func_pair.second);
8✔
1869

1870
        if (help_count > 1 && name != func_pair.first->second->ht_name) {
8✔
1871
            while (func_pair.first != func_pair.second) {
×
1872
                if (find(kw.begin(), kw.end(), func_pair.first->second->ht_name)
×
1873
                    == kw.end())
×
1874
                {
1875
                    ++func_pair.first;
×
1876
                } else {
1877
                    func_pair.second = next(func_pair.first);
×
1878
                    break;
×
1879
                }
1880
            }
1881
        }
1882
        for (auto func_iter = func_pair.first; func_iter != func_pair.second;
17✔
1883
             ++func_iter)
9✔
1884
        {
1885
            retval.emplace_back(func_iter->second);
9✔
1886
        }
1887
    }
1888

1889
    return retval;
8✔
1890
}
10✔
1891

1892
template<>
1893
Result<lnav::sql::dialect, std::string>
1894
from(string_fragment sf)
4✔
1895
{
1896
    if (sf == "sql"_frag) {
4✔
1897
        return Ok(lnav::sql::dialect::sql);
×
1898
    }
1899
    if (sf == "sqlite"_frag) {
4✔
1900
        return Ok(lnav::sql::dialect::sqlite);
×
1901
    }
1902
    if (sf == "plpgsql"_frag) {
4✔
1903
        return Ok(lnav::sql::dialect::plpgsql);
8✔
1904
    }
1905
    if (sf == "prql"_frag) {
×
1906
        return Ok(lnav::sql::dialect::prql);
×
1907
    }
1908
    return Err(fmt::format(FMT_STRING("unknown SQL dialect: {}"), sf));
×
1909
}
1910

1911
namespace lnav {
1912
namespace sql {
1913

1914
auto_mem<char, sqlite3_free>
1915
mprintf(const char* fmt, ...)
485,778✔
1916
{
1917
    auto_mem<char, sqlite3_free> retval;
485,778✔
1918
    va_list args;
1919

1920
    va_start(args, fmt);
485,778✔
1921
    retval = sqlite3_vmprintf(fmt, args);
485,778✔
1922
    va_end(args);
485,778✔
1923

1924
    return retval;
971,556✔
1925
}
×
1926

1927
bool
1928
is_prql(const string_fragment& sf)
3,220✔
1929
{
1930
    auto trimmed = sf.trim().skip(string_fragment::tag1{';'});
3,220✔
1931

1932
    return (trimmed.startswith("let ") || trimmed.startswith("from"));
6,440✔
1933
}
1934

1935
static const char* const prql_transforms[] = {
1936
    "aggregate",
1937
    "append",
1938
    "derive",
1939
    "filter",
1940
    "from",
1941
    "group",
1942
    "join",
1943
    "loop",
1944
    "select",
1945
    "sort",
1946
    "take",
1947
    "window",
1948

1949
    nullptr,
1950
};
1951

1952
const char* const prql_keywords[] = {
1953
    "average", "avg", "case", "count", "count_distinct", "false", "func",
1954
    "into",    "let", "max",  "min",   "module",         "null",  "prql",
1955
    "stddev",  "sum", "true", "type",
1956

1957
    nullptr,
1958
};
1959

1960
std::string
1961
prql_keyword_re()
9✔
1962
{
1963
    std::string retval = "(?:";
9✔
1964
    bool first = true;
9✔
1965

1966
    for (const char* kw : prql_keywords) {
171✔
1967
        if (kw == nullptr) {
171✔
1968
            break;
9✔
1969
        }
1970
        if (!first) {
162✔
1971
            retval.append("|");
153✔
1972
        } else {
1973
            first = false;
9✔
1974
        }
1975
        retval.append("\\b");
162✔
1976
        retval.append(kw);
162✔
1977
        retval.append("\\b");
162✔
1978
    }
1979
    retval += ")";
9✔
1980

1981
    return retval;
9✔
1982
}
×
1983

1984
std::string
1985
prql_transform_re()
9✔
1986
{
1987
    std::string retval = "(?:";
9✔
1988
    bool first = true;
9✔
1989

1990
    for (const char* kw : prql_transforms) {
117✔
1991
        if (kw == nullptr) {
117✔
1992
            break;
9✔
1993
        }
1994
        if (!first) {
108✔
1995
            retval.append("|");
99✔
1996
        } else {
1997
            first = false;
9✔
1998
        }
1999
        retval.append("\\b");
108✔
2000
        retval.append(kw);
108✔
2001
        retval.append("\\b");
108✔
2002
    }
2003
    retval += ")";
9✔
2004

2005
    return retval;
9✔
2006
}
×
2007

2008
constexpr string_attr_type<void> PRQL_STAGE_ATTR("prql_stage");
2009
constexpr string_attr_type<void> PRQL_TRANSFORM_ATTR("prql_transform");
2010
constexpr string_attr_type<void> PRQL_KEYWORD_ATTR("prql_keyword");
2011
constexpr string_attr_type<void> PRQL_IDENTIFIER_ATTR("prql_ident");
2012
constexpr string_attr_type<void> PRQL_FQID_ATTR("prql_fqid");
2013
constexpr string_attr_type<void> PRQL_DOT_ATTR("prql_dot");
2014
constexpr string_attr_type<void> PRQL_PIPE_ATTR("prql_pipe");
2015
constexpr string_attr_type<void> PRQL_STRING_ATTR("prql_string");
2016
constexpr string_attr_type<void> PRQL_NUMBER_ATTR("prql_number");
2017
constexpr string_attr_type<void> PRQL_OPERATOR_ATTR("prql_oper");
2018
constexpr string_attr_type<void> PRQL_PAREN_ATTR("prql_paren");
2019
constexpr string_attr_type<void> PRQL_UNTERMINATED_PAREN_ATTR(
2020
    "prql_unterminated_paren");
2021
constexpr string_attr_type<void> PRQL_GARBAGE_ATTR("prql_garbage");
2022
constexpr string_attr_type<void> PRQL_COMMENT_ATTR("prql_comment");
2023

2024
void
2025
annotate_prql_statement(attr_line_t& al)
10✔
2026
{
2027
    static const std::string keyword_re_str = R"(\A)" + prql_keyword_re();
10✔
2028
    static const std::string transform_re_str = R"(\A)" + prql_transform_re();
10✔
2029

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

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

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

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

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

2206
    stable_sort(sa.begin(), sa.end());
10✔
2207
}
10✔
2208

2209
}  // namespace sql
2210

2211
namespace prql {
2212

2213
std::string
2214
quote_ident(std::string id)
×
2215
{
2216
    static const auto PLAIN_NAME
2217
        = pcre2pp::code::from_const("^[a-zA-Z_][a-zA-Z_0-9]*$");
×
2218

2219
    if (PLAIN_NAME.find_in(id).ignore_error()) {
×
2220
        return id;
×
2221
    }
2222

2223
    auto buf = auto_buffer::alloc(id.length() + 8);
×
2224
    quote_content(buf, id, '`');
×
2225

2226
    return fmt::format(FMT_STRING("`{}`"), buf.in());
×
2227
}
2228

2229
}  // namespace prql
2230

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