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

tstack / lnav / 23353629531-2860

20 Mar 2026 05:00PM UTC coverage: 69.008% (-0.004%) from 69.012%
23353629531-2860

push

github

tstack
[scripts] fix double quotes misuse in breakpoint script

52733 of 76416 relevant lines covered (69.01%)

522102.81 hits per line

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

80.09
/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)
236✔
882
{
883
    auto* tld = (struct table_list_data*) ptr;
236✔
884

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

890
    return tld->tld_callbacks->smc_table_list(
472✔
891
        tld->tld_callbacks, ncols, colvalues, colnames);
236✔
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();
241✔
938
             ++table_iter)
236✔
939
        {
940
            auto_mem<char, sqlite3_free> table_query;
236✔
941
            smc.smc_table_name = *table_iter;
236✔
942

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

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

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

981
    return retval;
2✔
982
}
2✔
983

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

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

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

1000
    schema_out += attach_sql;
5✔
1001

1002
    return 0;
5✔
1003
}
5✔
1004

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

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

1014
    schema_out += create_sql;
236✔
1015

1016
    return 0;
236✔
1017
}
236✔
1018

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1144
    return false;
1,719✔
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)
564,537✔
1156
{
1157
    bool needs_quote = false;
564,537✔
1158
    size_t quote_count = 0, alloc_size;
564,537✔
1159
    auto_mem<char, sqlite3_free> retval;
564,537✔
1160

1161
    for (int lpc = 0; ident[lpc]; lpc++) {
5,534,894✔
1162
        if ((lpc == 0 && isdigit(ident[lpc]))
4,970,357✔
1163
            || (!isalnum(ident[lpc]) && ident[lpc] != '_'))
4,970,357✔
1164
        {
1165
            needs_quote = true;
65,726✔
1166
        }
1167
        if (ident[lpc] == '"') {
4,970,357✔
1168
            quote_count += 1;
×
1169
        }
1170
    }
1171

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

1178
        if (needs_quote) {
564,537✔
1179
            curr[0] = '"';
43,387✔
1180
            curr += 1;
43,387✔
1181
        }
1182
        for (size_t lpc = 0; ident[lpc] != '\0'; lpc++) {
5,534,894✔
1183
            switch (ident[lpc]) {
4,970,357✔
1184
                case '"':
×
1185
                    curr[0] = '"';
×
1186
                    curr += 1;
×
1187
                default:
4,970,357✔
1188
                    curr[0] = ident[lpc];
4,970,357✔
1189
                    break;
4,970,357✔
1190
            }
1191
            curr += 1;
4,970,357✔
1192
        }
1193
        if (needs_quote) {
564,537✔
1194
            curr[0] = '"';
43,387✔
1195
            curr += 1;
43,387✔
1196
        }
1197

1198
        *curr = '\0';
564,537✔
1199
    }
1200

1201
    return retval;
564,537✔
1202
}
×
1203

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

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

1212
        if (isalnum(ch) || ch == '_') {
100,695✔
1213
            retval[lpc] = tolower(ch);
88,881✔
1214
        } else {
1215
            retval[lpc] = '_';
11,814✔
1216
        }
1217
    }
1218

1219
    return retval;
11,114✔
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,
784✔
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;
784✔
1280
    bool done = false;
784✔
1281
    int param_count;
1282

1283
    sqlite3_clear_bindings(stmt);
784✔
1284

1285
    param_count = sqlite3_bind_parameter_count(stmt);
