github.com/kubecost/golang-migrate-duckdb/v4@v4.17.0-duckdb.1/database/pgx/v5/pgx_test.go (about)

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