Skip to content

Commit c05581b

Browse files
committed
store db migrates in the database if it supports it
1 parent 67ef559 commit c05581b

File tree

6 files changed

+794
-0
lines changed

6 files changed

+794
-0
lines changed
Lines changed: 275 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,275 @@
1+
package postgres
2+
3+
import (
4+
"database/sql"
5+
"testing"
6+
7+
"github.com/dhui/dktest"
8+
"github.com/golang-migrate/migrate/v4/database"
9+
"github.com/golang-migrate/migrate/v4/dktesting"
10+
_ "github.com/lib/pq"
11+
)
12+
13+
var storageSpecs = []dktesting.ContainerSpec{
14+
{ImageName: "postgres:13", Options: opts},
15+
}
16+
17+
func TestStorageMigrations(t *testing.T) {
18+
testStorageBasicOperations(t)
19+
}
20+
21+
func TestSyncMigrations(t *testing.T) {
22+
testSyncMultipleMigrations(t)
23+
}
24+
25+
func TestStorageSchemaUpgrade(t *testing.T) {
26+
testSchemaUpgrade(t)
27+
}
28+
29+
func TestStorageErrorHandling(t *testing.T) {
30+
testStorageErrorCases(t)
31+
}
32+
33+
func testStorageBasicOperations(t *testing.T) {
34+
dktesting.ParallelTest(t, storageSpecs, func(t *testing.T, c dktest.ContainerInfo) {
35+
d := setupPostgresDriver(t, c)
36+
defer closeDriver(t, d)
37+
38+
// Cast to storage driver
39+
storageDriver := castToStorageDriver(t, d)
40+
41+
// Test storing and retrieving migrations
42+
testUpScript := []byte("CREATE TABLE test_table (id SERIAL PRIMARY KEY, name VARCHAR(255));")
43+
testDownScript := []byte("DROP TABLE test_table;")
44+
45+
// Store migration (both up and down)
46+
err := storageDriver.StoreMigration(1, testUpScript, testDownScript)
47+
if err != nil {
48+
t.Fatalf("Failed to store migration: %v", err)
49+
}
50+
51+
// Retrieve migration
52+
retrievedUp, retrievedDown, err := storageDriver.GetMigration(1)
53+
if err != nil {
54+
t.Fatalf("Failed to retrieve migration: %v", err)
55+
}
56+
57+
if string(retrievedUp) != string(testUpScript) {
58+
t.Errorf("Retrieved up migration doesn't match. Expected: %s, Got: %s", testUpScript, retrievedUp)
59+
}
60+
61+
if string(retrievedDown) != string(testDownScript) {
62+
t.Errorf("Retrieved down migration doesn't match. Expected: %s, Got: %s", testDownScript, retrievedDown)
63+
}
64+
65+
// Test getting stored migrations list
66+
versions, err := storageDriver.GetStoredMigrations()
67+
if err != nil {
68+
t.Fatalf("Failed to get stored migrations: %v", err)
69+
}
70+
71+
if len(versions) != 1 || versions[0] != 1 {
72+
t.Errorf("Expected stored migrations [1], got %v", versions)
73+
}
74+
})
75+
}
76+
77+
func testSyncMultipleMigrations(t *testing.T) {
78+
// This test would require setting up a source driver with multiple migrations
79+
// For now, we'll test the basic storage functionality only
80+
t.Skip("SyncMigrations requires source driver setup - testing basic storage instead")
81+
}
82+
83+
func testSchemaUpgrade(t *testing.T) {
84+
dktesting.ParallelTest(t, storageSpecs, func(t *testing.T, c dktest.ContainerInfo) {
85+
ip, port, err := c.FirstPort()
86+
if err != nil {
87+
t.Fatal(err)
88+
}
89+
90+
// Create a direct database connection to set up old schema
91+
connStr := pgConnectionString(ip, port)
92+
db, err := sql.Open("postgres", connStr)
93+
if err != nil {
94+
t.Fatal(err)
95+
}
96+
defer db.Close()
97+
98+
// Create the old schema format (without storage columns)
99+
_, err = db.Exec(`CREATE TABLE schema_migrations (
100+
version bigint NOT NULL PRIMARY KEY,
101+
dirty boolean NOT NULL
102+
)`)
103+
if err != nil {
104+
t.Fatalf("Failed to create old schema: %v", err)
105+
}
106+
107+
// Insert some existing migration records
108+
_, err = db.Exec(`INSERT INTO schema_migrations (version, dirty) VALUES (1, false), (2, false)`)
109+
if err != nil {
110+
t.Fatalf("Failed to insert existing records: %v", err)
111+
}
112+
113+
// Close the direct connection
114+
db.Close()
115+
116+
// Now create the postgres driver which should trigger schema upgrade
117+
d := setupPostgresDriver(t, c)
118+
defer closeDriver(t, d)
119+
120+
// Cast to storage driver (this should trigger the schema upgrade)
121+
storageDriver := castToStorageDriver(t, d)
122+
123+
// Try to store a migration - this should work after schema upgrade
124+
err = storageDriver.StoreMigration(3, []byte("CREATE TABLE test_upgrade (id SERIAL);"), []byte("DROP TABLE test_upgrade;"))
125+
if err != nil {
126+
t.Fatalf("Failed to store migration after schema upgrade: %v", err)
127+
}
128+
129+
// Get underlying sql.DB for verification
130+
pgDriver := d.(*Postgres)
131+
dbVerify := pgDriver.db
132+
133+
// Verify the schema has the new columns
134+
verifySchemaUpgrade(t, dbVerify)
135+
verifyExistingRecordsPreserved(t, storageDriver)
136+
})
137+
}
138+
139+
func testStorageErrorCases(t *testing.T) {
140+
dktesting.ParallelTest(t, storageSpecs, func(t *testing.T, c dktest.ContainerInfo) {
141+
d := setupPostgresDriver(t, c)
142+
defer closeDriver(t, d)
143+
144+
storageDriver := castToStorageDriver(t, d)
145+
146+
// Test retrieving non-existent migration
147+
_, _, err := storageDriver.GetMigration(999)
148+
if err == nil {
149+
t.Error("Expected error when retrieving non-existent migration")
150+
}
151+
152+
// Store a valid migration first
153+
err = storageDriver.StoreMigration(1, []byte("CREATE TABLE test (id SERIAL);"), []byte("DROP TABLE test;"))
154+
if err != nil {
155+
t.Fatalf("Failed to store valid migration: %v", err)
156+
}
157+
158+
// Test storing duplicate migration (should update, not error)
159+
err = storageDriver.StoreMigration(1, []byte("CREATE TABLE test_updated (id SERIAL);"), []byte("DROP TABLE test_updated;"))
160+
if err != nil {
161+
t.Errorf("Unexpected error when updating existing migration: %v", err)
162+
}
163+
164+
// Verify the migration was updated
165+
upScript, _, err := storageDriver.GetMigration(1)
166+
if err != nil {
167+
t.Fatalf("Failed to retrieve updated migration: %v", err)
168+
}
169+
170+
expected := "CREATE TABLE test_updated (id SERIAL);"
171+
if string(upScript) != expected {
172+
t.Errorf("Migration was not updated. Expected: %s, Got: %s", expected, upScript)
173+
}
174+
})
175+
}
176+
177+
// Helper functions
178+
179+
func setupPostgresDriver(t *testing.T, c dktest.ContainerInfo) database.Driver {
180+
ip, port, err := c.FirstPort()
181+
if err != nil {
182+
t.Fatal(err)
183+
}
184+
185+
p := &Postgres{}
186+
addr := pgConnectionString(ip, port)
187+
188+
d, err := p.Open(addr)
189+
if err != nil {
190+
t.Fatal(err)
191+
}
192+
return d
193+
}
194+
195+
func closeDriver(t *testing.T, d database.Driver) {
196+
if err := d.Close(); err != nil {
197+
t.Error(err)
198+
}
199+
}
200+
201+
func castToStorageDriver(t *testing.T, d database.Driver) database.MigrationStorageDriver {
202+
storageDriver, ok := d.(database.MigrationStorageDriver)
203+
if !ok {
204+
t.Fatal("Postgres driver does not implement MigrationStorageDriver interface")
205+
}
206+
return storageDriver
207+
}
208+
209+
func verifySchemaUpgrade(t *testing.T, db *sql.DB) {
210+
rows, err := db.Query(`SELECT column_name FROM information_schema.columns
211+
WHERE table_name = 'schema_migrations' ORDER BY column_name`)
212+
if err != nil {
213+
t.Fatalf("Failed to query schema columns: %v", err)
214+
}
215+
defer rows.Close()
216+
217+
var columns []string
218+
for rows.Next() {
219+
var col string
220+
if err := rows.Scan(&col); err != nil {
221+
t.Fatalf("Failed to scan column name: %v", err)
222+
}
223+
columns = append(columns, col)
224+
}
225+
226+
expectedColumns := []string{"created_at", "dirty", "down_script", "up_script", "version"}
227+
if len(columns) != len(expectedColumns) {
228+
t.Errorf("Expected %d columns, got %d: %v", len(expectedColumns), len(columns), columns)
229+
}
230+
231+
for i, expected := range expectedColumns {
232+
if i >= len(columns) || columns[i] != expected {
233+
t.Errorf("Column mismatch at position %d. Expected: %s, Got: %v", i, expected, columns)
234+
break
235+
}
236+
}
237+
}
238+
239+
func verifyExistingRecordsPreserved(t *testing.T, storageDriver database.MigrationStorageDriver) {
240+
// Verify that existing version records are preserved during schema upgrade
241+
// We should be able to query the table directly to see all version records
242+
pgDriver := storageDriver.(*Postgres)
243+
db := pgDriver.db
244+
245+
// Check that all version records (with and without scripts) are preserved
246+
rows, err := db.Query(`SELECT version FROM schema_migrations ORDER BY version ASC`)
247+
if err != nil {
248+
t.Fatalf("Failed to query version records: %v", err)
249+
}
250+
defer rows.Close()
251+
252+
var versions []uint
253+
for rows.Next() {
254+
var version int64
255+
if err := rows.Scan(&version); err != nil {
256+
t.Fatalf("Failed to scan version: %v", err)
257+
}
258+
versions = append(versions, uint(version))
259+
}
260+
261+
// Should have at least 3 version records: 1, 2 (original), and 3 (newly stored)
262+
if len(versions) < 3 {
263+
t.Errorf("Expected at least 3 version records after upgrade, got %d: %v", len(versions), versions)
264+
}
265+
266+
// Check that GetStoredMigrations only returns the one with scripts (version 3)
267+
storedVersions, err := storageDriver.GetStoredMigrations()
268+
if err != nil {
269+
t.Fatalf("Failed to get stored migrations: %v", err)
270+
}
271+
272+
if len(storedVersions) != 1 || storedVersions[0] != 3 {
273+
t.Errorf("Expected GetStoredMigrations to return [3], got %v", storedVersions)
274+
}
275+
}

database/postgres/postgres_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@ func Test(t *testing.T) {
100100
t.Run("testWithInstanceConcurrent", testWithInstanceConcurrent)
101101
t.Run("testWithConnection", testWithConnection)
102102

103+
// Storage functionality tests
104+
t.Run("TestStorageMigrations", TestStorageMigrations)
105+
t.Run("TestSyncMigrations", TestSyncMigrations)
106+
t.Run("TestStorageSchemaUpgrade", TestStorageSchemaUpgrade)
107+
t.Run("TestStorageErrorHandling", TestStorageErrorHandling)
108+
103109
t.Cleanup(func() {
104110
for _, spec := range specs {
105111
t.Log("Cleaning up ", spec.ImageName)

0 commit comments

Comments
 (0)