784✔
1286
    for (int lpc = 0; lpc < param_count; lpc++) {
784✔
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,568✔
1313
        int retcode = sqlite3_step(stmt);
784✔
1314
        switch (retcode) {
784✔
1315
            case SQLITE_OK:
783✔
1316
            case SQLITE_DONE:
1317
                done = true;
783✔
1318
                break;
783✔
1319

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

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

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

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

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

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

1370
static void
1371
sql_compile_script(sqlite3* db,
727✔
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;
727✔
1378

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

1385
        while (isspace(*script) && script[0]) {
3,079✔
1386
            script += 1;
1,568✔
1387
        }
1388
        for (const char* ch = script_orig; ch < script && ch[0]; ch++) {
139,580✔
1389
            if (*ch == '\n') {
138,069✔
1390
                line_number += 1;
4,680✔
1391
            }
1392
        }
1393

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

1424
        script = tail;
784✔
1425
    }
1,511✔
1426
}
727✔
1427

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

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

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

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

1473
    collator.clear();
53,688✔
1474
    for (const auto& test_value : TYPE_TEST_VALUE) {
145,626✔
1475
        log_info("  testing sample: %s", test_value.sample);
141,600✔
1476
        const auto find_res
1477
            = re.find_in(string_fragment::from_c_str(test_value.sample),
283,200✔
1478
                         PCRE2_ANCHORED)
1479
                  .ignore_error();
141,600✔
1480
        if (find_res && find_res->f_all.sf_begin == 0
206,025✔
1481
            && find_res->f_remaining.empty())
206,025✔
1482
        {
1483
            log_info("    matched!");
49,662✔
1484
            matches.push_back(index);
49,662✔
1485
            break;
49,662✔
1486
        }
1487
        if (!find_res) {
91,938✔
1488
            log_info("    mismatch");
77,175✔
1489
        } else if (!find_res->f_remaining.empty()) {
14,763✔
1490
            log_info("    incomplete match");
14,763✔
1491
        }
1492

1493
        index += 1;
91,938✔
1494
    }
1495

1496
    if (matches.size() == 1) {
53,688✔
1497
        retval = TYPE_TEST_VALUE[matches.front()].sqlite_type;
49,662✔
1498
        collator = TYPE_TEST_VALUE[matches.front()].collator;
49,662✔
1499
    }
1500

1501
    return retval;
53,688✔
1502
}
53,688✔
1503

1504
const char*
1505
sqlite3_type_to_string(int type)
564,541✔
1506
{
1507
    switch (type) {
564,541✔
1508
        case SQLITE_FLOAT:
10,200✔
1509
            return "FLOAT";
10,200✔
1510
        case SQLITE_INTEGER:
135,449✔
1511
            return "INTEGER";
135,449✔
1512
        case SQLITE_TEXT:
418,891✔
1513
            return "TEXT";
418,891✔
1514
        case SQLITE_NULL:
1✔
1515
            return "NULL";
1✔
1516
        case SQLITE_BLOB:
×
1517
            return "BLOB";
×
1518
    }
1519

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

1522
    return nullptr;
1523
}
1524

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

1532
int
1533
sqlite_authorizer(void* pUserData,
7,354✔
1534
                  int action_code,
1535
                  const char* detail1,
1536
                  const char* detail2,
1537
                  const char* detail3,
1538
                  const char* detail4)
1539
{
1540
    if (action_code == SQLITE_ATTACH) {
7,354✔
1541
        return SQLITE_DENY;
4✔
1542
    }
1543
    return SQLITE_OK;
7,350✔
1544
}
1545

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

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

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

1561
    return attr_line_t(errmsg);
×
1562
}
1563

1564
static void
1565
append_kw(std::string& retval, bool& first, const char* kw)
275,801✔
1566
{
1567
    if (!first) {
275,801✔
1568
        retval.append("|");
275,023✔
1569
    } else {
1570
        first = false;
778✔
1571
    }
1572
    retval.append("\\b");
275,801✔
1573
    retval.append(kw);
275,801✔
1574
    retval.append("\\b");
275,801✔
1575
}
275,801✔
1576

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

1583
    switch (dia) {
778✔
1584
        case lnav::sql::dialect::sqlite:
389✔
1585
            for (const char* kw : sqlite_keywords) {
56,794✔
1586
                append_kw(retval, first, kw);
56,405✔
1587
            }
1588
            break;
389✔
1589
        default:
389✔
1590
            for (const char* kw : pg_sql_keywords) {
219,785✔
1591
                append_kw(retval, first, kw);
219,396✔
1592
            }
1593
            break;
389✔
1594
    }
1595

1596
    retval += ")";
778✔
1597

1598
    return retval;
