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  }