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

stephenafamo / bob / 16854790794

09 Aug 2025 11:24PM UTC coverage: 41.065% (+0.2%) from 40.883%
16854790794

push

github

web-flow
Merge pull request #533 from stephenafamo/plugins-galore

Add Plugins `enums`, `models`, `factory`, `queries` and `dberrors`

158 of 287 new or added lines in 17 files covered. (55.05%)

25 existing lines in 9 files now uncovered.

9275 of 22586 relevant lines covered (41.07%)

278.63 hits per line

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

0.0
/gen/bobgen-sql/driver/sql.go
1
package driver
2

3
import (
4
        "context"
5
        "database/sql"
6
        "fmt"
7
        "io"
8
        "io/fs"
9
        "log"
10
        "os"
11

12
        "github.com/lib/pq"
13
        "github.com/stephenafamo/bob/gen"
14
        helpers "github.com/stephenafamo/bob/gen/bobgen-helpers"
15
        mysqlDriver "github.com/stephenafamo/bob/gen/bobgen-mysql/driver"
16
        psqlDriver "github.com/stephenafamo/bob/gen/bobgen-psql/driver"
17
        sqliteDriver "github.com/stephenafamo/bob/gen/bobgen-sqlite/driver"
18
        "github.com/stephenafamo/bob/gen/drivers"
19
        "github.com/testcontainers/testcontainers-go"
20
        mysqltest "github.com/testcontainers/testcontainers-go/modules/mysql"
21
        "github.com/testcontainers/testcontainers-go/modules/postgres"
22
)
23

24
type Config struct {
25
        helpers.Config `yaml:",squash"`
26

27
        // What dialect to generate with
28
        // psql | mysql | sqlite
29
        Dialect string
30
        // Glob pattern to match migration files
31
        Pattern string
32
        // The database schemas to generate models for
33
        Schemas []string
34
        // The name of this schema will not be included in the generated models
35
        // a context value can then be used to set the schema at runtime
36
        // useful for multi-tenant setups
37
        SharedSchema string `yaml:"shared_schema"`
38
        // How many tables to fetch in parallel
39
        Concurrency int
40
        // Which UUID package to use (gofrs or google)
41
        UUIDPkg string `yaml:"uuid_pkg"`
42
        fs      fs.FS
43
}
44

NEW
45
func RunPostgres(ctx context.Context, state *gen.State[any], config Config, plugins ...gen.Plugin) error {
×
46
        d, err := getPsqlDriver(ctx, config)
×
47
        if err != nil {
×
48
                return fmt.Errorf("getting psql driver: %w", err)
×
49
        }
×
50

NEW
51
        return gen.Run(ctx, state, d, plugins...)
×
52
}
53

54
func getPsqlDriver(ctx context.Context, config Config) (psqlDriver.Interface, error) {
×
55
        postgresContainer, err := postgres.Run(
×
56
                ctx, "postgres:16",
×
57
                postgres.BasicWaitStrategies(),
×
58
                testcontainers.WithLogger(log.New(io.Discard, "", log.LstdFlags)),
×
59
        )
×
60
        if err != nil {
×
61
                return nil, fmt.Errorf("failed to start container: %w", err)
×
62
        }
×
63
        defer func() {
×
64
                if err := testcontainers.TerminateContainer(postgresContainer); err != nil {
×
65
                        log.Printf("failed to terminate container: %s", err)
×
66
                }
×
67
        }()
68

69
        dsn, err := postgresContainer.ConnectionString(ctx, "sslmode=disable")
×
70
        if err != nil {
×
71
                return nil, fmt.Errorf("failed to get connection string: %w", err)
×
72
        }
×
73

74
        db, err := sql.Open("postgres", dsn)
×
75
        if err != nil {
×
76
                return nil, fmt.Errorf("failed to connect to database: %w", err)
×
77
        }
×
78
        defer db.Close()
×
79

×
80
        if err := helpers.Migrate(ctx, db, config.fs, config.Pattern); err != nil {
×
81
                return nil, fmt.Errorf("migrating: %w", err)
×
82
        }
×
83
        db.Close() // close early
×
84

×
85
        config.Dsn = dsn
×
86
        d := wrapDriver(ctx, psqlDriver.New(psqlDriver.Config{
×
87
                Config:       config.Config,
×
88
                Schemas:      pq.StringArray(config.Schemas),
×
89
                SharedSchema: config.SharedSchema,
×
90
                Concurrency:  config.Concurrency,
×
91
                UUIDPkg:      config.UUIDPkg,
×
92
        }))
×
93

×
94
        return d, nil
×
95
}
96

NEW
97
func RunMySQL(ctx context.Context, state *gen.State[any], config Config, plugins ...gen.Plugin) error {
×
98
        d, err := getMySQLDriver(ctx, config)
×
99
        if err != nil {
×
100
                return fmt.Errorf("getting mysql driver: %w", err)
×
101
        }
×
102

NEW
103
        return gen.Run(ctx, state, d, plugins...)
×
104
}
105

