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