github.com/Elate-DevOps/migrate/v4@v4.0.12/database/redshift/redshift_test.go (about) 1 package redshift 2 3 // error codes https://github.com/lib/pq/blob/master/error.go 4 5 import ( 6 "bytes" 7 "context" 8 "database/sql" 9 sqldriver "database/sql/driver" 10 "fmt" 11 "io" 12 "log" 13 "strconv" 14 "strings" 15 "testing" 16 17 "github.com/Elate-DevOps/migrate/v4" 18 "github.com/Elate-DevOps/migrate/v4/database" 19 "github.com/dhui/dktest" 20 21 dt "github.com/Elate-DevOps/migrate/v4/database/testing" 22 "github.com/Elate-DevOps/migrate/v4/dktesting" 23 24 _ "github.com/Elate-DevOps/migrate/v4/source/file" 25 ) 26 27 var ( 28 opts = dktest.Options{PortRequired: true, ReadyFunc: isReady} 29 specs = []dktesting.ContainerSpec{ 30 {ImageName: "postgres:8", Options: opts}, 31 } 32 ) 33 34 func redshiftConnectionString(host, port string) string { 35 return connectionString("redshift", host, port) 36 } 37 38 func pgConnectionString(host, port string) string { 39 return connectionString("postgres", host, port) 40 } 41 42 func connectionString(schema, host, port string) string { 43 return fmt.Sprintf("%s://postgres@%s:%s/postgres?sslmode=disable", schema, host, port) 44 } 45 46 func isReady(ctx context.Context, c dktest.ContainerInfo) bool { 47 ip, port, err := c.FirstPort() 48 if err != nil { 49 return false 50 } 51 52 db, err := sql.Open("postgres", pgConnectionString(ip, port)) 53 if err != nil { 54 return false 55 } 56 defer func() { 57 if err := db.Close(); err != nil { 58 log.Println("close error:", err) 59 } 60 }() 61 if err = db.PingContext(ctx); err != nil { 62 switch err { 63 case sqldriver.ErrBadConn, io.EOF: 64 return false 65 default: 66 log.Println(err) 67 } 68 return false 69 } 70 71 return true 72 } 73 74 func Test(t *testing.T) { 75 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 76 ip, port, err := c.FirstPort() 77 if err != nil { 78 t.Fatal(err) 79 } 80 81 addr := redshiftConnectionString(ip, port) 82 p := &Redshift{} 83 d, err := p.Open(addr) 84 if err != nil { 85 t.Fatal(err) 86 } 87 defer func() { 88 if err := d.Close(); err != nil { 89 t.Error(err) 90 } 91 }() 92 dt.Test(t, d, []byte("SELECT 1")) 93 }) 94 } 95 96 func TestMigrate(t *testing.T) { 97 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 98 ip, port, err := c.FirstPort() 99 if err != nil { 100 t.Fatal(err) 101 } 102 103 addr := redshiftConnectionString(ip, port) 104 p := &Redshift{} 105 d, err := p.Open(addr) 106 if err != nil { 107 t.Fatal(err) 108 } 109 defer func() { 110 if err := d.Close(); err != nil { 111 t.Error(err) 112 } 113 }() 114 m, err := migrate.NewWithDatabaseInstance("file://./examples/migrations", "postgres", d) 115 if err != nil { 116 t.Fatal(err) 117 } 118 dt.TestMigrate(t, m) 119 }) 120 } 121 122 func TestMultiStatement(t *testing.T) { 123 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 124 ip, port, err := c.FirstPort() 125 if err != nil { 126 t.Fatal(err) 127 } 128 129 addr := redshiftConnectionString(ip, port) 130 p := &Redshift{} 131 d, err := p.Open(addr) 132 if err != nil { 133 t.Fatal(err) 134 } 135 defer func() { 136 if err := d.Close(); err != nil { 137 t.Error(err) 138 } 139 }() 140 if err := d.Run(bytes.NewReader([]byte("CREATE TABLE foo (foo text); CREATE TABLE bar (bar text);"))); err != nil { 141 t.Fatalf("expected err to be nil, got %v", err) 142 } 143 144 // make sure second table exists 145 var exists bool 146 if err := d.(*Redshift).conn.QueryRowContext(context.Background(), "SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'bar' AND table_schema = (SELECT current_schema()))").Scan(&exists); err != nil { 147 t.Fatal(err) 148 } 149 if !exists { 150 t.Fatalf("expected table bar to exist") 151 } 152 }) 153 } 154 155 func TestErrorParsing(t *testing.T) { 156 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 157 ip, port, err := c.FirstPort() 158 if err != nil { 159 t.Fatal(err) 160 } 161 162 addr := redshiftConnectionString(ip, port) 163 p := &Redshift{} 164 d, err := p.Open(addr) 165 if err != nil { 166 t.Fatal(err) 167 } 168 defer func() { 169 if err := d.Close(); err != nil { 170 t.Error(err) 171 } 172 }() 173 174 wantErr := `migration failed: syntax error at or near "TABLEE" (column 37) in line 1: CREATE TABLE foo ` + 175 `(foo text); CREATE TABLEE bar (bar text); (details: pq: syntax error at or near "TABLEE")` 176 if err := d.Run(bytes.NewReader([]byte("CREATE TABLE foo (foo text); CREATE TABLEE bar (bar text);"))); err == nil { 177 t.Fatal("expected err but got nil") 178 } else if err.Error() != wantErr { 179 t.Fatalf("expected '%s' but got '%s'", wantErr, err.Error()) 180 } 181 }) 182 } 183 184 func TestFilterCustomQuery(t *testing.T) { 185 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 186 ip, port, err := c.FirstPort() 187 if err != nil { 188 t.Fatal(err) 189 } 190 191 addr := fmt.Sprintf("postgres://postgres@%v:%v/postgres?sslmode=disable&x-custom=foobar", ip, port) 192 p := &Redshift{} 193 d, err := p.Open(addr) 194 if err != nil { 195 t.Fatal(err) 196 } 197 defer func() { 198 if err := d.Close(); err != nil { 199 t.Error(err) 200 } 201 }() 202 }) 203 } 204 205 func TestWithSchema(t *testing.T) { 206 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 207 ip, port, err := c.FirstPort() 208 if err != nil { 209 t.Fatal(err) 210 } 211 212 addr := redshiftConnectionString(ip, port) 213 p := &Redshift{} 214 d, err := p.Open(addr) 215 if err != nil { 216 t.Fatal(err) 217 } 218 defer func() { 219 if err := d.Close(); err != nil { 220 t.Error(err) 221 } 222 }() 223 224 // create foobar schema 225 if err := d.Run(bytes.NewReader([]byte("CREATE SCHEMA foobar AUTHORIZATION postgres"))); err != nil { 226 t.Fatal(err) 227 } 228 if err := d.SetVersion(1, false); err != nil { 229 t.Fatal(err) 230 } 231 232 // re-connect using that schema 233 d2, err := p.Open(fmt.Sprintf("postgres://postgres@%v:%v/postgres?sslmode=disable&search_path=foobar", ip, port)) 234 if err != nil { 235 t.Fatal(err) 236 } 237 defer func() { 238 if err := d2.Close(); err != nil { 239 t.Error(err) 240 } 241 }() 242 243 version, _, err := d2.Version() 244 if err != nil { 245 t.Fatal(err) 246 } 247 if version != database.NilVersion { 248 t.Fatal("expected NilVersion") 249 } 250 251 // now update version and compare 252 if err := d2.SetVersion(2, false); err != nil { 253 t.Fatal(err) 254 } 255 version, _, err = d2.Version() 256 if err != nil { 257 t.Fatal(err) 258 } 259 if version != 2 { 260 t.Fatal("expected version 2") 261 } 262 263 // meanwhile, the public schema still has the other version 264 version, _, err = d.Version() 265 if err != nil { 266 t.Fatal(err) 267 } 268 if version != 1 { 269 t.Fatal("expected version 2") 270 } 271 }) 272 } 273 274 func TestWithInstance(t *testing.T) { 275 } 276 277 func TestRedshift_Lock(t *testing.T) { 278 dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) { 279 ip, port, err := c.FirstPort() 280 if err != nil { 281 t.Fatal(err) 282 } 283 284 addr := pgConnectionString(ip, port) 285 p := &Redshift{} 286 d, err := p.Open(addr) 287 if err != nil { 288 t.Fatal(err) 289 } 290 291 dt.Test(t, d, []byte("SELECT 1")) 292 293 ps := d.(*Redshift) 294 295 err = ps.Lock() 296 if err != nil { 297 t.Fatal(err) 298 } 299 300 err = ps.Unlock() 301 if err != nil { 302 t.Fatal(err) 303 } 304 305 err = ps.Lock() 306 if err != nil { 307 t.Fatal(err) 308 } 309 310 err = ps.Unlock() 311 if err != nil { 312 t.Fatal(err) 313 } 314 }) 315 } 316 317 func Test_computeLineFromPos(t *testing.T) { 318 testcases := []struct { 319 pos int 320 wantLine uint 321 wantCol uint 322 input string 323 wantOk bool 324 }{ 325 { 326 15, 2, 6, "SELECT *\nFROM foo", true, // foo table does not exists 327 }, 328 { 329 16, 3, 6, "SELECT *\n\nFROM foo", true, // foo table does not exists, empty line 330 }, 331 { 332 25, 3, 7, "SELECT *\nFROM foo\nWHERE x", true, // x column error 333 }, 334 { 335 27, 5, 7, "SELECT *\n\nFROM foo\n\nWHERE x", true, // x column error, empty lines 336 }, 337 { 338 10, 2, 1, "SELECT *\nFROMM foo", true, // FROMM typo 339 }, 340 { 341 11, 3, 1, "SELECT *\n\nFROMM foo", true, // FROMM typo, empty line 342 }, 343 { 344 17, 2, 8, "SELECT *\nFROM foo", true, // last character 345 }, 346 { 347 18, 0, 0, "SELECT *\nFROM foo", false, // invalid position 348 }, 349 } 350 for i, tc := range testcases { 351 t.Run("tc"+strconv.Itoa(i), func(t *testing.T) { 352 run := func(crlf bool, nonASCII bool) { 353 var name string 354 if crlf { 355 name = "crlf" 356 } else { 357 name = "lf" 358 } 359 if nonASCII { 360 name += "-nonascii" 361 } else { 362 name += "-ascii" 363 } 364 t.Run(name, func(t *testing.T) { 365 input := tc.input 366 if crlf { 367 input = strings.Replace(input, "\n", "\r\n", -1) 368 } 369 if nonASCII { 370 input = strings.Replace(input, "FROM", "FRÖM", -1) 371 } 372 gotLine, gotCol, gotOK := computeLineFromPos(input, tc.pos) 373 374 if tc.wantOk { 375 t.Logf("pos %d, want %d:%d, %#v", tc.pos, tc.wantLine, tc.wantCol, input) 376 } 377 378 if gotOK != tc.wantOk { 379 t.Fatalf("expected ok %v but got %v", tc.wantOk, gotOK) 380 } 381 if gotLine != tc.wantLine { 382 t.Fatalf("expected line %d but got %d", tc.wantLine, gotLine) 383 } 384 if gotCol != tc.wantCol { 385 t.Fatalf("expected col %d but got %d", tc.wantCol, gotCol) 386 } 387 }) 388 } 389 run(false, false) 390 run(true, false) 391 run(false, true) 392 run(true, true) 393 }) 394 } 395 }