Using the Bolt protocol to make openCypher queries to Neptune - Amazon Neptune

Using the Bolt protocol to make openCypher queries to Neptune

Bolt is a statement-oriented client/server protocol initially developed by Neo4j and licensed under the Creative Commons 3.0 Attribution-ShareAlike license. It is client-driven, meaning that the client always initiates message exchanges.

To connect to Neptune using Neo4j's Bolt drivers, simply replace the URL and Port number with your cluster endpoints using the bolt URI scheme. If you have a single Neptune instance running, use the read_write endpoint. If multiple instances are running, then two drivers are recommended, one for the writer and another for all the read replicas. If you have only the default two endpoints, a read_write and a read_only driver are sufficient, but if you have custom endpoints as well, consider creating a driver instance for each one.

Neptune allows up to 1000 concurrent Bolt connections.

Neptune supports the following Neo4j Bolt message specification version: 4.0.0.

For examples of openCypher queries in various languages that use the Bolt drivers, see the Neo4j Drivers & Language Guides documentation.

Using Bolt with Java to connect to Neptune

You can download a driver for whatever version you want to use from the Maven MVN repository, or can add this dependency to your project:

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

Then, to connect to Neptune in Java using one of these Bolt drivers, create a driver instance for the primary/writer instance in your cluster using code like the following:

import org.neo4j.driver.Driver; import org.neo4j.driver.GraphDatabase; final Driver driver = GraphDatabase.driver("bolt://(your cluster endpoint URL):8182", AuthTokens.none(), Config.builder().withEncryption() .withTrustStrategy(TrustStrategy.trustSystemCertificates()) .build());

If you have one or more reader replicas, you can similarly create a driver instance for them using code like this:

final Driver read_only_driver = // (without connection timeout) GraphDatabase.driver("bolt://(your cluster endpoint URL):8182", Config.builder().withEncryption() .withTrustStrategy(TrustStrategy.trustSystemCertificates()) .build());

Or, with a timeout:

final Driver read_only_timeout_driver = // (with connection timeout) GraphDatabase.driver("bolt://(your cluster endpoint URL):8182", Config.builder().withConnectionTimeout(30, TimeUnit.SECONDS) .withEncryption() .withTrustStrategy(TrustStrategy.trustSystemCertificates()) .build());

If you have custom endpoints, it may also be worthwhile to create a driver instance for each one.

A Python openCypher query example using Bolt

Here is how to make an openCypher query in Python using Bolt:

python -m pip install neo4j
from neo4j import GraphDatabase uri = "bolt://(your cluster endpoint URL):8182" driver = GraphDatabase.driver(uri, auth=("username", "password"), encrypted=True)

Note that the auth parameters are ignored.

A .NET openCypher query example using Bolt

Here is how to make an openCypher query in .NET using Bolt:

