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 }