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  }