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