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  }