Chapter 4. Drivers

Table of Contents

This chapter contains the complete documentation of the Neo4j Bolt drivers.

Neo4j Drivers are drivers for the Neo4j database. The drivers use the Bolt protocol and have uniform design and use.

4.1. Introduction

The preferred way to access a Neo4j server from an application is to use a Neo4j driver.

The Neo4j Driver API is the preferred means of programmatic interaction with a Neo4j database server. It implements the Bolt protocol and is available in four languages: C#.NET, Java, JavaScript, and Python.

The API is defined independently of any programming language. This allows for a high degree of uniformity across languages. Uniformity means that the same features are included in all the drivers. The uniformity also influences the design of the API in each language. This provides consistency across drivers, while retaining affinity with the idioms of each programming language.

4.2. Getting Started

Get up and running quickly with a minimal working example.

4.2.1. Get the driver

4.2.1.1. Versions

Consult the version table to determine which version of the driver to use with a particular Neo4j server.

Table 4.1. Supported versions
Driver version Neo4j version Bolt protocol version

1.0

3.0

1

You can download the driver source or acquire it with one of the dependency managers of your language.

Example 4.1. Example: Acquire the driver

Using NuGet in Visual Studio:

PM> Install-Package Neo4j.Driver -Version 1.0.2

When using Maven, add this to your pom.xml file:

<dependencies>
    <dependency>
        <groupId>org.neo4j.driver</groupId>
        <artifactId>neo4j-java-driver</artifactId>
        <version>1.0.5</version>
    </dependency>
</dependencies>

For Gradle or Grails, this is how to add the dependency:

compile 'org.neo4j.driver:neo4j-java-driver:1.0.5'

For other build systems, see information available at Maven Central.

npm install neo4j-driver@1.0.3
pip install neo4j-driver==1.0.2

4.2.2. Use the driver

Each Neo4j driver has a database object for creating a driver. To use a driver, follow this pattern:

  1. Ask the database object for a new driver.
  2. Ask the driver object for a new session.
  3. Use the session object to run statements. It returns an object representing the results.
  4. Process the results.
  5. Close the session.
Example 4.2. Example: Use the driver

For a minimal working example, the following imports are necessary.

using Neo4j.Driver.V1;

These can then be used to run a statement against the server.

using (var driver = GraphDatabase.Driver("bolt://localhost", AuthTokens.Basic("neo4j", "neo4j")))
using (var session = driver.Session())
{
    session.Run("CREATE (a:Person {name:'Arthur', title:'King'})");
    var result = session.Run("MATCH (a:Person) WHERE a.name = 'Arthur' RETURN a.name AS name, a.title AS title");

    foreach (var record in result)
    {
        Output.WriteLine($"{record["title"].As<string>()} {record["name"].As<string>()}");
    }
}

For a minimal working example, the following imports are necessary.

import org.neo4j.driver.v1.*;

These can then be used to run a statement against the server.

Driver driver = GraphDatabase.driver( "bolt://localhost", AuthTokens.basic( "neo4j", "neo4j" ) );
Session session = driver.session();

session.run( "CREATE (a:Person {name:'Arthur', title:'King'})" );

StatementResult result = session.run( "MATCH (a:Person) WHERE a.name = 'Arthur' RETURN a.name AS name, a.title AS title" );
while ( result.hasNext() )
{
    Record record = result.next();
    System.out.println( record.get( "title" ).asString() + " " + record.get("name").asString() );
}

session.close();
driver.close();

For a minimal working example, the following imports are necessary.

var neo4j = require('neo4j-driver').v1;

These can then be used to run a statement against the server.

var driver = neo4j.driver("bolt://localhost", neo4j.auth.basic("neo4j", "neo4j"));
var session = driver.session();
session
  .run( "CREATE (a:Person {name:'Arthur', title:'King'})" )
  .then( function()
  {
    return session.run( "MATCH (a:Person) WHERE a.name = 'Arthur' RETURN a.name AS name, a.title AS title" )
  })
  .then( function( result ) {
    console.log( result.records[0].get("title") + " " + result.records[0].get("name") );
    session.close();
    driver.close();
  })

