github.com/Elate-DevOps/migrate/v4@v4.0.12/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  	"errors"
    10  	"fmt"
    11  	"io"
    12  	"log"
    13  	"strconv"
    14  	"strings"
    15  	"sync"
    16  	"testing"
    17  
    18  	"github.com/Elate-DevOps/migrate/v4"
    19  
    20  	"github.com/dhui/dktest"
    21  
    22  	"github.com/Elate-DevOps/migrate/v4/database"
    23  	dt "github.com/Elate-DevOps/migrate/v4/database/testing"
    24  	"github.com/Elate-DevOps/migrate/v4/dktesting"
    25  	_ "github.com/Elate-DevOps/migrate/v4/source/file"
    26  )
    27  
    28  const (
    29  	pgPassword = "postgres"
    30  )
    31  
    32  var (
    33  	opts = dktest.Options{
    34  		Env:          map[string]string{"POSTGRES_PASSWORD": pgPassword},
    35  		PortRequired: true, ReadyFunc: isReady,
    36  	}
    37  	// Supported versions: https://www.postgresql.org/support/versioning/
    38  	specs = []dktesting.ContainerSpec{
    39  		{ImageName: "postgres:9.5", Options: opts},
    40  		{ImageName: "postgres:9.6", Options: opts},
    41  		{ImageName: "postgres:10", Options: opts},
    42  		{ImageName: "postgres:11", Options: opts},
    43  		{ImageName: "postgres:12", Options: opts},
    44  	}
    45  )
    46  
    47  func pgConnectionString(host, port string, options ...string) string {
    48  	options = append(options, "sslmode=disable")
    49  	return fmt.Sprintf("postgres://postgres:%s@%s:%s/postgres?%s", pgPassword, host, port, strings.Join(options, "&"))
    50  }
    51  
    52  func isReady(ctx context.Context, c dktest.ContainerInfo) bool {
    53  	ip, port, err := c.FirstPort()
    54  	if err != nil {
    55  		return false
    56  	}
    57  
    58  	db, err := sql.Open("postgres", pgConnectionString(ip, port))
    59  	if err != nil {
    60  		return false
    61  	}
    62  	defer func() {
    63  		if err := db.Close(); err != nil {
    64  			log.Println("close error:", err)
    65  		}
    66  	}()
    67  	if err = db.PingContext(ctx); err != nil {
    68  		switch err {
    69  		case sqldriver.ErrBadConn, io.EOF:
    70  			return false
    71  		default:
    72  			log.Println(err)
    73  		}
    74  		return false
    75  	}
    76  
    77  	return true
    78  }
    79  
    80  func mustRun(t *testing.T, d database.Driver, statements []string) {
    81  	for _, statement := range statements {
    82  		if err := d.Run(strings.NewReader(statement)); err != nil {
    83  			t.Fatal(err)
    84  		}
    85  	}
    86  }
    87  
    88  func Test(t *testing.T) {
    89  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
    90  		ip, port, err := c.FirstPort()
    91  		if err != nil {
    92  			t.Fatal(err)
    93  		}
    94  
    95  		addr := pgConnectionString(ip, port)
    96  		p := &Postgres{}
    97  		d, err := p.Open(addr)
    98  		if err != nil {
    99  			t.Fatal(err)
   100  		}
   101  		defer func() {
   102  			if err := d.Close(); err != nil {
   103  				t.Error(err)
   104  			}
   105  		}()
   106  		dt.Test(t, d, []byte("SELECT 1"))
   107  	})
   108  }
   109  
   110  func TestMigrate(t *testing.T) {
   111  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
   112  		ip, port, err := c.FirstPort()
   113  		if err != nil {
   114  			t.Fatal(err)
   115  		}
   116  
   117  		addr := pgConnectionString(ip, port)
   118  		p := &Postgres{}
   119  		d, err := p.Open(addr)
   120  		if err != nil {
   121  			t.Fatal(err)
   122  		}
   123  		defer func() {
   124  			if err := d.Close(); err != nil {
   125  				t.Error(err)
   126  			}
   127  		}()
   128  		m, err := migrate.NewWithDatabaseInstance("file://./examples/migrations", "postgres", d)
   129  		if err != nil {
   130  			t.Fatal(err)
   131  		}
   132  		dt.TestMigrate(t, m)
   133  	})
   134  }
   135  
   136  func TestMultipleStatements(t *testing.T) {
   137  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
   138  		ip, port, err := c.FirstPort()
   139  		if err != nil {
   140  			t.Fatal(err)
   141  		}
   142  
   143  		addr := pgConnectionString(ip, port)
   144  		p := &Postgres{}
   145  		d, err := p.Open(addr)
   146  		if err != nil {
   147  			t.Fatal(err)
   148  		}
   149  		defer func() {
   150  			if err := d.Close(); err != nil {
   151  				t.Error(err)
   152  			}
   153  		}()
   154  		if err := d.Run(strings.NewReader("CREATE TABLE foo (foo text); CREATE TABLE bar (bar text);")); err != nil {
   155  			t.Fatalf("expected err to be nil, got %v", err)
   156  		}
   157  
   158  		// make sure second table exists
   159  		var exists bool
   160  		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 {
   161  			t.Fatal(err)
   162  		}
   163  		if !exists {
   164  			t.Fatalf("expected table bar to exist")
   165  		}
   166  	})
   167  }
   168  
   169  func TestMultipleStatementsInMultiStatementMode(t *testing.T) {
   170  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
   171  		ip, port, err := c.FirstPort()
   172  		if err != nil {
   173  			t.Fatal(err)
   174  		}
   175  
   176  		addr := pgConnectionString(ip, port, "x-multi-statement=true")
   177  		p := &Postgres{}
   178  		d, err := p.Open(addr)
   179  		if err != nil {
   180  			t.Fatal(err)
   181  		}
   182  		defer func() {
   183  			if err := d.Close(); err != nil {
   184  				t.Error(err)
   185  			}
   186  		}()
   187  		if err := d.Run(strings.NewReader("CREATE TABLE foo (foo text); CREATE INDEX CONCURRENTLY idx_foo ON foo (foo);")); err != nil {
   188  			t.Fatalf("expected err to be nil, got %v", err)
   189  		}
   190  
   191  		// make sure created index exists
   192  		var exists bool
   193  		if err := d.(*Postgres).conn.QueryRowContext(context.Background(), "SELECT EXISTS (SELECT 1 FROM pg_indexes WHERE schemaname = (SELECT current_schema()) AND indexname = 'idx_foo')").Scan(&exists); err != nil {
   194  			t.Fatal(err)
   195  		}
   196  		if !exists {
   197  			t.Fatalf("expected table bar to exist")
   198  		}
   199  	})
   200  }
   201  
   202  func TestErrorParsing(t *testing.T) {
   203  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
   204  		ip, port, err := c.FirstPort()
   205  		if err != nil {
   206  			t.Fatal(err)
   207  		}
   208  
   209  		addr := pgConnectionString(ip, port)
   210  		p := &Postgres{}
   211  		d, err := p.Open(addr)
   212  		if err != nil {
   213  			t.Fatal(err)
   214  		}
   215  		defer func() {
   216  			if err := d.Close(); err != nil {
   217  				t.Error(err)
   218  			}
   219  		}()
   220  
   221  		wantErr := `migration failed: syntax error at or near "TABLEE" (column 37) in line 1: CREATE TABLE foo ` +
   222  			`(foo text); CREATE TABLEE bar (bar text); (details: pq: syntax error at or near "TABLEE")`
   223  		if err := d.Run(strings.NewReader("CREATE TABLE foo (foo text); CREATE TABLEE bar (bar text);")); err == nil {
   224  			t.Fatal("expected err but got nil")
   225  		} else if err.Error() != wantErr {
   226  			t.Fatalf("expected '%s' but got '%s'", wantErr, err.Error())
   227  		}
   228  	})
   229  }
   230  
   231  func TestFilterCustomQuery(t *testing.T) {
   232  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
   233  		ip, port, err := c.FirstPort()
   234  		if err != nil {
   235  			t.Fatal(err)
   236  		}
   237  
   238  		addr := fmt.Sprintf("postgres://postgres:%s@%v:%v/postgres?sslmode=disable&x-custom=foobar",
   239  			pgPassword, ip, port)
   240  		p := &Postgres{}
   241  		d, err := p.Open(addr)
   242  		if err != nil {
   243  			t.Fatal(err)
   244  		}
   245  		defer func() {
   246  			if err := d.Close(); err != nil {
   247  				t.Error(err)
   248  			}
   249  		}()
   250  	})
   251  }
   252  
   253  func TestWithSchema(t *testing.T) {
   254  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
   255  		ip, port, err := c.FirstPort()
   256  		if err != nil {
   257  			t.Fatal(err)
   258  		}
   259  
   260  		addr := pgConnectionString(ip, port)
   261  		p := &Postgres{}
   262  		d, err := p.Open(addr)
   263  		if err != nil {
   264  			t.Fatal(err)
   265  		}
   266  		defer func() {
   267  			if err := d.Close(); err != nil {
   268  				t.Fatal(err)
   269  			}
   270  		}()
   271  
   272  		// create foobar schema
   273  		if err := d.Run(strings.NewReader("CREATE SCHEMA foobar AUTHORIZATION postgres")); err != nil {
   274  			t.Fatal(err)
   275  		}
   276  		if err := d.SetVersion(1, false); err != nil {
   277  			t.Fatal(err)
   278  		}
   279  
   280  		// re-connect using that schema
   281  		d2, err := p.Open(fmt.Sprintf("postgres://postgres:%s@%v:%v/postgres?sslmode=disable&search_path=foobar",
   282  			pgPassword, ip, port))
   283  		if err != nil {
   284  			t.Fatal(err)
   285  		}
   286  		defer func() {
   287  			if err := d2.Close(); err != nil {
   288  				t.Fatal(err)
   289  			}
   290  		}()
   291  
   292  		version, _, err := d2.Version()
   293  		if err != nil {
   294  			t.Fatal(err)
   295  		}
   296  		if version != database.NilVersion {
   297  			t.Fatal("expected NilVersion")
   298  		}
   299  
   300  		// now update version and compare
   301  		if err := d2.SetVersion(2, false); err != nil {
   302  			t.Fatal(err)
   303  		}
   304  		version, _, err = d2.Version()
   305  		if err != nil {
   306  			t.Fatal(err)
   307  		}
   308  		if version != 2 {
   309  			t.Fatal("expected version 2")
   310  		}
   311  
   312  		// meanwhile, the public schema still has the other version
   313  		version, _, err = d.Version()
   314  		if err != nil {
   315  			t.Fatal(err)
   316  		}
   317  		if version != 1 {
   318  			t.Fatal("expected version 2")
   319  		}
   320  	})
   321  }
   322  
   323  func TestMigrationTableOption(t *testing.T) {
   324  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
   325  		ip, port, err := c.FirstPort()
   326  		if err != nil {
   327  			t.Fatal(err)
   328  		}
   329  
   330  		addr := pgConnectionString(ip, port)
   331  		p := &Postgres{}
   332  		d, _ := p.Open(addr)
   333  		defer func() {
   334  			if err := d.Close(); err != nil {
   335  				t.Fatal(err)
   336  			}
   337  		}()
   338  
   339  		// create migrate schema
   340  		if err := d.Run(strings.NewReader("CREATE SCHEMA migrate AUTHORIZATION postgres")); err != nil {
   341  			t.Fatal(err)
   342  		}
   343  
   344  		// bad unquoted x-migrations-table parameter
   345  		wantErr := "x-migrations-table must be quoted (for instance '\"migrate\".\"schema_migrations\"') when x-migrations-table-quoted is enabled, current value is: migrate.schema_migrations"
   346  		d, err = p.Open(fmt.Sprintf("postgres://postgres:%s@%v:%v/postgres?sslmode=disable&x-migrations-table=migrate.schema_migrations&x-migrations-table-quoted=1",
   347  			pgPassword, ip, port))
   348  		if (err != nil) && (err.Error() != wantErr) {
   349  			t.Fatalf("expected '%s' but got '%s'", wantErr, err.Error())
   350  		}
   351  
   352  		// too many quoted x-migrations-table parameters
   353  		wantErr = "\"\"migrate\".\"schema_migrations\".\"toomany\"\" MigrationsTable contains too many dot characters"
   354  		d, err = p.Open(fmt.Sprintf("postgres://postgres:%s@%v:%v/postgres?sslmode=disable&x-migrations-table=\"migrate\".\"schema_migrations\".\"toomany\"&x-migrations-table-quoted=1",
   355  			pgPassword, ip, port))
   356  		if (err != nil) && (err.Error() != wantErr) {
   357  			t.Fatalf("expected '%s' but got '%s'", wantErr, err.Error())
   358  		}
   359  
   360  		// good quoted x-migrations-table parameter
   361  		d, err = p.Open(fmt.Sprintf("postgres://postgres:%s@%v:%v/postgres?sslmode=disable&x-migrations-table=\"migrate\".\"schema_migrations\"&x-migrations-table-quoted=1",
   362  			pgPassword, ip, port))
   363  		if err != nil {
   364  			t.Fatal(err)
   365  		}
   366  
   367  		// make sure migrate.schema_migrations table exists
   368  		var exists bool
   369  		if err := d.(*Postgres).conn.QueryRowContext(context.Background(), "SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'schema_migrations' AND table_schema = 'migrate')").Scan(&exists); err != nil {
   370  			t.Fatal(err)
   371  		}
   372  		if !exists {
   373  			t.Fatalf("expected table migrate.schema_migrations to exist")
   374  		}
   375  
   376  		d, err = p.Open(fmt.Sprintf("postgres://postgres:%s@%v:%v/postgres?sslmode=disable&x-migrations-table=migrate.schema_migrations",
   377  			pgPassword, ip, port))
   378  		if err != nil {
   379  			t.Fatal(err)
   380  		}
   381  		if err := d.(*Postgres).conn.QueryRowContext(context.Background(), "SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'migrate.schema_migrations' AND table_schema = (SELECT current_schema()))").Scan(&exists); err != nil {
   382  			t.Fatal(err)
   383  		}
   384  		if !exists {
   385  			t.Fatalf("expected table 'migrate.schema_migrations' to exist")
   386  		}
   387  	})
   388  }
   389  
   390  func TestFailToCreateTableWithoutPermissions(t *testing.T) {
   391  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
   392  		ip, port, err := c.FirstPort()
   393  		if err != nil {
   394  			t.Fatal(err)
   395  		}
   396  
   397  		addr := pgConnectionString(ip, port)
   398  
   399  		// Check that opening the postgres connection returns NilVersion
   400  		p := &Postgres{}
   401  
   402  		d, err := p.Open(addr)
   403  		if err != nil {
   404  			t.Fatal(err)
   405  		}
   406  
   407  		defer func() {
   408  			if err := d.Close(); err != nil {
   409  				t.Error(err)
   410  			}
   411  		}()
   412  
   413  		// create user who is not the owner. Although we're concatenating strings in an sql statement it should be fine
   414  		// since this is a test environment and we're not expecting to the pgPassword to be malicious
   415  		mustRun(t, d, []string{
   416  			"CREATE USER not_owner WITH ENCRYPTED PASSWORD '" + pgPassword + "'",
   417  			"CREATE SCHEMA barfoo AUTHORIZATION postgres",
   418  			"GRANT USAGE ON SCHEMA barfoo TO not_owner",
   419  			"REVOKE CREATE ON SCHEMA barfoo FROM PUBLIC",
   420  			"REVOKE CREATE ON SCHEMA barfoo FROM not_owner",
   421  		})
   422  
   423  		// re-connect using that schema
   424  		d2, err := p.Open(fmt.Sprintf("postgres://not_owner:%s@%v:%v/postgres?sslmode=disable&search_path=barfoo",
   425  			pgPassword, ip, port))
   426  
   427  		defer func() {
   428  			if d2 == nil {
   429  				return
   430  			}
   431  			if err := d2.Close(); err != nil {
   432  				t.Fatal(err)
   433  			}
   434  		}()
   435  
   436  		var e *database.Error
   437  		if !errors.As(err, &e) || err == nil {
   438  			t.Fatal("Unexpected error, want permission denied error. Got: ", err)
   439  		}
   440  
   441  		if !strings.Contains(e.OrigErr.Error(), "permission denied for schema barfoo") {
   442  			t.Fatal(e)
   443  		}
   444  
   445  		// re-connect using that x-migrations-table and x-migrations-table-quoted
   446  		d2, err = p.Open(fmt.Sprintf("postgres://not_owner:%s@%v:%v/postgres?sslmode=disable&x-migrations-table=\"barfoo\".\"schema_migrations\"&x-migrations-table-quoted=1",
   447  			pgPassword, ip, port))
   448  
   449  		if !errors.As(err, &e) || err == nil {
   450  			t.Fatal("Unexpected error, want permission denied error. Got: ", err)
   451  		}
   452  
   453  		if !strings.Contains(e.OrigErr.Error(), "permission denied for schema barfoo") {
   454  			t.Fatal(e)
   455  		}
   456  	})
   457  }
   458  
   459  func TestCheckBeforeCreateTable(t *testing.T) {
   460  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
   461  		ip, port, err := c.FirstPort()
   462  		if err != nil {
   463  			t.Fatal(err)
   464  		}
   465  
   466  		addr := pgConnectionString(ip, port)
   467  
   468  		// Check that opening the postgres connection returns NilVersion
   469  		p := &Postgres{}
   470  
   471  		d, err := p.Open(addr)
   472  		if err != nil {
   473  			t.Fatal(err)
   474  		}
   475  
   476  		defer func() {
   477  			if err := d.Close(); err != nil {
   478  				t.Error(err)
   479  			}
   480  		}()
   481  
   482  		// create user who is not the owner. Although we're concatenating strings in an sql statement it should be fine
   483  		// since this is a test environment and we're not expecting to the pgPassword to be malicious
   484  		mustRun(t, d, []string{
   485  			"CREATE USER not_owner WITH ENCRYPTED PASSWORD '" + pgPassword + "'",
   486  			"CREATE SCHEMA barfoo AUTHORIZATION postgres",
   487  			"GRANT USAGE ON SCHEMA barfoo TO not_owner",
   488  			"GRANT CREATE ON SCHEMA barfoo TO not_owner",
   489  		})
   490  
   491  		// re-connect using that schema
   492  		d2, err := p.Open(fmt.Sprintf("postgres://not_owner:%s@%v:%v/postgres?sslmode=disable&search_path=barfoo",
   493  			pgPassword, ip, port))
   494  		if err != nil {
   495  			t.Fatal(err)
   496  		}
   497  
   498  		if err := d2.Close(); err != nil {
   499  			t.Fatal(err)
   500  		}
   501  
   502  		// revoke privileges
   503  		mustRun(t, d, []string{
   504  			"REVOKE CREATE ON SCHEMA barfoo FROM PUBLIC",
   505  			"REVOKE CREATE ON SCHEMA barfoo FROM not_owner",
   506  		})
   507  
   508  		// re-connect using that schema
   509  		d3, err := p.Open(fmt.Sprintf("postgres://not_owner:%s@%v:%v/postgres?sslmode=disable&search_path=barfoo",
   510  			pgPassword, ip, port))
   511  		if err != nil {
   512  			t.Fatal(err)
   513  		}
   514  
   515  		version, _, err := d3.Version()
   516  		if err != nil {
   517  			t.Fatal(err)
   518  		}
   519  
   520  		if version != database.NilVersion {
   521  			t.Fatal("Unexpected version, want database.NilVersion. Got: ", version)
   522  		}
   523  
   524  		defer func() {
   525  			if err := d3.Close(); err != nil {
   526  				t.Fatal(err)
   527  			}
   528  		}()
   529  	})
   530  }
   531  
   532  func TestParallelSchema(t *testing.T) {
   533  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
   534  		ip, port, err := c.FirstPort()
   535  		if err != nil {
   536  			t.Fatal(err)
   537  		}
   538  
   539  		addr := pgConnectionString(ip, port)
   540  		p := &Postgres{}
   541  		d, err := p.Open(addr)
   542  		if err != nil {
   543  			t.Fatal(err)
   544  		}
   545  		defer func() {
   546  			if err := d.Close(); err != nil {
   547  				t.Error(err)
   548  			}
   549  		}()
   550  
   551  		// create foo and bar schemas
   552  		if err := d.Run(strings.NewReader("CREATE SCHEMA foo AUTHORIZATION postgres")); err != nil {
   553  			t.Fatal(err)
   554  		}
   555  		if err := d.Run(strings.NewReader("CREATE SCHEMA bar AUTHORIZATION postgres")); err != nil {
   556  			t.Fatal(err)
   557  		}
   558  
   559  		// re-connect using that schemas
   560  		dfoo, err := p.Open(fmt.Sprintf("postgres://postgres:%s@%v:%v/postgres?sslmode=disable&search_path=foo",
   561  			pgPassword, ip, port))
   562  		if err != nil {
   563  			t.Fatal(err)
   564  		}
   565  		defer func() {
   566  			if err := dfoo.Close(); err != nil {
   567  				t.Error(err)
   568  			}
   569  		}()
   570  
   571  		dbar, err := p.Open(fmt.Sprintf("postgres://postgres:%s@%v:%v/postgres?sslmode=disable&search_path=bar",
   572  			pgPassword, ip, port))
   573  		if err != nil {
   574  			t.Fatal(err)
   575  		}
   576  		defer func() {
   577  			if err := dbar.Close(); err != nil {
   578  				t.Error(err)
   579  			}
   580  		}()
   581  
   582  		if err := dfoo.Lock(); err != nil {
   583  			t.Fatal(err)
   584  		}
   585  
   586  		if err := dbar.Lock(); err != nil {
   587  			t.Fatal(err)
   588  		}
   589  
   590  		if err := dbar.Unlock(); err != nil {
   591  			t.Fatal(err)
   592  		}
   593  
   594  		if err := dfoo.Unlock(); err != nil {
   595  			t.Fatal(err)
   596  		}
   597  	})
   598  }
   599  
   600  func TestPostgres_Lock(t *testing.T) {
   601  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
   602  		ip, port, err := c.FirstPort()
   603  		if err != nil {
   604  			t.Fatal(err)
   605  		}
   606  
   607  		addr := pgConnectionString(ip, port)
   608  		p := &Postgres{}
   609  		d, err := p.Open(addr)
   610  		if err != nil {
   611  			t.Fatal(err)
   612  		}
   613  
   614  		dt.Test(t, d, []byte("SELECT 1"))
   615  
   616  		ps := d.(*Postgres)
   617  
   618  		err = ps.Lock()
   619  		if err != nil {
   620  			t.Fatal(err)
   621  		}
   622  
   623  		err = ps.Unlock()
   624  		if err != nil {
   625  			t.Fatal(err)
   626  		}
   627  
   628  		err = ps.Lock()
   629  		if err != nil {
   630  			t.Fatal(err)
   631  		}
   632  
   633  		err = ps.Unlock()
   634  		if err != nil {
   635  			t.Fatal(err)
   636  		}
   637  	})
   638  }
   639  
   640  func TestWithInstance_Concurrent(t *testing.T) {
   641  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
   642  		ip, port, err := c.FirstPort()
   643  		if err != nil {
   644  			t.Fatal(err)
   645  		}
   646  
   647  		// The number of concurrent processes running WithInstance
   648  		const concurrency = 30
   649  
   650  		// We can instantiate a single database handle because it is
   651  		// actually a connection pool, and so, each of the below go
   652  		// routines will have a high probability of using a separate
   653  		// connection, which is something we want to exercise.
   654  		db, err := sql.Open("postgres", pgConnectionString(ip, port))
   655  		if err != nil {
   656  			t.Fatal(err)
   657  		}
   658  		defer func() {
   659  			if err := db.Close(); err != nil {
   660  				t.Error(err)
   661  			}
   662  		}()
   663  
   664  		db.SetMaxIdleConns(concurrency)
   665  		db.SetMaxOpenConns(concurrency)
   666  
   667  		var wg sync.WaitGroup
   668  		defer wg.Wait()
   669  
   670  		wg.Add(concurrency)
   671  		for i := 0; i < concurrency; i++ {
   672  			go func(i int) {
   673  				defer wg.Done()
   674  				_, err := WithInstance(db, &Config{})
   675  				if err != nil {
   676  					t.Errorf("process %d error: %s", i, err)
   677  				}
   678  			}(i)
   679  		}
   680  	})
   681  }
   682  
   683  func TestWithConnection(t *testing.T) {
   684  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
   685  		ip, port, err := c.FirstPort()
   686  		if err != nil {
   687  			t.Fatal(err)
   688  		}
   689  
   690  		db, err := sql.Open("postgres", pgConnectionString(ip, port))
   691  		if err != nil {
   692  			t.Fatal(err)
   693  		}
   694  		defer func() {
   695  			if err := db.Close(); err != nil {
   696  				t.Error(err)
   697  			}
   698  		}()
   699  
   700  		ctx := context.Background()
   701  		conn, err := db.Conn(ctx)
   702  		if err != nil {
   703  			t.Fatal(err)
   704  		}
   705  
   706  		p, err := WithConnection(ctx, conn, &Config{})
   707  		if err != nil {
   708  			t.Fatal(err)
   709  		}
   710  
   711  		defer func() {
   712  			if err := p.Close(); err != nil {
   713  				t.Error(err)
   714  			}
   715  		}()
   716  		dt.Test(t, p, []byte("SELECT 1"))
   717  	})
   718  }
   719  
   720  func Test_computeLineFromPos(t *testing.T) {
   721  	testcases := []struct {
   722  		pos      int
   723  		wantLine uint
   724  		wantCol  uint
   725  		input    string
   726  		wantOk   bool
   727  	}{
   728  		{
   729  			15, 2, 6, "SELECT *\nFROM foo", true, // foo table does not exists
   730  		},
   731  		{
   732  			16, 3, 6, "SELECT *\n\nFROM foo", true, // foo table does not exists, empty line
   733  		},
   734  		{
   735  			25, 3, 7, "SELECT *\nFROM foo\nWHERE x", true, // x column error
   736  		},
   737  		{
   738  			27, 5, 7, "SELECT *\n\nFROM foo\n\nWHERE x", true, // x column error, empty lines
   739  		},
   740  		{
   741  			10, 2, 1, "SELECT *\nFROMM foo", true, // FROMM typo
   742  		},
   743  		{
   744  			11, 3, 1, "SELECT *\n\nFROMM foo", true, // FROMM typo, empty line
   745  		},
   746  		{
   747  			17, 2, 8, "SELECT *\nFROM foo", true, // last character
   748  		},
   749  		{
   750  			18, 0, 0, "SELECT *\nFROM foo", false, // invalid position
   751  		},
   752  	}
   753  	for i, tc := range testcases {
   754  		t.Run("tc"+strconv.Itoa(i), func(t *testing.T) {
   755  			run := func(crlf bool, nonASCII bool) {
   756  				var name string
   757  				if crlf {
   758  					name = "crlf"
   759  				} else {
   760  					name = "lf"
   761  				}
   762  				if nonASCII {
   763  					name += "-nonascii"
   764  				} else {
   765  					name += "-ascii"
   766  				}
   767  				t.Run(name, func(t *testing.T) {
   768  					input := tc.input
   769  					if crlf {
   770  						input = strings.Replace(input, "\n", "\r\n", -1)
   771  					}
   772  					if nonASCII {
   773  						input = strings.Replace(input, "FROM", "FRÖM", -1)
   774  					}
   775  					gotLine, gotCol, gotOK := computeLineFromPos(input, tc.pos)
   776  
   777  					if tc.wantOk {
   778  						t.Logf("pos %d, want %d:%d, %#v", tc.pos, tc.wantLine, tc.wantCol, input)
   779  					}
   780  
   781  					if gotOK != tc.wantOk {
   782  						t.Fatalf("expected ok %v but got %v", tc.wantOk, gotOK)
   783  					}
   784  					if gotLine != tc.wantLine {
   785  						t.Fatalf("expected line %d but got %d", tc.wantLine, gotLine)
   786  					}
   787  					if gotCol != tc.wantCol {
   788  						t.Fatalf("expected col %d but got %d", tc.wantCol, gotCol)
   789  					}
   790  				})
   791  			}
   792  			run(false, false)
   793  			run(true, false)
   794  			run(false, true)
   795  			run(true, true)
   796  		})
   797  	}
   798  }