👋 hey there, I have an interesting use case that is not solved by most migrations tools, and I was wondering if you would be open to the suggestion. First please allow me to describe the problem.
I have a database that supports postgres and sqlite, however, the ddl syntax can be slightly different which means I need two different (but very similar) sets of migrations. In the interest of reducing a significant amount of duplicate code, it would be great if I could leverage templates to have a single set of migrations.
For example:
CREATE TABLE IF NOT EXISTS person (
id {{ if eq .driver "sqlite3" }}INTEGER PRIMARY KEY AUTOINCREMENT{{ else }}SERIAL PRIMARY KEY{{ end }}
,name TEXT
,last TEXT
,first TEXT
,address TEXT
,dob DATE
);
I suspect that templating would be outside the scope of this library, for good reason. Instead, I would propose that this library provides a hook for pre-processing. This would allow me to provide a callback function that executes a template and returns the final sql statement. For example:
// Normally we wouldn't just string interpolate the version like this,
// but because we know the version has been matched against the regexes, we know it's safe.
if _, err := tx.ExecContext(ctx, `update `+m.table+` set version = '`+version+`'`); err != nil {
return err
}
+ stmt := string(content)
+ if m.process != nil {
+ stmt = m.process(ctx, stmt)
+ }
+ if _, err := tx.ExecContext(ctx, stmt); err != nil {
- if _, err := tx.ExecContext(ctx, string(content)); err != nil {
return err
}
This would allow me to do something like this:
driver := "postgres"
opts.Process = func(before string) (string, error) {
t, err := template.New("_").Parse(before)
if err != nil {
return before, err
}
var buf bytes.Buffer
err := t.Execute(buf, map[string]string{"driver": driver})
return buf.String(), err
}
Alternatively, instead of a pre-processing hook, you could allow the user to override the execution:
+ if m.exec != nil {
+ if err := m.exec(ctx, tx, string(content)); err != nil {
+ return err
+ }
+ } else {
if _, err := tx.ExecContext(ctx, string(content)); err != nil {
return err
}
+ }
I probably prefer the first option but the second option might provide some additional flexibility (for example, maybe I want to log the sql statement on error, or skip execution if the template generates an empty string). I think this would be a pretty small change that would solve a very real world problem for authors that are support multiple database vendors. If you are open to supporting this feature, I would be happy to submit a pull request.