Install-Package Neo4j.Driver-4.3.0
using Neo4j.Driver; namespace hello { // This example creates a node and reads a node in a Neptune // Cluster where IAM Authentication is not enabled. public class HelloWorldExample : IDisposable { private bool _disposed = false; private readonly IDriver _driver; private static string url = "bolt://(your cluster endpoint URL):8182"; private static string createNodeQuery = "CREATE (a:Greeting) SET a.message = 'HelloWorldExample'"; private static string readNodeQuery = "MATCH(n:Greeting) RETURN n.message"; ~HelloWorldExample() => Dispose(false); public HelloWorldExample(string uri) { _driver = GraphDatabase.Driver(uri, AuthTokens.None, o => o.WithEncryptionLevel(EncryptionLevel.Encrypted)); } public void createNode() { // Open a session using (var session = _driver.Session()) { // Run the query in a write transaction var greeting = session.WriteTransaction(tx => { var result = tx.Run(createNodeQuery); // Consume the result return result.Consume(); }); // The output will look like this: // ResultSummary{Query=`CREATE (a:Greeting) SET a.message = 'HelloWorldExample"..... Console.WriteLine(greeting); } } public void retrieveNode() { // Open a session using (var session = _driver.Session()) { // Run the query in a read transaction var greeting = session.ReadTransaction(tx => { var result = tx.Run(readNodeQuery); // Consume the result. Read the single node // created in a previous step. return result.Single()[0].As<string>();; }); // The output will look like this: // HelloWorldExample Console.WriteLine(greeting); } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing) { _driver?.Dispose(); } _disposed = true; } public static void Main() { using (var apiCaller = new HelloWorldExample(url)) { apiCaller.createNode(); apiCaller.retrieveNode(); } } } }

A Java openCypher query example using Bolt with IAM authentication

Here is how to make an openCypher query in Java using Bolt with with IAM authentication:

public class Example { /** * Neptune regions: * us-east-1, us-east-2, us-west-1, us-west-2, ca-central-1, * sa-east-1, eu-north-1, eu-west-1, eu-west-2, eu-west-3, * eu-central-1, me-south-1, af-south-1, ap-east-1, ap-northeast-1, * ap-northeast-2, ap-southeast-1, ap-southeast-2, ap-south-1, cn-north-1, * cn-northwest-1, us-gov-east-1, us-gov-west-1 */ private static final String SERVICE_REGION = "us-east-1"; // Access key private static final String ACCESS_KEY = "(access key id)"; // Secret key private static final String SECRET_KEY = "(secret access key)"; // AWS_SESSION_TOKEN is an optional environment variable. // Specify a session token only if you are using temporary security credentials private static final String AWS_SESSION_TOKEN = "(session token)"; private static final Gson GSON = new Gson(); // Query to create the node private static final String CREATE_NODE_QUERY = "CREATE (n:Node {message : 'HelloWorld'})"; // Query to read the node private static final String READ_NODE_QUERY = "MATCH (n:Node) RETURN n"; // URL of the Neptune cluster private static final String URL = "bolt://(your cluster endpoint URL):8182"; public static void main(String[] args) { // Create the driver final Driver driver = GraphDatabase.driver(URL, AuthTokens.basic("username", getSignedHeader()), getDefaultConfig()); // Consume and print the output of the created node System.out.println(driver.session().run(CREATE_NODE_QUERY).consume()); // Consume and print the output after reading the node created in the previous step. final Record rec = driver.session().run(READ_NODE_QUERY).list().get(0); // If your node name changes in the return statement, change the variable accordingly. System.out.println(rec.get("n").asNode().toString()); // Close the driver and it will close all the necessary resources driver.close(); } private static Config getDefaultConfig() { return Config.builder() .withConnectionTimeout(30, TimeUnit.SECONDS) .withMaxConnectionPoolSize(1000) .withDriverMetrics() .withLeakedSessionsLogging() .withEncryption() .withTrustStrategy(TrustStrategy.trustSystemCertificates()) .build(); } private static String getSignedHeader() { // If you are using permanent credentials, use the BasicAWSCredentials access key and secret key final BasicAWSCredentials permanentCreds = new BasicAWSCredentials(ACCESS_KEY, SECRET_KEY); final AWSCredentialsProvider creds = new AWSStaticCredentialsProvider(permanentCreds); // Or, if you are using temporary credentials, use the BasicSessionCredentials to // pass the access key, secret key, and session token, like this: // final BasicSessionCredentials temporaryCredentials = new BasicSessionCredentials(ACCESS_KEY, SECRET_KEY, AWS_SESSION_TOKEN); // final AWSCredentialsProvider tempCreds = new AWSStaticCredentialsProvider(temporaryCredentials); String signedHeader = ""; final Request<Void> request = new DefaultRequest<Void>("neptune-db"); // Request to neptune request.setHttpMethod(HttpMethodName.GET); request.setEndpoint(URI.create("https://(your cluster endpoint URL)")); final AWS4Signer signer = new AWS4Signer(); signer.setRegionName(SERVICE_REGION); signer.setServiceName(request.getServiceName()); signer.sign(request, creds.getCredentials()); signedHeader = getAuthInfoJson(request); return signedHeader; } private static String getAuthInfoJson(final Request<Void> request) { final Map<String, Object> obj = new HashMap<>(); obj.put("Authorization", request.getHeaders().get("Authorization")); obj.put("HttpMethod", request.getHttpMethod()); obj.put("X-Amz-Date", request.getHeaders().get("X-Amz-Date")); obj.put("Host", request.getEndpoint().getHost()); // If temporary credentials are used, include the security token in // the request, like this: // obj.put("X-Amz-Security-Token", request.getHeaders().get("X-Amz-Security-Token")); final String json = GSON.toJson(obj); return json; } }

A Python openCypher query example using Bolt with IAM authentication

Here is how to make an openCypher query in Python using Bolt with with IAM authentication:

import json from neo4j import GraphDatabase from botocore.auth import SigV4Auth from botocore.awsrequest import AWSRequest from botocore.credentials import Credentials # Access key ACCESS_KEY = "(your access key)" # Secret key SECRET_KEY = "(your secret key)" # AWS_SESSION_TOKEN is an optional environment variable. # Specify a session token only if you are using temporary security credentials. AWS_SESSION_TOKEN = "(session token)" # Neptune regions: # us-east-1, us-east-2, us-west-1, us-west-2, ca-central-1, # sa-east-1, eu-north-1, eu-west-1, eu-west-2, eu-west-3, # eu-central-1, me-south-1, af-south-1, ap-east-1, ap-northeast-1, # ap-northeast-2, ap-southeast-1, ap-southeast-2, ap-south-1, cn-north-1, # cn-northwest-1, us-gov-east-1, us-gov-west-1 # For example: SERVICE_REGION = "us-east-1" # Query to create the node CREATE_NODE_QUERY = "CREATE (n:Node {message : 'HelloWorld'})" # Query to read the node READ_NODE_QUERY = "MATCH (n:Node) RETURN n" # URI for the cluster uri = "bolt://(your cluster endpoint URL):8182" # Create the credentials credentials = Credentials(ACCESS_KEY, SECRET_KEY) # Or, if you are using temporary credentials, pass the session token as follows: # credentials = Credentials(ACCESS_KEY, SECRET_KEY, token=AWS_SESSION_TOKEN) sigv4 = SigV4Auth(credentials, 'neptune-db', SERVICE_REGION) request = AWSRequest(method='GET', url="http(s)://(your cluster endpoint URL)/opencypher") request.headers.add_header('host', '(your cluster endpoint URL)') # Sign the request sigv4.add_auth(request) auth_obj = { "Authorization" : request.headers['Authorization'], "HttpMethod" : "GET", "X-Amz-Date" : request.headers['X-Amz-Date'], # include X-Amz-Security-Token if you are using temporary credentials, like this: #"X-Amz-Security-Token": request.headers['X-Amz-Security-Token'], "Host" : request.headers['host'] } driver = GraphDatabase.driver(uri, auth=("USERNAME", json.dumps(auth_obj)), encrypted=True) # Create the node result = driver.session().run(CREATE_NODE_QUERY) # Read the node created in the previous object result = driver.session().run(READ_NODE_QUERY) # The output will look like this: <Record n=<Node properties={'message': 'HelloWorld'}>> print(result.single()) # Close the driver driver.close()

A Node.js example using IAM authentication and Bolt

var AWS = require('aws-sdk'); const neo4j = require('neo4j-driver') const aws = require('aws-sdk') const region = 'us-east-1'; const neptune_endpoint = "(your cluster endpoint URL)"; const neptune_port = 8182; async function init() { const endpoint = new aws.Endpoint(neptune_endpoint); endpoint.port = neptune_port; var request = new aws.HttpRequest(endpoint, region); request.method = 'GET' request.headers['host'] = neptune_endpoint; // MUST BE LOWERCASE request.path = "/opencypher" // OPTION: Use CredentialProviderChain instead of EnvironmentCredentials // var provider = new aws.CredentialProviderChain(); // const creds = await provider.resolvePromise(); var creds = new AWS.EnvironmentCredentials('AWS'); var signer = new aws.Signers.V4(request, 'neptune-db'); signer.addAuthorization(creds, new Date()); var auth_info = { "Authorization": request.headers['Authorization'], "HttpMethod": request.method, "X-Amz-Date": request.headers['X-Amz-Date'], "Host": request.headers['host'] "X-Amz-Security-Token": request.headers['x-amz-security-token'] } const auth_string = JSON.stringify(auth_info); return neo4j.driver("bolt://" + neptune_endpoint + ":" + neptune_port, neo4j.auth.basic('USERNAME', auth_string), { encrypted: 'ENCRYPTION_ON', trust: 'TRUST_SYSTEM_CA_SIGNED_CERTIFICATES' } ) } function request(driver) { const session = driver.session() session.run('MATCH (n) RETURN count(*) AS count') .then(result => { console.log(result) result.records.forEach(record => { console.log(record.get('count')) }) }) .catch(error => { console.log(error) }) .then(() => session.close()) } async function shutdown(driver) { // on application exit: await driver.close() } init().then(driver => { request(driver); return driver; }).then(driver => { shutdown(driver); }).catch (err => { console.log(err); });

Bolt connection behavior in Neptune

Here are some things to keep in mind about Neptune Bolt connections:

  • Because Bolt connections are created at the TCP layer, you can't use an Application Load Balancer in front of them, as you can with an HTTP endpoint.

  • The port that Neptune uses for Bolt connections is 8182.

  • Based on the Bolt preamble passed to it, the Neptune server selects the highest appropriate Bolt version (1, 2, 3, or 4.0).

  • The maximum number of connections to the Neptune server that a client can have open at any point in time is 1,000.

  • If the client doesn't close a connection after a query, that connection can be used to execute the next query.

  • However, if a connection is idle for 20 minutes, the server closes it automatically.

  • If IAM authentication is not enabled, you can use AuthTokens.none() rather than supplying a dummy user name and password. For example, in Java:

    GraphDatabase.driver("bolt://(your cluster endpoint URL):8182", AuthTokens.none(), Config.builder().withEncryption().withTrustStrategy(TrustStrategy.trustSystemCertificates()).build());
  • When IAM authentication is enabled, a Bolt connection is always disconnected a few minutes more than 10 days after it was established if it hasn't already closed for some other reason.

  • If the client sends a query for execution over a connection without having consumed the results of a previous query, the new query is discarded. To discard the previous results instead, the client must send a reset message over the connection.

  • Only one transaction at a time can be created on a given connection.

  • If an exception occurs during a transaction, the Neptune server rolls back the transaction and closes the connection. In this case, the driver creates a new connection for the next query.

  • Be aware that sessions are not thread-safe. Multiple parallel operations must use multiple separate sessions.