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