For a minimal working example, the following imports are necessary.

from neo4j.v1 import GraphDatabase, basic_auth

These can then be used to run a statement against the server.

driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neo4j", "neo4j"))
session = driver.session()

session.run("CREATE (a:Person {name:'Arthur', title:'King'})")

result = session.run("MATCH (a:Person) WHERE a.name = 'Arthur' RETURN a.name AS name, a.title AS title")
for record in result:
    print("%s %s" % (record["title"], record["name"]))

session.close()

As seen above, it is possible to run statements directly from the session object. The session will take care of opening and closing the transaction. The session also provides the opportunity for a user to manage the transaction explicitly. See Section 4.4.2, “Transaction management” for details.

4.3. Driver

A driver is used to connect to a Neo4j server. It provides sessions that are used to execute statements and retrieve results.

4.3.1. Construction

The driver package includes a graph database object. This object provides driver instances. When requesting a driver instance from the database object a URL is provided. The URL declares the protocol, host name, and port for the Neo4j server.

4.3.1.1. Bolt URL format

A Bolt URL follows the standard URL pattern of scheme://hostname:port. For example, bolt://server:1234 would connect using the Bolt protocol to server on port 1234. If no port is provided in the URL, the default port 7687 is used. For example, bolt://server amounts to the same as bolt://server:7687.

Example 4.3. Example: Create a driver
var driver = GraphDatabase.Driver("bolt://localhost", AuthTokens.Basic("neo4j", "neo4j"));
Driver driver = GraphDatabase.driver( "bolt://localhost", AuthTokens.basic("neo4j", "neo4j") );
driverGlobal = neo4j.driver("bolt://localhost", neo4j.auth.basic("neo4j", "neo4j"));
driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neo4j", "neo4j"))

4.3.2. Configuration

In addition to the Bolt URL, the driver can be configured for:

  • session pool size
  • session pool behavior
  • authentication strategies
  • logging
Name Description Default

Session pool size (*)

Max number of sessions per URL.

50

Logging

Provide a logging facility for the driver.

N/A

(*) The .NET and Python drivers provide idle session pool size, which is the maximum number of idle sessions to be pooled by the driver. There is no limit to how many sessions that can be created, but a maximum limits how many sessions will be buffered after they are returned to the session pool.

Example 4.4. Example: Create a driver with configuration
var driver = GraphDatabase.Driver("bolt:localhost", AuthTokens.Basic("neo4j", "neo4j"),
    Config.Builder.WithMaxIdleSessionPoolSize(10).ToConfig());
Driver driver = GraphDatabase.driver(
        "bolt://localhost",
        AuthTokens.basic("neo4j", "neo4j"),
        Config.build().withMaxSessions( 10 ).toConfig() );
var driver = neo4j.driver("bolt://localhost", neo4j.auth.basic("neo4j", "neo4j"), {connectionPoolSize: 50});
driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neo4j", "neo4j"), max_pool_size=10)

4.3.2.1. Encryption and authentication

All Neo4j drivers support encrypting the traffic between the Neo4j driver and the Neo4j instance using SSL/TLS. Neo4j will by default allow both encrypted and unencrypted connections from drivers. This can be modified to require encryption for all connections. Please see the Neo4j Operations Manual → Configuring Bolt Connectors for more information.

We strongly encourage using encryption to keep authentication credentials and data stored in Neo4j secure. Because of this most drivers will turn encryption on by default. However, due to technical limitations, the .NET driver does not have encryption enabled by default. For similar reasons, the JavaScript driver does not have encryption enabled by default when running inside a Web Browser.

4.3.2.1.1. Encryption

The driver can be configured to turn on or off TLS encryption. Encryption should only be turned off in a trusted, internal network. While most drivers default to enabling encryption, it is good practice to configure encryption explicitly. This makes it clear encryption is used for other developers reading the code and minimizes the risk of mistakes from relying on defaults.

