At this point, we should take a look at the Cypher type system. As Neo4j is written in Java (the j in Neo4j stands for Java after all), there are some discrepancies between the types stored in the Neo4j database and native Go types.
Some values like strings, floats, booleans, and nulls map directly to Go types, but more complex types need special handling.
Go Types to Neo4j Types
Go Type | Neo4j Cypher Type | Notes |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
See Durations |
|
|
|
|
|
|
|
|
|
|
|
* When a time.Time value is sent/received through the driver and its Zone() returns a name of Offset, the value is stored with its offset value rather than its zone name.
Let’s take a look at some of these types in more detail.
Nodes & Relationships
Working with Nodes and Relationships are very similar.
Both have an .Id
property which represents the internal Neo4j ID as an int64
and a .Props
property which holds a map of properties.
As an example, let’s take the following code snippet:
result, err := transaction.Run(
"MATCH path = (p:Person)-[r:ACTED_IN]->(m:Movie {title: $title}) RETURN p, r, m, path",
map[string]interface{}{"title": "Arthur"})
if err != nil {
return nil, err
}
The query will return one record for each :Person
and :Movie
node with an :ACTED_IN
relationship between them.
Extracting Values
As mentioned in the previous lesson, you can use the alias as defined in the RETURN
portion of the Cypher statement:
for result.NextRecord(&record) {
// Get the value of `m`
movie, _ := record.Get("m")
movieByAlias := movie.(neo4j.Node) // m, a :Movie node as type neo4j.Node()
}
Or alternatively, you can either retrieve a value by its index in the Values
property:
// Check keys to find the index
fmt.Println(result.Keys()) // ['p', 'm', 'a', 'path']
for result.NextRecord(&record) {
// Get the first value, in this case `p`
personByIndex := record.Values[0].(neo4j.Node) // p, a :Person node as type neo4j.Node()
}
When casting an item, you can use the second returned value to check that the value has been successfully cast as the requested type.
// Get a Relationship and check assertion
actedInRelationship, _ := record.Get("r")
actedIn, ok := actedInRelationship.(neo4j.Relationship)
if !ok {
// Value is not a relationship
return nil, fmt.Errorf("expected a neo4j.Relationship, got: %v", reflect.TypeOf(result))
}
Working with Nodes
Node struct Definition
type Node struct {
// Deprecated: Id is deprecated and will be removed in 6.0. Use ElementId instead.
Id int64 // Id of this Node.
ElementId string // ElementId of this Node.
Labels []string // Labels attached to this Node.
Props map[string]any // Properties of this Node.
}
This definition has been taken from the source code for the Neo4j Go Driver
neo4j.Node
is a struct
that allows you to access:
-
.Id
- The Internal ID of the Node as anint64
eg8491
-
.Labels
- An array of strings representing the labels attributed to the Node
eg.['Person
, 'Actor'] -
.Props
- A map of properties assigned to the Node
eg.{name: 'Tom Hanks', tmdbId: '31' }
personNode, _ := record.Get("p")
person := personNode.(neo4j.Node)
fmt.Println(person.Id) // (1)
fmt.Println(person.Labels) // (2)
fmt.Println(person.Props) // (3)
Internal IDs
Internal IDs refer to the position in the Neo4j store files where the record is held. These numbers can be re-used, a best practice is to always look up a node by an indexed property rather than relying on an internal ID.Working with Relationships
Relationship struct Definition
type Relationship struct {
// Deprecated: Id is deprecated and will be removed in 6.0. Use ElementId instead.
Id int64 // Id of this Relationship.
ElementId string // ElementId of this Relationship.
// Deprecated: StartId is deprecated and will be removed in 6.0. Use StartElementId instead.
StartId int64 // Id of the start Node of this Relationship.
StartElementId string // ElementId of the start Node of this Relationship.
// Deprecated: EndId is deprecated and will be removed in 6.0. Use EndElementId instead.
EndId int64 // Id of the end Node of this Relationship.
EndElementId string // ElementId of the end Node of this Relationship.
Type string // Type of this Relationship.
Props map[string]any // Properties of this Relationship.
}
This definition has been taken from the source code for the Neo4j Go Driver
neo4j.Relationship
is a struct
that allows you to access:
-
.Id
- The internal ID of the relationship as anint64
eg.9876
-
.Type
- The type of the relationship
eg.ACTED_IN
-
.Props
- A map of properties assigned to the Relationship
eg.{role: 'Woody'}
-
.StartId
- The internal ID for the node at the start of the relationship -
.EndId
- The internal ID for the node at the end of the relationship
Working with Paths
Path struct Definition
type Path struct {
Nodes []Node // All the nodes in the path.
Relationships []Relationship
}
This definition has been taken from the source code for the Neo4j Go Driver
If you return a path of nodes and relationships, they will be returned as an instance of a neo4j.Path
.
returnedPath, _ := record.Get("path")
path := returnedPath.(neo4j.Path)
nodes := path.Nodes
relationships := path.Relationships
for node := range nodes {
fmt.Println(node) // neo4j.Node
}
for relationship := range relationships {
fmt.Println(relationship) // neo4j.Relationship
}
Path Segments
A path is split into segments representing each relationship in the path.
For example, say we have a path of (p:Person)-[:ACTED_IN]→(m:Movie)-[:IN_GENRE]→(g:Genre)
, there would be two relationships.
-
(p:Person)-[:ACTED_IN]→(m:Movie)
-
(m:Movie)-[:IN_GENRE]→(g:Genre)
You can access the relationships within the path through the .Relationships
property, and the nodes in the path can be accessed through the .Nodes
property.
returnedPath, _ := record.Get("path")
path := returnedPath.(neo4j.Path)
nodes := path.Nodes
relationships := path.Relationships
for node := range nodes {
fmt.Println(node) // neo4j.Node
}
for relationship := range relationships {
fmt.Println(relationship) // neo4j.Relationship
}
Temporal Data Types
Temporal data types are extensions of Go’s time.Time
type.
You can access the individual parts of the Time
struct using the appropriate method.
timeProperty, _ := record.Get("time")
time := timeProperty.(neo4j.Time).Time()
fmt.Println(time.Year()) // 2022
fmt.Println(time.Month()) // January
fmt.Println(time.Day()) // 4
// For Time, DateTime,
fmt.Println(time.Day()) // 4
Read more time support from gobyexample.com
Durations
The neo4j.Duration
type provides properties for accessing the Neo4j duration
type.
A Neo4j duration type contains the following data:
-
Months
- anint64
-
Days
- anint64
-
Seconds
- anint64
-
Nanos
- anint
// duration('P1Y2M3DT12H34M56S')
// 1 year, 2 months, 3 days; 12 hours, 34 minutes, 56 seconds
durationProperty, _ := record.Get("duration")
duration := durationProperty.(neo4j.Duration)
fmt.Println(duration.Months) // 14 (1 year, 2 months = 14 months)
fmt.Println(duration.Days) // 3
fmt.Println(duration.Seconds) // 45296
fmt.Println(duration.Nanos) // 987600000
Spatial Data Types
Cypher has built-in support for handling spatial values (Point
s), and the underlying database supports storing these point values as properties on nodes and relationships.
Points
Points can be stored in Neo4j as 2D points (x
and y
, or latitude
and longitude
) or 3D points (x
, y
and z
, or latitude
, longitude
and height
).
The Neo4j Go Driver provides two types to represent these types, Point2D
and Point3D
.
Both of these types have .X
and .Y
properties along with a .SpatialRefId
which is used to represent the ID of the coordinate reference system.
SpatialRefId | Description | Cypher Example |
---|---|---|
7203 |
Point2D in the cartesian space. |
|
4326 |
Point2D in the WGS84 space. |
|
9157 |
Point3D in the cartesian space. |
|
4979 |
Point3D in the WGS84 space. |
|
Point2D
A Point2D
struct represents a two-dimensional in Cartesian space or in the WGS84 space.
The .SpatialRefId
property indicates the type of the coordinate and the .X
and .Y
property represent the location.
When a type is created with latitude
and longitude
values, the values are saved as Y
and X
respectively.
wgs842DResult, _ := record.Get("wgs842D")
wgs842D := wgs842DResult.(neo4j.Point2D)
// {SpatialRefId: xxxx, X: 10, y: 20}
cartesian2DResult, _ := record.Get("cartesian2D")
cartesian2D := cartesian2DResult.(neo4j.Point2D)
// {SpatialRefId: xxxx, X: 10, y: 20}
Point3D
The Point3D
struct is similar to the Point2D
type, with the addition of a .Z
property.
wgs842DResult, _ := record.Get("wgs842D")
wgs842D := wgs842DResult.(neo4j.Point2D)
// {SpatialRefId: xxxx, X: 10, y: 20}
cartesian2DResult, _ := record.Get("cartesian2D")
cartesian2D := cartesian2DResult.(neo4j.Point2D)
// {SpatialRefId: xxxx, X: 10, y: 20}
Distance
When using the point.distance
function in Cypher, the distance calculated between two points is returned as a float.
WITH point({x: 1, y:1}) AS one,
point({x: 10, y: 10}) AS two
RETURN point.distance(one, two) // 12.727922061357855
For more information on Spatial types, see the Cypher Manual.
Check Your Understanding
1. Accessing Node Properties
Which of the following options is a valid method for accessing the name
property on the node object node
?
-
❏
node["name"]
-
❏
node.Props.name
-
✓
node.Props["name"]
-
❏
node.Properties["name"]
-
❏
node.Get("name")
-
❏
PropertyOf(node, "name")
Hint
Node and relationship properties are stored in the Props
member.
Solution
The answer is node.Props["name"]
.
2. Relationship Types
What method would you use to access the type of the relationship actedIn
?
-
❏
actedIn["type"]
-
❏
actedIn.Labels
-
✓
actedIn.Type
-
❏
neo4j.GetRelationshipType(actedIn)
Hint
You would use this method to access the type of the relationship.
Solution
The answer is actedIn.Type
.
Lesson Summary
In this lesson you have learned how to handle data returned by a Cypher statement.
As we progress through this module, you will use the knowledge gained so far to read data from, and write data back to the database. In the next lesson you will learn how to add type safety to your Go project.