Skip to content

Commit 435edbc

Browse files
authored
Add DDL isolation support for Aurora DSQL compatibility (#970)
1 parent 2c9813a commit 435edbc

3 files changed

Lines changed: 46 additions & 12 deletions

File tree

provider_options.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,15 @@ func WithLogger(l Logger) ProviderOption {
173173
})
174174
}
175175

176+
// WithIsolateDDL executes DDL operations separately from DML operations. This is useful for
177+
// databases like AWS Aurora DSQL that don't support mixing DDL and DML within the same transaction.
178+
func WithIsolateDDL(b bool) ProviderOption {
179+
return configFunc(func(c *config) error {
180+
c.isolateDDL = b
181+
return nil
182+
})
183+
}
184+
176185
type config struct {
177186
store database.Store
178187

@@ -192,6 +201,7 @@ type config struct {
192201
disableVersioning bool
193202
allowMissing bool
194203
disableGlobalRegistry bool
204+
isolateDDL bool
195205

196206
logger Logger
197207
}

provider_run.go

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ func (p *Provider) runIndividually(
172172
if err != nil {
173173
return err
174174
}
175-
if useTx {
175+
if useTx && !p.cfg.isolateDDL {
176176
return beginTx(ctx, conn, func(tx *sql.Tx) error {
177177
if err := p.runMigration(ctx, tx, m, direction); err != nil {
178178
return err
@@ -316,20 +316,34 @@ func (p *Provider) tryEnsureVersionTable(ctx context.Context, conn *sql.Conn) er
316316
}
317317
// Fallthrough to create the table.
318318
} else if err != nil {
319-
return fmt.Errorf("failed to check if version table exists: %w", err)
319+
return fmt.Errorf("check if version table exists: %w", err)
320320
}
321321

322-
if err := beginTx(ctx, conn, func(tx *sql.Tx) error {
323-
if err := p.store.CreateVersionTable(ctx, tx); err != nil {
324-
return err
322+
if p.cfg.isolateDDL {
323+
// If isolation is enabled, we create the version table separately to ensure subsequent
324+
// DML operations are not mixed with DDL.
325+
if err := p.store.CreateVersionTable(ctx, conn); err != nil {
326+
return retry.RetryableError(fmt.Errorf("create version table: %w", err))
327+
}
328+
if err := p.store.Insert(ctx, conn, database.InsertRequest{Version: 0}); err != nil {
329+
return retry.RetryableError(fmt.Errorf("insert zero version: %w", err))
330+
}
331+
} else {
332+
// If DDL isolation is not enabled, we can create the version table and insert the zero
333+
// version in a single transaction.
334+
if err := beginTx(ctx, conn, func(tx *sql.Tx) error {
335+
if err := p.store.CreateVersionTable(ctx, tx); err != nil {
336+
return err
337+
}
338+
return p.store.Insert(ctx, tx, database.InsertRequest{Version: 0})
339+
}); err != nil {
340+
// Mark the error as retryable so we can try again. It's possible that another instance
341+
// is creating the table at the same time and the checks above will succeed on the next
342+
// iteration.
343+
return retry.RetryableError(fmt.Errorf("create version table: %w", err))
325344
}
326-
return p.store.Insert(ctx, tx, database.InsertRequest{Version: 0})
327-
}); err != nil {
328-
// Mark the error as retryable so we can try again. It's possible that another instance
329-
// is creating the table at the same time and the checks above will succeed on the next
330-
// iteration.
331-
return retry.RetryableError(fmt.Errorf("failed to create version table: %w", err))
332345
}
346+
333347
return nil
334348
})
335349
}
@@ -431,7 +445,6 @@ func (p *Provider) runGo(ctx context.Context, db database.DBTxConn, m *Migration
431445
// runSQL is a helper function that runs the given SQL statements in the given direction. It must
432446
// only be called after the migration has been parsed.
433447
func (p *Provider) runSQL(ctx context.Context, db database.DBTxConn, m *Migration, direction bool) error {
434-
435448
if !m.sql.Parsed {
436449
return fmt.Errorf("sql migrations must be parsed")
437450
}

provider_run_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,17 @@ INSERT INTO owners (owner_name) VALUES ('seed-user-3');
360360
assertStatus(t, status[1], goose.StatePending, newSource(goose.TypeSQL, "00002_partial_error.sql", 2), true)
361361
assertStatus(t, status[2], goose.StatePending, newSource(goose.TypeSQL, "00003_insert_data.sql", 3), true)
362362
})
363+
t.Run("isolate_ddl", func(t *testing.T) {
364+
ctx := context.Background()
365+
p, _ := newProviderWithDB(t, goose.WithIsolateDDL(true))
366+
// Apply all migrations
367+
res, err := p.Up(ctx)
368+
require.NoError(t, err)
369+
require.Len(t, res, 7)
370+
currentVersion, err := p.GetDBVersion(ctx)
371+
require.NoError(t, err)
372+
require.EqualValues(t, 7, currentVersion)
373+
})
363374
}
364375

365376
func TestConcurrentProvider(t *testing.T) {

0 commit comments

Comments
 (0)