Further query mechanisms
Implicit (or auto-commit) transactions
This is the most basic and limited form with which to run a Cypher query.
The driver will not automatically retry implicit transactions, as it does instead for queries run with Driver.executeQuery()
and with managed transactions.
Implicit transactions should only be used when the other driver query interfaces do not fit the purpose, or for quick prototyping.
You run an implicit transaction with the method Session.run()
, which returns a Result
object.
let session = driver.session({database: 'neo4j'})
try {
const result = await session.run(
'MERGE (a:Person {name: $name})',
{ name: 'Alice'}
)
} finally {
await session.close()
}
Since the driver cannot figure out whether the query in a session.run()
call requires a read or write session with the database, it defaults to write. If your implicit transaction contains read queries only, there is a performance gain in making the driver aware by setting the keyword argument defaultAccessMode=neo4j.session.READ
when creating the session.
Implicit transactions are the only ones that can be used for CALL { … } IN TRANSACTIONS queries.
|
You can run an implicit transaction with a reactive session as well. |
Import CSV files
The most common use case for using Session.run()
is for importing large CSV files into the database with the LOAD CSV
Cypher clause, and preventing timeout errors due to the size of the transaction.
let session = driver.session({database: 'neo4j'})
try {
let result = await session.run(`
LOAD CSV FROM 'https://data.neo4j.com/bands/artists.csv' AS line
CALL {
WITH line
MERGE (:Artist {name: line[1], age: toInteger(line[2])})
} IN TRANSACTIONS OF 2 ROWS
`)
console.log(result.summary.counters.updates())
} finally {
await session.close()
}
While LOAD CSV can be a convenience, there is nothing wrong in deferring the parsing of the CSV file to your JavaScript application and avoiding LOAD CSV .
In fact, moving the parsing logic to the application can give you more control over the importing process.
For efficient bulk data insertion, see Performance → Batch data creation.
|
For more information, see Cypher → Clauses → Load CSV.
Transaction configuration
You can exert further control on implicit transactions by providing an optional third parameter of type TransactionConfig
to session.run()
.
The configuration allows to specify a query timeout and to attach metadata to the transaction.
For more information, see Transactions — Transaction configuration.
let session = driver.session({database: 'neo4j'})
let result = await session.run(
'MATCH (a:Person) RETURN count(a) AS people',
{}, // query parameters
{ timeout: 5000, metadata: {'appName': 'peopleTracker'} } // transactionConfig
)
Dynamic values in property keys, relationship types, and labels
In general, you should not concatenate parameters directly into a query, but rather use query parameters. There can however be circumstances where your query structure prevents the usage of parameters in all its parts. In fact, although parameters can be used for literals and expressions as well as node and relationship ids, they cannot be used for the following constructs:
-
property keys, so
MATCH (n) WHERE n.$param = 'something'
is invalid; -
relationship types, so
MATCH (n)-[:$param]→(m)
is invalid; -
labels, so
MATCH (n:$param)
is invalid.
For those queries, you are forced to use string concatenation.
To protect against Cypher injections, you should enclose the dynamic values in backticks and escape them yourself.
Notice that Cypher processes Unicode, so take care of the Unicode literal \u0060
as well.
let dangerousLabel = 'Special Person\\u0060'
// convert \u0060 to literal backtick, then escape backticks
let escapedLabel = dangerousLabel.replace(/\\u0060/g, '`').replace(/`/g, '``')
let result = await driver.executeQuery(
'MATCH (p:`' + escapedLabel + '`) RETURN p.name',
{},
{database: 'neo4j'}
)
console.log(`Executed query: ${result.summary.query.text}`)
Another workaround, which avoids string concatenation, is using the APOC procedure apoc.merge.node
.
It supports dynamic labels and property keys, but only for node merging.
apoc.merge.node
to create a node with dynamic labels/property keyslet propertyKey = 'name'
let label = 'Person'
let result = await driver.executeQuery(
'CALL apoc.merge.node($labels, $properties)',
{labels: [label], properties: {property_key: 'Alice'}},
{database: 'neo4j'}
)
If you are running Neo4j in Docker, APOC needs to be enabled when starting the container. See APOC → Installation → Docker. |
Logging
When creating a Driver
instance, you may optionally specify its logging configuration.
Logging is off by default.
To turn it on, specify the logging
option when you initialize the driver.
As value, use the function neo4j.logging.console()
, which logs to console and takes an optional parameter level
. The logging level can be either error
, warn
, info
, or debug
.
Enabling one level automatically enables all higher priority ones, and its default is info
.
debug
to consoleneo4j.driver(
URI,
neo4j.auth.basic(USER, PASSWORD),
{ // driver config
logging: neo4j.logging.console('debug')
}
)
1681215847749 INFO Routing driver 0 created for server address localhost:7687
1681215847765 INFO Routing table is stale for database: "neo4j" and access mode: "WRITE": RoutingTable[database=neo4j, expirationTime=0, currentTime=1681215847765, routers=[], readers=[], writers=[]]
1681215847773 DEBUG Connection [0][] created towards localhost:7687(127.0.0.1)
1681215847773 DEBUG Connection [0][] C: HELLO {user_agent: 'neo4j-javascript/5.3.0', ...}
1681215847778 DEBUG Connection [0][] S: SUCCESS {"signature":112,"fields":[{"server":"Neo4j/5.8.0","connection_id":"bolt-1782","hints":{"connection.recv_timeout_seconds":{"low":120,"high":0}}}]}
1681215847778 DEBUG Connection [0][bolt-1782] created for the pool localhost:7687
1681215847778 DEBUG Connection [0][bolt-1782] acquired from the pool localhost:7687
1681215847779 DEBUG Connection [0][bolt-1782] C: ROUTE {"address":"localhost:7687"} [] {"db":"neo4j"}
1681215847781 DEBUG Connection [0][bolt-1782] S: SUCCESS {"signature":112,"fields":[{"rt":{"servers":[{"addresses":["localhost:7687"],"role":"WRITE"},{"addresses":["localhost:7687"],"role":"READ"},{"addresses":["localhost:7687"],"role":"ROUTE"}],"ttl":{"low":300,"high":0},"db":"neo4j"}}]}
You may also specify a custom logger function, for example to log to a file.
In that case, the logging
option expects two properties:
-
level
: eithererror
,warn
,info
, ordebug
. Enabling one level automatically enables all higher priority ones. Defaults toinfo
. -
logger
: a function invoked when a message needs to be logged. Takeslevel, message
as input.
error
to consoleneo4j.driver(
URI,
neo4j.auth.basic(USER, PASSWORD),
{ // driver config
logging: {
level: 'error',
logger: (level, message) => console.log(level + ' ' + message)
}
}
)
You can find more information about logging in the API documentation.
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.