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

stephenafamo / bob / 14991965966

13 May 2025 08:31AM UTC coverage: 36.665% (-0.2%) from 36.87%
14991965966

Pull #413

github

stephenafamo
More query refactoring
Pull Request #413: More query refactoring

64 of 327 new or added lines in 36 files covered. (19.57%)

6 existing lines in 2 files now uncovered.

6507 of 17747 relevant lines covered (36.67%)

212.33 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
        antlrhelpers "github.com/stephenafamo/bob/gen/bobgen-helpers/parser/antlrhelpers"
13
        "github.com/stephenafamo/bob/gen/drivers"
14
        "github.com/stephenafamo/bob/internal"
15
        sqliteparser "github.com/stephenafamo/sqlparser/sqlite"
16
)
17

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

22
type Parser struct {
23
        db tables
24
}
25

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

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

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

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

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

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

×
68
                        Columns: cols,
×
NEW
69
                        Args:    v.GetArgs(stmtStart, stmtStop, TranslateColumnType),
×
70
                        Mods:    stmtToMod{info},
×
71
                }
×
72
        }
73

74
        return queries, nil
×
75
}
76

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

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

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

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

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

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

116
        return infos, nil
×
117
}
118

119
type stmtToMod struct {
120
        info StmtInfo
121
}
122

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

130
type errorListener struct {
131
        *antlr.DefaultErrorListener
132

133
        err string
134
}
135

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

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