github.com/amacneil/dbmate@v1.16.3-0.20230225174651-ca89b10d75d7/pkg/driver/clickhouse/clickhouse_test.go (about)

     1  package clickhouse
     2  
     3  import (
     4  	"database/sql"
     5  	"net/url"
     6  	"os"
     7  	"testing"
     8  
     9  	"github.com/amacneil/dbmate/pkg/dbmate"
    10  	"github.com/amacneil/dbmate/pkg/dbutil"
    11  
    12  	"github.com/stretchr/testify/require"
    13  )
    14  
    15  func testClickHouseDriver(t *testing.T) *Driver {
    16  	u := dbutil.MustParseURL(os.Getenv("CLICKHOUSE_TEST_URL"))
    17  	drv, err := dbmate.New(u).Driver()
    18  	require.NoError(t, err)
    19  
    20  	return drv.(*Driver)
    21  }
    22  
    23  func prepTestClickHouseDB(t *testing.T) *sql.DB {
    24  	drv := testClickHouseDriver(t)
    25  
    26  	// drop any existing database
    27  	err := drv.DropDatabase()
    28  	require.NoError(t, err)
    29  
    30  	// create database
    31  	err = drv.CreateDatabase()
    32  	require.NoError(t, err)
    33  
    34  	// connect database
    35  	db, err := sql.Open("clickhouse", drv.databaseURL.String())
    36  	require.NoError(t, err)
    37  
    38  	return db
    39  }
    40  
    41  func TestGetDriver(t *testing.T) {
    42  	db := dbmate.New(dbutil.MustParseURL("clickhouse://"))
    43  	drvInterface, err := db.Driver()
    44  	require.NoError(t, err)
    45  
    46  	// driver should have URL and default migrations table set
    47  	drv, ok := drvInterface.(*Driver)
    48  	require.True(t, ok)
    49  	require.Equal(t, db.DatabaseURL.String(), drv.databaseURL.String())
    50  	require.Equal(t, "schema_migrations", drv.migrationsTableName)
    51  }
    52  
    53  func TestConnectionString(t *testing.T) {
    54  	t.Run("simple", func(t *testing.T) {
    55  		u, err := url.Parse("clickhouse://user:pass@host/db")
    56  		require.NoError(t, err)
    57  
    58  		s := connectionString(u)
    59  		require.Equal(t, "tcp://host:9000?database=db&password=pass&username=user", s)
    60  	})
    61  
    62  	t.Run("canonical", func(t *testing.T) {
    63  		u, err := url.Parse("clickhouse://host:9000?database=db&password=pass&username=user")
    64  		require.NoError(t, err)
    65  
    66  		s := connectionString(u)
    67  		require.Equal(t, "tcp://host:9000?database=db&password=pass&username=user", s)
    68  	})
    69  }
    70  
    71  func TestClickHouseCreateDropDatabase(t *testing.T) {
    72  	drv := testClickHouseDriver(t)
    73  
    74  	// drop any existing database
    75  	err := drv.DropDatabase()
    76  	require.NoError(t, err)
    77  
    78  	// create database
    79  	err = drv.CreateDatabase()
    80  	require.NoError(t, err)
    81  
    82  	// check that database exists and we can connect to it
    83  	func() {
    84  		db, err := sql.Open("clickhouse", drv.databaseURL.String())
    85  		require.NoError(t, err)
    86  		defer dbutil.MustClose(db)
    87  
    88  		err = db.Ping()
    89  		require.NoError(t, err)
    90  	}()
    91  
    92  	// drop the database
    93  	err = drv.DropDatabase()
    94  	require.NoError(t, err)
    95  
    96  	// check that database no longer exists
    97  	func() {
    98  		db, err := sql.Open("clickhouse", drv.databaseURL.String())
    99  		require.NoError(t, err)
   100  		defer dbutil.MustClose(db)
   101  
   102  		err = db.Ping()
   103  		require.EqualError(t, err, "code: 81, message: Database dbmate_test doesn't exist")
   104  	}()
   105  }
   106  
   107  func TestClickHouseDumpSchema(t *testing.T) {
   108  	drv := testClickHouseDriver(t)
   109  	drv.migrationsTableName = "test_migrations"
   110  
   111  	// prepare database
   112  	db := prepTestClickHouseDB(t)
   113  	defer dbutil.MustClose(db)
   114  	err := drv.CreateMigrationsTable(db)
   115  	require.NoError(t, err)
   116  
   117  	// insert migration
   118  	tx, err := db.Begin()
   119  	require.NoError(t, err)
   120  	err = drv.InsertMigration(tx, "abc1")
   121  	require.NoError(t, err)
   122  	err = tx.Commit()
   123  	require.NoError(t, err)
   124  	tx, err = db.Begin()
   125  	require.NoError(t, err)
   126  	err = drv.InsertMigration(tx, "abc2")
   127  	require.NoError(t, err)
   128  	err = tx.Commit()
   129  	require.NoError(t, err)
   130  
   131  	// DumpSchema should return schema
   132  	schema, err := drv.DumpSchema(db)
   133  	require.NoError(t, err)
   134  	require.Contains(t, string(schema), "CREATE TABLE "+drv.databaseName()+".test_migrations")
   135  	require.Contains(t, string(schema), "--\n"+
   136  		"-- Dbmate schema migrations\n"+
   137  		"--\n\n"+
   138  		"INSERT INTO test_migrations (version) VALUES\n"+
   139  		"    ('abc1'),\n"+
   140  		"    ('abc2');\n")
   141  
   142  	// DumpSchema should return error if command fails
   143  	values := drv.databaseURL.Query()
   144  	values.Set("database", "fakedb")
   145  	drv.databaseURL.RawQuery = values.Encode()
   146  	db, err = sql.Open("clickhouse", drv.databaseURL.String())
   147  	require.NoError(t, err)
   148  
   149  	schema, err = drv.DumpSchema(db)
   150  	require.Nil(t, schema)
   151  	require.EqualError(t, err, "code: 81, message: Database fakedb doesn't exist")
   152  }
   153  
   154  func TestClickHouseDatabaseExists(t *testing.T) {
   155  	drv := testClickHouseDriver(t)
   156  
   157  	// drop any existing database
   158  	err := drv.DropDatabase()
   159  	require.NoError(t, err)
   160  
   161  	// DatabaseExists should return false
   162  	exists, err := drv.DatabaseExists()
   163  	require.NoError(t, err)
   164  	require.Equal(t, false, exists)
   165  
   166  	// create database
   167  	err = drv.CreateDatabase()
   168  	require.NoError(t, err)
   169  
   170  	// DatabaseExists should return true
   171  	exists, err = drv.DatabaseExists()
   172  	require.NoError(t, err)
   173  	require.Equal(t, true, exists)
   174  }
   175  
   176  func TestClickHouseDatabaseExists_Error(t *testing.T) {
   177  	drv := testClickHouseDriver(t)
   178  	values := drv.databaseURL.Query()
   179  	values.Set("username", "invalid")
   180  	drv.databaseURL.RawQuery = values.Encode()
   181  
   182  	exists, err := drv.DatabaseExists()
   183  	require.EqualError(
   184  		t,
   185  		err,
   186  		"code: 516, message: invalid: Authentication failed: password is incorrect or there is no user with such name",
   187  	)
   188  	require.Equal(t, false, exists)
   189  }
   190  
   191  func TestClickHouseCreateMigrationsTable(t *testing.T) {
   192  	t.Run("default table", func(t *testing.T) {
   193  		drv := testClickHouseDriver(t)
   194  		db := prepTestClickHouseDB(t)
   195  		defer dbutil.MustClose(db)
   196  
   197  		// migrations table should not exist
   198  		count := 0
   199  		err := db.QueryRow("select count(*) from schema_migrations").Scan(&count)
   200  		require.EqualError(
   201  			t,
   202  			err,
   203  			"code: 60, message: Table dbmate_test.schema_migrations doesn't exist",
   204  		)
   205  
   206  		// use driver function to check the same as above
   207  		exists, err := drv.MigrationsTableExists(db)
   208  		require.NoError(t, err)
   209  		require.Equal(t, false, exists)
   210  
   211  		// create table
   212  		err = drv.CreateMigrationsTable(db)
   213  		require.NoError(t, err)
   214  
   215  		// migrations table should exist
   216  		err = db.QueryRow("select count(*) from schema_migrations").Scan(&count)
   217  		require.NoError(t, err)
   218  
   219  		// use driver function to check the same as above
   220  		exists, err = drv.MigrationsTableExists(db)
   221  		require.NoError(t, err)
   222  		require.Equal(t, true, exists)
   223  
   224  		// create table should be idempotent
   225  		err = drv.CreateMigrationsTable(db)
   226  		require.NoError(t, err)
   227  	})
   228  
   229  	t.Run("custom table", func(t *testing.T) {
   230  		drv := testClickHouseDriver(t)
   231  		drv.migrationsTableName = "testMigrations"
   232  
   233  		db := prepTestClickHouseDB(t)
   234  		defer dbutil.MustClose(db)
   235  
   236  		// migrations table should not exist
   237  		count := 0
   238  		err := db.QueryRow("select count(*) from \"testMigrations\"").Scan(&count)
   239  		require.EqualError(
   240  			t,
   241  			err,
   242  			"code: 60, message: Table dbmate_test.testMigrations doesn't exist",
   243  		)
   244  
   245  		// use driver function to check the same as above
   246  		exists, err := drv.MigrationsTableExists(db)
   247  		require.NoError(t, err)
   248  		require.Equal(t, false, exists)
   249  
   250  		// create table
   251  		err = drv.CreateMigrationsTable(db)
   252  		require.NoError(t, err)
   253  
   254  		// migrations table should exist
   255  		err = db.QueryRow("select count(*) from \"testMigrations\"").Scan(&count)
   256  		require.NoError(t, err)
   257  
   258  		// use driver function to check the same as above
   259  		exists, err = drv.MigrationsTableExists(db)
   260  		require.NoError(t, err)
   261  		require.Equal(t, true, exists)
   262  
   263  		// create table should be idempotent
   264  		err = drv.CreateMigrationsTable(db)
   265  		require.NoError(t, err)
   266  	})
   267  }
   268  
   269  func TestClickHouseSelectMigrations(t *testing.T) {
   270  	drv := testClickHouseDriver(t)
   271  	drv.migrationsTableName = "test_migrations"
   272  
   273  	db := prepTestClickHouseDB(t)
   274  	defer dbutil.MustClose(db)
   275  
   276  	err := drv.CreateMigrationsTable(db)
   277  	require.NoError(t, err)
   278  
   279  	tx, err := db.Begin()
   280  	require.NoError(t, err)
   281  	stmt, err := tx.Prepare("insert into test_migrations (version) values (?)")
   282  	require.NoError(t, err)
   283  	_, err = stmt.Exec("abc2")
   284  	require.NoError(t, err)
   285  	_, err = stmt.Exec("abc1")
   286  	require.NoError(t, err)
   287  	_, err = stmt.Exec("abc3")
   288  	require.NoError(t, err)
   289  	err = tx.Commit()
   290  	require.NoError(t, err)
   291  
   292  	migrations, err := drv.SelectMigrations(db, -1)
   293  	require.NoError(t, err)
   294  	require.Equal(t, true, migrations["abc1"])
   295  	require.Equal(t, true, migrations["abc2"])
   296  	require.Equal(t, true, migrations["abc2"])
   297  
   298  	// test limit param
   299  	migrations, err = drv.SelectMigrations(db, 1)
   300  	require.NoError(t, err)
   301  	require.Equal(t, true, migrations["abc3"])
   302  	require.Equal(t, false, migrations["abc1"])
   303  	require.Equal(t, false, migrations["abc2"])
   304  }
   305  
   306  func TestClickHouseInsertMigration(t *testing.T) {
   307  	drv := testClickHouseDriver(t)
   308  	drv.migrationsTableName = "test_migrations"
   309  
   310  	db := prepTestClickHouseDB(t)
   311  	defer dbutil.MustClose(db)
   312  
   313  	err := drv.CreateMigrationsTable(db)
   314  	require.NoError(t, err)
   315  
   316  	count := 0
   317  	err = db.QueryRow("select count(*) from test_migrations").Scan(&count)
   318  	require.NoError(t, err)
   319  	require.Equal(t, 0, count)
   320  
   321  	// insert migration
   322  	tx, err := db.Begin()
   323  	require.NoError(t, err)
   324  	err = drv.InsertMigration(tx, "abc1")
   325  	require.NoError(t, err)
   326  	err = tx.Commit()
   327  	require.NoError(t, err)
   328  
   329  	err = db.QueryRow("select count(*) from test_migrations where version = 'abc1'").Scan(&count)
   330  	require.NoError(t, err)
   331  	require.Equal(t, 1, count)
   332  }
   333  
   334  func TestClickHouseDeleteMigration(t *testing.T) {
   335  	drv := testClickHouseDriver(t)
   336  	drv.migrationsTableName = "test_migrations"
   337  
   338  	db := prepTestClickHouseDB(t)
   339  	defer dbutil.MustClose(db)
   340  
   341  	err := drv.CreateMigrationsTable(db)
   342  	require.NoError(t, err)
   343  
   344  	tx, err := db.Begin()
   345  	require.NoError(t, err)
   346  	stmt, err := tx.Prepare("insert into test_migrations (version) values (?)")
   347  	require.NoError(t, err)
   348  	_, err = stmt.Exec("abc2")
   349  	require.NoError(t, err)
   350  	_, err = stmt.Exec("abc1")
   351  	require.NoError(t, err)
   352  	err = tx.Commit()
   353  	require.NoError(t, err)
   354  
   355  	tx, err = db.Begin()
   356  	require.NoError(t, err)
   357  	err = drv.DeleteMigration(tx, "abc2")
   358  	require.NoError(t, err)
   359  	err = tx.Commit()
   360  	require.NoError(t, err)
   361  
   362  	count := 0
   363  	err = db.QueryRow("select count(*) from test_migrations final where applied").Scan(&count)
   364  	require.NoError(t, err)
   365  	require.Equal(t, 1, count)
   366  }
   367  
   368  func TestClickHousePing(t *testing.T) {
   369  	drv := testClickHouseDriver(t)
   370  
   371  	// drop any existing database
   372  	err := drv.DropDatabase()
   373  	require.NoError(t, err)
   374  
   375  	// ping database
   376  	err = drv.Ping()
   377  	require.NoError(t, err)
   378  
   379  	// ping invalid host should return error
   380  	drv.databaseURL.Host = "clickhouse:404"
   381  	err = drv.Ping()
   382  	require.Error(t, err)
   383  	require.Contains(t, err.Error(), "connect: connection refused")
   384  }
   385  
   386  func TestClickHouseQuotedMigrationsTableName(t *testing.T) {
   387  	t.Run("default name", func(t *testing.T) {
   388  		drv := testClickHouseDriver(t)
   389  		name := drv.quotedMigrationsTableName()
   390  		require.Equal(t, "schema_migrations", name)
   391  	})
   392  
   393  	t.Run("custom name", func(t *testing.T) {
   394  		drv := testClickHouseDriver(t)
   395  		drv.migrationsTableName = "fooMigrations"
   396  
   397  		name := drv.quotedMigrationsTableName()
   398  		require.Equal(t, "fooMigrations", name)
   399  	})
   400  
   401  	t.Run("quoted name", func(t *testing.T) {
   402  		drv := testClickHouseDriver(t)
   403  		drv.migrationsTableName = "bizarre\"$name"
   404  
   405  		name := drv.quotedMigrationsTableName()
   406  		require.Equal(t, `"bizarre""$name"`, name)
   407  	})
   408  }