Example 4.5. Example: Configure driver to require TLS encryption
var driver = GraphDatabase.Driver("bolt://localhost:7687", AuthTokens.Basic("neo4j", "neo4j"),
    Config.Builder.WithEncryptionLevel(EncryptionLevel.Encrypted).ToConfig());
Driver driver = GraphDatabase.driver( "bolt://localhost", AuthTokens.basic("neo4j", "neo4j"),
        Config.build().withEncryptionLevel( Config.EncryptionLevel.REQUIRED ).toConfig() );
var driver = neo4j.driver("bolt://localhost", neo4j.auth.basic("neo4j", "neo4j"), {
  // In NodeJS, encryption is on by default. In the web bundle, it is off.
  encrypted:true
});
driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neo4j", "neo4j"), encrypted=True)
4.3.2.1.2. Trust

When establishing an encrypted connection, it needs to be verified that the remote peer is who we expected to connect to. Without verifying this, an attacker can easily perform a so-called "man-in-the-middle" attack, tricking the driver to establish an encrypted connection with her instead of the Neo4j Instance. Because of this, Neo4j drivers do not offer a way to establish encrypted connections without also establishing trust.

Two trust strategies are available:

  • Trust on first use.
  • Trust signed certificate.

Trust on first use means that the driver will trust the first connection to a host to be safe and intentional. On subsequent connections, the driver will verify that it communicates with the same host as on the first connection. To ensure that this authentication strategy is valid, the first connection to the server must be established in a trusted network environment.

This strategy is the same as the default strategy used by the ssh command line tool. It provides a good degree of security with no configuration. For this reason it is the default strategy on all platforms that support it.

Because of technical limitations this strategy is not available for the .NET platform or when using the JavaScript driver inside a web browser.

Example 4.6. Example: Configure driver to trust on first use
// Not supported in .Net driver
Driver driver = GraphDatabase.driver( "bolt://localhost", AuthTokens.basic("neo4j", "neo4j"), Config.build()
        .withEncryptionLevel( Config.EncryptionLevel.REQUIRED )
        .withTrustStrategy( Config.TrustStrategy.trustOnFirstUse( new File( "/path/to/neo4j_known_hosts" ) ) )
        .toConfig() );
var driver = neo4j.driver("bolt://localhost", neo4j.auth.basic("neo4j", "neo4j"), {
  // Note that trust-on-first-use is not available in the browser bundle,
  // in NodeJS, trust-on-first-use is the default trust mode. In the browser
  // it is TRUST_SIGNED_CERTIFICATES.
  trust: "TRUST_ON_FIRST_USE",
  encrypted:true
});
driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neo4j", "neo4j"), encrypted=True, trust=TRUST_ON_FIRST_USE)

Trust signed certificate means that the driver will only connect to a Neo4j server if it provides a certificate trusted by the driver. A trusted certificate is a certificate explicitly configured to be trusted by the driver or, more commonly, a certificate signed by a certificate the driver trusts. All drivers support this trust strategy.

The way to install a trusted certificate for use by a driver varies for different drivers. The Java driver directly accepts the file path to the trusted certificate and loads the certificate automatically at runtime. The .NET and Python drivers require the certificate to first be installed into the operating system’s trusted certificate store.

Example 4.7. Example: Configure driver to trust signed certificate
var driver = GraphDatabase.Driver("bolt://localhost:7687", AuthTokens.Basic("neo4j", "neo4j"),
    Config.Builder.WithEncryptionLevel(EncryptionLevel.Encrypted).ToConfig());
Driver driver = GraphDatabase.driver( "bolt://localhost", AuthTokens.basic("neo4j", "neo4j"), Config.build()
        .withEncryptionLevel( Config.EncryptionLevel.REQUIRED )
        .withTrustStrategy( Config.TrustStrategy.trustSignedBy( new File( "/path/to/ca-certificate.pem") ) )
        .toConfig() );
