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