Repository navigation
Expand file tree
/
Copy pathdialects.go
More file actions
181 lines (139 loc) · 5.33 KB
/
Copy pathdialects.go
File metadata and controls
181 lines (139 loc) · 5.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
// SPDX-FileCopyrightText: 2026 The DMorph contributors.
// SPDX-License-Identifier: MPL-2.0
package dmorph
import (
"context"
"database/sql"
"errors"
"fmt"
)
// NamedParamsDialect is a convenience type for databases that manage the necessary operations solely using
// queries. Defining the CreateTemplate, AppliedTemplate and RegisterTemplate enables the NamedParamsDialect to
// perform all the necessary operations to fulfill the Dialect interface.
type NamedParamsDialect struct {
CreateTemplate string // statement ensuring the existence of the migration table
AppliedTemplate string // statement getting applied migrations ordered by application date
RegisterTemplate string // statement registering a migration
}
// EnsureMigrationTableExists ensures that the migration table, saving the applied migrations ids, exists.
func (b NamedParamsDialect) EnsureMigrationTableExists(ctx context.Context, db *sql.DB, tableName string) error {
tx, err := db.BeginTx(ctx, nil)
if err != nil {
return wrapIfError("could not start transaction", err)
}
// Safety net for unexpected panics or returns. We can always call Rollback,
// as it does semantically nothing in case of a previous successful commit
defer func() { _ = tx.Rollback() }()
if _, execErr := tx.ExecContext(ctx, fmt.Sprintf(b.CreateTemplate, tableName)); execErr != nil {
rollbackErr := tx.Rollback()
return errors.Join(execErr, rollbackErr)
}
if err := tx.Commit(); err != nil {
rollbackErr := tx.Rollback()
return errors.Join(err, rollbackErr)
}
return nil
}
// AppliedMigrations gets the already applied migrations from the database, ordered by application date.
func (b NamedParamsDialect) AppliedMigrations(
ctx context.Context,
db *sql.DB,
tableName string,
groupName string) ([]string, error) {
rows, rowsErr := db.QueryContext(ctx, fmt.Sprintf(b.AppliedTemplate, tableName),
sql.Named("mgroup", groupName))
if rowsErr != nil {
return nil, wrapIfError("could not get applied migrations", rowsErr)
}
defer func() { _ = rows.Close() }()
var result []string
var tmp string
var scanErr error
for rows.Next() && scanErr == nil {
if scanErr = rows.Scan(&tmp); scanErr == nil {
result = append(result, tmp)
}
}
return result, errors.Join(rows.Err(), scanErr)
}
// RegisterMigration registers a migration in the migration table.
func (b NamedParamsDialect) RegisterMigration(
ctx context.Context,
tx *sql.Tx,
id string,
tableName string,
groupName string) error {
_, err := tx.ExecContext(ctx, fmt.Sprintf(b.RegisterTemplate, tableName),
sql.Named("id", id),
sql.Named("mgroup", groupName))
return wrapIfError("could not register migration", err)
}
// ParamName represents a named parameter for use in SQL queries or migrations.
type ParamName string
const (
// ParamNameID represents the "id" parameter used in SQL queries or migration operations.
ParamNameID ParamName = "id"
// ParamNameMGroup represents the "mgroup" parameter used in SQL queries or migration operations.
ParamNameMGroup ParamName = "mgroup"
)
// NumberedParamsDialect extends NamedParamsDialect to support positional parameterized SQL queries.
type NumberedParamsDialect struct {
NamedParamsDialect
AppliedMigrationsParamsOrder []ParamName // defines the order of parameters for retrieving applied migrations.
RegisterMigrationParamsOrder []ParamName // defines the order of parameters for registering a migration.
}
// EnsureMigrationTableExists ensures that the migration table, saving the applied migrations ids, exists.
func (b NumberedParamsDialect) EnsureMigrationTableExists(ctx context.Context, db *sql.DB, tableName string) error {
return b.NamedParamsDialect.EnsureMigrationTableExists(ctx, db, tableName)
}
// AppliedMigrations gets the already applied migrations from the database, ordered by application date.
func (b NumberedParamsDialect) AppliedMigrations(
ctx context.Context,
db *sql.DB,
tableName string,
groupName string) ([]string, error) {
params := make([]any, 0, len(b.AppliedMigrationsParamsOrder))
for _, p := range b.AppliedMigrationsParamsOrder {
switch p {
case ParamNameMGroup:
params = append(params, groupName)
default:
return nil, fmt.Errorf("unexpected param name %v: %w", p, ErrParamNameInvalid)
}
}
rows, rowsErr := db.QueryContext(ctx, fmt.Sprintf(b.AppliedTemplate, tableName), params...)
if rowsErr != nil {
return nil, wrapIfError("could not get applied migrations", rowsErr)
}
defer func() { _ = rows.Close() }()
var result []string
var tmp string
var scanErr error
for rows.Next() && scanErr == nil {
if scanErr = rows.Scan(&tmp); scanErr == nil {
result = append(result, tmp)
}
}
return result, errors.Join(rows.Err(), scanErr)
}
// RegisterMigration registers a migration in the migration table.
func (b NumberedParamsDialect) RegisterMigration(
ctx context.Context,
tx *sql.Tx,
id string,
tableName string,
groupName string) error {
params := make([]any, 0, len(b.RegisterMigrationParamsOrder))
for _, p := range b.RegisterMigrationParamsOrder {
switch p {
case ParamNameID:
params = append(params, id)
case ParamNameMGroup:
params = append(params, groupName)
default:
return fmt.Errorf("unexpected param name %v: %w", p, ErrParamNameInvalid)
}
}
_, err := tx.ExecContext(ctx, fmt.Sprintf(b.RegisterTemplate, tableName), params...)
return wrapIfError("could not register migration", err)
}