Skip to content

Commit

Permalink
Merge branch 'master' into encodeArbitrarySliceTypes
Browse files Browse the repository at this point in the history
  • Loading branch information
scott-wilson committed Aug 9, 2018
2 parents 9bd94ec + c68f220 commit b9ba765
Show file tree
Hide file tree
Showing 14 changed files with 441 additions and 169 deletions.
28 changes: 21 additions & 7 deletions .travis.yml
Expand Up @@ -3,15 +3,29 @@ language: go
sudo: false

os:
- linux
- osx

- linux
go:
- 1.4.3
- 1.5.4
- 1.6.2
- tip
- "1.4.3"
- "1.5.4"
- "1.6.4"
- "1.7.6"
- "1.8.4"
- "1.9.1"
- "tip"

#exclude specific go versions in osx build because according to https://github.com/golang/go/issues/17824, go 1.6.4 (and anything prior does not support osx 10.12) which results in unpredicable behavior (sometimes pass, sometimes hangs, sometimes fail due to segfault)

matrix:
include:
- os: osx
go: "1.7.6"
- os: osx
go: "1.8.4"
- os: osx
go: "1.9.1"
- os: osx
go: "tip"

#before_install:
#- go get github.com/mattn/goveralls
#- go get golang.org/x/tools/cmd/cover
Expand Down
194 changes: 114 additions & 80 deletions README.md
@@ -1,7 +1,10 @@
# Golang Neo4J Bolt Driver
[![Build Status](https://travis-ci.org/johnnadratowski/golang-neo4j-bolt-driver.svg?branch=master)](https://travis-ci.org/johnnadratowski/golang-neo4j-bolt-driver) *Tested against Golang 1.4.3 and up*
[![Build Status](https://travis-ci.org/johnnadratowski/golang-neo4j-bolt-driver.svg?branch=master)](https://travis-ci.org/johnnadratowski/golang-neo4j-bolt-driver)
[![GoDoc](https://godoc.org/github.com/johnnadratowski/golang-neo4j-bolt-driver?status.svg)](https://godoc.org/github.com/johnnadratowski/golang-neo4j-bolt-driver)


**ANNOUNCEMENT: I must apologize to the community for not being more responsive. Because of personal life events I am really not able to properly maintain this codebase. Certain other events lead me to believe an official Neo4J Golang driver was to be released soon, but it seems like that may not necessarily be the case. Since I am unable to properly maintain this codebase at this juncture, at this point I think it makes sense to open up this repo to collaborators who are interested in helping with maintenance. Please feel free to email me directly if you're interested.**

Implements the Neo4J Bolt Protocol specification:
As of the time of writing this, the current version is v3.1.0-M02

Expand All @@ -28,7 +31,7 @@ go get github.com/johnnadratowski/golang-neo4j-bolt-driver
```go
func quickNDirty() {
driver := bolt.NewDriver()
conn, _ := driver.OpenNeo("bolt://localhost:7687")
conn, _ := driver.OpenNeo("bolt://username:password@localhost:7687")
defer conn.Close()

// Start by creating a node
Expand Down Expand Up @@ -70,110 +73,95 @@ func quickNDirty() {
#### Slow n' Clean

```go
func slowNClean() {
driver := bolt.NewDriver()
conn, err := driver.OpenNeo("bolt://localhost:7687")
if err != nil {
panic(err)
}
defer conn.Close()

// Here we prepare a new statement. This gives us the flexibility to
// cancel that statement without any request sent to Neo
stmt, err := conn.PrepareNeo("CREATE (n:NODE {foo: {foo}, bar: {bar}})")
if err != nil {
panic(err)
}
// Constants to be used throughout the example
const (
URI = "bolt://username:password@localhost:7687"
CreateNode = "CREATE (n:NODE {foo: {foo}, bar: {bar}})"
GetNode = "MATCH (n:NODE) RETURN n.foo, n.bar"
RelationNode = "MATCH path=(n:NODE)-[:REL]->(m) RETURN path"
DeleteNodes = "MATCH (n) DETACH DELETE n"
)

// Executing a statement just returns summary information
result, err := stmt.ExecNeo(map[string]interface{}{"foo": 1, "bar": 2.2})
if err != nil {
panic(err)
}
numResult, err := result.RowsAffected()
if err != nil {
panic(err)
}
fmt.Printf("CREATED ROWS: %d\n", numResult) // CREATED ROWS: 1
func main() {
con := createConnection()
defer con.Close()

// Closing the statment will also close the rows
stmt.Close()
st := prepareSatement(CreateNode, con)
executeStatement(st)

// Lets get the node. Once again I can cancel this with no penalty
stmt, err = conn.PrepareNeo("MATCH (n:NODE) RETURN n.foo, n.bar")
if err != nil {
panic(err)
}
st = prepareSatement(GetNode, con)
rows := queryStatement(st)
consumeRows(rows, st)

// Even once I get the rows, if I do not consume them and close the
// rows, Neo will discard and not send the data
rows, err := stmt.QueryNeo(nil)
if err != nil {
panic(err)
}
pipe := preparePipeline(con)
executePipeline(pipe)

// This interface allows you to consume rows one-by-one, as they
// come off the bolt stream. This is more efficient especially
// if you're only looking for a particular row/set of rows, as
// you don't need to load up the entire dataset into memory
data, _, err := rows.NextNeo()
if err != nil {
panic(err)
}
st = prepareSatement(RelationNode, con)
rows = queryStatement(st)
consumeMetadata(rows, st)

// This query only returns 1 row, so once it's done, it will return
// the metadata associated with the query completion, along with
// io.EOF as the error
_, _, err = rows.NextNeo()
if err != io.EOF {
panic(err)
}
fmt.Printf("COLUMNS: %#v\n", rows.Metadata()["fields"].([]interface{})) // COLUMNS: n.foo,n.bar
fmt.Printf("FIELDS: %d %f\n", data[0].(int64), data[1].(float64)) // FIELDS: 1 2.2
cleanUp(DeleteNodes, con)
}

func createConnection() bolt.Conn {
driver := bolt.NewDriver()
con, err := driver.OpenNeo(URI)
handleError(err)
return con
}

stmt.Close()
// Here we prepare a new statement. This gives us the flexibility to
// cancel that statement without any request sent to Neo
func prepareSatement(query string, con bolt.Conn) bolt.Stmt {
st, err := con.PrepareNeo(query)
handleError(err)
return st
}

// Here we prepare a new pipeline statement for running multiple
// queries concurrently
pipeline, err := conn.PreparePipeline(
// Here we prepare a new pipeline statement for running multiple
// queries concurrently
func preparePipeline(con bolt.Conn) bolt.PipelineStmt {
pipeline, err := con.PreparePipeline(
"MATCH (n:NODE) CREATE (n)-[:REL]->(f:FOO)",
"MATCH (n:NODE) CREATE (n)-[:REL]->(b:BAR)",
"MATCH (n:NODE) CREATE (n)-[:REL]->(z:BAZ)",
"MATCH (n:NODE) CREATE (n)-[:REL]->(f:FOO)",
"MATCH (n:NODE) CREATE (n)-[:REL]->(b:BAR)",
"MATCH (n:NODE) CREATE (n)-[:REL]->(z:BAZ)",
)
if err != nil {
panic(err)
}
handleError(err)
return pipeline
}

func executePipeline(pipeline bolt.PipelineStmt) {
pipelineResults, err := pipeline.ExecPipeline(nil, nil, nil, nil, nil, nil)
if err != nil {
panic(err)
}
handleError(err)

for _, result := range pipelineResults {
numResult, _ := result.RowsAffected()
fmt.Printf("CREATED ROWS: %d\n", numResult) // CREATED ROWS: 2 (per each iteration)
}

err = pipeline.Close()
if err != nil {
panic(err)
}

stmt, err = conn.PrepareNeo("MATCH path=(n:NODE)-[:REL]->(m) RETURN path")
if err != nil {
panic(err)
}
handleError(err)
}

rows, err = stmt.QueryNeo(nil)
if err != nil {
panic(err)
}
func queryStatement(st bolt.Stmt) bolt.Rows {
// Even once I get the rows, if I do not consume them and close the
// rows, Neo will discard and not send the data
rows, err := st.QueryNeo(nil)
handleError(err)
return rows
}

func consumeMetadata(rows bolt.Rows, st bolt.Stmt) {
// Here we loop through the rows until we get the metadata object
// back, meaning the row stream has been fully consumed

var err error
err = nil

for err == nil {
var row []interface{}
row, _, err = rows.NextNeo()
Expand All @@ -183,14 +171,53 @@ func slowNClean() {
fmt.Printf("PATH: %#v\n", row[0].(graph.Path)) // Prints all paths
}
}
st.Close()
}

stmt.Close()
func consumeRows(rows bolt.Rows, st bolt.Stmt) {
// This interface allows you to consume rows one-by-one, as they
// come off the bolt stream. This is more efficient especially
// if you're only looking for a particular row/set of rows, as
// you don't need to load up the entire dataset into memory
data, _, err := rows.NextNeo()
handleError(err)

result, _ = conn.ExecNeo(`MATCH (n) DETACH DELETE n`, nil)
// This query only returns 1 row, so once it's done, it will return
// the metadata associated with the query completion, along with
// io.EOF as the error
_, _, err = rows.NextNeo()
handleError(err)
fmt.Printf("COLUMNS: %#v\n", rows.Metadata()["fields"].([]interface{})) // COLUMNS: n.foo,n.bar
fmt.Printf("FIELDS: %d %f\n", data[0].(int64), data[1].(float64)) // FIELDS: 1 2.2

st.Close()
}

// Executing a statement just returns summary information
func executeStatement(st bolt.Stmt) {
result, err := st.ExecNeo(map[string]interface{}{"foo": 1, "bar": 2.2})
handleError(err)
numResult, err := result.RowsAffected()
handleError(err)
fmt.Printf("CREATED ROWS: %d\n", numResult) // CREATED ROWS: 1

// Closing the statment will also close the rows
st.Close()
}

func cleanUp(query string, con bolt.Conn) {
result, _ := con.ExecNeo(query, nil)
fmt.Println(result)
numResult, _ = result.RowsAffected()
numResult, _ := result.RowsAffected()
fmt.Printf("Rows Deleted: %d", numResult) // Rows Deleted: 13
}

// Here we create a simple function that will take care of errors, helping with some code clean up
func handleError(err error) {
if err != nil {
panic(err)
}
}
```
## API

Expand Down Expand Up @@ -237,6 +264,13 @@ In order to get CI, I made a recorder mechanism so you don't need to run neo4j a

You need access to a running Neo4J database to develop for this project, so that you can run the tests to generate the recordings.

## Supported Builds
* Linux (1.4.x, 1.5.x, 1.6.x, 1.7.x, 1.8.x, 1.9.x and tip)
* MacOs (1.7.x, 1.8.x, 1.9.x and tip)
*_according to https://github.com/golang/go/issues/17824, go 1.6.4 (and anything prior does not support osx 10.12) which results in unpredicable behavior (sometimes it is okay, sometimes build hangs, and sometimes build fail due to segfault)_*



## TODO

* Cypher Parser to implement NumInput and pre-flight checking
Expand Down

0 comments on commit b9ba765

Please sign in to comment.