github.com/brandonmartin/migrate/v4@v4.14.2/database/oracle/oracle_test.go (about)

     1  package oracle
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"database/sql"
     7  	"fmt"
     8  	"log"
     9  	"os"
    10  	"sync"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/dhui/dktest"
    15  	"github.com/godror/godror"
    16  	"github.com/golang-migrate/migrate/v4"
    17  	dt "github.com/golang-migrate/migrate/v4/database/testing"
    18  	"github.com/golang-migrate/migrate/v4/dktesting"
    19  	_ "github.com/golang-migrate/migrate/v4/source/file"
    20  	"github.com/stretchr/testify/require"
    21  )
    22  
    23  var (
    24  	opts  = dktest.Options{PortRequired: true, ReadyFunc: isReady, Timeout: time.Minute * 40, ReadyTimeout: time.Minute * 3, PullTimeout: time.Minute * 30}
    25  	specs = []dktesting.ContainerSpec{
    26  		{ImageName: "maxnilz/oracle-xe:18c", Options: opts},
    27  	}
    28  )
    29  
    30  type dsnFunc func(t *testing.T, args ...interface{}) string
    31  
    32  func oracleEnvDsn(t *testing.T, _ ...interface{}) string {
    33  	//E.g: oci8://user/password@localhost:1521/ORCLPDB1
    34  	dsn := os.Getenv("MIGRATE_TEST_ORACLE_DSN")
    35  	if dsn == "" {
    36  		t.Skip("MIGRATE_TEST_ORACLE_DSN not found, skip the test case")
    37  	}
    38  	return dsn
    39  }
    40  
    41  func isDKHonored(t *testing.T) {
    42  	s := os.Getenv("MIGRATE_TEST_ENABLE_ORACLE_CONTAINER")
    43  	if s != "true" {
    44  		t.Skip("MIGRATE_TEST_ENABLE_ORACLE_CONTAINER not found, skip the dk test case")
    45  	}
    46  }
    47  
    48  func oracleDKDsn(t *testing.T, args ...interface{}) string {
    49  	c := args[0].(dktest.ContainerInfo)
    50  	ip, port, err := c.Port(1521)
    51  	if err != nil {
    52  		t.Fatal(err)
    53  	}
    54  	return oracleConnectionString(ip, port)
    55  }
    56  
    57  func oracleConnectionString(host, port string) string {
    58  	return fmt.Sprintf("oracle://oracle:oracle@%s:%s/XEPDB1", host, port)
    59  }
    60  
    61  func TestParseStatements(t *testing.T) {
    62  	cases := []struct {
    63  		migration       string
    64  		expectedQueries []string
    65  	}{
    66  		{migration: `
    67  CREATE TABLE USERS (
    68    USER_ID integer unique,
    69    NAME    varchar(40),
    70    EMAIL   varchar(40)
    71  );
    72  
    73  ---
    74  --
    75  BEGIN
    76  EXECUTE IMMEDIATE 'DROP TABLE USERS';
    77  EXCEPTION
    78      WHEN OTHERS THEN
    79          IF SQLCODE != -942 THEN
    80              RAISE;
    81          END IF;
    82  END;
    83  
    84  ---
    85  -- comment
    86  --
    87  CREATE TABLE USERS (
    88     USER_ID integer unique,
    89     NAME    varchar(40),
    90     EMAIL   varchar(40)
    91  );
    92  ---
    93  --`,
    94  			expectedQueries: []string{
    95  				`CREATE TABLE USERS (
    96    USER_ID integer unique,
    97    NAME    varchar(40),
    98    EMAIL   varchar(40)
    99  )`,
   100  				`BEGIN
   101  EXECUTE IMMEDIATE 'DROP TABLE USERS';
   102  EXCEPTION
   103      WHEN OTHERS THEN
   104          IF SQLCODE != -942 THEN
   105              RAISE;
   106          END IF;
   107  END;`,
   108  				`CREATE TABLE USERS (
   109     USER_ID integer unique,
   110     NAME    varchar(40),
   111     EMAIL   varchar(40)
   112  )`,
   113  			}},
   114  		{migration: `
   115  -- comment
   116  CREATE TABLE USERS (
   117    USER_ID integer unique,
   118    NAME    varchar(40),
   119    EMAIL   varchar(40)
   120  );
   121  -- this is comment
   122  ALTER TABLE USERS ADD CITY varchar(100);
   123  `,
   124  			expectedQueries: []string{
   125  				`CREATE TABLE USERS (
   126    USER_ID integer unique,
   127    NAME    varchar(40),
   128    EMAIL   varchar(40)
   129  )`,
   130  				`ALTER TABLE USERS ADD CITY varchar(100)`,
   131  			}},
   132  	}
   133  	for _, c := range cases {
   134  		queries, err := parseStatements(bytes.NewBufferString(c.migration), &Config{PLSQLStatementSeparator: plsqlDefaultStatementSeparator})
   135  		require.Nil(t, err)
   136  		require.Equal(t, c.expectedQueries, queries)
   137  	}
   138  }
   139  
   140  func TestOpen(t *testing.T) {
   141  	testOpen(t, oracleEnvDsn)
   142  }
   143  
   144  func TestMigrate(t *testing.T) {
   145  	testMigrate(t, oracleEnvDsn)
   146  }
   147  
   148  func TestLockWorks(t *testing.T) {
   149  	testLockWorks(t, oracleEnvDsn)
   150  }
   151  
   152  func TestWithInstanceConcurrent(t *testing.T) {
   153  	testWithInstanceConcurrent(t, oracleEnvDsn)
   154  }
   155  
   156  func isReady(ctx context.Context, c dktest.ContainerInfo) bool {
   157  	ip, port, err := c.Port(1521)
   158  	if err != nil {
   159  		return false
   160  	}
   161  	db, err := sql.Open("godror", oracleConnectionString(ip, port))
   162  	if err != nil {
   163  		return false
   164  	}
   165  	defer func() {
   166  		if err := db.Close(); err != nil {
   167  			log.Println("close error:", err)
   168  		}
   169  	}()
   170  	if err = db.PingContext(ctx); err != nil {
   171  		oraErr, ok := godror.AsOraErr(err)
   172  		if ok {
   173  			if oraErr.Code() == 12514 || oraErr.Code() == 12547 {
   174  				// log the not ready very 60s
   175  				if time.Now().Unix()%60 == 0 {
   176  					log.Println(oracleConnectionString(ip, port), "not ready, ora code:", oraErr.Code())
   177  				}
   178  			} else {
   179  				log.Printf("%s got ora error code: %v\n", oracleConnectionString(ip, port), oraErr.Code())
   180  			}
   181  		} else {
   182  			log.Printf("%s got unexpected err: %v", oracleConnectionString(ip, port), err)
   183  		}
   184  		return false
   185  	}
   186  
   187  	return true
   188  }
   189  
   190  // Since start a oracle container is very time expensive, just try to start one and reuse it for different test case.
   191  func TestAllInOneWithDK(t *testing.T) {
   192  	isDKHonored(t)
   193  	dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
   194  		testOpen(t, oracleDKDsn, c)
   195  		testMigrate(t, oracleDKDsn, c)
   196  		testLockWorks(t, oracleDKDsn, c)
   197  		testWithInstanceConcurrent(t, oracleDKDsn, c)
   198  	})
   199  }
   200  
   201  func testOpen(t *testing.T, oracleDsnFunc dsnFunc, args ...interface{}) {
   202  	ora := &Oracle{}
   203  	d, err := ora.Open(oracleDsnFunc(t, args...))
   204  	require.Nil(t, err)
   205  	require.NotNil(t, d)
   206  	defer func() {
   207  		if err := d.Close(); err != nil {
   208  			t.Error(err)
   209  		}
   210  	}()
   211  	ora = d.(*Oracle)
   212  	require.Equal(t, defaultMigrationsTable, ora.config.MigrationsTable)
   213  
   214  	tbName := ""
   215  	err = ora.conn.QueryRowContext(context.Background(), `SELECT tname FROM tab WHERE tname = :1`, ora.config.MigrationsTable).Scan(&tbName)
   216  	require.Nil(t, err)
   217  	require.Equal(t, ora.config.MigrationsTable, tbName)
   218  
   219  	dt.Test(t, d, []byte(`BEGIN DBMS_OUTPUT.PUT_LINE('hello'); END;`))
   220  }
   221  
   222  func testMigrate(t *testing.T, oracleDsnFunc dsnFunc, args ...interface{}) {
   223  	p := &Oracle{}
   224  	d, err := p.Open(oracleDsnFunc(t, args...))
   225  	if err != nil {
   226  		t.Fatal(err)
   227  	}
   228  	defer func() {
   229  		if err := d.Close(); err != nil {
   230  			t.Error(err)
   231  		}
   232  	}()
   233  	m, err := migrate.NewWithDatabaseInstance("file://./examples/migrations", "", d)
   234  	if err != nil {
   235  		t.Fatal(err)
   236  	}
   237  	dt.TestMigrate(t, m)
   238  }
   239  
   240  func testLockWorks(t *testing.T, oracleDsnFunc dsnFunc, args ...interface{}) {
   241  	p := &Oracle{}
   242  	d, err := p.Open(oracleDsnFunc(t, args...))
   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  	dt.Test(t, d, []byte(`BEGIN DBMS_OUTPUT.PUT_LINE('hello'); END;`))
   253  
   254  	ora := d.(*Oracle)
   255  
   256  	err = ora.Lock()
   257  	if err != nil {
   258  		t.Fatal(err)
   259  	}
   260  
   261  	err = ora.Unlock()
   262  	if err != nil {
   263  		t.Fatal(err)
   264  	}
   265  
   266  	err = ora.Lock()
   267  	if err != nil {
   268  		t.Fatal(err)
   269  	}
   270  
   271  	err = ora.Unlock()
   272  	if err != nil {
   273  		t.Fatal(err)
   274  	}
   275  }
   276  
   277  func testWithInstanceConcurrent(t *testing.T, oracleDsnFunc dsnFunc, args ...interface{}) {
   278  	// The number of concurrent processes running WithInstance
   279  	const concurrency = 30
   280  
   281  	// We can instantiate a single database handle because it is
   282  	// actually a connection pool, and so, each of the below go
   283  	// routines will have a high probability of using a separate
   284  	// connection, which is something we want to exercise.
   285  	db, err := sql.Open("godror", oracleDsnFunc(t, args...))
   286  	if err != nil {
   287  		t.Fatal(err)
   288  	}
   289  	defer func() {
   290  		if err := db.Close(); err != nil {
   291  			t.Error(err)
   292  		}
   293  	}()
   294  
   295  	db.SetMaxIdleConns(concurrency)
   296  	db.SetMaxOpenConns(concurrency)
   297  
   298  	var wg sync.WaitGroup
   299  	defer wg.Wait()
   300  
   301  	wg.Add(concurrency)
   302  	for i := 0; i < concurrency; i++ {
   303  		go func(i int) {
   304  			defer wg.Done()
   305  			_, err := WithInstance(db, &Config{})
   306  			if err != nil {
   307  				t.Errorf("process %d error: %s", i, err)
   308  			}
   309  		}(i)
   310  	}
   311  }