github.com/amacneil/dbmate@v1.16.3-0.20230225174651-ca89b10d75d7/pkg/driver/sqlite/sqlite_test.go (about) 1 //go:build cgo 2 // +build cgo 3 4 package sqlite 5 6 import ( 7 "database/sql" 8 "os" 9 "testing" 10 11 "github.com/amacneil/dbmate/pkg/dbmate" 12 "github.com/amacneil/dbmate/pkg/dbutil" 13 14 "github.com/stretchr/testify/require" 15 ) 16 17 func testSQLiteDriver(t *testing.T) *Driver { 18 u := dbutil.MustParseURL(os.Getenv("SQLITE_TEST_URL")) 19 drv, err := dbmate.New(u).Driver() 20 require.NoError(t, err) 21 22 return drv.(*Driver) 23 } 24 25 func prepTestSQLiteDB(t *testing.T) *sql.DB { 26 drv := testSQLiteDriver(t) 27 28 // drop any existing database 29 err := drv.DropDatabase() 30 require.NoError(t, err) 31 32 // create database 33 err = drv.CreateDatabase() 34 require.NoError(t, err) 35 36 // connect database 37 db, err := drv.Open() 38 require.NoError(t, err) 39 40 return db 41 } 42 43 func TestGetDriver(t *testing.T) { 44 db := dbmate.New(dbutil.MustParseURL("sqlite://")) 45 drvInterface, err := db.Driver() 46 require.NoError(t, err) 47 48 // driver should have URL and default migrations table set 49 drv, ok := drvInterface.(*Driver) 50 require.True(t, ok) 51 require.Equal(t, db.DatabaseURL.String(), drv.databaseURL.String()) 52 require.Equal(t, "schema_migrations", drv.migrationsTableName) 53 } 54 55 func TestConnectionString(t *testing.T) { 56 t.Run("relative", func(t *testing.T) { 57 u := dbutil.MustParseURL("sqlite:foo/bar.sqlite3?mode=ro") 58 require.Equal(t, "foo/bar.sqlite3?mode=ro", ConnectionString(u)) 59 }) 60 61 t.Run("relative with dot", func(t *testing.T) { 62 u := dbutil.MustParseURL("sqlite:./foo/bar.sqlite3?mode=ro") 63 require.Equal(t, "./foo/bar.sqlite3?mode=ro", ConnectionString(u)) 64 }) 65 66 t.Run("relative with double dot", func(t *testing.T) { 67 u := dbutil.MustParseURL("sqlite:../foo/bar.sqlite3?mode=ro") 68 require.Equal(t, "../foo/bar.sqlite3?mode=ro", ConnectionString(u)) 69 }) 70 71 t.Run("absolute", func(t *testing.T) { 72 u := dbutil.MustParseURL("sqlite:/tmp/foo.sqlite3?mode=ro") 73 require.Equal(t, "/tmp/foo.sqlite3?mode=ro", ConnectionString(u)) 74 }) 75 76 t.Run("two slashes", func(t *testing.T) { 77 // interpreted as absolute path 78 u := dbutil.MustParseURL("sqlite://tmp/foo.sqlite3?mode=ro") 79 require.Equal(t, "/tmp/foo.sqlite3?mode=ro", ConnectionString(u)) 80 }) 81 82 t.Run("three slashes", func(t *testing.T) { 83 // interpreted as absolute path 84 u := dbutil.MustParseURL("sqlite:///tmp/foo.sqlite3?mode=ro") 85 require.Equal(t, "/tmp/foo.sqlite3?mode=ro", ConnectionString(u)) 86 }) 87 88 t.Run("four slashes", func(t *testing.T) { 89 // interpreted as absolute path 90 // supported for backwards compatibility 91 u := dbutil.MustParseURL("sqlite:////tmp/foo.sqlite3?mode=ro") 92 require.Equal(t, "/tmp/foo.sqlite3?mode=ro", ConnectionString(u)) 93 }) 94 95 t.Run("relative with space", func(t *testing.T) { 96 u := dbutil.MustParseURL("sqlite:foo bar.sqlite3?mode=ro") 97 require.Equal(t, "foo bar.sqlite3?mode=ro", ConnectionString(u)) 98 }) 99 100 t.Run("relative with space and dot", func(t *testing.T) { 101 u := dbutil.MustParseURL("sqlite:./foo bar.sqlite3?mode=ro") 102 require.Equal(t, "./foo bar.sqlite3?mode=ro", ConnectionString(u)) 103 }) 104 105 t.Run("relative with space and double dot", func(t *testing.T) { 106 u := dbutil.MustParseURL("sqlite:../foo bar.sqlite3?mode=ro") 107 require.Equal(t, "../foo bar.sqlite3?mode=ro", ConnectionString(u)) 108 }) 109 110 t.Run("absolute with space", func(t *testing.T) { 111 u := dbutil.MustParseURL("sqlite:/foo bar.sqlite3?mode=ro") 112 require.Equal(t, "/foo bar.sqlite3?mode=ro", ConnectionString(u)) 113 }) 114 115 t.Run("two slashes with space in path", func(t *testing.T) { 116 // interpreted as absolute path 117 u := dbutil.MustParseURL("sqlite://tmp/foo bar.sqlite3?mode=ro") 118 require.Equal(t, "/tmp/foo bar.sqlite3?mode=ro", ConnectionString(u)) 119 }) 120 121 t.Run("three slashes with space in path", func(t *testing.T) { 122 // interpreted as absolute path 123 u := dbutil.MustParseURL("sqlite:///tmp/foo bar.sqlite3?mode=ro") 124 require.Equal(t, "/tmp/foo bar.sqlite3?mode=ro", ConnectionString(u)) 125 }) 126 127 t.Run("three slashes with space in path (1st dir)", func(t *testing.T) { 128 // interpreted as absolute path 129 u := dbutil.MustParseURL("sqlite:///tm p/foo bar.sqlite3?mode=ro") 130 require.Equal(t, "/tm p/foo bar.sqlite3?mode=ro", ConnectionString(u)) 131 }) 132 133 t.Run("four slashes with space", func(t *testing.T) { 134 // interpreted as absolute path 135 // supported for backwards compatibility 136 u := dbutil.MustParseURL("sqlite:////tmp/foo bar.sqlite3?mode=ro") 137 require.Equal(t, "/tmp/foo bar.sqlite3?mode=ro", ConnectionString(u)) 138 }) 139 } 140 141 func TestSQLiteCreateDropDatabase(t *testing.T) { 142 drv := testSQLiteDriver(t) 143 path := ConnectionString(drv.databaseURL) 144 145 // drop any existing database 146 err := drv.DropDatabase() 147 require.NoError(t, err) 148 149 // create database 150 err = drv.CreateDatabase() 151 require.NoError(t, err) 152 153 // check that database exists 154 _, err = os.Stat(path) 155 require.NoError(t, err) 156 157 // drop the database 158 err = drv.DropDatabase() 159 require.NoError(t, err) 160 161 // check that database no longer exists 162 _, err = os.Stat(path) 163 require.NotNil(t, err) 164 require.Equal(t, true, os.IsNotExist(err)) 165 } 166 167 func TestSQLiteDumpSchema(t *testing.T) { 168 drv := testSQLiteDriver(t) 169 drv.migrationsTableName = "test_migrations" 170 171 // prepare database 172 db := prepTestSQLiteDB(t) 173 defer dbutil.MustClose(db) 174 err := drv.CreateMigrationsTable(db) 175 require.NoError(t, err) 176 177 // insert migration 178 err = drv.InsertMigration(db, "abc1") 179 require.NoError(t, err) 180 err = drv.InsertMigration(db, "abc2") 181 require.NoError(t, err) 182 183 // create a table that will trigger `sqlite_sequence` system table 184 _, err = db.Exec("CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT)") 185 require.NoError(t, err) 186 187 // DumpSchema should return schema 188 schema, err := drv.DumpSchema(db) 189 require.NoError(t, err) 190 require.Contains(t, string(schema), "CREATE TABLE t (id INTEGER PRIMARY KEY AUTOINCREMENT)") 191 require.Contains(t, string(schema), "CREATE TABLE IF NOT EXISTS \"test_migrations\"") 192 require.Contains(t, string(schema), ");\n-- Dbmate schema migrations\n"+ 193 "INSERT INTO \"test_migrations\" (version) VALUES\n"+ 194 " ('abc1'),\n"+ 195 " ('abc2');\n") 196 197 // sqlite_* tables should not be present in the dump (.schema --nosys) 198 require.NotContains(t, string(schema), "sqlite_") 199 200 // DumpSchema should return error if command fails 201 drv.databaseURL = dbutil.MustParseURL(".") 202 schema, err = drv.DumpSchema(db) 203 require.Nil(t, schema) 204 require.Error(t, err) 205 require.EqualError(t, err, "Error: unable to open database \"/.\": unable to open database file") 206 } 207 208 func TestSQLiteDatabaseExists(t *testing.T) { 209 drv := testSQLiteDriver(t) 210 211 // drop any existing database 212 err := drv.DropDatabase() 213 require.NoError(t, err) 214 215 // DatabaseExists should return false 216 exists, err := drv.DatabaseExists() 217 require.NoError(t, err) 218 require.Equal(t, false, exists) 219 220 // create database 221 err = drv.CreateDatabase() 222 require.NoError(t, err) 223 224 // DatabaseExists should return true 225 exists, err = drv.DatabaseExists() 226 require.NoError(t, err) 227 require.Equal(t, true, exists) 228 } 229 230 func TestSQLiteCreateMigrationsTable(t *testing.T) { 231 t.Run("default table", func(t *testing.T) { 232 drv := testSQLiteDriver(t) 233 db := prepTestSQLiteDB(t) 234 defer dbutil.MustClose(db) 235 236 // migrations table should not exist 237 count := 0 238 err := db.QueryRow("select count(*) from schema_migrations").Scan(&count) 239 require.Error(t, err) 240 require.Regexp(t, "no such table: schema_migrations", err.Error()) 241 242 // create table 243 err = drv.CreateMigrationsTable(db) 244 require.NoError(t, err) 245 246 // migrations table should exist 247 err = db.QueryRow("select count(*) from schema_migrations").Scan(&count) 248 require.NoError(t, err) 249 250 // create table should be idempotent 251 err = drv.CreateMigrationsTable(db) 252 require.NoError(t, err) 253 }) 254 255 t.Run("custom table", func(t *testing.T) { 256 drv := testSQLiteDriver(t) 257 drv.migrationsTableName = "test_migrations" 258 259 db := prepTestSQLiteDB(t) 260 defer dbutil.MustClose(db) 261 262 // migrations table should not exist 263 count := 0 264 err := db.QueryRow("select count(*) from test_migrations").Scan(&count) 265 require.Error(t, err) 266 require.Regexp(t, "no such table: test_migrations", err.Error()) 267 268 // create table 269 err = drv.CreateMigrationsTable(db) 270 require.NoError(t, err) 271 272 // migrations table should exist 273 err = db.QueryRow("select count(*) from test_migrations").Scan(&count) 274 require.NoError(t, err) 275 276 // create table should be idempotent 277 err = drv.CreateMigrationsTable(db) 278 require.NoError(t, err) 279 }) 280 } 281 282 func TestSQLiteSelectMigrations(t *testing.T) { 283 drv := testSQLiteDriver(t) 284 drv.migrationsTableName = "test_migrations" 285 286 db := prepTestSQLiteDB(t) 287 defer dbutil.MustClose(db) 288 289 err := drv.CreateMigrationsTable(db) 290 require.NoError(t, err) 291 292 _, err = db.Exec(`insert into test_migrations (version) 293 values ('abc2'), ('abc1'), ('abc3')`) 294 require.NoError(t, err) 295 296 migrations, err := drv.SelectMigrations(db, -1) 297 require.NoError(t, err) 298 require.Equal(t, true, migrations["abc1"]) 299 require.Equal(t, true, migrations["abc2"]) 300 require.Equal(t, true, migrations["abc2"]) 301 302 // test limit param 303 migrations, err = drv.SelectMigrations(db, 1) 304 require.NoError(t, err) 305 require.Equal(t, true, migrations["abc3"]) 306 require.Equal(t, false, migrations["abc1"]) 307 require.Equal(t, false, migrations["abc2"]) 308 } 309 310 func TestSQLiteInsertMigration(t *testing.T) { 311 drv := testSQLiteDriver(t) 312 drv.migrationsTableName = "test_migrations" 313 314 db := prepTestSQLiteDB(t) 315 defer dbutil.MustClose(db) 316 317 err := drv.CreateMigrationsTable(db) 318 require.NoError(t, err) 319 320 count := 0 321 err = db.QueryRow("select count(*) from test_migrations").Scan(&count) 322 require.NoError(t, err) 323 require.Equal(t, 0, count) 324 325 // insert migration 326 err = drv.InsertMigration(db, "abc1") 327 require.NoError(t, err) 328 329 err = db.QueryRow("select count(*) from test_migrations where version = 'abc1'"). 330 Scan(&count) 331 require.NoError(t, err) 332 require.Equal(t, 1, count) 333 } 334 335 func TestSQLiteDeleteMigration(t *testing.T) { 336 drv := testSQLiteDriver(t) 337 drv.migrationsTableName = "test_migrations" 338 339 db := prepTestSQLiteDB(t) 340 defer dbutil.MustClose(db) 341 342 err := drv.CreateMigrationsTable(db) 343 require.NoError(t, err) 344 345 _, err = db.Exec(`insert into test_migrations (version) 346 values ('abc1'), ('abc2')`) 347 require.NoError(t, err) 348 349 err = drv.DeleteMigration(db, "abc2") 350 require.NoError(t, err) 351 352 count := 0 353 err = db.QueryRow("select count(*) from test_migrations").Scan(&count) 354 require.NoError(t, err) 355 require.Equal(t, 1, count) 356 } 357 358 func TestSQLitePing(t *testing.T) { 359 drv := testSQLiteDriver(t) 360 path := ConnectionString(drv.databaseURL) 361 362 // drop any existing database 363 err := drv.DropDatabase() 364 require.NoError(t, err) 365 366 // ping database 367 err = drv.Ping() 368 require.NoError(t, err) 369 370 // check that the database was created (sqlite-only behavior) 371 _, err = os.Stat(path) 372 require.NoError(t, err) 373 374 // drop the database 375 err = drv.DropDatabase() 376 require.NoError(t, err) 377 378 // create directory where database file is expected 379 err = os.Mkdir(path, 0755) 380 require.NoError(t, err) 381 defer func() { 382 err = os.RemoveAll(path) 383 require.NoError(t, err) 384 }() 385 386 // ping database should fail 387 err = drv.Ping() 388 require.EqualError(t, err, "unable to open database file: is a directory") 389 } 390 391 func TestSQLiteQuotedMigrationsTableName(t *testing.T) { 392 t.Run("default name", func(t *testing.T) { 393 drv := testSQLiteDriver(t) 394 name := drv.quotedMigrationsTableName() 395 require.Equal(t, `"schema_migrations"`, name) 396 }) 397 398 t.Run("custom name", func(t *testing.T) { 399 drv := testSQLiteDriver(t) 400 drv.migrationsTableName = "fooMigrations" 401 402 name := drv.quotedMigrationsTableName() 403 require.Equal(t, `"fooMigrations"`, name) 404 }) 405 }