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

stephenafamo / bob / 15331028363

29 May 2025 06:42PM UTC coverage: 36.705% (+0.04%) from 36.668%
15331028363

push

github

web-flow
Merge pull request #433 from stephenafamo/codegen-driver

Rename `driver_name` to `driver` in codegen config

13 of 25 new or added lines in 5 files covered. (52.0%)

3 existing lines in 2 files now uncovered.

7980 of 21741 relevant lines covered (36.7%)

108.49 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/fs"
8
        "os"
9
        "path/filepath"
10

11
        embeddedpostgres "github.com/fergusstrange/embedded-postgres"
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
)
22

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

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

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

50
        return gen.Run(ctx, state, d)
×
51
}
52

53
func getPsqlDriver(ctx context.Context, config Config) (psqlDriver.Interface, error) {
×
54
        port, err := helpers.GetFreePort()
×
55
        if err != nil {
×
56
                return nil, fmt.Errorf("could not get a free port: %w", err)
×
57
        }
×
58

59
        dbConfig := embeddedpostgres.
×
60
                DefaultConfig().
×
61
                RuntimePath(filepath.Join(os.TempDir(), "bobgen_sql")).
×
62
                Port(uint32(port))
×
63
        dsn := dbConfig.GetConnectionURL() + "?sslmode=disable"
×
64

×
65
        postgres := embeddedpostgres.NewDatabase(dbConfig)
×
66
        if err := postgres.Start(); err != nil {
×
67
                return nil, fmt.Errorf("starting embedded postgres: %w", err)
×
68
        }
×
69
        defer func() {
×
70
                if err := postgres.Stop(); err != nil {
×
71
                        fmt.Println("Error stopping postgres:", err)
×
72
                }
×
73
        }()
74

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

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

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

×
95
        return d, nil
×
96
}
97

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

104
        return gen.Run(ctx, state, d)
×
105
}
106

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

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

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

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

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

×
145
        return d, nil
×
146
}
147

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

154
        return gen.Run(ctx, state, d)
×
155
}
156

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

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

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

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

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

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

×
199
        return d, nil
×
200
}
201

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

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

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