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

stephenafamo / bob / 15646986598

14 Jun 2025 01:31AM UTC coverage: 36.722% (-0.3%) from 37.021%
15646986598

push

github

web-flow
Merge pull request #460 from stephenafamo/nested-query

Nest columns with `.` in query codegen

12 of 234 new or added lines in 13 files covered. (5.13%)

5 existing lines in 2 files now uncovered.

8171 of 22251 relevant lines covered (36.72%)

268.43 hits per line

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

5.41
/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/antlr4-go/antlr/v4"
10
        "github.com/stephenafamo/bob/gen/bobgen-helpers/parser"
11
        antlrhelpers "github.com/stephenafamo/bob/gen/bobgen-helpers/parser/antlrhelpers"
12
        "github.com/stephenafamo/bob/gen/drivers"
13
        "github.com/stephenafamo/bob/gen/language"
14
        "github.com/stephenafamo/bob/internal"
15
        sqliteparser "github.com/stephenafamo/sqlparser/sqlite"
16
)
17

18
func New(t tables, driver string) Parser {
9✔
19
        return Parser{db: t, driver: driver}
9✔
20
}
9✔
21

22
type Parser struct {
23
        db     tables
24
        driver string
25
}
26

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

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

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

47
                cols := make([]drivers.QueryCol, len(info.Columns))
×
48
                for i, col := range info.Columns {
×
49
                        cols[i] = drivers.QueryCol{
×
50
                                Name:     col.Name,
×
51
                                DBName:   col.Name,
×
52
                                Nullable: internal.Pointer(col.Type.Nullable()),
×
53
                                TypeName: TranslateColumnType(col.Type.ConfirmedDBType(), p.driver),
×
54
                        }.Merge(col.Config)
×
55
                }
×
56
                parser.FixDuplicateColNames(cols)
×
57

×
58
                name, configStr, _ := strings.Cut(info.Comment, " ")
×
59
                queries[i] = drivers.Query{
×
NEW
60
                        Name:    name,
×
NEW
61
                        SQL:     formatted,
×
NEW
62
                        Type:    info.QueryType,
×
NEW
63
                        Config:  parser.ParseQueryConfig(configStr),
×
64
                        Columns: cols,
×
65
                        Args: v.GetArgs(stmtStart, stmtStop, func(s string) (string, []string) {
×
66
                                return TranslateColumnType(s, p.driver), nil
×
67
                        }, v.getCommentToRight),
×
68
                        Mods: stmtToMod{info},
69
                }
70
        }
71

72
        return queries, nil
×
73
}
74

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

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

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

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

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

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

114
        return infos, nil
×
115
}
116

117
type stmtToMod struct {
118
        info StmtInfo
119
}
120

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

128
type errorListener struct {
129
        *antlr.DefaultErrorListener
130

131
        err string
132
}
133

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

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