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

stephenafamo / bob / 26545899460

27 May 2026 11:57PM UTC coverage: 45.863% (+0.5%) from 45.371%
26545899460

Pull #697

github

Roman A. Grigorovich
Merge branch 'main' into fix/sql-identifier-quoting-v2
Pull Request #697: fix: quote SQL identifiers across dialects

125 of 177 new or added lines in 30 files covered. (70.62%)

5 existing lines in 5 files now uncovered.

11251 of 24532 relevant lines covered (45.86%)

664.18 hits per line

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

59.21
/test/utils/utils.go
1
package testutils
2

3
import (
4
        "context"
5
        "database/sql"
6
        "fmt"
7
        "regexp"
8
        "strings"
9
        "testing"
10

11
        "github.com/google/go-cmp/cmp"
12
        "github.com/stephenafamo/bob"
13
        "github.com/stephenafamo/scan"
14
)
15

16
type Testcases map[string]Testcase
17

18
// Also used to generate documentation
19
type Testcase struct {
20
        Query        bob.Query
21
        ExpectedSQL  string
22
        ExpectedArgs []any
23
        Doc          string
24
}
25

26
var (
27
        oneOrMoreSpace      = regexp.MustCompile(`\s+`)
28
        spaceAroundBrackets = regexp.MustCompile(`\s*([\(|\)])\s*`)
29
        spaceAroundCommas   = regexp.MustCompile(`\s*,\s*`)
30
        spaceAroundDots     = regexp.MustCompile(`\s*\.\s*`)
31
        wrappedClauseSimple = regexp.MustCompile(`\b(ON|WHERE|AND)\s+\(\s*([^\(\)]*?)\s*\)`)
32
)
33

34
func Clean(s string) string {
56✔
35
        s = strings.TrimSpace(s)
56✔
36
        s = strings.ReplaceAll(s, `"`, "")
56✔
37
        s = oneOrMoreSpace.ReplaceAllLiteralString(s, " ")
56✔
38
        s = spaceAroundBrackets.ReplaceAllString(s, " $1 ")
56✔
39
        s = spaceAroundCommas.ReplaceAllString(s, ", ")
56✔
40
        s = spaceAroundDots.ReplaceAllString(s, ".")
56✔
41
        s = wrappedClauseSimple.ReplaceAllString(s, "$1 $2")
56✔
42
        for strings.Contains(s, "( (") || strings.Contains(s, ") )") {
56✔
NEW
43
                s = strings.ReplaceAll(s, "( (", "( ")
×
NEW
44
                s = strings.ReplaceAll(s, ") )", " )")
×
NEW
45
        }
×
46
        return strings.TrimSpace(s)
56✔
47
}
48

49
type FormatFunc = func(string) (string, error)
50

51
func QueryDiff(a, b string, clean FormatFunc) (string, error) {
410✔
52
        if clean == nil {
426✔
53
                clean = func(s string) (string, error) { return Clean(s), nil }
48✔
54
        }
55

56
        cleanA, err := clean(a)
410✔
57
        if err != nil {
410✔
58
                return "", fmt.Errorf("%s\n%w", a, err)
×
59
        }
×
60

61
        cleanB, err := clean(b)
410✔
62
        if err != nil {
410✔
63
                return "", fmt.Errorf("%s\n%w", b, err)
×
64
        }
×
65

66
        return cmp.Diff(cleanA, cleanB), nil
410✔
67
}
68

69
func ArgsDiff(a, b []any) string {
408✔
70
        return cmp.Diff(a, b)
408✔
71
}
408✔
72

73
func ErrDiff(a, b error) string {
14✔
74
        return cmp.Diff(a, b)
14✔
75
}
14✔
76

77
func RunTests(t *testing.T, cases Testcases, format FormatFunc) {
32✔
78
        t.Helper()
32✔
79
        for name, tc := range cases {
426✔
80
                t.Run(name, func(t *testing.T) {
788✔
81
                        sql, args, err := bob.Build(context.Background(), tc.Query)
394✔
82
                        if err != nil {
394✔
83
                                t.Fatalf("error: %v", err)
×
84
                        }
×
85
                        diff, err := QueryDiff(tc.ExpectedSQL, sql, format)
394✔
86
                        if err != nil {
394✔
87
                                t.Fatalf("error: %v", err)
×
88
                        }
×
89
                        if diff != "" {
394✔
90
                                fmt.Println(sql)
×
91
                                fmt.Println(args)
×
92
                                t.Fatalf("diff: %s", diff)
×
93
                        }
×
94
                        if diff := ArgsDiff(tc.ExpectedArgs, args); diff != "" {
394✔
95
                                t.Fatalf("diff: %s", diff)
×
96
                        }
×
97
                })
98
        }
99
}
100

101
type ExpressionTestcases map[string]ExpressionTestcase
102

103
// Also used to generate documentation
104
type ExpressionTestcase struct {
105
        Expression    bob.Expression
106
        ExpectedSQL   string
107
        ExpectedArgs  []any
108
        ExpectedError error
109
        Doc           string
110
}
111

112
func RunExpressionTests(t *testing.T, d bob.Dialect, cases ExpressionTestcases) {
2✔
113
        t.Helper()
2✔
114
        for name, tc := range cases {
16✔
115
                t.Run(name, func(t *testing.T) {
28✔
116
                        b := &strings.Builder{}
14✔
117
                        args, err := bob.Express(context.Background(), b, d, 1, tc.Expression)
14✔
118
                        sql := b.String()
14✔
119

14✔
120
                        if diff := ErrDiff(tc.ExpectedError, err); diff != "" {
14✔
121
                                t.Fatalf("diff: %s", diff)
×
122
                        }
×
123
                        if diff, _ := QueryDiff(tc.ExpectedSQL, sql, nil); diff != "" {
14✔
124
                                fmt.Println(sql)
×
125
                                fmt.Println(args)
×
126
                                t.Fatalf("diff: %s", diff)
×
127
                        }
×
128
                        if diff := ArgsDiff(tc.ExpectedArgs, args); diff != "" {
14✔
129
                                t.Fatalf("diff: %s", diff)
×
130
                        }
×
131
                })
132
        }
133
}
134

135
type NoopExecutor struct{}
136

137
func (n NoopExecutor) QueryContext(ctx context.Context, query string, args ...any) (scan.Rows, error) {
×
138
        return nil, nil //nolint:nilnil
×
139
}
×
140

141
func (n NoopExecutor) ExecContext(_ context.Context, _ string, _ ...any) (sql.Result, error) {
×
142
        return nil, nil //nolint:nilnil
×
143
}
×
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