Data types and mapping to Cypher types
The tables in this section show the mapping between Cypher data types and Go types.
When accessing a record’s content, all its properties are of type any .
This means that you have to cast them to the relevant Go type if you want to use methods/features defined on such types.
For example, if the name property coming from the database is a string, record.AsMap()["name"][1] would result in an invalid operation error at compilation time.
For it to work, cast the value to string before using it as a string: name := record.AsMap()["name"].(string) and then name[1] .
|
Core types
Cypher Type | Go Type |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Temporal types
The driver provides a set of temporal data types compliant with ISO-8601 and Cypher. Sub-second values are measured to nanosecond precision.
The driver’s types rely on Go’s time
types.
All temporal types, except neo4j.Duration
, are in fact time.Date
objects under the hood.
This means that:
-
if you want to query the database with a temporal type, instantiate a
time.Date
object and use it as query parameter (i.e. you don’t need to care about driver’s types) -
if you retrieve a temporal object that you had previously inserted starting from a
time.Date
object, you will get back atime.Date
object (i.e. you don’t need to care about driver’s types) -
if you receive a temporal object using one of Cypher temporal functions, you will get back the corresponding driver type as displayed in the table below. You may then use
.Time()
on them to convert them into Gotime.Date
objects.Cypher Type Go Type DATE
neo4j.Date
ZONED TIME
neo4j.OffsetTime
LOCAL TIME
neo4j.LocalTime
ZONED DATETIME
neo4j.Time
LOCAL DATETIME
neo4j.LocalDateTime
DURATION
neo4j.Duration
package main
import (
"fmt"
"context"
"time"
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
"reflect"
)
func main() {
ctx := context.Background()
// Connection to database
dbUri := "<URI for Neo4j database>"
dbUser := "<Username>"
dbPassword := "<Password>"
driver, _ := neo4j.NewDriverWithContext(
dbUri,
neo4j.BasicAuth(dbUser, dbPassword, ""))
driver.VerifyConnectivity(ctx)
// Define a date, with timezone
location, _ := time.LoadLocation("Europe/Stockholm")
friendsSince := time.Date(2006, time.December, 16, 13, 59, 59, 999999999, location)
result, err := neo4j.ExecuteQuery(ctx, driver, `
MERGE (a:Person {name: $name})
MERGE (b:Person {name: $friend})
MERGE (a)-[friendship:KNOWS {since: $friendsSince}]->(b)
RETURN friendship.since AS date
`, map[string]any{
"name": "Alice",
"friend": "Bob",
"friendsSince": friendsSince,
}, neo4j.EagerResultTransformer,
neo4j.ExecuteQueryWithDatabase("neo4j"))
if err != nil {
panic(err)
}
date, _ := result.Records[0].Get("date")
fmt.Println(reflect.TypeOf(date)) // time.Time
fmt.Println(date) // 2006-12-16 13:59:59.999999999 +0200 EET
}
package main
import (
"fmt"
"context"
"time"
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
"reflect"
)
func main() {
ctx := context.Background()
// Connection to database
dbUri := "<URI for Neo4j database>"
dbUser := "<Username>"
dbPassword := "<Password>"
driver, _ := neo4j.NewDriverWithContext(
dbUri,
neo4j.BasicAuth(dbUser, dbPassword, ""))
driver.VerifyConnectivity(ctx)
// Query and return a neo4j.Time object
result, err := neo4j.ExecuteQuery(ctx, driver, `
MERGE (a:Person {name: $name})
MERGE (b:Person {name: $friend})
MERGE (a)-[friendship:KNOWS {since: time()}]->(b)
RETURN friendship.since AS time
`, map[string]any{
"name": "Alice",
"friend": "Sofia",
}, neo4j.EagerResultTransformer,
neo4j.ExecuteQueryWithDatabase("neo4j"))
if err != nil {
panic(err)
}
time, _ := result.Records[0].Get("time")
fmt.Println(reflect.TypeOf(time)) // time.Time
castDate, _ := time.(neo4j.Time) // cast from `any` to `neo4j.Time`
fmt.Println(castDate.Time()) // -0001-11-30 12:18:08.973 +0000 Offset
}
Duration
Represents the difference between two points in time.
duration := neo4j.Duration{
Months: 1,
Days: 2,
Seconds: 3,
Nanos: 4,
}
fmt.Println(duration) // 'P1Y2DT3.000000004S'
For full documentation, see API documentation → Duration.
Spatial types
Cypher supports spatial values (points), and Neo4j can store these point values as properties on nodes and relationships.
The object attribute SpatialRefId
(short for Spatial Reference Identifier) is a number identifying the coordinate system the spatial type is to be interpreted in.
You can think of it as a unique identifier for each spatial type.
Cypher Type | Go Type | SpatialRefId |
---|---|---|
|
|
7203 |
|
|
4326 |
|
|
9157 |
|
|
4979 |
Spatial types are implemented in the dbtype package, so that the actual types are dbtype.Point2D/3D .
However, they are also imported in the main neo4j package, so that they can also be used as neo4j.Point2D/3D .
|
Point2D
The type Point2D
can be used to represent either a 2D Cartesian point or a 2D World Geodetic System (WGS84) point, depending on the value of SpatialRefId
.
// A 2D Cartesian Point
cartesian2d := neo4j.Point2D{
X: 1.23,
Y: 4.56,
SpatialRefId: 7203,
}
fmt.Println(cartesian2d)
// Point{srId=7203, x=1.230000, y=4.560000}
// A 2D WGS84 Point
wgs842d := neo4j.Point2D{
X: 1.23,
Y: 4.56,
SpatialRefId: 9157,
}
fmt.Println(wgs842d)
// Point{srId=9157, x=1.230000, y=4.560000}
Point3D
The type Point3D
can be used to represent either a 3D Cartesian point or a 3D World Geodetic System (WGS84) point, depending on the value of SpatialRefId
.
// A 3D Cartesian Point
cartesian3d := neo4j.Point3D{
X: 1.23,
Y: 4.56,
Z: 7.89,
SpatialRefId: 9157,
}
fmt.Println(cartesian3d)
// Point{srId=9157, x=1.230000, y=4.560000, z=7.890000}
// A 3D WGS84 Point
wgs843d := neo4j.Point3D{
X: 1.23,
Y: 4.56,
Z: 7.89,
SpatialRefId: 4979,
}
fmt.Println(wgs843d)
// Point{srId=4979, x=1.230000, y=4.560000, z=7.890000}
Graph types
Graph types are only returned as query results and may not be used as parameters.
Cypher Type | Python Type |
---|---|
|
|
|
|
|
|
Node
Represents a node in a graph.
The property ElementId
contains the database internal identifier for the entity.
This should be used with care, as no guarantees are given about the mapping between id values and elements outside the scope of a single transaction. In other words, using an ElementId
to MATCH
an element across different transactions is risky.
result, err := neo4j.ExecuteQuery(ctx, driver, `
MERGE (p:Person {name: $name}) RETURN p AS person, p.name as name
`, map[string]any{
"name": "Alice",
}, neo4j.EagerResultTransformer,
neo4j.ExecuteQueryWithDatabase("neo4j"))
if err != nil {
panic(err)
}
node, _ := result.Records[0].AsMap()["person"].(neo4j.Node)
fmt.Println("Node ID:", node.ElementId)
fmt.Println("Node labels:", node.Labels)
fmt.Println("Node properties:", node.Props)
// Node ID: 4:2691aa68-87cc-467d-9d09-431df9f5c456:0
// Node labels: [Person]
// Node properties: map[name:Alice]
For full documentation, see API documentation → Node.
Relationship
Represents a relationship in a graph.
The property ElementId
contains the database internal identifier for the entity.
This should be used with care, as no guarantees are given about the mapping between id values and elements outside the scope of a single transaction.
result, err := neo4j.ExecuteQuery(ctx, driver, `
MERGE (p:Person {name: $name})
MERGE (p)-[r:KNOWS {status: $status, since: date()}]->(friend:Person {name: $friendName})
RETURN r AS friendship
`, map[string]any{
"name": "Alice",
"status": "BFF",
"friendName": "Bob",
}, neo4j.EagerResultTransformer,
neo4j.ExecuteQueryWithDatabase("neo4j"))
if err != nil {
panic(err)
}
relationship, _ := result.Records[0].AsMap()["friendship"].(neo4j.Relationship)
fmt.Println("Relationship ID:", relationship.ElementId)
fmt.Println("Relationship type:", relationship.Type)
fmt.Println("Relationship properties:", relationship.Props)
fmt.Println("Relationship start elID:", relationship.StartElementId)
fmt.Println("Relationship end elID:", relationship.EndElementId)
// Relationship ID: 5:2691aa68-87cc-467d-9d09-431df9f5c456:0
// Relationship type: KNOWS
// Relationship properties: map[since:{0 63824025600 <nil>} status:BFF]
// Relationship start elID: 4:2691aa68-87cc-467d-9d09-431df9f5c456:0
// Relationship end elID: 4:2691aa68-87cc-467d-9d09-431df9f5c456:1
For full documentation, see API documentation → Relationship.
Path
Represents a path in a graph.
package main
import (
"fmt"
"context"
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
)
func main() {
ctx := context.Background()
// Connection to database
dbUri := "<URI for Neo4j database>"
dbUser := "<Username>"
dbPassword := "<Password>"
driver, _ := neo4j.NewDriverWithContext(
dbUri,
neo4j.BasicAuth(dbUser, dbPassword, ""))
driver.VerifyConnectivity(ctx)
// Create some :Person nodes linked by :KNOWS relationships
addFriend(ctx, driver, "Alice", "BFF", "Bob")
addFriend(ctx, driver, "Bob", "Fiends", "Sofia")
addFriend(ctx, driver, "Sofia", "Acquaintances", "Sofia")
// Follow :KNOWS relationships outgoing from Alice three times, return as path
result, err := neo4j.ExecuteQuery(ctx, driver, `
MATCH path=(:Person {name: $name})-[:KNOWS*3]->(:Person)
RETURN path AS friendshipChain
`, map[string]any{
"name": "Alice",
}, neo4j.EagerResultTransformer,
neo4j.ExecuteQueryWithDatabase("neo4j"))
if err != nil {
panic(err)
}
path := result.Records[0].AsMap()["friendshipChain"].(neo4j.Path)
fmt.Println("-- Path breakdown --")
for i := range path.Relationships {
name := path.Nodes[i].Props["name"]
status := path.Relationships[i].Props["status"]
friendName := path.Nodes[i+1].Props["name"]
fmt.Printf("%s is friends with %s (%s)\n", name, friendName, status)
}
}
func addFriend(ctx context.Context, driver neo4j.DriverWithContext, name string, status string, friendName string) {
_, err := neo4j.ExecuteQuery(ctx, driver, `
MERGE (p:Person {name: $name})
MERGE (p)-[r:KNOWS {status: $status, since: date()}]->(friend:Person {name: $friendName})
`, map[string]any{
"name": name,
"status": status,
"friendName": friendName,
}, neo4j.EagerResultTransformer,
neo4j.ExecuteQueryWithDatabase("neo4j"))
if err != nil {
panic(err)
}
}
For full documentation, see API documentation → Path.
Exceptions
For the most part, the driver simply forwards any error the server may raise. For a list of errors the server can return, see the Status code page.
Some server errors are marked as safe to retry without need to alter the original request.
Examples of such errors are deadlocks, memory issues, or connectivity issues.
When an error is raised, the function neo4j.IsRetryable(error)
gives insights into whether a further attempt might be successful.
This is particular useful when running queries in explicit transactions, to know if a failed query should be run again. Note that managed transactions already implement a retry mechanism, so you don’t need to implement your own.
Glossary
- LTS
-
A Long Term Support release is one guaranteed to be supported for a number of years. Neo4j 4.4 is LTS, and Neo4j 5 will also have an LTS version.
- Aura
-
Aura is Neo4j’s fully managed cloud service. It comes with both free and paid plans.
- Cypher
-
Cypher is Neo4j’s graph query language that lets you retrieve data from the database. It is like SQL, but for graphs.
- APOC
-
Awesome Procedures On Cypher (APOC) is a library of (many) functions that can not be easily expressed in Cypher itself.
- Bolt
-
Bolt is the protocol used for interaction between Neo4j instances and drivers. It listens on port 7687 by default.
- ACID
-
Atomicity, Consistency, Isolation, Durability (ACID) are properties guaranteeing that database transactions are processed reliably. An ACID-compliant DBMS ensures that the data in the database remains accurate and consistent despite failures.
- eventual consistency
-
A database is eventually consistent if it provides the guarantee that all cluster members will, at some point in time, store the latest version of the data.
- causal consistency
-
A database is causally consistent if read and write queries are seen by every member of the cluster in the same order. This is stronger than eventual consistency.
- NULL
-
The null marker is not a type but a placeholder for absence of value. For more information, see Cypher → Working with
null
. - transaction
-
A transaction is a unit of work that is either committed in its entirety or rolled back on failure. An example is a bank transfer: it involves multiple steps, but they must all succeed or be reverted, to avoid money being subtracted from one account but not added to the other.
- backpressure
-
Backpressure is a force opposing the flow of data. It ensures that the client is not being overwhelmed by data faster than it can handle.
- transaction function
-
A transaction function is a callback executed by an
ExecuteRead
orExecuteWrite
call. The driver automatically re-executes the callback in case of server failure. - DriverWithContext
-
A
DriverWithContext
object holds the details required to establish connections with a Neo4j database.