1,556✔
1599
}
×
1600

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

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

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

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

1687
    const auto& line = al.get_string();
1,520✔
1688
    auto& sa = al.get_attrs();
1,520✔
1689

1690
    if (lnav::sql::is_prql(line)) {
1,520✔
1691
        lnav::sql::annotate_prql_statement(al);
11✔
1692
        return;
19✔
1693
    }
1694

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

1704
    auto remaining = string_fragment::from_str(line);
1,501✔
1705
    while (!remaining.empty()) {
22,654✔
1706
        auto ws_find_res = ws_pattern.find_in(remaining).ignore_error();
21,153✔
1707
        if (ws_find_res) {
21,153✔
1708
            remaining = ws_find_res->f_remaining;
7,266✔
1709
            continue;
10,830✔
1710
        }
1711
        const auto& kw_pat = dia == lnav::sql::dialect::sqlite
13,887✔
1712
            ? sqlite_keyword_re
1713
            : sql_keyword_re;
1714
        auto kw_pat_find_res = kw_pat.find_in(remaining).ignore_error();
13,887✔
1715
        if (kw_pat_find_res) {
13,887✔
1716
            sa.emplace_back(to_line_range(kw_pat_find_res->f_all),
3,564✔
1717
                            SQL_KEYWORD_ATTR.value());
7,128✔
1718
            remaining = kw_pat_find_res->f_remaining;
3,564✔
1719
            continue;
3,564✔
1720
        }
1721
        for (const auto& pat : PATTERNS) {
49,037✔
1722
            auto pat_find_res = pat.re.find_in(remaining).ignore_error();
49,037✔
1723
            if (pat_find_res) {
49,037✔
1724
                sa.emplace_back(to_line_range(pat_find_res->f_all),
10,323✔
1725
                                pat.type->value());
20,646✔
1726
                remaining = pat_find_res->f_remaining;
10,323✔
1727
                break;
10,323✔
1728
            }
1729
        }
1730
    }
1731

1732
    string_attrs_t::const_iterator iter;
1,501✔
1733
    int start = 0;
1,501✔
1734

1735
    while ((iter = find_string_attr(sa, &SQL_IDENTIFIER_ATTR, start))
6,392✔
1736
           != sa.end())
9,782✔
1737
    {
1738
        string_attrs_t::const_iterator piter;
3,390✔
1739

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

1745
            while (depth > 0
1,105✔
1746
                   && (piter = find_string_attr(sa, &SQL_PAREN_ATTR, pstart))
5,347✔
1747
                       != sa.end())
5,347✔
1748
            {
1749
                if (line[piter->sa_range.lr_start] == '(') {
1,406✔
1750
                    depth += 1;
157✔
1751
                } else {
1752
                    depth -= 1;
1,249✔
1753
                }
1754
                pstart = piter->sa_range.lr_end;
1,406✔
1755
            }
1756

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

1770
    // remove_string_attr(sa, &SQL_PAREN_ATTR);
1771
    stable_sort(sa.begin(), sa.end());
1,501✔
1772
}
1773

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

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

1784
    x = al.nearest_text(x);
10✔
1785

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

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

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

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

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

1818
        auto func_pair = lnav::sql::prql_functions.equal_range(fqid);
×
1819

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

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

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

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

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

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

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

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

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

1887
    return retval;
8✔
1888
}
10✔
1889

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

1909
namespace lnav {
1910
namespace sql {
1911

1912
auto_mem<char, sqlite3_free>
1913
mprintf(const char* fmt, ...)
564,541✔
1914
{
1915
    auto_mem<char, sqlite3_free> retval;
564,541✔
1916
    va_list args;
1917

1918
    va_start(args, fmt);
564,541✔
1919
    retval = sqlite3_vmprintf(fmt, args);
564,541✔
1920
    va_end(args);
564,541✔
1921

1922
    return retval;
1,129,082✔
1923
}
×
1924

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

1930
    return (trimmed.startswith("let ") || trimmed.startswith("from"));
6,500✔
1931
}
1932

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

1947
    nullptr,
1948
};
1949

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

1955
    nullptr,