var driver = neo4j.driver("bolt://localhost", neo4j.auth.basic("neo4j", "neo4j"), {
  trust: "TRUST_SIGNED_CERTIFICATES",
  // Configuring which certificates to trust here is only available
  // in NodeJS. In the browser bundle the browsers list of trusted
  // certificates is used, due to technical limitations in some browsers.
  trustedCertificates : ["path/to/ca.crt"],
  encrypted:true
});
driver = GraphDatabase.driver("bolt://localhost", auth=basic_auth("neo4j", "neo4j"), encrypted=True, trust=TRUST_SIGNED_CERTIFICATES)
4.3.2.1.3. Authenticate user

The server will require the driver to provide authentication credentials for the user to connect to the database, unless authentication has been disabled.

Example 4.8. Example: Connecting the driver to a server with authentication disabled
var driver = GraphDatabase.Driver("bolt://localhost:7687",
    Config.Builder.WithEncryptionLevel(EncryptionLevel.Encrypted).ToConfig());
Driver driver = GraphDatabase.driver( "bolt://localhost",
        Config.build().withEncryptionLevel( Config.EncryptionLevel.REQUIRED ).toConfig() );
var driver = neo4j.driver("bolt://localhost", {
  // In NodeJS, encryption is on by default. In the web bundle, it is off.
  encrypted:true
});
driver = GraphDatabase.driver("bolt://localhost", encrypted=True)

If communication between the driver and the server is not encrypted, authentication credentials are sent as plain text. It is highly recommended to use encryption when using authentication.

When connecting to Neo4j for the first time, the user must change the default password. Given a fresh Neo4j server installation, any attempt to connect to the Neo4j server with the default password will provide a notifcation indicating that the credentials have expired and the password needs to be updated. The browser can be used to change the expired password.

4.3.3. Lifecycle

For most use cases it is recommended to use a single driver instance throughout an application. The only reason to create multiple driver instances is to connect to multiple servers. This may be useful when connecting to multiple members of a cluster, or for pulling data out of one database to push to another. In other cases, a single driver instance usually serves the needs of a single application.

4.4. Session

All interaction with a Neo4j server takes place within a session.

A session is used to run statements against Neo4j. A session is obtained from a driver object. It allows for running statements against the database.

The driver has a session pool which can be configured. Sessions are returned to the pool when they are closed. It is important to close sessions, to allow them to return to the pool and be reused.

It is important to provide for the closing of a session in the case where an exception is thrown. Make sure that the exceptional path of execution, as well as the ordinary, closes the session.

4.4.1. Run a statement

The session can run statements directly. If a statement is run directly from a session, the session will take care of wrapping it in a transaction.

Example 4.9. Example: Run a statement
var result = session.Run("CREATE (person:Person {name: {name}})",
    new Dictionary<string, object> {{"name", "Arthur"}});
StatementResult result =
        session.run( "CREATE (person:Person {name: {name}})", Values.parameters( "name", "Arthur" ) );
session
  .run( "CREATE (person:Person {name: {name}})", {name: "Arthur"} )
result = session.run("CREATE (person:Person {name: {name}})", {"name": "Arthur"})

It is always recommended to use parameters, but it is possible to run a query with literal values as well.

Using parameters have significant performance and security benefits, including:

  • It allows the query planner to reuse its plans, which makes queries much more efficient.
  • It protects the database from Cypher injection attacks, where malicious query clauses are added from poorly typed or filtered input.
Example 4.10. Example: Run a statement without parameters
var result = session.Run("CREATE (p:Person { name: 'Arthur' })");
StatementResult result = session.run( "CREATE (p:Person { name: 'Arthur' })" );
session
  .run( "CREATE (p:Person { name: 'Arthur' })" )
result = session.run("CREATE (person:Person {name: 'Arthur'})")

4.4.2. Transaction management

When the session runs a statement directly, it creates an implicit transaction. The session can also create explicit transactions to be managed manually. When explicitly told to begin a new transaction, the session will return a transaction object. The transaction object allows for fine-grained transaction control.