106
func getMySQLDriver(ctx context.Context, config Config) (mysqlDriver.Interface, error) {
×
107
        mysqlContainer, err := mysqltest.Run(ctx,
×
108
                "mysql:8.0.35",
×
109
                mysqltest.WithDatabase("bobgen"),
×
110
                mysqltest.WithUsername("root"),
×
111
                mysqltest.WithPassword("password"),
×
112
        )
×
113
        defer func() {
×
114
                if err := testcontainers.TerminateContainer(mysqlContainer); err != nil {
×
115
                        fmt.Printf("failed to terminate MySQL container: %v\n", err)
×
116
                }
×
117
        }()
118
        if err != nil {
×
119
                return nil, fmt.Errorf("failed to start container: %w", err)
×
120
        }
×
121

122
        dsn, err := mysqlContainer.ConnectionString(ctx, "tls=skip-verify", "multiStatements=true", "parseTime=true")
×
123
        if err != nil {
×
124
                return nil, fmt.Errorf("failed to get connection string: %w", err)
×
125
        }
×
126

127
        db, err := sql.Open("mysql", dsn)
×
128
        if err != nil {
×
129
                return nil, fmt.Errorf("failed to connect to database: %w", err)
×
130
        }
×
131
        defer db.Close()
×
132

×
133
        if err := helpers.Migrate(ctx, db, config.fs, config.Pattern); err != nil {
×
134
                return nil, fmt.Errorf("migrating: %w", err)
×
135
        }
×
136
        db.Close() // close early
×
137

×
138
        config.Dsn = dsn
×
139
        d := wrapDriver(ctx, mysqlDriver.New(mysqlDriver.Config{
×
140
                Config:      config.Config,
×
141
                Concurrency: config.Concurrency,
×
142
        }))
×
143

×
144
        return d, nil
×
145
}
146

NEW
147
func RunSQLite(ctx context.Context, state *gen.State[any], config Config, plugins ...gen.Plugin) error {
×
148
        d, err := getSQLiteDriver(ctx, config)
×
149
        if err != nil {
×
150
                return fmt.Errorf("getting sqlite driver: %w", err)
×
151
        }
×
152

NEW
153
        return gen.Run(ctx, state, d, plugins...)
×
154
}
155

156
func getSQLiteDriver(ctx context.Context, config Config) (sqliteDriver.Interface, error) {
×
157
        tmp, err := os.CreateTemp("", "bobgen_sqlite")
×
158
        if err != nil {
×
159
                return nil, fmt.Errorf("creating temp file: %w", err)
×
160
        }
×
161
        defer tmp.Close()
×
162

×
163
        db, err := sql.Open("sqlite", tmp.Name())
×
164
        if err != nil {
×
165
                return nil, fmt.Errorf("failed to connect to database: %w", err)
×
166
        }
×
167
        defer db.Close()
×
168

×
169
        attach := make(map[string]string)
×
170
        for _, schema := range config.Schemas {
×
171
                tmp, err := os.CreateTemp("", "bobgen_sqlite_"+schema)
×
172
                if err != nil {
×
173
                        return nil, fmt.Errorf("creating temp file: %w", err)
×
174
                }
×
175
                defer tmp.Close()
×
176

×
177
                attach[schema] = tmp.Name()
×
178
                _, err = db.ExecContext(ctx, fmt.Sprintf(
×
179
                        "attach database '%s' as %s", tmp.Name(), schema,
×
180
                ))
×
181
                if err != nil {
×
182
                        return nil, fmt.Errorf("could not attach %q: %w", schema, err)
×
183
                }
×
184
        }
185

186
        if err := helpers.Migrate(ctx, db, config.fs, config.Pattern); err != nil {
×
187
                return nil, fmt.Errorf("migrating: %w", err)
×
188
        }
×
189
        db.Close() // close early
×
190

×
191
        config.Dsn = "file:" + tmp.Name()
×
192
        d := sqliteDriver.New(sqliteDriver.Config{
×
193
                Config:       config.Config,
×
194
                Attach:       attach,
×
195
                SharedSchema: config.SharedSchema,
×
196
        })
×
197

×
198
        return d, nil
×
199
}
200

201
func wrapDriver[T, C, I any](ctx context.Context, d drivers.Interface[T, C, I]) driver[T, C, I] {
×
202
        info, err := d.Assemble(ctx)
×
203
        return driver[T, C, I]{d, info, err}
×
204
}
×
205

206
type driver[T, C, I any] struct {
207
        drivers.Interface[T, C, I]
208
        info *drivers.DBInfo[T, C, I]
209
        err  error
210
}
211

212
func (d driver[T, C, I]) Assemble(context.Context) (*drivers.DBInfo[T, C, I], error) {
×
213
        return d.info, d.err
×
214
}
×
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