1956
};
1957

1958
std::string
1959
prql_keyword_re()
10✔
1960
{
1961
    std::string retval = "(?:";
10✔
1962
    bool first = true;
10✔
1963

1964
    for (const char* kw : prql_keywords) {
190✔
1965
        if (kw == nullptr) {
190✔
1966
            break;
10✔
1967
        }
1968
        if (!first) {
180✔
1969
            retval.append("|");
170✔
1970
        } else {
1971
            first = false;
10✔
1972
        }
1973
        retval.append("\\b");
180✔
1974
        retval.append(kw);
180✔
1975
        retval.append("\\b");
180✔
1976
    }
1977
    retval += ")";
10✔
1978

1979
    return retval;
10✔
1980
}
×
1981

1982
std::string
1983
prql_transform_re()
10✔
1984
{
1985
    std::string retval = "(?:";
10✔
1986
    bool first = true;
10✔
1987

1988
    for (const char* kw : prql_transforms) {
130✔
1989
        if (kw == nullptr) {
130✔
1990
            break;
10✔
1991
        }
1992
        if (!first) {
120✔
1993
            retval.append("|");
110✔
1994
        } else {
1995
            first = false;
10✔
1996
        }
1997
        retval.append("\\b");
120✔
1998
        retval.append(kw);
120✔
1999
        retval.append("\\b");
120✔
2000
    }
2001
    retval += ")";
10✔
2002

2003
    return retval;
10✔
2004
}
×
2005

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

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

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

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

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

2117
    auto stages = std::vector<int>{};
11✔
2118
    std::vector<std::pair<char, int>> groups;
11✔
2119
    std::vector<line_range> fqids;
11✔
2120
    std::optional<line_range> id_start;
11✔
2121
    const string_attr_type_base* last_attr_type = nullptr;
11✔
2122
    bool saw_id_dot = false;
11✔
2123
    for (const auto& attr : sa) {
109✔
2124
        if (groups.empty() && attr.sa_type == &PRQL_PIPE_ATTR
194✔
2125
            && last_attr_type != &PRQL_PIPE_ATTR)
194✔
2126
        {
2127
            stages.push_back(attr.sa_range.lr_start);
19✔
2128
        }
2129
        last_attr_type = attr.sa_type;
98✔
2130
        if (!id_start) {
98✔
2131
            if (attr.sa_type == &PRQL_IDENTIFIER_ATTR) {
56✔
2132
                id_start = attr.sa_range;
23✔
2133
                saw_id_dot = false;
23✔
2134
            }
2135
        } else if (!saw_id_dot) {
42✔
2136
            if (attr.sa_type == &PRQL_DOT_ATTR) {
35✔
2137
                saw_id_dot = true;
7✔
2138
            } else {
2139
                fqids.emplace_back(id_start.value());
28✔
2140
                if (attr.sa_type == &PRQL_IDENTIFIER_ATTR) {
28✔
2141
                    id_start = attr.sa_range;
7✔
2142
                } else {
2143
                    id_start = std::nullopt;
21✔
2144
                }
2145
                saw_id_dot = false;
28✔
2146
            }
2147
        } else {
2148
            if (attr.sa_type == &PRQL_IDENTIFIER_ATTR) {
7✔
2149
                id_start = line_range{
7✔
2150
                    id_start.value().lr_start,
7✔
2151
                    attr.sa_range.lr_end,
7✔
2152
                };
7✔
2153
            } else {
2154
                id_start = std::nullopt;
×
2155
            }
2156
            saw_id_dot = false;
7✔
2157
        }
2158
        if (attr.sa_type != &PRQL_PAREN_ATTR) {
98✔
2159
            continue;
96✔
2160
        }
2161

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

2208
    stable_sort(sa.begin(), sa.end());
11✔
2209
}
11✔
2210

2211
}  // namespace sql
2212

2213
namespace prql {
2214

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

2221
    if (PLAIN_NAME.find_in(id).ignore_error()) {
×
2222
        return id;
×
2223
    }
2224

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

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

2231
}  // namespace prql
2232

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

© 2026 Coveralls, Inc