For details on transactions in Neo4j see the Neo4j Java Developer Reference.

Example 4.11. Example: Run a statement in a manually managed transaction
using (var tx = session.BeginTransaction())
{
    tx.Run("CREATE (:Person {name: 'Guinevere'})");
    tx.Success();
}
try ( Transaction tx = session.beginTransaction() )
{
    tx.run( "CREATE (:Person {name: 'Guinevere'})" );
    tx.success();
}
var tx = session.beginTransaction();
tx.run( "CREATE (:Person {name: 'Guinevere'})" );
tx.commit();
with session.begin_transaction() as tx:
    tx.run("CREATE (:Person {name: 'Guinevere'})")
    tx.success = True

The manually managed transaction can also be rolled back.

Example 4.12. Example: Roll back a manually managed transaction
using (var tx = session.BeginTransaction())
{
    tx.Run("CREATE (:Person {name: 'Merlin'})");
    // optional to explicitly call tx.Failure();
}
try ( Transaction tx = session.beginTransaction() )
{
    tx.run( "CREATE (:Person {name: 'Merlin'})" );
    tx.failure();
}
var tx = session.beginTransaction();
tx.run( "CREATE (:Person {name: 'Merlin'})" );
tx.rollback();
with session.begin_transaction() as tx:
    tx.run("CREATE (:Person {name: 'Merlin'})")
    tx.success = False

4.5. Results

Results returned from Neo4j are presented as a stream of records.

When a statement is run, results are returned as a record stream. The stream itself is represented by a result cursor which is used to access the individual records. The individual records can be accessed one at a time, or all at once. Results are valid until the next statement is run or until the end of the current transaction, whichever comes first. To hold on to the results while running additional statements, or to use the results outside of the scope of the current transaction, the results must be retained by assigning them to a variable.

4.5.1. The result cursor

A result cursor provides access to the stream of records that make up the results. The cursor is moved through the stream and points to each record in turn. Before the cursor is moved onto the first record, it points before the first record. When the cursor has traversed to the end of the result stream, summary information and metadata are available.

Result records are loaded lazily as the cursor is moved through the stream. This means that the cursor must be moved to the first result before this result can be consumed. It also means that the entire stream must be explicitly consumed before summary information and metadata is available. It is generally best practice to explicitly consume results and close sessions, in particular when running update statements. This applies even if the summary information is not required. Failing to consume a result can lead to unpredictable behaviour as there will be no guarantee that the server has seen and handled the Cypher statement.

Example 4.13. Example: Use a result cursor
var searchTerm = "Sword";
var result = session.Run("MATCH (weapon:Weapon) WHERE weapon.name CONTAINS {term} RETURN weapon.name",
    new Dictionary<string, object> { { "term", searchTerm } });

Output.WriteLine($"List of weapons called {searchTerm}:");
foreach (var record in result)
{
    Output.WriteLine(record["weapon.name"].As<string>());
}
String searchTerm = "Sword";
StatementResult result = session.run( "MATCH (weapon:Weapon) WHERE weapon.name CONTAINS {term} RETURN weapon.name",
        Values.parameters( "term", searchTerm ) );

System.out.println("List of weapons called " + searchTerm + ":");
while ( result.hasNext() )
{
    Record record = result.next();
    System.out.println(record.get("weapon.name").asString());
}
var searchTerm = "Sword";
session
  .run( "MATCH (weapon:Weapon) WHERE weapon.name CONTAINS {term} RETURN weapon.name", {term : searchTerm} )
  .subscribe({
    onNext: function(record) {
      console.log("" + record.get("weapon.name"));
    },
    onCompleted: function() {
      session.close();
    },
    onError: function(error) {
      console.log(error);
    }
  });
search_term = "Sword"
result = session.run("MATCH (weapon:Weapon) WHERE weapon.name CONTAINS {term} "
                     "RETURN weapon.name", {"term": search_term})
print("List of weapons called %r:" % search_term)
for record in result:
    print(record["weapon.name"])

4.5.2. The record

