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

stephenafamo / bob / 14939098091

09 May 2025 10:59PM UTC coverage: 36.868% (-4.6%) from 41.506%
14939098091

push

github

stephenafamo
Move reusable sqlite parser components to antlrhelpers package

0 of 758 new or added lines in 12 files covered. (0.0%)

547 existing lines in 8 files now uncovered.

6533 of 17720 relevant lines covered (36.87%)

212.48 hits per line

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

5.22
/gen/bobgen-sqlite/driver/parser/parse.go
1
package parser
2

3
import (
4
        "context"
5
        "errors"
6
        "fmt"
7
        "strings"
8

9
        "github.com/aarondl/opt/omit"
10
        "github.com/antlr4-go/antlr/v4"
11
        "github.com/stephenafamo/bob/gen/bobgen-helpers/parser"
12
        "github.com/stephenafamo/bob/gen/drivers"
13
        "github.com/stephenafamo/bob/internal"
14
        sqliteparser "github.com/stephenafamo/sqlparser/sqlite"
15
)
16

17
func New(t tables) Parser {
26✔
18
        return Parser{db: t}
26✔
19
}
26✔
20

21
type Parser struct {
22
        db tables
23
}
24

25
func (p Parser) ParseFolders(ctx context.Context, paths ...string) ([]drivers.QueryFolder, error) {
26✔
26
        return parser.ParseFolders(ctx, p, paths...)
26✔
27
}
26✔
28

29
func (p Parser) ParseQueries(_ context.Context, s string) ([]drivers.Query, error) {
×
30
        v := NewVisitor(p.db)
×
31
        infos, err := p.parse(v, s)
×
32
        if err != nil {
×
33
                return nil, fmt.Errorf("parse: %w", err)
×
34
        }
×
35

36
        queries := make([]drivers.Query, len(infos))
×
37
        for i, info := range infos {
×
NEW
38
                stmtStart := info.Node.GetStart().GetStart()
×
NEW
39
                stmtStop := info.Node.GetStop().GetStop()
×
NEW
40
                formatted, err := internal.EditStringSegment(s, stmtStart, stmtStop, info.EditRules...)
×
41
                if err != nil {
×
42
                        return nil, fmt.Errorf("format: %w", err)
×
43
                }
×
44

NEW
45
                cols := make([]drivers.QueryCol, len(info.Columns))
×
NEW
46
                for i, col := range info.Columns {
×
47
                        cols[i] = drivers.QueryCol{
×
NEW
48
                                Name:     col.Name,
×
NEW
49
                                DBName:   col.Name,
×
NEW
50
                                Nullable: omit.From(col.Type.Nullable()),
×
NEW
51
                                TypeName: TranslateColumnType(col.Type.ConfirmedDBType()),
×
NEW
52
                        }.Merge(col.Config)
×
UNCOV
53
                }
×
54

NEW
55
                name, configStr, _ := strings.Cut(info.Comment, " ")
×
56
                queries[i] = drivers.Query{
×
57
                        Name: name,
×
58
                        SQL:  formatted,
×
NEW
59
                        Type: info.QueryType,
×
60

×
61
                        Config: drivers.QueryConfig{
×
62
                                RowName:      name + "Row",
×
63
                                RowSliceName: "",
×
64
                                GenerateRow:  true,
×
65
                        }.Merge(parser.ParseQueryConfig(configStr)),
×
66

×
67
                        Columns: cols,
×
68
                        Args:    v.getArgs(stmtStart, stmtStop),
×
69
                        Mods:    stmtToMod{info},
×
70
                }
×
71
        }
72

73
        return queries, nil
×
74
}
75

NEW
76
func (Parser) parse(v *visitor, input string) ([]StmtInfo, error) {
×
UNCOV
77
        el := &errorListener{}
×
UNCOV
78

×
UNCOV
79
        // Get all hidden tokens (usually comments) and add edit rules to remove them
×
NEW
80
        v.BaseRules = []internal.EditRule{}
×
UNCOV
81
        hiddenLexer := sqliteparser.NewSQLiteLexer(antlr.NewInputStream(input))
×
UNCOV
82
        hiddenStream := antlr.NewCommonTokenStream(hiddenLexer, 1)
×
UNCOV
83
        hiddenStream.Fill()
×
UNCOV
84
        for _, token := range hiddenStream.GetAllTokens() {
×
UNCOV
85
                switch token.GetTokenType() {
×
86
                case sqliteparser.SQLiteParserSINGLE_LINE_COMMENT,
UNCOV
87
                        sqliteparser.SQLiteParserMULTILINE_COMMENT:
×
NEW
88
                        v.BaseRules = append(
×
NEW
89
                                v.BaseRules,
×
UNCOV
90
                                internal.Delete(token.GetStart(), token.GetStop()),
×
UNCOV
91
                        )
×
92
                }
93
        }
94

95
        // Get the regular tokens (usually the SQL statement)
UNCOV
96
        lexer := sqliteparser.NewSQLiteLexer(antlr.NewInputStream(input))
×
UNCOV
97
        stream := antlr.NewCommonTokenStream(lexer, 0)
×
UNCOV
98
        sqlParser := sqliteparser.NewSQLiteParser(stream)
×
UNCOV
99
        sqlParser.AddErrorListener(el)
×
UNCOV
100

×
UNCOV
101
        tree := sqlParser.Parse()
×
UNCOV
102
        if el.err != "" {
×
103
                return nil, errors.New(el.err)
×
104
        }
×
105

NEW
106
        infos, ok := tree.Accept(v).([]StmtInfo)
×
NEW
107
        if v.Err != nil {
×
NEW
108
                return nil, fmt.Errorf("visitor: %w", v.Err)
×
UNCOV
109
        }
×
110

UNCOV
111
        if !ok {
×
112
                return nil, fmt.Errorf("visitor: expected stmtInfo, got %T", infos)
×
113
        }
×
114

UNCOV
115
        return infos, nil
×
116
}
117

