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

stephenafamo / bob / 14932831999

09 May 2025 03:55PM UTC coverage: 41.506% (+0.1%) from 41.359%
14932831999

push

github

stephenafamo
Move common query parsing code to gen/bobgen-helpers/parser/

27 of 200 new or added lines in 10 files covered. (13.5%)

1 existing line in 1 file now uncovered.

7403 of 17836 relevant lines covered (41.51%)

213.38 hits per line

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

27.83
/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 {
28✔
18
        return Parser{db: t}
28✔
19
}
28✔
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 {
×
38
                stmtStart := info.stmt.GetStart().GetStart()
×
39
                stmtStop := info.stmt.GetStop().GetStop()
×
40
                formatted, err := internal.EditStringSegment(s, stmtStart, stmtStop, info.editRules...)
×
41
                if err != nil {
×
42
                        return nil, fmt.Errorf("format: %w", err)
×
43
                }
×
44

45
                cols := make([]drivers.QueryCol, len(info.columns))
×
46
                for i, col := range info.columns {
×
47
                        cols[i] = drivers.QueryCol{
×
48
                                Name:     col.name,
×
49
                                DBName:   col.name,
×
50
                                Nullable: omit.From(col.typ.Nullable()),
×
51
                                TypeName: col.typ.Type(p.db),
×
52
                        }.Merge(col.config)
×
53
                }
×
54

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

×
61
                        Config: drivers.QueryConfig{
×
62
                                RowName:      name + "Row",
×
63
                                RowSliceName: "",
×
64
                                GenerateRow:  true,
×
NEW
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

76
func (Parser) parse(v *visitor, input string) ([]stmtInfo, error) {
2✔
77
        el := &errorListener{}
2✔
78

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

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

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

106
        infos, ok := tree.Accept(v).([]stmtInfo)
2✔
107
        if v.err != nil {
2✔
108
                return nil, fmt.Errorf("visitor: %w", v.err)
×
109
        }
×
110

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

115
        return infos, nil
2✔
116
}
117

118
type stmtToMod struct {
119
        info stmtInfo
120
}
121

122
func (s stmtToMod) IncludeInTemplate(i drivers.Importer) string {
×
123
        for _, im := range s.info.imports {
×
124
                i.Import(im...)
×
125
        }
×
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{""},
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,
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",
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",
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{""},
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", "", ""},
226
                calcReturnType: func(args ...string) string {
×
227
                        return args[1]
×
228
                },
×
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

© 2026 Coveralls, Inc