The result record stream is made up of individual records. A record provides an immutable view of a part of a result. It is an ordered map of keys and values. These key-value pairs are called fields. As the fields are both keyed and ordered, the value of a field can be accessed either by position (0-based Integer) or by key (String).

  • To access values by position, use a 0-based integer.
  • To access values by key, use a string.
Example 4.14. Example: Use a record
var searchTerm = "Arthur";
var result = session.Run("MATCH (weapon:Weapon) WHERE weapon.owner CONTAINS {term} RETURN weapon.name, weapon.material, weapon.size",
    new Dictionary<string, object> { { "term", searchTerm } });

Output.WriteLine($"List of weapons owned by {searchTerm}:");
foreach (var record in result)
{
    var list = record.Keys.Select(key => $"{key}: {record[key]}").ToList();
    Output.WriteLine(string.Join(", ", list));
}
String searchTerm = "Arthur";
StatementResult result = session.run( "MATCH (weapon:Weapon) WHERE weapon.owner CONTAINS {term} RETURN weapon.name, weapon.material, weapon.size",
        Values.parameters( "term", searchTerm ) );

System.out.println("List of weapons owned by " + searchTerm + ":");
while ( result.hasNext() )
{
    Record record = result.next();
    List<String> sword = new ArrayList<>();
    for ( String key : record.keys() )
    {
        sword.add( key + ": " + record.get(key) );
    }
    System.out.println(sword);
}
var searchTerm = "Arthur";
session
  .run( "MATCH (weapon:Weapon) WHERE weapon.owner CONTAINS {term} RETURN weapon.name, weapon.material, weapon.size", {term : searchTerm} )
  .subscribe({
    onNext: function(record) {
      var sword = [];
      record.forEach(function(value, key)
      {
        sword.push(key + ": " + value);
      });
      console.log(sword);
    },
    onCompleted: function() {
      session.close();
    },
    onError: function(error) {
      console.log(error);
    }
  });
search_term = "Arthur"
result = session.run("MATCH (weapon:Weapon) WHERE weapon.owner CONTAINS {term} "
                     "RETURN weapon.name, weapon.material, weapon.size", {"term": search_term})
print("List of weapons owned by %r:" % search_term)
for record in result:
    print(", ".join("%s: %s" % (key, record[key]) for key in record.keys()))

4.5.2.1. 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 each 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.

Example 4.15. Example: Retain results for further processing
var result = session.Run("MATCH (knight:Person:Knight) WHERE knight.castle = {castle} RETURN knight.name AS name",
    new Dictionary<string, object> { { "castle", "Camelot" } });

var records = result.ToList();
session.Dispose();

foreach (var record in records)
{
    Output.WriteLine($"{record["name"].As<string>()} is a knight of Camelot");
}
List<Record> records;
try ( Session session = driver.session() )
{
    StatementResult result = session.run(
            "MATCH (knight:Person:Knight) WHERE knight.castle = {castle} RETURN knight.name AS name",
            Values.parameters( "castle", "Camelot" ) );

    records = result.list();
}

for ( Record record : records )
{
    System.out.println( record.get( "name" ).asString() + " is a knight of Camelot" );
}
session
  .run("MATCH (knight:Person:Knight) WHERE knight.castle = {castle} RETURN knight.name AS name", {castle: "Camelot"})
  .then(function (result) {
    var records = [];
    for (i = 0; i < result.records.length; i++) {
      records.push(result.records[i]);
    }
    return records;
  })
  .then(function (records) {
    for(i = 0; i < records.length; i ++)
    {
      console.log(records[i].get("name") + " is a knight of Camelot");
    }
  });
session = driver.session()
result = session.run("MATCH (knight:Person:Knight) WHERE knight.castle = {castle} "
                     "RETURN knight.name AS name", {"castle": "Camelot"})
retained_result = list(result)
session.close()
for record in retained_result:
    print("%s is a knight of Camelot" % record["name"])
