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