Working with Cypher values
The Cypher type system
Drivers translate between application language types and the Cypher type system. To pass parameters and process results, it is important to know the basics of how Cypher works with types and to understand how the Cypher types are mapped in the driver.
The table below shows the available data types. All can be potentially found in results although not all can be used as parameters.
Cypher Type | Parameter | Result |
---|---|---|
|
✔ |
✔ |
|
✔ |
✔ |
|
✔ |
✔ |
|
✔ |
✔ |
|
✔ |
✔ |
|
✔ |
✔ |
|
✔ |
✔ |
|
✔ |
✔ |
|
✔ |
✔ |
|
✔ |
✔ |
|
✔ |
✔ |
|
✔ |
✔ |
|
✔ |
✔ |
|
✔ |
✔ |
|
✔ |
✔ |
|
✔ |
|
|
✔ |
|
|
✔ |
* The null marker is not a type but a placeholder for absence of value.
For information on how to work with null in Cypher, please refer to Cypher Manual → Working with null
.
** Nodes, relationships and paths are passed in results as snapshots of the original graph entities. While the original entity IDs are included in these snapshots, no permanent link is retained back to the underlying server-side entities, which may be deleted or otherwise altered independently of the client copies. Graph structures may not be used as parameters because it depends on application context whether such a parameter would be passed by reference or by value, and Cypher provides no mechanism to denote this. Equivalent functionality is available by simply passing either the ID for pass-by-reference, or an extracted map of properties for pass-by-value.
The Neo4j driver maps Cypher types to and from native language types as depicted in the table below. Custom types (those not available in the language or standard library) are highlighted in bold.
Neo4j type | .NET type |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* Time zone names adhere to the IANA system, rather than the Windows system. Inbound conversion is carried out using Extended Windows-Olson zid mapping as defined by Unicode CLDR.
Neo4j type | Go type |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* 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.
Neo4j type | Java type |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* A Duration
or Period
passed as a parameter will always be implicitly converted to IsoDuration
.
Neo4j type | JavaScript type |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* JavaScript has no native integer type so a custom type is provided. For convenience, this can be disabled through configuration so that the native Number type is used instead. Note that this can lead to a loss of precision.
Neo4j type | Python 2 type | Python 3 type |
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
* While Cypher uses 64-bit signed integers, int
can only hold integers up to sys.maxint
in Python 2; long
is used for values above this.
** In Python 2, a str
passed as a parameter will always be implicitly converted to unicode
via UTF-8.
*** A timedelta
passed as a parameter will always be implicitly converted to neotime.Duration
.
† Where tzinfo is not None.
†† Where tzinfo is None.
Statement results
A statement result is comprised of a stream of records. The result is typically handled by the receiving application as it arrives, although it is also possible to retain all or part of a result for future consumption.
Records
A record provides an immutable view of part of a result. It is an ordered map of keys and values. As values within a record are both keyed and ordered, that value can be accessed either by position (0-based integer) or by key (string).
The buffer
Most drivers contain a result buffer. This provides a staging point for results, and divides result handling into fetching (moving from the network to the buffer) and consuming (moving from the buffer to the application).
If results are consumed in the same order as they are produced, records merely pass through the buffer; if they are consumed out of order, the buffer will be utilized to retain records until they are consumed by the application. For large results, this may require a significant amount of memory and impact performance. For this reason, it is recommended to consume results in order wherever possible.
Consuming the stream
Query results will often be consumed as a stream. Drivers provide a language-idiomatic way to iterate through a result stream.
public List<string> GetPeople()
{
using (var session = Driver.Session())
{
return session.ReadTransaction(tx =>
{
var result = tx.Run("MATCH (a:Person) RETURN a.name ORDER BY a.name");
return result.Select(record => record[0].As<string>()).ToList();
});
}
}
func getPeople(driver neo4j.Driver) ([]string, error) {
session, err := driver.Session(neo4j.AccessModeRead)
if err != nil {
return nil, err
}
defer session.Close()
people, err := session.ReadTransaction(func(tx neo4j.Transaction) (interface{}, error) {
var list []string
result, err := tx.Run("MATCH (a:Person) RETURN a.name ORDER BY a.name", nil)
if err != nil {
return nil, err
}
for result.Next() {
list = append(list, result.Record().GetByIndex(0).(string))
}
if err = result.Err(); err != nil {
return nil, err
}
return list, nil
})
if err != nil {
return nil, err
}
return people.([]string), nil
}
public List<String> getPeople()
{
try ( Session session = driver.session() )
{
return session.readTransaction( new TransactionWork<List<String>>()
{
@Override
public List<String> execute( Transaction tx )
{
return matchPersonNodes( tx );
}
} );
}
}
private static List<String> matchPersonNodes( Transaction tx )
{
List<String> names = new ArrayList<>();
StatementResult result = tx.run( "MATCH (a:Person) RETURN a.name ORDER BY a.name" );
while ( result.hasNext() )
{
names.add( result.next().get( 0 ).asString() );
}
return names;
}
const session = driver.session()
const result = session.run(
'MATCH (a:Person) RETURN a.name ORDER BY a.name'
)
const collectedNames = []
result.subscribe({
onNext: record => {
const name = record.get(0)
collectedNames.push(name)
},
onCompleted: () => {
session.close()
console.log('Names: ' + collectedNames.join(', '))
},
onError: error => {
console.log(error)
}
})
def get_people(self):
with self._driver.session() as session:
return session.read_transaction(self.match_person_nodes)
@staticmethod
def match_person_nodes(tx):
result = tx.run("MATCH (a:Person) RETURN a.name ORDER BY a.name")
return [record["a.name"] for record in result]
Retaining results
The result record stream is available until another statement is run in the session, or until the current transaction is closed. To hold on to the results beyond this scope, the results need to be explicitly retained. For retaining results, each driver offers methods that collect the result stream and translate it into standard data structures for that language. Retained results can be processed while the session is free to take on the next workload. The saved results can also be used directly to run a new statement.
public int AddEmployees(string companyName)
{
using (var session = Driver.Session())
{
var persons =
session.ReadTransaction(tx => tx.Run("MATCH (a:Person) RETURN a.name AS name").ToList());
return persons.Sum(person => session.WriteTransaction(tx =>
{
tx.Run("MATCH (emp:Person {name: $person_name}) " +
"MERGE (com:Company {name: $company_name}) " +
"MERGE (emp)-[:WORKS_FOR]->(com)",
new {person_name = person["name"].As<string>(), company_name = companyName});
return 1;
}));
}
}
func addPersonsAsEmployees(driver neo4j.Driver, companyName string) (int, error) {
session, err := driver.Session(neo4j.AccessModeWrite)
if err != nil {
return 0, err
}
defer session.Close()
persons, err := neo4j.Collect(session.ReadTransaction(func(tx neo4j.Transaction) (interface{}, error) {
return tx.Run("MATCH (a:Person) RETURN a.name AS name", nil)
}))
if err != nil {
return 0, err
}
employees := 0
for _, person := range persons {
_, err = session.WriteTransaction(func(tx neo4j.Transaction) (interface{}, error) {
return tx.Run("MATCH (emp:Person {name: $person_name}) "+
"MERGE (com:Company {name: $company_name}) "+
"MERGE (emp)-[:WORKS_FOR]->(com)", map[string]interface{}{"person_name": person.GetByIndex(0), "company_name": companyName})
})
if err != nil {
return 0, err
}
employees++
}
return employees, nil
}
public int addEmployees( final String companyName )
{
try ( Session session = driver.session() )
{
int employees = 0;
List<Record> persons = session.readTransaction( new TransactionWork<List<Record>>()
{
@Override
public List<Record> execute( Transaction tx )
{
return matchPersonNodes( tx );
}
} );
for ( final Record person : persons )
{
employees += session.writeTransaction( new TransactionWork<Integer>()
{
@Override
public Integer execute( Transaction tx )
{
tx.run( "MATCH (emp:Person {name: $person_name}) " +
"MERGE (com:Company {name: $company_name}) " +
"MERGE (emp)-[:WORKS_FOR]->(com)",
parameters( "person_name", person.get( "name" ).asString(), "company_name",
companyName ) );
return 1;
}
} );
}
return employees;
}
}
private static List<Record> matchPersonNodes( Transaction tx )
{
return tx.run( "MATCH (a:Person) RETURN a.name AS name" ).list();
}
const session = driver.session()
const readTxPromise = session.readTransaction(tx =>
tx.run('MATCH (a:Person) RETURN a.name AS name')
)
const addEmployeesPromise = readTxPromise.then(result => {
const nameRecords = result.records
let writeTxsPromise = Promise.resolve()
for (let i = 0; i < nameRecords.length; i++) {
const name = nameRecords[i].get('name')
writeTxsPromise = writeTxsPromise.then(() =>
session.writeTransaction(tx =>
tx.run(
'MATCH (emp:Person {name: $person_name}) ' +
'MERGE (com:Company {name: $company_name}) ' +
'MERGE (emp)-[:WORKS_FOR]->(com)',
{ person_name: name, company_name: companyName }
)
)
)
}
return writeTxsPromise.then(() => nameRecords.length)
})
addEmployeesPromise.then(employeesCreated => {
session.close()
console.log('Created ' + employeesCreated + ' employees')
})
def add_employees(self, company_name):
with self._driver.session() as session:
employees = 0
persons = session.read_transaction(self.match_person_nodes)
for person in persons:
employees += session.write_transaction(self.add_employee_to_company, person, company_name)
return employees
@staticmethod
def add_employee_to_company(tx, person, company_name):
tx.run("MATCH (emp:Person {name: $person_name}) "
"MERGE (com:Company {name: $company_name}) "
"MERGE (emp)-[:WORKS_FOR]->(com)",
person_name=person["name"], company_name=company_name)
return 1
@staticmethod
def match_person_nodes(tx):
return list(tx.run("MATCH (a:Person) RETURN a.name AS name"))
Statement result summaries
Supplementary information such as query statistics, timings and server information can be obtained from the statement result summary. If this detail is accessed before the entire result has been consumed, the remainder of the result will be buffered.
See also the language-specific driver API documentation.