Example 4.16. Example: Use results in a new query
var result = session.Run("MATCH (knight:Person:Knight) WHERE knight.castle = {castle} RETURN id(knight) AS knight_id",
    new Dictionary<string, object> { { "castle", "Camelot" } });

foreach (var record in result)
{
    session.Run("MATCH (knight) WHERE id(knight) = {id} " +
                "MATCH (king:Person) WHERE king.name = {king} " +
                "CREATE (knight)-[:DEFENDS]->(king)",
        new Dictionary<string, object> {{"id", record["knight_id"]}, {"king", "Arthur"}});
}
StatementResult result = session.run( "MATCH (knight:Person:Knight) WHERE knight.castle = {castle} RETURN id(knight) AS knight_id",
        Values.parameters( "castle", "Camelot" ) );

for ( Record record : result.list() )
{
    session.run( "MATCH (knight) WHERE id(knight) = {id} " +
            "MATCH (king:Person) WHERE king.name = {king} " +
            "CREATE (knight)-[:DEFENDS]->(king)",
            Values.parameters( "id", record.get( "knight_id" ), "king", "Arthur" ) );
}
session
  .run("MATCH (knight:Person:Knight) WHERE knight.castle = {castle} RETURN id(knight) AS knight_id", {"castle": "Camelot"})
  .subscribe({
    onNext: function(record) {
      session
        .run("MATCH (knight) WHERE id(knight) = {id} MATCH (king:Person) WHERE king.name = {king} CREATE (knight)-[:DEFENDS]->(king)",
        {"id": record.get("knight_id"), "king": "Arthur"});
    },
    onCompleted: function() {
      session
        .run("MATCH (:Knight)-[:DEFENDS]->() RETURN count(*)")
        .then(function (result) {
          console.log("Count is " + result.records[0].get(0).toInt());
        });
    },
    onError: function(error) {
      console.log(error);
    }
  });
result = session.run("MATCH (knight:Person:Knight) WHERE knight.castle = {castle} "
                     "RETURN id(knight) AS knight_id", {"castle": "Camelot"})
for record in result:
    session.run("MATCH (knight) WHERE id(knight) = {id} "
                "MATCH (king:Person) WHERE king.name = {king} "
                "CREATE (knight)-[:DEFENDS]->(king)", {"id": record["knight_id"], "king": "Arthur"})

4.5.3. Result summary

Metadata can be retrieved along with the result stream. An example is the query plan obtained by prepending a Cypher query with PROFILE or EXPLAIN. The following example will produce an explain plan with accurate numbers by running the query in the database. This will output a query profile (see Section 3.8.2, “How do I profile a query?” for more information about query profiling).

Example 4.17. Example: Profile query and print execution plan
var result = session.Run("PROFILE MATCH (p:Person {name: {name}}) RETURN id(p)",
                new Dictionary<string, object> { { "name", "Arthur" } });

IResultSummary summary = result.Consume();

Output.WriteLine(summary.StatementType.ToString());
Output.WriteLine(summary.Profile.ToString());
StatementResult result = session.run( "PROFILE MATCH (p:Person { name: {name} }) RETURN id(p)",
        Values.parameters( "name", "Arthur" ) );

ResultSummary summary = result.consume();

System.out.println( summary.statementType() );
System.out.println( summary.profile() );
session
  .run("PROFILE MATCH (p:Person {name: {name}}) RETURN id(p)", {name: "Arthur"})
  .then(function (result) {
    console.log(result.summary.profile);
  });
result = session.run("PROFILE MATCH (p:Person {name: {name}}) "
                     "RETURN id(p)", {"name": "Arthur"})
summary = result.consume()
print(summary.statement_type)
print(summary.profile)

Neo4j may also provide notifications for a query, for example, warning when a query will cause the execution engine to build a cartesian product. The following example will produce an explain plan with estimated numbers, without actually running the query in the database.

Example 4.18. Example: Explain query and print notifications
var summary = session.Run("EXPLAIN MATCH (king), (queen) RETURN king, queen").Consume();

