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 }