Skip to content

Commit 149d0ce

Browse files
[baseline] Make pgroll pull pull only migrations after the most recent baseline (#833)
Make the `pgroll pull` command pull only migrations after the most recent baseline. A baseline migration defines a new starting point for the schema history, and as such, `pgroll pull` should not consider migrations prior to the most recent baseline. The change here is to the `State.SchemaHistory` method, which now only returns migrations after the most recent baseline. Part of #364
1 parent 2aee64f commit 149d0ce

File tree

3 files changed

+143
-2
lines changed

3 files changed

+143
-2
lines changed

pkg/roll/missing_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,61 @@ func TestMissingMigrations(t *testing.T) {
211211
require.Len(t, migs, 0)
212212
})
213213
})
214+
215+
t.Run("baseline migrations are not considered missing", func(t *testing.T) {
216+
fs := fstest.MapFS{}
217+
218+
testutils.WithMigratorAndConnectionToContainer(t, func(m *roll.Roll, _ *sql.DB) {
219+
ctx := context.Background()
220+
221+
// Create a baseline migration
222+
err := m.CreateBaseline(ctx, "01_initial_version")
223+
require.NoError(t, err)
224+
225+
// Get missing migrations
226+
migs, err := m.MissingMigrations(ctx, fs)
227+
require.NoError(t, err)
228+
229+
// Assert that there are no migrations missing
230+
require.Len(t, migs, 0)
231+
})
232+
})
233+
234+
t.Run("migrations before a baseline migration are not considered missing", func(t *testing.T) {
235+
fs := fstest.MapFS{}
236+
237+
testutils.WithMigratorAndConnectionToContainer(t, func(m *roll.Roll, _ *sql.DB) {
238+
ctx := context.Background()
239+
240+
// Apply a migration to the target database
241+
err := m.Start(ctx, exampleMig(t, "01_migration_1"), backfill.NewConfig())
242+
require.NoError(t, err)
243+
err = m.Complete(ctx)
244+
require.NoError(t, err)
245+
246+
// Create a baseline migration
247+
err = m.CreateBaseline(ctx, "01_initial_version")
248+
require.NoError(t, err)
249+
250+
// Apply another migration to the target database
251+
err = m.Start(ctx, exampleMig(t, "02_migration_2"), backfill.NewConfig())
252+
require.NoError(t, err)
253+
err = m.Complete(ctx)
254+
require.NoError(t, err)
255+
256+
// Get missing migrations
257+
migs, err := m.MissingMigrations(ctx, fs)
258+
require.NoError(t, err)
259+
260+
// Assert that there is one migration missing from the local directory
261+
require.Len(t, migs, 1)
262+
263+
// Assert that the missing migration is the one that was applied after the baseline
264+
mig, err := migrations.ParseMigration(migs[0])
265+
require.NoError(t, err)
266+
require.Equal(t, exampleMig(t, "02_migration_2"), mig)
267+
})
268+
})
214269
}
215270

216271
func TestMissingMigrationsWithOldMigrationFormats(t *testing.T) {

pkg/state/history.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,15 @@ type HistoryEntry struct {
2424
func (s *State) SchemaHistory(ctx context.Context, schema string) ([]HistoryEntry, error) {
2525
rows, err := s.pgConn.QueryContext(ctx,
2626
fmt.Sprintf(`SELECT name, migration, created_at
27-
FROM %s.migrations
28-
WHERE schema=$1 ORDER BY created_at`,
27+
FROM %[1]s.migrations
28+
WHERE schema=$1
29+
AND created_at > COALESCE(
30+
(
31+
SELECT MAX(created_at) FROM %[1]s.migrations
32+
WHERE schema = $1 AND migration_type = 'baseline'
33+
),
34+
'-infinity'::timestamptz
35+
) ORDER BY created_at`,
2936
pq.QuoteIdentifier(s.schema)), schema)
3037
if err != nil {
3138
return nil, err

pkg/state/history_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,85 @@ func TestSchemaHistoryReturnsFullSchemaHistory(t *testing.T) {
7979
})
8080
}
8181

82+
func TestSchemaHistoryDoesNotReturnBaselineMigrations(t *testing.T) {
83+
t.Parallel()
84+
85+
t.Run("baseline migration does not appear in schema history", func(t *testing.T) {
86+
testutils.WithStateAndConnectionToContainer(t, func(state *state.State, db *sql.DB) {
87+
ctx := context.Background()
88+
89+
// Create a baseline migration
90+
err := state.CreateBaseline(ctx, "public", "01_initial_version")
91+
require.NoError(t, err)
92+
93+
// Get the schema history
94+
res, err := state.SchemaHistory(ctx, "public")
95+
require.NoError(t, err)
96+
97+
// Assert that the schema history is empty
98+
assert.Equal(t, 0, len(res))
99+
})
100+
})
101+
102+
t.Run("migrations before the most recent baseline do not appear in the schema history", func(t *testing.T) {
103+
testutils.WithStateAndConnectionToContainer(t, func(state *state.State, db *sql.DB) {
104+
ctx := context.Background()
105+
106+
// Execute DDL to create an inferred migration
107+
_, err := db.ExecContext(ctx, "CREATE TABLE users (id int)")
108+
require.NoError(t, err)
109+
110+
// Create a baseline migration
111+
err = state.CreateBaseline(ctx, "public", "01_initial_version")
112+
require.NoError(t, err)
113+
114+
// Get the schema history
115+
res, err := state.SchemaHistory(ctx, "public")
116+
require.NoError(t, err)
117+
118+
// Assert that the schema history is empty
119+
assert.Equal(t, 0, len(res))
120+
})
121+
})
122+
123+
t.Run("migrations after the most recent baseline are included in the history", func(t *testing.T) {
124+
testutils.WithStateAndConnectionToContainer(t, func(state *state.State, db *sql.DB) {
125+
ctx := context.Background()
126+
127+
// Execute DDL to create an inferred migration
128+
_, err := db.ExecContext(ctx, "CREATE TABLE users (id int)")
129+
require.NoError(t, err)
130+
131+
// Create a baseline migration
132+
err = state.CreateBaseline(ctx, "public", "01_initial_version")
133+
require.NoError(t, err)
134+
135+
// Execute DDL to create another inferred migration
136+
_, err = db.ExecContext(ctx, "CREATE TABLE fruits (id int)")
137+
require.NoError(t, err)
138+
139+
// Get the schema history
140+
res, err := state.SchemaHistory(ctx, "public")
141+
require.NoError(t, err)
142+
143+
// Assert that one migration is present in the schema history
144+
require.Equal(t, 1, len(res))
145+
146+
// Deserialize the migration from the history.
147+
mig, err := migrations.ParseMigration(&res[0].Migration)
148+
require.NoError(t, err)
149+
150+
// Assert that the migration is the one that was created after the baseline
151+
expectedOperations := migrations.Operations{
152+
&migrations.OpRawSQL{
153+
Up: "CREATE TABLE fruits (id int)",
154+
},
155+
}
156+
assert.Equal(t, expectedOperations, mig.Operations)
157+
})
158+
})
159+
}
160+
82161
func ptr[T any](v T) *T {
83162
return &v
84163
}

0 commit comments

Comments
 (0)