foreach (var notification in summary.Notifications)
{
    Output.WriteLine(notification.ToString());
}
ResultSummary summary = session.run( "EXPLAIN MATCH (king), (queen) RETURN king, queen" ).consume();

for ( Notification notification : summary.notifications() )
{
    System.out.println( notification );
}
session
  .run("EXPLAIN MATCH (king), (queen) RETURN king, queen")
  .then(function (result) {
    var notifications = result.summary.notifications, i;
    for (i = 0; i < notifications.length; i++) {
      console.log(notifications[i].code);
    }
  });
result = session.run("EXPLAIN MATCH (king), (queen) RETURN king, queen")
summary = result.consume()
for notification in summary.notifications:
    print(notification)

4.6. Types

A driver translates between the type system of its language and the type system used in Neo4j.

Neo4j has a system of data types for processing and storing data. Drivers translate between application language types and the Neo4j type system. To pass parameters and process results, it is important to know the basics of the Neo4j type system and to understand how the Neo4j types are mapped in the driver.

The following Neo4j types are valid both for parameters and results:

  • Boolean
  • Integer
  • Float
  • String
  • List
  • Map

Maps and lists can be processed by the database and used in a statement. Lists in which the elements share a single primitive Neo4j type can be stored as properties on nodes and relationships. Maps, and lists in which the elements have mixed types, can not be stored as properties.

In addition to the types valid both for parameters and results, the following Neo4j types are valid for results only:

  • Node
  • Relationship
  • Path

The Neo4j driver maps Neo4j types to native language types as follows:

Example 4.19. Map Neo4j types to native language types
Neo4j .NET

Null

null

Boolean

System.Boolean

Integer

System.Int64

Float

System.Double

String

System.String

List

System.Collections.Generic.IList<T>

Map

System.Collections.IDictionary<TKey, TValue>

Node

Neo4j.Driver.V1.INode (*)

Relationship

Neo4j.Driver.V1.IRelationship (*)

Path

Neo4j.Driver.V1.IPath (*)

Neo4j Java

Null

null

Boolean

java.lang.Boolean

Integer

java.lang.Long

Float

java.lang.Double

String

java.lang.String

List

java.util.List<T>

Map

java.util.Map<K, V>

Node

org.neo4j.driver.v1.types.Node (*)

Relationship

org.neo4j.driver.v1.types.Relationship (*)

Path

org.neo4j.driver.v1.types.Path (*)

Neo4j JavaScript

Null

null

Boolean

Boolean

Integer

Integer (*)

Float

Number

String

String

List

Array

Map

Object

Node

Node (*)

Relationship

Relationship (*)

Path

Path (*)

Neo4j Python

Null

None

Boolean

bool

Integer

int/long (†)

Float

float

String

str/unicode (†)

List

list

Map

dict

Node

Node (*)

Relationship

Relationship (*)

Path

Path (*)

4.7. Errors

There are two types of errors that the user of a driver may need to be aware of. The more likely error to encounter is the Cypher error. A Cypher error occurs when the server is unable to run the statement that it receives from the driver.

Cypher errors encountered using a Neo4j driver are the same as those that can be encountered anywhere Cypher is used. This includes the Neo4j Browser, Neo4j Shell, the Cypher HTTP endpoint, the embedded API, and within a Java stored procedure. See Section A.1, “Neo4j Status Codes” for further details.

Example 4.20. Handling a Cypher error
try
{
    session.Run("This will cause a syntax error").Consume();
}
catch (ClientException)
{
    throw new InvalidOperationException("Something really bad has happened!");
}
try
{
    session.run( "This will cause a syntax error" ).consume();
}
catch ( ClientException e )
{
    throw new RuntimeException("Something really bad has happened!");
}
session
  .run("Then will cause a syntax error")
  .catch( function(err) {
    expect(err.fields[0].code).toBe( "Neo.ClientError.Statement.SyntaxError" );
    done();
  });
try:
    session.run("This will cause a syntax error").consume()
except CypherError:
    raise RuntimeError("Something really bad has happened!")
finally:
    session.close()