118
type stmtToMod struct {
119
        info StmtInfo
120
}
121

122
func (s stmtToMod) IncludeInTemplate(i drivers.Importer) string {
×
NEW
123
        for _, im := range s.info.Imports {
×
124
                i.Import(im...)
×
125
        }
×
NEW
126
        return s.info.Mods.String()
×
127
}
128

129
type errorListener struct {
130
        *antlr.DefaultErrorListener
131

132
        err string
133
}
134

135
func (el *errorListener) SyntaxError(recognizer antlr.Recognizer, offendingSymbol any, line, column int, msg string, e antlr.RecognitionException) {
×
136
        el.err = msg
×
137
}
×
138

139
//nolint:gochecknoglobals
140
var defaultFunctions = Functions{
141
        "abs": {
142
                RequiredArgs: 1,
143
                Args:         []string{""},
NEW
144
                CalcReturnType: func(args ...string) string {
×
145
                        if args[0] == "INTEGER" {
×
146
                                return "INTEGER"
×
147
                        }
×
148
                        return "REAL"
×
149
                },
150
        },
151
        "changes": {
152
                ReturnType: "INTEGER",
153
        },
154
        "char": {
155
                RequiredArgs: 1,
156
                Variadic:     true,
157
                Args:         []string{"INTEGER"},
158
                ReturnType:   "TEXT",
159
        },
160
        "coalesce": {
161
                RequiredArgs:         1,
162
                Variadic:             true,
163
                Args:                 []string{""},
164
                ShouldArgsBeNullable: true,
NEW
165
                CalcReturnType: func(args ...string) string {
×
166
                        for _, arg := range args {
×
167
                                if arg != "" {
×
168
                                        return arg
×
169
                                }
×
170
                        }
171
                        return ""
×
172
                },
173
                CalcNullable: allNullable,
174
        },
175
        "concat": {
176
                RequiredArgs: 1,
177
                Variadic:     true,
178
                Args:         []string{"TEXT"},
179
                ReturnType:   "TEXT",
180
                CalcNullable: neverNullable,
181
        },
182
        "concat_ws": {
183
                RequiredArgs: 2,
184
                Variadic:     true,
185
                Args:         []string{"TEXT", "TEXT"},
186
                ReturnType:   "TEXT",
NEW
187
                CalcNullable: func(args ...func() bool) func() bool {
×
188
                        return args[0]
×
189
                },
×
190
        },
191
        "format": {
192
                RequiredArgs: 2,
193
                Variadic:     true,
194
                Args:         []string{"TEXT", ""},
195
                ReturnType:   "TEXT",
NEW
196
                CalcNullable: func(args ...func() bool) func() bool {
×
197
                        return args[0]
×
198
                },
×
199
        },
200
        "glob": {
201
                RequiredArgs: 2,
202
                Args:         []string{"TEXT", "TEXT"},
203
                ReturnType:   "BOOLEAN",
204
        },
205
        "hex": {
206
                RequiredArgs: 1,
207
                Args:         []string{""},
208
                ReturnType:   "TEXT",
209
        },
210
        "ifnull": {
211
                RequiredArgs: 2,
212
                Args:         []string{""},
NEW
213
                CalcReturnType: func(args ...string) string {
×
214
                        for _, arg := range args {
×
215
                                if arg != "" {
×
216
                                        return arg
×
217
                                }
×
218
                        }
219
                        return ""
×
220
                },
221
                CalcNullable: allNullable,
222
        },
223
        "iif": {
224
                RequiredArgs: 3,
225
                Args:         []string{"BOOLEAN", "", ""},
NEW
226
                CalcReturnType: func(args ...string) string {
×
227
                        return args[1]
×
228
                },
×
NEW
229
                CalcNullable: func(args ...func() bool) func() bool {
×
230
                        return anyNullable(args[1], args[2])
×
231
                },
×
232
        },
233
}
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

© 2025 Coveralls, Inc