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

stephenafamo / bob / 24426211318

14 Apr 2026 10:31PM UTC coverage: 42.89% (-0.05%) from 42.935%
24426211318

push

github

web-flow
Merge pull request #649 from manhrev/feat/bobgen-sql-custom-image

Allow use of custom image in bobgen-sql

0 of 8 new or added lines in 1 file covered. (0.0%)

8 existing lines in 1 file now uncovered.

10056 of 23446 relevant lines covered (42.89%)

599.18 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/stephenafamo/bob/gen/plugins"
20
        "github.com/testcontainers/testcontainers-go"
21
        mysqltest "github.com/testcontainers/testcontainers-go/modules/mysql"
22
        "github.com/testcontainers/testcontainers-go/modules/postgres"
23
)
24

25
const (
26
        defaultPostgresDriverImage = "pgvector/pgvector:0.8.0-pg16"
27
        defaultMySQLDriverImage    = "mysql:8.0.35"
28
)
29

30
type Config struct {
31
        helpers.Config `yaml:",squash"`
32

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

53
func RunPostgres(ctx context.Context, state *gen.State[any], config Config, pluginsConfig plugins.Config) error {
×
54
        d, err := getPsqlDriver(ctx, config)
×
55
        if err != nil {
×
56
                return fmt.Errorf("getting psql driver: %w", err)
×
57
        }
×
58

59
        plugins := plugins.Setup[any, any, psqlDriver.IndexExtra](pluginsConfig, gen.PSQLTemplates)
×
60

×
61
        return gen.Run(ctx, state, d, plugins...)
×
62
}
63

64
func getPsqlDriver(ctx context.Context, config Config) (psqlDriver.Interface, error) {
×
NEW
65
        if config.DriverImage == "" {
×
NEW
66
                config.DriverImage = defaultPostgresDriverImage
×
NEW
67
        }
×
68

69
        postgresContainer, err := postgres.Run(
×
NEW
70
                ctx, config.DriverImage,
×
71
                postgres.BasicWaitStrategies(),
×
72
                testcontainers.WithLogger(log.New(io.Discard, "", log.LstdFlags)),
×
73
        )
×
74
        if err != nil {
×
75
                return nil, fmt.Errorf("failed to start container: %w", err)
×
76
        }
×
77
        defer func() {
×
78
                if err := testcontainers.TerminateContainer(postgresContainer); err != nil {
×
79
                        log.Printf("failed to terminate container: %s", err)
×
80
                }
×
81
        }()
82

83
        dsn, err := postgresContainer.ConnectionString(ctx, "sslmode=disable")
×
84
        if err != nil {
×
85
                return nil, fmt.Errorf("failed to get connection string: %w", err)
×
86
        }
×
87

88
        db, err := sql.Open("postgres", dsn)
×
89
        if err != nil {
×
90
                return nil, fmt.Errorf("failed to connect to database: %w", err)
×
91
        }
×
92
        defer db.Close()
×
93

×
94
        if err := helpers.Migrate(ctx, db, config.fs, config.Pattern); err != nil {
×
95
                return nil, fmt.Errorf("migrating: %w", err)
×
96
        }
×
97
        db.Close() // close early
×
98

×
99
        config.Dsn = dsn
×
100
        d := wrapDriver(ctx, psqlDriver.New(psqlDriver.Config{
×
101
                Config:       config.Config,
×
102
                Schemas:      pq.StringArray(config.Schemas),
×
103
                SharedSchema: config.SharedSchema,
×
104
                Concurrency:  config.Concurrency,
×
105
                UUIDPkg:      config.UUIDPkg,
×
106
        }))
×
107

×
108
        return d, nil
×
109
}
110

111
func RunMySQL(ctx context.Context, state *gen.State[any], config Config, pluginsConfig plugins.Config) error {
×
112
        d, err := getMySQLDriver(ctx, config)
×
113
        if err != nil {
×
114
                return fmt.Errorf("getting mysql driver: %w", err)
×
115
        }
×
116

117
        plugins := plugins.Setup[any, any, any](pluginsConfig, gen.MySQLTemplates)
×
118

×
119
        return gen.Run(ctx, state, d, plugins...)
×
120
}
121

122
func getMySQLDriver(ctx context.Context, config Config) (mysqlDriver.Interface, error) {
×
NEW
123
        if config.DriverImage == "" {
×
NEW
124
                config.DriverImage = defaultMySQLDriverImage
×
NEW
125
        }
×
126

127
        mysqlContainer, err := mysqltest.Run(ctx,
×
NEW
128
                config.DriverImage,
×
129
                mysqltest.WithDatabase("bobgen"),
×
130
                mysqltest.WithUsername("root"),
×
131
                mysqltest.WithPassword("password"),
×
132
        )
×
133
        defer func() {
×
134
                if err := testcontainers.TerminateContainer(mysqlContainer); err != nil {
×
135
                        fmt.Printf("failed to terminate MySQL container: %v\n", err)
×
136
                }
×
137
        }()
138
        if err != nil {
×
139
                return nil, fmt.Errorf("failed to start container: %w", err)
×
140
        }
×
141

142
        dsn, err := mysqlContainer.ConnectionString(ctx, "tls=skip-verify", "multiStatements=true", "parseTime=true")
×
143
        if err != nil {
×
144
                return nil, fmt.Errorf("failed to get connection string: %w", err)
×
145
        }
×
146

147
        db, err := sql.Open("mysql", dsn)
×
148
        if err != nil {
×
149
                return nil, fmt.Errorf("failed to connect to database: %w", err)
×
150
        }
×
151
        defer db.Close()
×
152

×
153
        if err := helpers.Migrate(ctx, db, config.fs, config.Pattern); err != nil {
×
154
                return nil, fmt.Errorf("migrating: %w", err)
×
155
        }
×
156
        db.Close() // close early
×
157

×
158
        config.Dsn = dsn
×
159
        d := wrapDriver(ctx, mysqlDriver.New(mysqlDriver.Config{
×
160
                Config:      config.Config,
×
161
                Concurrency: config.Concurrency,
×
162
        }))
×
163

×
164
        return d, nil
×
165
}
166

167
func RunSQLite(ctx context.Context, state *gen.State[any], config Config, pluginsConfig plugins.Config) error {
×
168
        d, err := getSQLiteDriver(ctx, config)
×
169
        if err != nil {
×
170
                return fmt.Errorf("getting sqlite driver: %w", err)
×
171
        }
×
172

173
        plugins := plugins.Setup[any, any, sqliteDriver.IndexExtra](pluginsConfig, gen.SQLiteTemplates)
×
174

×
175
        return gen.Run(ctx, state, d, plugins...)
×
176
}
177

178
func getSQLiteDriver(ctx context.Context, config Config) (sqliteDriver.Interface, error) {
×
179
        tmp, err := os.CreateTemp("", "bobgen_sqlite")
×
180
        if err != nil {
×
181
                return nil, fmt.Errorf("creating temp file: %w", err)
×
182
        }
×
183
        defer tmp.Close()
×
184

×
185
        db, err := sql.Open("sqlite", tmp.Name())
×
186
        if err != nil {
×
187
                return nil, fmt.Errorf("failed to connect to database: %w", err)
×
188
        }
×
189
        defer db.Close()
×
190

×
191
        attach := make(map[string]string)
×
192
        for _, schema := range config.Schemas {
×
193
                tmp, err := os.CreateTemp("", "bobgen_sqlite_"+schema)
×
194
                if err != nil {
×
195
                        return nil, fmt.Errorf("creating temp file: %w", err)
×
196
                }
×
197
                defer tmp.Close()
×
198

×
199
                attach[schema] = tmp.Name()
×
200
                _, err = db.ExecContext(ctx, fmt.Sprintf(
×
201
                        "attach database '%s' as %s", tmp.Name(), schema,
×
202
                ))
×
203
                if err != nil {
×
204
                        return nil, fmt.Errorf("could not attach %q: %w", schema, err)
×
205
                }
×
206
        }
207

208
        if err := helpers.Migrate(ctx, db, config.fs, config.Pattern); err != nil {
×
209
                return nil, fmt.Errorf("migrating: %w", err)
×
210
        }
×
211
        db.Close() // close early
×
212

×
213
        config.Dsn = "file:" + tmp.Name()
×
214
        d := sqliteDriver.New(sqliteDriver.Config{
×
215
                Config:       config.Config,
×
216
                Attach:       attach,
×
217
                SharedSchema: config.SharedSchema,
×
218
        })
×
219

×
220
        return d, nil
×
221
}
222

223
func wrapDriver[T, C, I any](ctx context.Context, d drivers.Interface[T, C, I]) driver[T, C, I] {
×
224
        info, err := d.Assemble(ctx)
×
225
        return driver[T, C, I]{d, info, err}
×
226
}
×
227

228
type driver[T, C, I any] struct {
229
        drivers.Interface[T, C, I]
230
        info *drivers.DBInfo[T, C, I]
231
        err  error
232
}
233

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