Skip to content

Commit 4bb3713

Browse files
authored
Merge pull request #5910 from hashicorp/backport/mikemountain-add-test-to-migration-fix
Manual backport of fix(bug): database migration does not rollback schema version on hook failure into release/0.19.x
2 parents 9a24c2d + a3c2393 commit 4bb3713

File tree

5 files changed

+67
-38
lines changed

5 files changed

+67
-38
lines changed

internal/db/schema/internal/postgres/postgres.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ func (p *Postgres) CommitRun(ctx context.Context) error {
202202
return nil
203203
}
204204

205-
// RollbackRun rolls back a transaction.
205+
// RollbackRun rolls back a transaction. If no transaction is active, it will return nil.
206206
func (p *Postgres) RollbackRun(ctx context.Context) error {
207207
const op = "postgres.(Postgres).RollbackRun"
208208
defer func() {

internal/db/schema/internal/postgres/postgres_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,38 @@ create table foo (
4646
assert.Equal(t, v, 1001)
4747
}
4848

49+
func TestRun_Rollback(t *testing.T) {
50+
ctx := context.Background()
51+
p, _, _ := setup(ctx, t)
52+
53+
statements := bytes.NewReader([]byte(`
54+
create table foo (
55+
id bigint primary key,
56+
bar text
57+
);
58+
`))
59+
60+
err := p.StartRun(ctx)
61+
require.NoError(t, err)
62+
63+
err = p.EnsureVersionTable(ctx)
64+
require.NoError(t, err)
65+
66+
err = p.EnsureMigrationLogTable(ctx)
67+
require.NoError(t, err)
68+
69+
err = p.Run(ctx, statements, 1001, "oss")
70+
require.NoError(t, err)
71+
72+
err = p.RollbackRun(ctx)
73+
require.NoError(t, err)
74+
75+
v, i, err := p.CurrentState(ctx, "oss")
76+
require.NoError(t, err)
77+
assert.False(t, i)
78+
assert.Equal(t, -1, v)
79+
}
80+
4981
func TestRun_NoTxn(t *testing.T) {
5082
ctx := context.Background()
5183
p, _, _ := setup(ctx, t)

internal/db/schema/manager.go

Lines changed: 18 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ type driver interface {
3333
StartRun(context.Context) error
3434
// CommitRun commits a transaction, if there is an error it should rollback the transaction.
3535
CommitRun(context.Context) error
36-
// RollbackRun rolls back a transaction.
36+
// RollbackRun rolls back a transaction. If no transaction is active, it should return nil.
3737
RollbackRun(context.Context) error
3838
// CheckHook is a hook that runs prior to a migration's statements.
3939
// It should run in the same transaction a corresponding Run call.
@@ -247,68 +247,54 @@ func (b *Manager) runMigrations(ctx context.Context, p *provider.Provider) ([]Re
247247
const op = "schema.(Manager).runMigrations"
248248

249249
var logEntries []RepairLog
250-
var errFinal error
250+
var retErr error
251251

252252
if startErr := b.driver.StartRun(ctx); startErr != nil {
253-
errFinal = errors.Wrap(ctx, startErr, op)
254-
return nil, errFinal
253+
return nil, errors.Wrap(ctx, startErr, op)
255254
}
256255

257256
defer func() {
258-
if errFinal != nil {
259-
errRollback := b.driver.RollbackRun(ctx)
260-
if errRollback != nil {
261-
errFinal = stderrors.Join(errFinal, errRollback)
262-
}
263-
errFinal = errors.Wrap(ctx, errFinal, op)
264-
return
265-
}
266-
if commitErr := b.driver.CommitRun(ctx); commitErr != nil {
267-
errFinal = errors.Wrap(ctx, commitErr, op)
257+
// rolling back a committed run is a no-op, so we can safely call this every time
258+
if err := b.driver.RollbackRun(ctx); err != nil {
259+
retErr = stderrors.Join(retErr, err)
268260
}
269261
}()
270262

271263
if ensureErr := b.driver.EnsureVersionTable(ctx); ensureErr != nil {
272-
errFinal = errors.Wrap(ctx, ensureErr, op)
273-
return nil, errFinal
264+
return nil, errors.Wrap(ctx, ensureErr, op)
274265
}
275266

276267
if ensureErr := b.driver.EnsureMigrationLogTable(ctx); ensureErr != nil {
277-
errFinal = errors.Wrap(ctx, ensureErr, op)
278-
return nil, errFinal
268+
return nil, errors.Wrap(ctx, ensureErr, op)
279269
}
280270

281271
for p.Next() {
282272
select {
283273
case <-ctx.Done():
284-
errFinal = errors.Wrap(ctx, ctx.Err(), op)
285-
return nil, errFinal
274+
return nil, errors.Wrap(ctx, ctx.Err(), op)
286275
default:
287276
// context is not done yet. Continue on to the next query to execute.
288277
}
289278

290279
if h := p.PreHook(); h != nil {
291280
problems, err := b.driver.CheckHook(ctx, h.CheckFunc)
292281
if err != nil {
293-
errFinal = errors.Wrap(ctx, err, op)
294-
return nil, errFinal
282+
return nil, errors.Wrap(ctx, err, op)
295283
}
296284

297285
if len(problems) > 0 {
298286
if !b.selectedRepairs.IsSet(p.Edition(), p.Version()) {
299-
errFinal = MigrationCheckError{
287+
return nil, MigrationCheckError{
300288
Version: p.Version(),
301289
Edition: p.Edition(),
302290
Problems: problems,
303291
RepairDescription: h.RepairDescription,
304292
}
305-
return nil, errFinal
306293
}
307294

308295
repairs, err := b.driver.RepairHook(ctx, h.RepairFunc)
309296
if err != nil {
310-
errFinal = errors.Wrap(ctx, err, op)
311-
return nil, errFinal
297+
return nil, errors.Wrap(ctx, err, op)
312298
}
313299
logEntries = append(logEntries, RepairLog{
314300
Version: p.Version(),
@@ -318,10 +304,13 @@ func (b *Manager) runMigrations(ctx context.Context, p *provider.Provider) ([]Re
318304
}
319305
}
320306
if runErr := b.driver.Run(ctx, bytes.NewReader(p.Statements()), p.Version(), p.Edition()); runErr != nil {
321-
errFinal = errors.Wrap(ctx, runErr, op)
322-
return nil, errFinal
307+
return nil, errors.Wrap(ctx, runErr, op)
323308
}
324309
}
325310

326-
return logEntries, nil
311+
if err := b.driver.CommitRun(ctx); err != nil {
312+
return nil, errors.Wrap(ctx, err, op)
313+
}
314+
315+
return logEntries, retErr
327316
}

internal/db/schema/manager_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -360,8 +360,8 @@ func TestApplyMigrationWithHooks(t *testing.T) {
360360
Editions: []schema.EditionState{
361361
{
362362
Name: "hooks",
363-
BinarySchemaVersion: 1001,
364-
DatabaseSchemaVersion: 1001,
363+
BinarySchemaVersion: 2001,
364+
DatabaseSchemaVersion: 2001,
365365
DatabaseSchemaState: schema.Equal,
366366
},
367367
},
@@ -379,7 +379,7 @@ func TestApplyMigrationWithHooks(t *testing.T) {
379379
0,
380380
edition.WithPreHooks(
381381
map[int]*migration.Hook{
382-
1001: {
382+
2001: {
383383
CheckFunc: func(ctx context.Context, tx *sql.Tx) (migration.Problems, error) {
384384
return migration.Problems{"failed"}, nil
385385
},
@@ -393,7 +393,7 @@ func TestApplyMigrationWithHooks(t *testing.T) {
393393
},
394394
nil,
395395
schema.MigrationCheckError{
396-
Version: 1001,
396+
Version: 2001,
397397
Edition: "hooks",
398398
Problems: migration.Problems{"failed"},
399399
RepairDescription: "repair all the things",
@@ -403,7 +403,7 @@ func TestApplyMigrationWithHooks(t *testing.T) {
403403
Editions: []schema.EditionState{
404404
{
405405
Name: "hooks",
406-
BinarySchemaVersion: 1001,
406+
BinarySchemaVersion: 2001,
407407
DatabaseSchemaVersion: 1,
408408
DatabaseSchemaState: schema.Behind,
409409
},
@@ -447,8 +447,8 @@ func TestApplyMigrationWithHooks(t *testing.T) {
447447
Editions: []schema.EditionState{
448448
{
449449
Name: "hooks",
450-
BinarySchemaVersion: 1001,
451-
DatabaseSchemaVersion: 1001,
450+
BinarySchemaVersion: 2001,
451+
DatabaseSchemaVersion: 2001,
452452
DatabaseSchemaState: schema.Equal,
453453
},
454454
},
@@ -494,7 +494,7 @@ func TestApplyMigrationWithHooks(t *testing.T) {
494494
Editions: []schema.EditionState{
495495
{
496496
Name: "hooks",
497-
BinarySchemaVersion: 1001,
497+
BinarySchemaVersion: 2001,
498498
DatabaseSchemaVersion: 1,
499499
DatabaseSchemaState: schema.Behind,
500500
},
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
-- Copyright (c) HashiCorp, Inc.
2+
-- SPDX-License-Identifier: BUSL-1.1
3+
4+
begin;
5+
create table test_five (
6+
id tt_public_id primary key
7+
);
8+
commit;

0 commit comments

Comments
 (0)