Skip to content
This repository has been archived by the owner on Jun 28, 2018. It is now read-only.

Add oracle oci8 driver #201

Open
wants to merge 10 commits into
base: v1
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 21 additions & 0 deletions driver/oci8/README.md
@@ -0,0 +1,21 @@
# Oci8 Driver

* Runs migrations in transcations.
That means that if a migration failes, it will be safely rolled back.
* Tries to return helpful error messages.
* Stores migration version details in table ``schema_migrations``.
This table will be auto-generated.


## Usage

```bash
migrate -url oci8://system/oracle@localhost:49161/xe -path ./db/migrations create add_field_to_table
migrate -url oci8://system/oracle@localhost:49161/xe -path ./db/migrations up
migrate help # for more info
```

## Authors

* Matthias Kadenbach, https://github.com/mattes
* Benjamin Radovsky, https://github.com/radovskyb
134 changes: 134 additions & 0 deletions driver/oci8/oci8.go
@@ -0,0 +1,134 @@
// Package oci8 implements the Driver interface.
package oci8

import (
"database/sql"
"errors"
"fmt"
"strings"

"github.com/mattes/migrate/driver"
"github.com/mattes/migrate/file"
"github.com/mattes/migrate/migrate/direction"

_ "github.com/mattn/go-oci8"
)

type Driver struct {
db *sql.DB
}

const (
tableName = "schema_migrations"
executeIfNotExists = `DECLARE
foundnum NUMBER := 0;
BEGIN
SELECT count(0) INTO foundnum FROM user_tables WHERE table_name = UPPER('%s');

IF foundnum = 0 THEN
EXECUTE IMMEDIATE '%s';
END IF;
END;`
)

func (driver *Driver) Initialize(url string) error {
filename := strings.SplitN(url, "oci8://", 2)
if len(filename) != 2 {
return errors.New("invalid oci8:// scheme")
}

db, err := sql.Open("oci8", filename[1])
if err != nil {
return err
}
if err := db.Ping(); err != nil {
return err
}
driver.db = db

return driver.ensureVersionTableExists()
}

func (driver *Driver) Close() error {
return driver.db.Close()
}

func (driver *Driver) ensureVersionTableExists() error {
// Construct the createIfNotExists statement.
createIfNotExists := fmt.Sprintf(
executeIfNotExists,
tableName,
"CREATE TABLE "+tableName+"(version NUMBER(19) NOT NULL PRIMARY KEY)",
)

_, err := driver.db.Exec(fmt.Sprintf(createIfNotExists))
return err
}

func (driver *Driver) FilenameExtension() string {
return "sql"
}

func (driver *Driver) Migrate(f file.File, pipe chan interface{}) {
defer close(pipe)
pipe <- f

tx, err := driver.db.Begin()
if err != nil {
pipe <- err
return
}

if f.Direction == direction.Up {
if _, err := tx.Exec("INSERT INTO "+tableName+" (version) VALUES (:1)", f.Version); err != nil {
pipe <- err
if err := tx.Rollback(); err != nil {
pipe <- err
}
return
}
} else if f.Direction == direction.Down {
if _, err := tx.Exec("DELETE FROM "+tableName+" WHERE version = :1", f.Version); err != nil {
pipe <- err
if err := tx.Rollback(); err != nil {
pipe <- err
}
return
}
}

if err := f.ReadContent(); err != nil {
pipe <- err
return
}

if _, err := tx.Exec(string(f.Content)); err != nil {
pipe <- err
if err := tx.Rollback(); err != nil {
pipe <- err
}
return
}

if err := tx.Commit(); err != nil {
pipe <- err
return
}
}

func (driver *Driver) Version() (uint64, error) {
var version uint64
err := driver.db.QueryRow("SELECT version FROM " + tableName + " ORDER BY version DESC").Scan(&version)
switch {
case err == sql.ErrNoRows:
return 0, nil
case err != nil:
return 0, err
default:
return version, nil
}
}

func init() {
driver.RegisterDriver("oci8", &Driver{})
}
119 changes: 119 additions & 0 deletions driver/oci8/oci8_test.go
@@ -0,0 +1,119 @@
package oci8

import (
"database/sql"
"fmt"
"os"
"testing"

"github.com/mattes/migrate/file"
"github.com/mattes/migrate/migrate/direction"
pipep "github.com/mattes/migrate/pipe"
)

func executeIfTableExistsPrep(table, query string) string {
return fmt.Sprintf(`DECLARE
foundnum NUMBER := 0;
BEGIN
SELECT count(0) INTO foundnum FROM user_tables WHERE table_name = UPPER('%s');

IF foundnum = 1 THEN
EXECUTE IMMEDIATE '%s';
END IF;
END;`, table, query)
}

func TestMigrate(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode.")
}

dsn := os.Getenv("OCI8_DB_DSN")

if dsn == "" {
t.Fatal("OCI8_DB_DSN environment variable not set, format (system/oracle@localhost:49161/xe)")
}

driverUrl := "oci8://" + dsn

db, err := sql.Open("oci8", dsn)
if err != nil {
t.Fatal(err)
}
defer db.Close()

_, err = db.Exec(executeIfTableExistsPrep("yolo", "DROP TABLE yolo"))
if err != nil {
t.Fatal(err)
}

_, err = db.Exec(executeIfTableExistsPrep("yolo", "DROP TABLE "+tableName))
if err != nil {
t.Fatal(err)
}

d := &Driver{}
if err := d.Initialize(driverUrl); err != nil {
t.Fatal(err)
}

files := []file.File{
{
Path: "/foobar",
FileName: "001_foobar.up.sql",
Version: 1,
Name: "foobar",
Direction: direction.Up,
Content: []byte(`
CREATE TABLE yolo (
id NUMBER(19) PRIMARY KEY
)
`),
},
{
Path: "/foobar",
FileName: "002_foobar.down.sql",
Version: 1,
Name: "foobar",
Direction: direction.Down,
Content: []byte("DROP TABLE yolo"),
},
{
Path: "/foobar",
FileName: "002_foobar.up.sql",
Version: 1,
Name: "foobar",
Direction: direction.Down,
Content: []byte(`
CREATE TABLE error (
THIS; WILL CAUSE; AN ERROR;
)
`),
},
}

pipe := pipep.New()
go d.Migrate(files[0], pipe)
errs := pipep.ReadErrors(pipe)
if len(errs) > 0 {
t.Fatal(errs)
}

pipe = pipep.New()
go d.Migrate(files[1], pipe)
errs = pipep.ReadErrors(pipe)
if len(errs) > 0 {
t.Fatal(errs)
}

pipe = pipep.New()
go d.Migrate(files[2], pipe)
errs = pipep.ReadErrors(pipe)
if len(errs) == 0 {
t.Error("expected test case to fail")
}

if err := d.Close(); err != nil {
t.Fatal(err)
}
}
1 change: 1 addition & 0 deletions main.go
Expand Up @@ -16,6 +16,7 @@ import (
_ "github.com/mattes/migrate/driver/crate"
_ "github.com/mattes/migrate/driver/mysql"
_ "github.com/mattes/migrate/driver/neo4j"
_ "github.com/mattes/migrate/driver/oci8"
_ "github.com/mattes/migrate/driver/postgres"
_ "github.com/mattes/migrate/driver/ql"
_ "github.com/mattes/migrate/driver/sqlite3"
Expand Down