Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Neo4J Support in Go Migrate [WIP] #1

Merged
merged 7 commits into from
Jan 7, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ ARG VERSION

RUN apk add --no-cache git gcc musl-dev

# dependencies
RUN apk add --update --no-cache ca-certificates cmake make g++ openssl-dev git curl pkgconfig

# build seabolt
RUN git clone -b 1.7 https://github.com/neo4j-drivers/seabolt.git /seabolt
WORKDIR /seabolt/build
RUN cmake -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_LIBDIR=lib .. && cmake --build . --target install

WORKDIR /go/src/github.com/golang-migrate/migrate

COPY . ./
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
SOURCE ?= file go_bindata github github_ee aws_s3 google_cloud_storage godoc_vfs gitlab
DATABASE ?= postgres mysql redshift cassandra spanner cockroachdb clickhouse mongodb sqlserver firebird
DATABASE ?= postgres mysql redshift cassandra spanner cockroachdb clickhouse mongodb sqlserver firebird neo4j
VERSION ?= $(shell git describe --tags 2>/dev/null | cut -c 2-)
TEST_FLAGS ?=
REPO_OWNER ?= $(shell cd .. && basename "$$(pwd)")
Expand Down
4 changes: 2 additions & 2 deletions database/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ var drivers = make(map[string]Driver)
// All other functions are tested by tests in database/testing.
// Saves you some time and makes sure all database drivers behave the same way.
// 5. Call Register in init().
// 6. Create a migrate/cli/build_<driver-name>.go file
// 6. Create a internal/cli/build_<driver-name>.go file
// 7. Add driver name in 'DATABASE' variable in Makefile
//
// Guidelines:
Expand Down Expand Up @@ -61,7 +61,7 @@ type Driver interface {
// all migrations have been run.
Unlock() error

// Run applies a migration to the database. migration is garantueed to be not nil.
// Run applies a migration to the database. migration is guaranteed to be not nil.
Run(migration io.Reader) error

// SetVersion saves version and dirty state.
Expand Down
161 changes: 161 additions & 0 deletions database/neo4j/neo4j.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package neo4j

import (
"fmt"
"io"
"io/ioutil"
neturl "net/url"

"github.com/golang-migrate/migrate/v4/database"
"github.com/neo4j/neo4j-go-driver/neo4j"
)

func init() {
db := Neo4J{}
database.Register("bolt", &db)
database.Register("neo4j", &db)
}

var DefaultMigrationsLabel = "SchemaMigration"

var (
ErrNilConfig = fmt.Errorf("no config")
)

type Config struct {
AuthToken neo4j.AuthToken
URL string
MigrationsLabel string
}

type Neo4J struct {
driver neo4j.Driver

// Open and WithInstance need to guarantee that config is never nil
config *Config
}

func WithInstance(config *Config) (database.Driver, error) {
if config == nil {
return nil, ErrNilConfig
}

neoDriver, err := neo4j.NewDriver(config.URL, config.AuthToken)
if err != nil {
return nil, err
}

driver := &Neo4J{
driver: neoDriver,
config: config,
}

if err := driver.ensureVersionConstraint(); err != nil {
return nil, err
}

return driver, nil
}

func (n *Neo4J) Open(url string) (database.Driver, error) {
uri, err := neturl.Parse(url)
if err != nil {
return nil, err
}
password, _ := uri.User.Password()
authToken := neo4j.BasicAuth(uri.User.Username(), password, "")

return WithInstance(&Config{
URL: url,
AuthToken: authToken,
MigrationsLabel: DefaultMigrationsLabel,
})
}

func (n *Neo4J) Close() error {
return n.driver.Close()
}

func (n *Neo4J) Lock() error {
return nil
}

func (n *Neo4J) Unlock() error {
return nil
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what are these? Just to fulfill interface methods?

doesn't seem like we need a sync.Mutex for Neo4J struct.

Also now that I see it, did you mean to have capital J for Neo4J?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, there is a generic Driver interface that go migrate has you fill out, and requests you return nil if the database isn't capable of locking (which Neo isn't).

You are correct about the J, I have replaced it with lowercase


func (n *Neo4J) Run(migration io.Reader) error {
body, err := ioutil.ReadAll(migration)
if err != nil {
return err
}

session, err := n.driver.Session(neo4j.AccessModeWrite)
if err != nil {
return err
}
defer session.Close()

_, err = session.Run(string(body[:]), nil)
return err
}

func (n *Neo4J) SetVersion(version int, dirty bool) error {
session, err := n.driver.Session(neo4j.AccessModeRead)
if err != nil {
return err
}
defer session.Close()

_, err = session.Run("MERGE (sm:$migration {version: $version, dirty: $dirty})",
map[string]interface{}{"migration": n.config.MigrationsLabel, "version": version, "dirty": dirty})

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm with this, wouldn't it be possible to get two nodes with same version but differing dirty bool state?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It shouldn't be because we run a ensureVersionConstraint function during driver creation that enforces a unique constraint on version number

return err
}

func (n *Neo4J) Version() (version int, dirty bool, err error) {
session, err := n.driver.Session(neo4j.AccessModeRead)
if err != nil {
return -1, false, err
}
defer session.Close()

result, err := session.Run("MATCH (sm:$migration) RETURN sm.version, sm.dirty ORDER BY sm.version DESC LIMIT 1",
map[string]interface{}{"migration": n.config.MigrationsLabel})
if err != nil {
return -1, false, err
}
if result.Next() {
versionResult, ok := result.Record().Get("version")
if !ok {
version = -1
} else {
version = versionResult.(int)
}
} else {
version = -1
}

return

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't return version here?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the variable are named in the function signature, they will all be returned with an empty return statement. I have changed it to return explicitly for consistency

}

func (n *Neo4J) Drop() error {
session, err := n.driver.Session(neo4j.AccessModeWrite); if err != nil {
return err
}
defer session.Close()

_, err = session.Run("MATCH (n) DETACH DELETE n", map[string]interface{}{})
return err
}

func (n *Neo4J) ensureVersionConstraint() (err error) {
session, err := n.driver.Session(neo4j.AccessModeWrite); if err != nil {
return err
}
defer session.Close()

_, err = session.Run("CREATE CONSTRAINT ON (a:$migration) ASSERT a.version IS UNIQUE",
map[string]interface{}{"migration": n.config.MigrationsLabel})
return err
}

75 changes: 75 additions & 0 deletions database/neo4j/neo4j_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package neo4j

import (
"context"
"fmt"
"log"
"testing"

"github.com/dhui/dktest"
"github.com/neo4j/neo4j-go-driver/neo4j"

dt "github.com/golang-migrate/migrate/v4/database/testing"
"github.com/golang-migrate/migrate/v4/dktesting"
_ "github.com/golang-migrate/migrate/v4/source/file"
)

var (
opts = dktest.Options{PortRequired: true, ReadyFunc: isReady}
specs = []dktesting.ContainerSpec{
{ImageName: "neo4j:3.5", Options: opts},
//{ImageName: "neo4j:3.5-enterprise", Options: opts},
}
)

func neoConnectionString(host, port string) string {
return fmt.Sprintf("bolt://neo4j:neo4j@%s:%s", host, port)
}

func isReady(ctx context.Context, c dktest.ContainerInfo) bool {
ip, port, err := c.Port(7687)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hardcoded port?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for a test, the function asks the docker test suite which docker port has been mapped to 7687, which is the bolt exposed port in the neo docker container. In real usage, the port would be provided by the URL the user gives

if err != nil {
return false
}

driver, err := neo4j.NewDriver(neoConnectionString(ip, port), neo4j.BasicAuth("neo4j", "neo4j", ""))
if err != nil {
return false
}
defer func() {
if err := driver.Close(); err != nil {
log.Println("close error:", err)
}
}()
session, err := driver.Session(neo4j.AccessModeRead)
if err != nil {
return false
}
_, err = session.Run("RETURN 1", nil)
if err != nil {
return false
}

return true
}

func Test(t *testing.T) {
dktesting.ParallelTest(t, specs, func(t *testing.T, c dktest.ContainerInfo) {
ip, port, err := c.Port(7687)
if err != nil {
t.Fatal(err)
}

n := &Neo4J{}
d, err := n.Open(neoConnectionString(ip, port))
if err != nil {
t.Fatal(err)
}
defer func() {
if err := d.Close(); err != nil {
t.Error(err)
}
}()
dt.Test(t, d, []byte("MATCH a RETURN a"))
})
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ require (
github.com/mattn/go-sqlite3 v1.10.0
github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c // indirect
github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8
github.com/neo4j-drivers/gobolt v1.7.4 // indirect
github.com/neo4j/neo4j-go-driver v1.7.4
github.com/pkg/errors v0.8.1 // indirect
github.com/satori/go.uuid v1.2.0 // indirect
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 // indirect
Expand Down
13 changes: 13 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712 h1:aaQcKT9WumO6JEJcRyTqFVq4XUZiUcKR2/GI31TOcz8=
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsouza/fake-gcs-server v1.7.0 h1:Un0BXUXrRWYSmYyC1Rqm2e2WJfTPyDy/HGMz31emTi8=
github.com/fsouza/fake-gcs-server v1.7.0/go.mod h1:5XIRs4YvwNbNoz+1JF8j6KLAyDh7RHGAyAK3EP2EsNk=
Expand All @@ -94,6 +95,7 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
Expand Down Expand Up @@ -127,6 +129,7 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc=
github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ=
Expand Down Expand Up @@ -161,8 +164,14 @@ github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8 h1:P48LjvUQpTReR3TQRbxSeSBsMXzfK0uol7eRcr7VBYQ=
github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA=
github.com/neo4j-drivers/gobolt v1.7.4 h1:80c7W+vtw39ES9Q85q9GZh4tJo+1MpQGpFTuo28CP+Y=
github.com/neo4j-drivers/gobolt v1.7.4/go.mod h1:O9AUbip4Dgre+CD3p40dnMD4a4r52QBIfblg5k7CTbE=
github.com/neo4j/neo4j-go-driver v1.7.4 h1:BgVVwYkG3DWcZGiOPUOkwkd54sSg+UHDaLYz3aiNCek=
github.com/neo4j/neo4j-go-driver v1.7.4/go.mod h1:aPO0vVr+WnhEJne+FgFjfsjzAnssPFLucHgGZ76Zb/U=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
Expand Down Expand Up @@ -192,6 +201,7 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
Expand Down Expand Up @@ -303,11 +313,14 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
Expand Down
7 changes: 7 additions & 0 deletions internal/cli/build_neo4j.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// +build neo4j

package cli

import (
_ "github.com/golang-migrate/migrate/v4/database/neo4j"
)